编写表面着色器
编写与光照交互的着色器是十分复杂的事情。有不同的光照类型、不同的阴影选项和不同的渲染路径(正向和延时渲染),着色器应以某种方法处理此种程度的复杂性。
Unity 的表面着色器 (Surface Shader) 是一种代码生成方法,用它来编写光照着色器比用低级的顶点/像素着色器程序容易得多。请注意,表面着色器 (Surface Shader) 中不包含自定义语言、魔法或忍者;它只会生成本应手写的所有重复代码。您仍要用 Cg/HLSL 语言来编写着色器代码。
相关的一些示例,请参阅表面着色器示例 和 表面着色器自定义光照示例。
工作原理
定义一个“表面函数”,此函数将您需要的任意 UV 或数据作为输入信息并填入输出结构 SurfaceOutput
。SurfaceOutput 大体上描述了表面的属性(它的反射率颜色、法线、发射量、高光等)。您要用 Cg/HLSL 语言来编写此代码。
然后,表面着色器 (Surface Shader) 编译器会计算出需要什么输入信息以及填入了什么输出信息等,并生成实际的顶点和像素着色器以及渲染通道 (rendering pass) 来处理正向和延时渲染。
标准的表面着色器输出结构如下:
struct SurfaceOutput { half3 Albedo; half3 Normal; half3 Emission; half Specular; half Gloss; half Alpha;};
样例
请参阅表面着色器示例、表面着色器自定义光照示例和表面着色器密铺页面。
表面着色器编译指令
表面着色器与其他任何着色器一样放置于 CGPROGRAM..ENDCG
块中。区别是:
必须将其放置于子着色器块中,而不能放在通道中。表面着色器自身会编译为多个通道。
它使用
#pragma surface ...
指令来表明它是个表面着色器。
#pragma surface
指令是:
#pragma surface surfaceFunction lightModel [optionalparams]
所需参数:
surfaceFunction - 表明哪个 Cg 函数有表面着色器代码。该函数格式应为
void surf (Input IN, inout SurfaceOutput o)
,其中“Input”是您定义的结构。Input 结构应包含表面函数所需的任何纹理坐标和额外自动变量。lightModel - 要使用的光照模型。内置的光照模型是
Lambert
(漫反射)和BlinnPhong
(高光)。有关如何编写自己的光照模型,请参阅自定义光照模型页面。
Optional parameters:
alpha
- Alpha 混合模式。将该参数用于半透明着色器。alphatest:VariableName
- Alpha 测试模式。将该参数用于透明镂空着色器。镂空值在有变量名 (VariableName) 的浮点型变量中。vertex:VertexFunction
- 自定义顶点修改函数。具体示例可见树皮着色器 (Tree Bark Shader)。finalcolor:ColorFunction
- 自定义最终颜色修改函数。请参阅表面着色器示例。exclude_path:prepass
或exclude_path:forward
- 请勿为给定的渲染路径生成通道。addshadow
- 添加阴影投射器 (shadow caster) 和集合通道 (collector pass)。通常与自定义顶点修改函数一起使用,使得阴影投射也可获得所有程序性顶点动画效果。dualforward
- 将双光照贴图用于正向渲染路径中。fullforwardshadows
- 支持正向渲染路径中的所有阴影类型。decal:add
- 附加的印花着色器(例如,地形 AddPass)。decal:blend
- 半透明印花着色器。softvegetation
- 使表面着色器仅在“软植被”(Soft Vegetation) 开启时被渲染。noambient
- 请勿应用任何环境光照或球面调和光照 (spherical harmonics light)。novertexlights
- 请勿在正向渲染 (Forward Rendering) 中应用任何球面调和光照 (spherical harmonic light) 或逐顶点光照 (per-vertex light)。nolightmap
- 在该着色器中禁用光照贴图支持(使着色器变小)。nodirlightmap
- 在该着色器中禁用方向性光照贴图支持(使着色器变小)。noforwardadd
- 禁用正向渲染附加通道。这会使着色器支持一个全方向灯,而其他所有光照则逐顶点/SH 被计算。也会使着色器变小。approxview
- 对于有需要的着色器,逐顶点而不是逐像素计算规范化视线方向。这种方法更快速,但当相机靠近表面时,视线方向不会完全正确。halfasview
- 将半方向向量(而非视线方向向量)传递到光照函数中。半方向向量将会被逐顶点计算和规范化。这种方法更快速,但不会完全正确。tessellate:TessFunction
- 使用 DX11 GPU 密铺 (tessellation);函数计算密铺 (tessellation) 系数。有关详细信息,请参阅表面着色器密铺。
此外,您还可以在 CGPROGRAM 块中编写 #pragma debug
,然后表面编译器 (surface compiler) 将产生大量生成代码的注释。您可以在着色器检视器中使用开放的编译着色器 (Open Compiled Shader) 进行查看。
表面着色器输入结构
输入结构 Input
通常具有着色器所需的所有纹理坐标。纹理坐标必须被命名为纹理名称前加“uv
”(或以“uv2
”开头以使用第二纹理坐标集)。
其他能放入 Input 结构中的值:
float3 viewDir
- 将包含视线方向,用于计算视差 (Parallax) 效果、边缘光照等。带
颜色 (COLOR)
语义的float4
- 将包含插值逐顶点颜色。float4 screenPos
- 将包含反射效果的屏幕空间位置。例如,在 Dark Unity 中由 WetStreet 着色器 (WetStreet Shader) 使用。float3 worldPos
- 将包含世界坐标空间位置。float3 worldRefl
- 将包含世界坐标反射向量,条件是表面着色器不写入 o.Normal。具体示例可见反射 - 漫反射着色器 (Reflect-Diffuse Shader)。float3 worldNormal
- 将包含世界坐标法线向量,条件是表面着色器不写入 o.Normal。float3 worldRefl; INTERNAL_DATA
- 将包含世界坐标反射向量,条件是表面着色器写入 o.Normal。要基于逐像素法线贴图获得反射向量,请使用WorldReflectionVector (IN, o.Normal)
。具体示例可见反射 - 凹凸着色器 (Reflect-Bumped Shader)。float3 worldNormal; INTERNAL_DATA
- 将包含世界坐标法线向量,条件是表面着色器写入 o.Normal。要基于逐像素法线贴图获得法线向量,请使用WorldNormalVector (IN, o.Normal)
。
表面着色器和 DirectX 11
目前表面着色器编译管线 (compilation pipeline) 的一些部分不理解 DirectX 11 HLSL 语法,因此如果是诸如 StructuredBuffers、 RWTextures 和其他非 DX9 语法等的 HLSL 功能,则您必须将它纳入仅特定于 DX11 的预处理器宏:有关详细信息,请参阅特定于平台的差异页面。
更多文档
表面着色器示例
表面着色器中的自定义光照模型
表面着色器光照示例
具有 DX11 密铺 (Tessellation) 的表面着色器