This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
专业(SIM)
UnigineEditor
界面概述
资源工作流程
Version Control
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
如何擴展編輯器功能
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
使用范例
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
创建内容
内容优化
材质
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

抽象材质

为了简化着色器编程并隐藏各种功能的实现复杂性,添加了抽象材质。与面向对象编程中的抽象类一样,它们用作某些参数和功能的模板定义。

抽象材质本身不能分配给对象,它用于从它继承其他抽象和基础材质,扩展其功能,这为您提供了很大的灵活性。

您可以创建自己的自定义抽象材质,也可以使用“开箱即用”提供的 Mesh / Mesh Transparent / Mesh Unlit / Decal 使用或不使用镶嵌来创建自定义基础材质。

注意
不可能创建抽象材质的实例。

继承和覆盖#

通过从抽象材质继承,您可以覆盖任何值并向子材质添加一些其他组件:

ULON
AbstractMaterial A
{
		Slider t = 0.5
}

BaseMaterial B <parent = A>
{
		Slider t = 1.0
		Slider t2 = 2.0
}
注意
抽象材质不支持多重继承。

当子材质实现同名着色器时,父子着色器的源代码合并(父着色器代码在子着色器之前):

ULON
AbstractMaterial A
{
		Shader example = 
		#{
				// A_fragment
		}#
}

AbstractMaterial B <parent = A>
{
		Shader example = 
		#{
				// B_fragment
		}#
}

// the result example shader for the abstract material B will look like this: 
Shader example = 
#{
		// A_fragment
		// B_fragment
#}

您可以使用继承和标记 #shader shader_name 扩展和包含另一个着色器的代码。

ULON
AbstractMaterial A
{
		// including the different shader code
		Shader a = 
		#{
				// shader code A
				#shader b
		}#

		Shader b = 
		#{
				// shader code B1
		}#
}

AbstractMaterial B <parent = A>
{
		// expanding the parent shader code
		Shader b = 
		#{
				// shader code B2
		}#
}

// the resulting example shader for the abstract material B will look like this: 
Shader a = 
#{
		// shader code A
		// shader code B1
		// shader code B2
#}

