PA8 Complex Materials
Shader 与渲染算法 - 高级属性 - 复杂材质 1
1 复刻编辑器:User Interface
当贴图、功能语法丰富起来,使用默认的 inspector 管理和拓展这些数值就显得尤为不便。此时,就需要自己做编辑器扩展了。
Unity 默认 shader 的 inspector 看起来就是个很好的 mimicking 对象。本章的任务就是将其完美复刻。
1.1 制作 GUI 的核心
本体:Material
Material
是 Unity 中表示材质的核心类,万物之源。它代表了一个渲染对象的材质,而这个材质可以具备各种属性。
显示屏:MaterialEditor
有 MaterialEditor 实例 editor:
editor.TexturePropertySingleLine
材质选择器 (可附加滑块或颜色选择器)
editor.TextureScaleOffsetProperty
Scale & Offset 输入框
editor.ShaderProperty
滑块 / 数值控件
editor.ColorProperty
颜色选择器
数据库:MaterialProperty
显示一切的 MaterialEditor
需要 MaterialProperty
类来寻找和操作 shader 中的各个属性。
1.2 CODING
Shader "Code/P8_ComplexMaterials/UserInterface" {
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
...
}
// to use a custom GUI, have to add the CustomEditor directive to a shader
CustomEditor "UserInterface_GUI"
}
2 混合的另一种可能性:Metal and Nonmetal

现在,材质中金属与非金属程度的控制是全局的(依靠一条 slider)。如果想创建更复杂的纹理,可以通过添加一个 metallic map,实现一个材质中金属与非金属部分更精细的混合控制。
2.1 复杂的想法 Sophisticated Idea
STEP 1: 添加 Metallic Map
Main:Properties 添加材质
Light:添加相应变量
STEP 2: 调整 metallic 逻辑
Main:添加对于 Material 的 shader feature 指令
Light:
GetMetallic
函数取得Metallic
值 -> Frag shader 中调用GetMetallic
函数作为能量守恒函数DiffuseAndSpecularFromMetallic
的参数GUI:添加 metallic map 材质选取窗口 -> 调整 map 和 slider 逻辑为 “OR”
STEP 3: 设置 GUI 切换 keyword
声明 Material 类型对象
target
-> 将其赋值为MaterialEditor.target
方法获取的材质SetKeyword
函数设置 keyword 启用状态 -> 在DoMetallic
函数中使用 ChangeCheck 监控调用SetKeyword
函数
2.2 CODING
Shader "Code/PA8_ComplexMaterials/MixMetalAndNonMetal" {
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
// STEP 1: 添加 Metallic Map
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
// STEP2: 添加对于 Material 的 shader feature 指令
#pragma shader_feature _METALLIC_MAP
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "MixMetalAndNonMetal_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ForwardAdd"
}
Blend One One
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma multi_compile_fwdadd_fullshadows
#pragma vertex Vert
#pragma fragment Frag
#include "MixMetalAndNonMetal_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma target 3.0
#pragma multi_compile_shadowcaster
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include "ComplexMaterials_Shadow.cginc"
ENDCG
}
}
CustomEditor "MixMetalAndNonMetal_GUI"
}
3 打包策略:Smoothness Maps

这一节实现的重点是复刻 Unity 对于 Smoothness 的打包策略。有三种主流存储方式:
由于很多情况下,材质的 metallic 属性和 smoothness 属性高度相关(金属区域比非金属区域更光滑),因此 Metallic Map 和 Smoothness Map 可以组合在一个纹理中,约定俗成是 Metallic 信息使用纹理的 R 通道,Smoothness 信息使用 A 通道;
此外,有些材质是完全非金属的(比如木材、塑料等)。这种情况下不需要 Metallic Map,而 Smoothness 对材质的表现依然重要,因此有两种方式,直接用一个纹理专门表示 Smoothness,或者将 Smoothness 存储在 Albedo Map 的 A 通道。
3.1 粗暴的想法 Rough Idea
STEP 1: 添加 Smoothness map
Main:添加 keywords
Light:
GetSmoothness
函数读取存储源中的 alpha 值 -> 在 BRDF 函数和CreateIndirectLight
函数中将GetSmoothness
返回值作为参数
STEP 2: 创建 smoothness 存储源选择
创建 enum 展示存储源选项 -> 使用
IsKeywordEnabled
函数检测 keyword ->DoSmoothness
函数使用当前 smoothness 源使用
EditorGUILayout.EnumPopup
方法创建 popup list -> 追踪 GUI control 控制并切换 keyword
STEP 3: 支持 Undo 操作
创建 wrapper 函数调用 editor.RegisterPropertyChangeUndo
方法 -> 在 DoSmoothness
函数动作改变前录制原有动作
3.2 CODING
Shader "Code/PA8_ComplexMaterials/SmoothnessMaps" {
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
// STEP 1: 添加 keywords
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "SmoothnessMaps_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ForwardAdd"
}
Blend One One
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma multi_compile_fwdadd_fullshadows
#pragma vertex Vert
#pragma fragment Frag
#include "SmoothnessMaps_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma target 3.0
#pragma multi_compile_shadowcaster
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include "ComplexMaterials_Shadow.cginc"
ENDCG
}
}
CustomEditor "SmoothnessMaps_GUI"
}
4 放射光源:Emissive
此前所有的光线都是通过反射 light 得到的,除此之外还有一些材质可以自发光,比如当它足够热的时候,不需要其他光源就可以看到它。Unity standard shader 将其实现为一张 emission map 和 color。
Shader "Code/PA8_ComplexMaterials/Emissive" {
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {} // 默认为 black
_Emission ("Emission", Color) = (0, 0, 0) // 仅需要 RGB 通道
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _EMISSION_MAP // 仅在 Base Pass 中使用
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "Emissive_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ForwardAdd"
}
Blend One One
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma multi_compile_fwdadd_fullshadows
#pragma vertex Vert
#pragma fragment Frag
#include "Emissive_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma target 3.0
#pragma multi_compile_shadowcaster
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include "ComplexMaterials_Shadow.cginc"
ENDCG
}
}
CustomEditor "Emissive_GUI"
}
APPENDIX
Anki
Reference
Last updated