介绍
表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型,本质上和顶点/片元着色器一样。实际上表面着色器是对顶点/片元着色器的一层封装
在Unity中,表面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。在编写Shader时,表面着色器采用更加面向组件的方式。处理贴图纹理坐标和变换矩阵的工作会在后台完成,用户不需要处理那么多复杂的数学运算。使用表面着色器,仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到向前、延迟渲染管线中等
注:表面着色器的实现代码需要放在CGPROGRAM … ENDCG代码块中而不是Pass结构中,他会自己编译到各个Pass中
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
Shader "Custom/sf" { // 显示在Unity Inspector上的属性,给用户提供控制 // 形式:属性名("Inspector显示的标签", 属性类型) = 初始化数据 Properties { // 这是一个Color,初始值为白色,也就是(1,1,1,1) _Color("Main Color", Color) = (1, 1, 1, 1) // 这是一个图片,用作模型的贴图,默认为全白 _MainTex("Main Tex", 2D) = "white" {} // 这是一个图片,用作模型的法线贴图,默认为没用任何凹凸效果的"bump",千万别设置成了"white"了 _BumpMap("Bump Map", 2D) = "bump" {} } // 本来SubShader下会有各种Pass来控制Shader // 但在表面着色器中,Unity会根据下面的代码自动生成各种Pass,提供给GPU使用 SubShader { // 标签RenderType设置为Opaque,指明渲染的物体是个不透明物体 Tags { "RenderType" = "Opaque" } // 类似于模型的LOD,设定值大于该数值才会渲染(不过我不太清楚LOD值是怎么算的,有了解的希望能指点一下) LOD 300 // CGPROGRAM和ENDCG,中间就是表面着色器的代码了 CGPROGRAM // 使用surf作为表面着色器的输入输出函数,并使用Lambert光照模型,没错指明光照模型只需要这么一行 #pragma surface surf Lambert // 选择模型目标为3.0,2.0能使用传递的参数比较少,防止shader奔溃所以选择3.0 #pragma target 3.0 // 申明Properties中的属性,以便在surf函数中调用 sampler2D _MainTex; sampler2D _BumpMap; fixed4 _Color; // 输入结构 struct Input { // 想要获取图片的uv坐标,固定格式就是图片名称前加uv float2 uv_MainTex; float2 uv_BumpMap; }; // surf函数,表面着色器的处理函数 void surf(Input IN, inout SurfaceOutput o) { // 使用tex2D函数对_MainTex的uv_MainTex坐标进行采样,获取到颜色值 fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); // 采样值与_Color混合输出到Unity预置输出结构SurfaceOutput的Albedo中 o.Albedo = tex.rgb * _Color.rgb; // 透明度操控是没有作用的,因为是不透明物体 o.Alpha = tex.a * _Color.a; // 获取法线用到了两个函数 // 首先tex2D类似上面的对_MainTex的采样,获取到颜色值 // 其次UnpackNormal把获取到颜色值的信息转换成法线值,最后输出到Normal中 o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); } ENDCG } // 申明备用的Shader,如果以上Pass无法运行的话 FallBack "Legacy Shaders/Diffuse" } |
编译指令
- 就像任何其他着色器一样,表面着色器放置在 CGPROGRAM..ENDCG 代码块内。不同之处在于:
- 它使用 #pragma surface … 指令来指示自己是表面着色器。
1 |
#pragma surface surfaceFunction lightMode[optionalparams] |
必需参数
- surfaceFunction - 具有表面着色器代码的 Cg 函数。该函数的格式应为 void surf (Input IN, inout SurfaceOutput o),其中 Input 是您定义的结构。Input 应包含表面函数所需的任何纹理坐标和额外自动变量。
- lightModel - 要使用的光照模型。内置光照模型是基于物理的 Standard 和 StandardSpecular,以及简单的非基于物理的 Lambert(漫射)和 BlinnPhong(镜面反射)。请参阅自定义光照模型页面以了解如何编写自己的光照模型。
- Standard 光照模型使用 SurfaceOutputStandard 输出结构,并与 Unity 中的标准(金属性工作流)着色器匹配。
- StandardSpecular 光照模型使用 SurfaceOutputStandardSpecular 输出结构,并与 Unity 中的标准(镜面反射设置)着色器匹配。
- Lambert 和 BlinnPhong 光照模型不是基于物理的(来自 Unity 4.x),但使用这两个光照模型的着色器在低端硬件上可以提高渲染速度。
可选参数
透明度和 Alpha 测试由alpha和alphatest指令控制。透明度通常可以有两种:传统的 Alpha 混合(用于淡出对象)或更符合物理规律的“预乘混合”(允许半透明表面保留适当的镜面反射)。启用半透明度会使生成的表面着色器代码包含混合命令;而启用 Alpha 镂空将根据给定的变量在生成的像素着色器中执行片元废弃。
- alpha 或 alpha:auto - 对于简单的光照函数,将选择淡化透明度(与 alpha:fade 相同);对于基于物理的光照函数,将选择预乘透明度(与 alpha:premul 相同)。
- alpha:blend - 启用 Alpha 混合。
- alpha:fade - 启用传统淡化透明度。
- alpha:premul - 启用预乘 Alpha 透明度。
- alphatest:VariableName - 启用 Alpha 镂空透明度。剪切值位于具有 VariableName 的浮点变量中。您可能还想使用 addshadow 指令生成正确的阴影投射物通道。
- keepalpha - 默认情况下,无论输出结构的 Alpha 输出是什么,或者光照函数返回什么,不透明表面着色器都将 1.0(白色)写入 Alpha 通道。使用此选项可以保持光照函数的 Alpha 值,即使对于不透明的表面着色器也是如此。
- decal:add - 附加贴花着色器(例如 terrain AddPass)。这适用于位于其他表面之上并使用附加混合的对象。请参阅表面着色器示例
- decal:blend - 半透明贴花着色器。这适用于位于其他表面之上并使用 Alpha 混合的对象。请参阅表面着色器示例
自定义修改器函数可用于更改或计算传入的顶点数据,或更改最终计算的片元颜色。
- vertex:VertexFunction - 自定义顶点修改函数。在生成的顶点着色器的开始处调用此函数,并且此函数可以修改或计算每顶点数据。请参阅表面着色器示例。
- finalcolor:ColorFunction - 自定义最终颜色修改函数。请参阅表面着色器示例。
- finalgbuffer:ColorFunction - 用于更改 G 缓冲区内容的自定义延迟路径。
- finalprepass:ColorFunction - 自定义预通道基本路径。
阴影和曲面细分 - 可以提供其他指令来控制阴影和曲面细分的处理方式。
- addshadow - 生成阴影投射物通道。常用于自定义的顶点修改,以便阴影投射也可以获得程序化顶点动画。通常情况下,着色器不需要任何特殊的阴影处理,因为它们可以通过回退机制来使用阴影投射物通道。
- fullforwardshadows - 支持前向渲染路径中的所有光源阴影类型。默认情况下,着色器仅支持前向渲染中来自一个方向光的阴影(以节省内部着色器变体数量)。如果在前向渲染中需要点光源阴影或聚光灯阴影,请使用此指令。
- tessellate:TessFunction - 使用 DX11 GPU 曲面细分;该函数计算曲面细分因子。有关详细信息,请参阅表面着色器曲面细分。
代码生成选项 - 默认情况下,生成的表面着色器代码会尝试处理所有可能的光照/阴影/光照贴图情况。但是在某些情况下,您知道您不需要其中的一部分,可以调整生成的代码以跳过它们。这样可以减小着色器,从而提高加载速度。
- exclude_path:deferred、exclude_path:forward 和 exclude_path:prepass - 不为给定的渲染路径(分别对应延迟着色路径、前向路径和旧版延迟路径)生成通道。
- noshadow - 禁用此着色器中的所有阴影接受支持。
- noambient - 不应用任何环境光照或光照探针。
- novertexlights - 在前向渲染中不应用任何光照探针或每顶点光源。
- nolightmap - 禁用此着色器中的所有光照贴图支持。
- nodynlightmap - 禁用此着色器中的运行时动态全局光照支持。
- nodirlightmap - 禁用此着色器中的方向光照贴图支持。
- nofog - 禁用所有内置雾效支持。
- nometa - 不生成“Meta”通道(由光照贴图和动态全局光照用于提取表面信息)。
- noforwardadd - 禁用前向渲染附加通道。这会使着色器支持一个完整方向光,所有其他光源均进行每顶点/SH 计算。也能减小着色器。
- nolppv - 禁用此着色器中的光照探针代理体支持。
- noshadowmask - 为此着色器禁用阴影遮罩支持(包括 Shadowmask 和 Distance Shadowmask)。
其他选项
- softvegetation - 仅在开启 Soft Vegetation 时才渲染表面着色器。
- interpolateview - 在顶点着色器中计算视图方向并进行插值;而不是在像素着色器中计算。这可以使像素着色器更快,但会额外消耗一个纹理插值器。
- halfasview - 将半方向矢量传入光照函数而不是视图方向。计算半方向并按每个顶点对其进行标准化。这更快,但并不完全正确。
- approxview - 在 Unity 5.0 中已删除。请改用 interpolateview。
- dualforward - 在前向渲染路径中使用双光照贴图。
- dithercrossfade - 使表面着色器支持抖动效果。然后,可将此着色器应用于使用细节级别组 (LOD Group) 组件(配置为交叉淡入淡出过渡模式)的游戏对象。
要了解与上述不同选项的具体区别,使用着色器检视面板 (Shader Inspector) 中的“Show Generated Code”按钮会很有帮助。
Input结构体
输入结构 Input 通常具有着色器所需的所有纹理坐标。纹理坐标必须命名为“uv”后跟纹理名称的形式(如果要使用第二个纹理坐标集,则以“uv2”开头)。
可以放入输入结构的其他值:
- float3 viewDir - 包含视图方向,用于计算视差效果、边缘光照等等。
- 具有 COLOR 语义的 float4 - 包含插值的每顶点颜色。
- float4 screenPos - 包含反射或屏幕空间效果的屏幕空间位置。请注意,这不适合 GrabPass;您需要使用 ComputeGrabScreenPos 函数自己计算自定义 UV。
- float3 worldPos - 包含世界空间位置。
- float3 worldRefl - 在表面着色器不写入 o.Normal 的情况下,包含世界反射矢量。有关示例,请参阅反光漫射 (Reflect-Diffuse) 着色器。
- float3 worldNormal - 在表面着色器不写入 o.Normal 的情况下,包含世界法线矢量。
- float3 worldRefl; INTERNAL_DATA - 在表面着色器写入 o.Normal 的情况下,包含世界反射矢量。要获得基于每像素法线贴图的反射矢量,请使用 WorldReflectionVector (IN, o.Normal)。有关示例,请参阅反光凹凸 (Reflect-Bumped) 着色器。
- float3 worldNormal; INTERNAL_DATA - 在表面着色器写入 o.Normal 的情况下,包含世界法线矢量。要获得基于每像素法线贴图的法线矢量,请使用 WorldNormalVector (IN, o.Normal)。
SurfaceOutput结构体
有了Input结构体来提供所需要的数据后,我们就可以据此来计算各种表面属性。因此,另外一个结构体就是用于存储这些表面属性的结构体,即SurfaceOutput,SurfaceOutputStandard和SurfaceOutputStandardSpecular。它会作为表面函数的输出,随后会作为光照函数的输入来进行各种光照计算。相比于Input结构体的自由性,这个结构体的变量是提前定义好的。不可以增加也不会减少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//SurfaceOutput声明在Lighting.cginc struct SurfaceOutput{ fixed3 Albedo; fixed3 Normal; fixed3 Emission; half Specular; fixed Gloss; fixed Alpha; } //而SurfaceOutputStandard和SurfaceOutputStandardSpecular的声明可以在UnityPBSLighting.cginc中找到。 struct SurfaceOutputStandard{ fixed3 Albedo; fixed3 Normal; half3 Emission; half Smoothness; half Occlusion; fixed Alpha; } struct SurfaceOutputStandardSpecular{ fixed3 Albedo; fixed3 Specular; fixed3 Normal; half3 Emission; half Smoothness; half Occlusion; fixed Alpha; } |
在一个表面着色器当中,我们只要选择其中之一即可,另外注意,SurfaceOutput主要是针对非物理光照,即Lambert和BlinnPhong光照,而SurfaceOutputStandard和SurfaceOutputStandardSpecular主要对应了光照函数Standard和StandardSpecular。一个是金属工作流程(Metallic Workflow)一个是高光工作流(Specular Workflow)
下面着重介绍一下SurfaceOutput结构体中的变量的含义。
- fixed3 Albedo:对光源的反射。通常由纹理采样和颜色乘积获得。
- fixed3 Normal : 表面法线方向
- fixed3 Emission : 自发光。Unity通常会在片元着色器最后输出前(并在最后的顶点函数被调用前,如果有的话),使用下面的语句进行简单的颜色叠加。
1 |
o.rgb += o.Emission; |
- half Specular :高光反射中的指数部分的系数,影响高光反射的计算。如果使用了BlinnPhong,那么它会使用下面的代码来计算高光反射强度。
1 |
float spec = pow(nh,s.Specular * 128) * s.Gloss; |
- fixed Gloss:高光反射中的强度系数,公式如上所示。
- fixed Alpha:透明通道,如果开启了透明度的话,会使用该值来进行颜色混合。
如果你和我一样不太喜欢极高度的封装(希望了解底层)并且你刻苦又努力(愿意去研究),那么你肯定会好奇为什么表面着色器可以这样,比如场景中明明没有灯光,但是表面着色器却并不是全黑的。
表面着色器示例
https://docs.unity.cn/cn/2020.2/Manual/SL-SurfaceShaderExamples.html
- 本文固定链接: http://www.u3d8.com/?p=2441
- 转载请注明: 网虫虫 在 u3d8.com 发表过