Unity Shader学习笔记(29)表面着色器(Surface Shader)
对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)和编译指令(#pragma surface)。
官网样例:
Shader "Example/Diffuse Bump" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; sampler2D _MainTex; sampler2D _BumpMap; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } ENDCG } Fallback "Diffuse" }
指明表面着色器使用的表面函数和光照函数,并设置可选参数。格式如下: #pragma surface surfaceFunction lightModel [optionalparams]
- 表面函数(surfaceFunction)
包含属性有反射率、光滑度、透明度等。使用输入结构体Input
来设置各种表面属性。 - 光照函数
- 使用表面函数的属性来模拟光照效果。Unity内置基于物理的光照模型Standard和StandardSpecular(在UnityPBSLighting.cginc中定义),非物理光照模型函数Lambert和BlinnPhong(在Lighting.cginc中定义)。
其他可选参数:
- 自定义修改函数:
- vertex:VertexFunction:顶点修改,实现一些顶点动画等。
- finalcolor:ColorFunction:最终颜色修改,实现雾效等。
- finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
- finalprepass:ColorFunction:prepass base路径修改?
- 阴影:
- addshadow:添加一个阴影投射Pass。一般FallBack通用的光照模式中可以找到,对于顶点动画、透明度等需要特殊处理。
- fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
- noshadow:取消所有阴影。
- 透明度混合和测试:
- alpha、alpha:auto:透明测试。
- alpha:blend:透明混合。
- alphatest:VariableName:VariableName用来剔除不满足的片元。
- 光照:
- noambient:不应用任何环境光照或光照探针(light probe)。
- novertexlights:不应用逐顶点光照。
- noforwardadd:去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。
- 控制代码生成:
- exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。
Input结构体(数据来源)
包含主纹理和法线纹理的采样坐标uv_MainTex
和uv_BumpMap
。采样纹理必须以“uv”作为前缀(或用”uv2”前缀表明为次纹理坐标集合)。
Unity会背后准备好数据,可以直接使用这些数据。
SurfaceOutput(表面属性)
存储表面属性的结构体,作为光照函数输入来计算光照。
// Lighting.cginc中定义 struct SurfaceOutput { fixed3 Albedo; // diffuse color fixed3 Normal; // tangent space normal, if written fixed3 Emission; half Specular; // specular power in 0..1 range,高光反射指数部分。 fixed Gloss; // specular intensity,高光反射强度系数。 fixed Alpha; // alpha for transparencies }; // UnityPBSLighing.cginc中定义 struct SurfaceOutputStandard { fixed3 Albedo; // base (diffuse or specular) color fixed3 Normal; // tangent space normal, if written half3 Emission; half Metallic; // 0=non-metal, 1=metal half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies }; struct SurfaceOutputStandardSpecular { fixed3 Albedo; // diffuse color fixed3 Specular; // specular color fixed3 Normal; // tangent space normal, if written half3 Emission; half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies };
Shader "Custom/Chapter 17/Normal Extrusion" { Properties { _ColorTint ("Color Tint", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _BumpMap ("Normalmap", 2D) = "bump" {} _Amount ("Extrusion Amount", Range(-0.5, 0.5)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" } LOD 300 CGPROGRAM // surf - 表面函数 // CustomLambert - 光照模式(兰伯特漫反射) // vertex:myvert - 自定义顶点修改函数 // finalcolor:mycolor - 自定义最后颜色修改函数 // addshadow - 生成阴影捕获的Pass.因为修改了顶点位置,需要重新投射 // exclude_path:deferred/exclude_path:prepass - 不要为延迟渲染路径生成相应的Pss // nometa - 不要生成元数据的Pass (that’s used by lightmapping & dynamic global illumination to extract surface information). #pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa #pragma target 3.0 fixed4 _ColorTint; sampler2D _MainTex; sampler2D _BumpMap; half _Amount; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; // 自定义顶点修改函数:把顶点延法线方向扩展。 void myvert (inout appdata_full v) { v.vertex.xyz += v.normal * _Amount; } // 表面函数 void surf (Input IN, inout SurfaceOutput o) { fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = tex.rgb; o.Alpha = tex.a; o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); } // 自定义兰伯特光照 half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) { half NdotL = dot(s.Normal, lightDir); half4 c; c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten); c.a = s.Alpha; return c; } // 自定义最后颜色修改函数 void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) { color *= _ColorTint; } ENDCG } FallBack "Legacy Shaders/Diffuse" }
表面着色器的渲染流水线
例如生成LightMode为ForwardBase的Pass流程:
1. 将表面着色器中CGPROGRAM
和ENDCG
之间的代码复制过来。
2. Unity根据上述代码生成结构体v2f_surf(顶点着色器的输出)。如果Input定义了一些变量但没有使用,生成的结构体也不会包含该变量。还会包含阴影纹理坐标、光照纹理坐标、逐顶点光照等。
3. 生成顶点着色器。
3.1. 如果定义了顶点修改函数,会先调用,或填充自定义Input结构体中的变量。Unity会分析该函数修改的数据,通过Input结构体把修改结果存储到v2f_surf相应变量。
3.2. 计算v2f_surf中其他变量:顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理等。
3.3. 把v2f_surf传递给片元着色器。
4. 生成片元着色器。
4.1. 将v2f_surf变量(纹理坐标、视角方向)填充到Input结构体。
4.2. 调用自定义表面函数,填充SurfaceOutput结构体。
4.3. 调用光照函数得到初始的颜色值。如果使用内置的Lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算。
4.4. 进行其他颜色叠加。例如没有光照烘培,会添加逐顶点光照的影响。
4.5. 调用最后的颜色修改函数。