相同的规则适用于脚本代码(使用 #script script_name 作为标记)。

父子材质中的元素团体可以合并同名的。两个组都应将 merge_group 参数设置为 true。第一个组条目定义了编辑器 UI 中该组的所有元素(父元素和子元素)所在的位置:

ULON
AbstractMaterial A
{
		Group A1
		{
				Slider T1

				Group example <merge_group=true>
				{
					   Slider T2
				}
		}
}

BaseMaterial B <parent = A>
{
		Group B1
		{
				Slider T3

				Group example <merge_group=true>
				{
					   Slider T4
				}
		}
}

// the result grouping for the BaseMaterial B will look like this ("example" in the BaseMaterial B is merged with the parent example group): 
Group A1
{
		Slider T1

		Group example
		{
			   Slider T2
			   Slider T4
		}
}

Group B1
{
		Slider T3
}
注意
同一材质中的组可以具有相同的名称。如果所有这些组都没有将 merge_group 参数设置为 true,它们将在编辑器的 UI 中显示为单独的组。

与传统基础材质逻辑的比较#

抽象材质概括了内部逻辑,并为派生的基础材质提供了简化的输入和输出接口。传统方法涉及大量重复工作,为相同类型的对象创建具有不同阴影的材质。因此建议使用抽象和基础材质混合。请参阅以下示例,这些示例有助于实现相同的修改后的 SSAO 效果。

抽象与基础材质的结合#

此示例演示了如何组合抽象和基础材质。

my_abstract.abstmat

ULON
AbstractMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>
{
	Texture2D color <source=procedural>
	
	Shader common=
	#{
	#}

	Shader base_shader=
	#{
		#include <core/materials/shaders/render/common.h>
		
		STRUCT_FRAG_BEGIN
			INIT_COLOR(float4)
		STRUCT_FRAG_END
		
		#shader common
		
		MAIN_FRAG_BEGIN(FRAGMENT_IN)
			#shader fragment
		MAIN_FRAG_END
	#}
	
	Pass post
	{
		Fragment=base_shader
	}
	
	Expression RENDER_CALLBACK_END_POST_MATERIALS=
	#{
		Texture source = engine.render.getTemporaryTexture(engine.render_state.getScreenColorTexture());
		
		source.copy(engine.render_state.getScreenColorTexture());
		setTexture("color", source);
		renderPassToTexture("post", engine.render_state.getScreenColorTexture());
		
		engine.render.releaseTemporaryTexture(source);
	#}
}

my_base.basemat

ULON
BaseMaterial <parent=my_abstract>
{
	Texture2D normal <source=gbuffer_normal>
	Texture2D scene_depth <source=current_depth>
	Texture2D modulation = "core/textures/common/checker_d.dds"
	Texture2D <source=ssao>

	Slider global_scale = 2

	Shader common=
	#{
		float3 triplanar_sample(float2 uv, float depth_in)
		{
			float3 normal_ws = mul3(s_imodelview, unpackGBufferNormal(TEXTURE(tex_normal, uv).rgb));
			float3 position_ws = s_camera_position + mul3(s_imodelview, nativeDepthToPositionVS(depth_in, uv));
			
			float4 texcoord = position_ws.xyyz * var_global_scale;
			float3 weight = triplanarWeightFast(normal_ws, 0.5f);
			
			return TEXTURE_TRIPLANAR(tex_modulation, texcoord, weight).rgb;
		}

		float4 final(float2 uv, float depth_in)
		{
			float4 base = TEXTURE(tex_color, uv);
			float ssao = TEXTURE(tex_ssao, uv).r;

			float3 effect = triplanar_sample(uv, depth_in);
			return float4(lerp(base.rgb * effect, base.rgb, ssao), base.a);
		}
	#}

	Shader fragment=
	#{
		float depth = TEXTURE(tex_scene_depth, IN_UV).r;
		if (depth == 0.0f || depth == 1.0f)
			discard;
		
		OUT_COLOR = final(IN_UV, depth);
	#}
}

仅基础材质#

这个例子演示了相同的用例更多但仅使用基础材质。

my_single_base.basemat

ULON
BaseMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>
{
	Texture2D color <source=procedural>
	Texture2D normal <source=gbuffer_normal>
	Texture2D scene_depth <source=current_depth>
	Texture2D modulation = "core/textures/common/checker_d.dds"
	Texture2D <source=ssao>

	Slider global_scale = 2
	
	Shader base_shader=
	#{
		#include <core/materials/shaders/render/common.h>
		
		STRUCT_FRAG_BEGIN
			INIT_COLOR(float4)
		STRUCT_FRAG_END
		
		float3 triplanar_sample(float2 uv, float depth_in)
		{
			float3 normal_ws = mul3(s_imodelview, unpackGBufferNormal(TEXTURE(tex_normal, uv).rgb));
			float3 position_ws = s_camera_position + mul3(s_imodelview, nativeDepthToPositionVS(depth_in, uv));
			
			float4 texcoord = position_ws.xyyz * var_global_scale;
			float3 weight = triplanarWeightFast(normal_ws, 0.5f);
			
			return TEXTURE_TRIPLANAR(tex_modulation, texcoord, weight).rgb;
		}

		float4 final(float2 uv, float depth_in)
		{
			float4 base = TEXTURE(tex_color, uv);
			float ssao = TEXTURE(tex_ssao, uv).r;

			float3 effect = triplanar_sample(uv, depth_in);
			return float4(lerp(effect, base.rgb, ssao), base.a);
		}
		
		MAIN_FRAG_BEGIN(FRAGMENT_IN)
			float depth = TEXTURE(tex_scene_depth, IN_UV).r;
			if (depth == 0.0f || depth == 1.0f)
				discard;
			
			OUT_COLOR = final(IN_UV, depth);
		MAIN_FRAG_END
	#}
	
	Pass post
	{
		Fragment=base_shader
	}
	
	Expression RENDER_CALLBACK_END_POST_MATERIALS=
	#{
		Texture source = engine.render.getTemporaryTexture(engine.render_state.getScreenColorTexture());
		
		source.copy(engine.render_state.getScreenColorTexture());
		setTexture("color", source);
		renderPassToTexture("post", engine.render_state.getScreenColorTexture());
		
		engine.render.releaseTemporaryTexture(source);
	#}
}

创建和使用您自己的材质#

让我们从头开始创建一个材质。对于这个例子,我们将使用基本的抽象材质来制作后期处理效果。

1. 创建父抽象材质#

创建一个扩展名为 *.abstmat 的新文本文件。文件名作为材质名,我们称之为my_abstract

抽象材质包含所有子材质继承的所有基本参数,以及基本着色器代码。这些基本的东西声明如下:

ULON
AbstractMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>
{

	// Let's add a screen texture, we will use it later in BaseMaterial
	Texture2D color <source=procedural>

	Shader common=
	#{
	#}

	// Here comes the core of our fragment shader:
	Shader base_shader= // Declare the shader node for further use
	#{
		#include <core/materials/shaders/render/common.h> //Base UUSL include
		
		// We render to a single texture with 4 channels
		STRUCT_FRAG_BEGIN
			INIT_COLOR(float4)
		STRUCT_FRAG_END
		
		// Here we'll add code from inherited materials
		#shader common
		
		// Declare the main fragment function
		MAIN_FRAG_BEGIN(FRAGMENT_IN)
			// Here we'll add code from inherited materials
			#shader fragment
		MAIN_FRAG_END
	#}
}

现在我们需要添加一个渲染通道,因为单个着色器本身不会渲染任何东西。由于我们正在实现后期效果材质,我们应该添加 post 通道并指定要用于它的着色器(我们将使用我们的 base_shader 作为片段着色器)。

注意
如果未指定顶点着色器,则将使用 core/materials/shaders/default/empty.vert 中的默认着色器。
ULON
Pass post
{
	Fragment=base_shader
}

UNIGINE 支持可编写脚本的材质使您能够在特定阶段执行表达式(代码片段)的功能渲染顺序,我们将使用它。

ULON
// Let's subscribe our USC expression to render callback after all Engine's post-effects
Expression RENDER_CALLBACK_END_POST_MATERIALS=
#{
	// Make a copy of the screen texture
	Texture source = engine.render.getTemporaryTexture(engine.render_state.getScreenColorTexture());
	source.copy(engine.render_state.getScreenColorTexture());

	// Set it as the source texture for the post-effect
	setTexture("color", source);

	// Render the pass "post" directly to the screen
	renderPassToTexture("post", engine.render_state.getScreenColorTexture());
	
	// Since we won't use a newly requested texture, we can tell the engine that anyone else can use it.
	engine.render.releaseTemporaryTexture(source);
#}

综上所述,我们得到了抽象材质的完整代码,您可以将其复制并粘贴到文本编辑器中,并将其作为 my_abstract.abstmat 文件保存到项目的 data 文件夹中:

my_abstract_base1.basemat

ULON
AbstractMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>
{

	// Let's add a screen texture, we will use it later in BaseMaterial
	Texture2D color <source=procedural>

	Shader common=
	#{
	#}

	// Here comes the core of our fragment shader:
	Shader base_shader= // Declare the shader node for further use
	#{
		#include <core/materials/shaders/render/common.h> //Base UUSL include
		
		// We render to a single texture with 4 channels
		STRUCT_FRAG_BEGIN
			INIT_COLOR(float4)
		STRUCT_FRAG_END
		
		// Here we'll add code from inherited materials
		#shader common
		
		// Declare the main fragment function
		MAIN_FRAG_BEGIN(FRAGMENT_IN)
			// Here we'll add code from inherited materials
			#shader fragment
		MAIN_FRAG_END
	#}

	// Adding a post pass to render our effect
	Pass post
	{
		Fragment=base_shader
	}

	// Let's subscribe our USC expression to render callback after all Engine's post-effects
	Expression RENDER_CALLBACK_END_POST_MATERIALS=
	#{
		// Make a copy of the screen texture
		Texture source = engine.render.getTemporaryTexture(engine.render_state.getScreenColorTexture());
		source.copy(engine.render_state.getScreenColorTexture());

		// Set it as the source texture for the post-effect
		setTexture("color", source);

		// Render the pass "post" directly to the screen
		renderPassToTexture("post", engine.render_state.getScreenColorTexture());
		
		// Since we won't use a newly requested texture, we can tell the engine that anyone else can use it.
		engine.render.releaseTemporaryTexture(source);
	#}
}

2.从抽象继承基础材质#

现在我们将从创建的抽象 (my_abstract.abstmat) 继承基础材质以扩展其功能。

首先,我们将实现一个简单的滤色器后期效果。

创建一个扩展名为 *.basemat 的新文件。同样,文件名用作基础材质名称。

描述基础材质,指定它具有抽象材质 my_abstract 作为父级:

ULON
BaseMaterial <parent=my_abstract>
{

	// Add the color parameter that will define the optical filter color
	Color my_color = [0.5 0.5 0.5 1]

	// All shader variables that are defined in the material should have a prefix to be referred to in shaders
	// For sliders, colors, and other basic parameters, it is defined with var_prefix currently set as 'var'
	// For textures, it is texture_prefix currently set as 'tex'
	Shader fragment=
	#{
		// Sample screen texture
		float4 sample = TEXTURE(tex_color, IN_UV);

		// Multiply the screen texture by specified color
		OUT_COLOR = sample * var_my_color;
	#}
}
注意

材质中定义的所有着色器变量都应该有一个前缀,以便它们可以在着色器中引用:

  • slider, color 的前缀和其他基本参数是使用 var_prefix 参数设置的。
  • 纹理的前缀是使用 texture_prefix 参数设置的。

例如:

ULON
// Setting prefixes
AbstractMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>

/*...*/

// Adding the parameter
Color my_color = [0.5 0.5 0.5 1]

/*...*/

// Refering to a variable
OUT_COLOR = sample * var_my_color;

将基础材质作为 my_abstract_base1.basemat 保存到项目的 data 文件夹中。

而已。我们编写的内容足以创建一个简单的后处理效果。

3. 添加另一个衍生材质#

现在我们将从我们的抽象材质中推导出另一种更复杂的材质。

此材质应使用三平面映射在遮挡区域应用指定的 modulation 纹理和指定的 intensity(根据 SSAO 贴图)

让我们创建一个新的基础材质文件并将相同的抽象材质 (my_abstract.abstmat) 设置为其父级。

这是我们第二个基础材质的完整代码(将其保存为 my_abstract_base2.basemat 到您项目的 data 文件夹):

my_abstract_base2.basemat

ULON
BaseMaterial <parent=my_abstract>
{
	// For this example more scene textures are required
	Texture2D normal <source=gbuffer_normal>
	Texture2D scene_depth <source=current_depth>
	Texture2D <source=ssao>

	// Modulation texture, UV scale, and effect intensity
	Texture2D modulation = "core/textures/common/checker_d.dds"
	Slider global_scale = 2
	Slider power = 10 <min=0 max=3>
	
	Shader common=
	#{
		float3 triplanar_sample(float2 uv, float depth_in)
		{
			float3 normal_ws = mul3(s_imodelview, unpackGBufferNormal(TEXTURE(tex_normal, uv).rgb));
			float3 position_ws = s_camera_position + mul3(s_imodelview, nativeDepthToPositionVS(depth_in, uv));
			
			float4 texcoord = position_ws.xyyz * var_global_scale;
			float3 weight = triplanarWeightFast(normal_ws, 0.5f);
			
			return TEXTURE_TRIPLANAR(tex_modulation, texcoord, weight).rgb;
		}

		float4 final(float2 uv, float depth_in)
		{
			float4 base = TEXTURE(tex_color, uv);
			float ssao = TEXTURE(tex_ssao, uv).r;

			float3 effect = triplanar_sample(uv, depth_in);
			return float4(lerp(base.rgb * effect * var_power, base.rgb, ssao), base.a);
		}
	#}

	Shader fragment=
	#{
		// Sample depth texture
		float depth = TEXTURE(tex_scene_depth, IN_UV).r;
		if (depth == 0.0f || depth == 1.0f)
		   discard;
			
		OUT_COLOR = final(IN_UV, depth);
	#}
}

4. 从基础材质继承用户材质并使用它们#

基础材质中声明的参数值只能在继承的用户材质中实时修改。因此,您需要从项目的基础材质中继承用户材质并根据需要进行调整(分配和更改参数值等)。

为了测试我们的第一个后期效果,请执行以下操作:

  • 找到 my_abstract_base1.basematMaterials窗口,右键单击它,选择 Create Child,然后将新材质重命名为 my_color_filter.

  • 因为它是后期效果,所以要全局应用材质,我们应该打开 Settings -> Render -> Scriptable Materials
  • 单击 Add New Scriptable Material 并指定 my_color_filter 材质。在 shis 之后,您可以更改过滤器颜色。

这是应用了我们的第一个后期材质的最终图像。

my_abstract_base2.basemat 基础材质重复上述步骤以测试第二个后效。下面是它的样子:

最新更新: 2024-05-03
Build: ()