抽象材质
为了简化着色器编程并隐藏各种功能的实现复杂性,添加了抽象材质。与面向对象编程中的抽象类一样,它们用作某些参数和功能的模板定义。
抽象材质本身不能分配给对象,它用于从它继承其他抽象和基础材质,扩展其功能,这为您提供了很大的灵活性。
您可以创建自己的自定义抽象材质,也可以使用“开箱即用”提供的 Mesh / Mesh Transparent /Mesh Unlit 使用或不使用镶嵌来创建自定义基础材质。
继承和覆盖#
通过从抽象材质继承,您可以覆盖任何值并向子材质添加一些其他组件:
AbstractMaterial A
{
Slider t = 0.5
}
BaseMaterial B <parent = A>
{
Slider t = 1.0
Slider t2 = 2.0
}
当子材质实现同名着色器时,父子着色器的源代码合并(父着色器代码在子着色器之前):
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 扩展和包含另一个着色器的代码。
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 中该组的所有元素(父元素和子元素)所在的位置:
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
}
与传统基础材质逻辑的比较#
抽象材质概括了内部逻辑,并为派生的基础材质提供了简化的输入和输出接口。传统方法涉及大量重复工作,为相同类型的对象创建具有不同阴影的材质。因此建议使用抽象和基础材质混合。请参阅以下示例,这些示例有助于实现相同的修改后的 SSAO 效果。
抽象与基础材质的结合#
此示例演示了如何组合抽象和基础材质。
my_abstract.abstmat
AbstractMaterial <preview_hidden=true var_prefix=var texture_prefix=tex>
{
Texture2D color <source=procedural>
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
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
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。
抽象材质包含所有子材质继承的所有基本参数,以及基本着色器代码。这些基本的东西声明如下:
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>
// 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 作为片段着色器)。
Pass post
{
Fragment=base_shader
}
UNIGINE 支持可编写脚本的材质使您能够在特定阶段执行表达式(代码片段)的功能渲染顺序,我们将使用它。
// 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
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>
// 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 作为父级:
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 参数设置的。
例如:
// 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
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.basemat 在Materials窗口,右键单击它,选择 Create Child,然后将新材质重命名为 my_color_filter.
- 因为它是后期效果,所以要全局应用材质,我们应该打开 Settings -> Render -> Scriptable Materials
- 单击 Add New Scriptable Material 并指定 my_color_filter 材质。在 shis 之后,您可以更改过滤器颜色。
这是应用了我们的第一个后期材质的最终图像。
对 my_abstract_base2.basemat 基础材质重复上述步骤以测试第二个后效。下面是它的样子: