表面着色器
与顶点/片元区别 | CG代码是直接而且必须写在subshader块中,unity会在背后生成多个Pass, CG中定义表面着色器的具体代码 |
结构 | 两个结构体、编译指令 |
工作原理 | 1.surfaceFunc它将您需要的所有 UV 或数据作为输入,并填充输出结构 SurfaceOutput 2.表面着色器编译器随后计算出需要的输入、填充的输出等等,并生成实际的顶点和像素着色器以及渲染通道来处理前向和延迟渲染 |
特点 | 生成的表面着色器代码会尝试处理所有可能的光照/阴影/光照贴图情况。 快速实现各种光照效果 |
缺点 | 失去各种优化和各种特效实现的控制–对性能有影响 无法完成一些自定义的渲染效果 |
建议 | 1.各种光源和全局光照==表面着色器,但注意性能 2.光源数目非常少 == 顶点/片元着色器 3.有很多自定义的渲染效果 == 顶点/片元着色器 |
编译指令
定义 | 编写者与unity沟通的重要手段 |
---|---|
作用 | 指明该表面着色器使用的表面函数和光照函数,并设置一些可选参数 |
位置 | 表面着色器的CG块中的第一句代码往往就是它的编译指令 |
格式 | #pragma surface surfaceFunction lightModel [optionalparams] #pragma surface :用于定义表面着色器 surfaceFunction:指明使用的表面函数 lightModel:使用的光照模型 optionalparams:可选参数来控制表面着色器的一些行为 |
- 表面函数
一个对象的表面属性定义了它的反射率、光滑度、透明度等值 surfaceFunction就用于定义这些表面函数 surf
/// <summary>
/// 表面函数
/// </summary>
/// <param name="IN">输入结构体包含表面函数所需的任何纹理坐标和额外自动变量</param>
/// <param name="SurfaceOutput">输出结构体 unity内置的结构体,需要配合不同的光照模型使用 </param>
void surf (Input IN, inout SurfaceOutput o)
void surf (Input IN, inout SurfaceOutputStandard o)
void surf (Input IN, inout SurfaceOutputStandardSpecular o)
- 光照函数
使用表面函数中设置的各种表面属性,来应用某些光照模型,进而模拟物体表面的光照效果。
光照模型函数:
内置光照模型 | cginc | ||
---|---|---|---|
基于物理 | Standard | 使用SurfaceOutputStandard输出结构体,并与unity中的标准着色器(金属性工作流)匹配 | UnityPBSLighting.cginc |
StandardSpecular | 使用SurfaceOutputStandardSpecular 输出结构体,并与unity中的标准着色器(镜面反射设置)匹配 | ||
非基于物理 | Lambert (漫反射光照)和 BlinnPhong(镜面反射光照) | 在低端硬件上可以提高渲染速度 | Lighting.cginc |
自定义光照函数
自定义光照函数 | 注意 | |
---|---|---|
声明光照模型 | 可包含多个名称以Lighting开头的常规函数 | 无需声明所有函数。光照模型不一定会使用视图方向。同样,如果仅在前向渲染中使用光照模型,请勿声明 _Deferred 和 _Prepass 函数。这确保了使用视图方向的着色器仅编译到前向渲染 |
自定义GI | 自定义光照贴图数据和探针的解码 | 要对 Unity 标准光照贴图和 SH 探针进行解码,您可以使用内置函数 DecodeLightmap 和 ShadeSHPerPixel |
//自定义光照贴图数据和探针的解码
half4 Lighting<Name>_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi)
常规函数 | 适用 |
---|---|
half4 Lighting < Name> (SurfaceOutput s, UnityGI gi) | 在_不依赖于_视图方向的光照模型的前向渲染路径中使用 |
half4 Lighting< Name> (SurfaceOutput s, half3 viewDir, UnityGI gi) | 在_依赖于_视图方向的光照模型的前向渲染路径中使用 |
half4 Lighting< Name>_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal) | 在延迟光照路径中使用 |
half4 Lighting< Name>_PrePass (SurfaceOutput s, half4 light) | 在光照预通道(旧版延迟)光照路径中使用 |
- 其他可选参数
可选参数 | 适用 | |
---|---|---|
自定义修改器函数 | 更改或计算传入的顶点数据/更改最终计算的片元颜色 | vertex:vertexFunc自定义顶点修改函数 finalcolor:ColorFunc 自定义最终颜色修改函数 finalgbuffer:ColorFunc 更改G缓冲区内容的自定义延迟路径 finalprepass:ColorFunc 自定义预通道基本路径 |
阴影 | 提供其他指令来控制阴影和曲面细分的处理方式 | addshadow:生成阴影投射物理通道。用于自定义的顶点修改,以便阴影投射可以获得程序化顶点动画。一般不需要修改 fullforwardshadows:支持前向渲染路径中的所有光源阴影类型。默认仅支持前向渲染中来自一个方向的阴影。如果前向渲染中需要点光源阴影或聚光灯阴影,就使用次指令。 tessellate:TessFunction :使用DX11 GPU曲面细分,计算曲面细分因子 noshadow:禁用此着色器中的所有阴影接受支持 noshadowmask:为此着色器禁用阴影遮罩支持(包括 Shadowmask 和 Distance Shadowmask) |
透明度混合和透明度测试 | 由alpha和alpha test指令控制 | 透明度:1.传统Alpha混合 – 用于淡出对象 2.更符合物理规律的预乘混合–允许半透明表面保留适当的镜面反射。 |
启用半透明度:使生成的表面着色器代码包含混合命令 启用Alpha镂空:根据给定的变量在生成的像素着色器中执行片元废弃(clip) | ||
光照 | 控制光照对物体的影响 | |
控制代码的生成 | 可以调整生成的代码以跳过不需要处理的。这样可以减小着色器,从而提高加载速度 | exclude_path:deferred(延迟着色)、exclude_path:forward(前向) 和 exclude_path(旧版延迟):prepass - 不为给定的渲染路径生成通道。 |
透明相关 | 解释 |
---|---|
alpha/alpha:auto | 简单的光照函数,将选择淡化透明度==alpha:fade 基于物理的光照函数,将选择预乘透明度 == alpha : premul |
alpha:blend | 启用Alpha 混合 |
alpha:fade | 启用传统的淡化透明度 |
alpha:premul | 启用预乘Alpha 透明度 |
alphatest:VariableName | 启用Alpha镂空透明度。VariableName:剪切值 |
keepalpha | 无论输出结构的 Alpha 输出是什么,或者光照函数返回什么,不透明表面着色器都将 1.0 写入 Alpha 通道 可以保持光照函数的 Alpha 值 |
decal:add | 附加贴花着色器 适用于位于其他表面之上并使用附加混合的对象 |
decal:blend | 半透明贴花着色器 适用于位于其他表面之上并使用 Alpha 混合的对象 |
光照指令 | 解释 |
---|---|
noambient | 不应用任何环境光照或光照探针 |
novertexlights | 在前向渲染中不应用任何光照探针或每顶点光源 |
noforwardadd | 禁用前向渲染附加通道。这会使着色器支持一个完整方向光,所有其他光源均进行每顶点/SH 计算。也能减小着色器。 |
nolightmap | 禁用此着色器中的所有光照贴图支持 |
nodynlightmap | 禁用此着色器中的运行时动态全局光照支持 |
nofog | 禁用所有内置雾效支持 |
nometa | 不生成“Meta”通道(由光照贴图和动态全局光照用于提取表面信息) |
nolppv | 禁用此着色器中的光照探针代理体支持 |
其他指令 | 解释 | |
---|---|---|
softvegetation | 仅在开启 Soft Vegetation 时才渲染表面着色器 | |
interpolateview | 在顶点着色器中计算视图方向并进行插值;而不是在像素着色器中计算 | 使像素着色器更快,但会额外消耗一个纹理插值器 |
halfasview | 将半方向矢量传入光照函数而不是视图方向 | 计算半方向并按每个顶点对其进行标准化,稍微快点 |
dualforward | 在前向渲染路径中使用双光照贴图 | |
dithercrossfade | 使表面着色器支持抖动效果 | 此着色器应用于使用细节级别组 (LOD Group) 组件(配置为交叉淡入淡出过渡模式)的游戏对象 |
贴花
通常用于在运行时向材质添加细节(例如,子弹冲击力效果)。贴花在延迟渲染中特别有用,因为贴花在照亮之前会改变 G 缓冲区,因此可以节省开销。
在常规情况下,贴花应该在不透明对象之后渲染,并且不应该是阴影投射物"ForceNoShadowCasting"=“True”
两个结构体
表面着色器中不同函数之间信息传递的桥梁
- Input结构体
具有自由性
float3 viewDir | 包含视图方向,用于计算视差效果、边缘光照等等 | |
使用COLOR 语义的 float4变量 | 包含插值的每顶点颜色 | |
float4 screenPos | 包含反射或屏幕空间效果的屏幕空间位置 | 这不适合 GrabPass;需要使用 ComputeGrabScreenPos 函数自己计算自定义 UV |
float3 worldPos | 包含世界空间位置 | |
float3 worldRefl | 在不修改 o.Normal_ 的情况下,包含世界反射方向 | |
float3 worldRefl; INTERNAL_DATA | 在修改o.Normal_ 的情况下,包含世界反射矢量 | 要获得基于每像素法线贴图的反射矢量,请使用 WorldReflectionVector(IN, o.Normal) |
float3 worldNormal | 在不修改 o.Normal_ 的情况下,包含世界法线方向 | |
loat3 worldNormal INTERNAL_DATA | 在修改了o.Normal_ 的情况下,包含世界法线矢量 | 要获得基于每像素法线贴图的法线矢量,请使用 WorldNormalVector (IN, o.Normal) |
- 标准输出结构:SurfaceOutput
结构体里面的变量是提前声明好,不能增加也不能减少。没有赋值就直接使用默认值
//表面着色器的标准输出结构
struct SurfaceOutput
{
fixed3 Albedo; // 漫射颜色<== 纹理采样 * 颜色属性
fixed3 Normal; // 切线空间法线(如果已写入)表面法线方向
fixed3 Emission;// 自发光 一般会在颜色输出前处理颜色叠加<== c.rgb += o.Emission
// float spec = pow(nh,s.Specular*128.0) *s.Gloss
half Specular; // 高光反射中指数部分的系数 [0,1]范围内的镜面反射能力
fixed Gloss; // 高光反射中强度系数。镜面反射强度
fixed Alpha; // 透明度 Alpha
};
//内置标准光照模型
struct SurfaceOutputStandard
{
fixed3 Albedo; // 基础(漫射或镜面反射)颜色
fixed3 Normal; // 切线空间法线(如果已写入)
half3 Emission; // 自发光
half Metallic; // 0=非金属,1=金属
half Smoothness; // 0=粗糙,1=平滑
half Occlusion; // 遮挡(默认为 1)
fixed Alpha; // 透明度 Alpha
};
//标准镜面反射光照模型
struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // 漫射颜色
fixed3 Specular; // 镜面反射颜色
fixed3 Normal; // 切线空间法线(如果已写入)
half3 Emission; // 自发光
half Smoothness; // 0=粗糙,1=平滑
half Occlusion; // 遮挡(默认为 1)
fixed Alpha; // 透明度 Alpha
};
表面着色器生成顶点/片元着色器
- 直接将表面着色器中CGPROGRAM和ENDCG之间的代码复制。
包括Input结构体、表面函数、光照函数等变量和函数的定义。
- Unity分析代码,并据此生成顶点着色器的输出—v2f_surf结构体(用于顶点着色器和片元着色器之间进行数据传递)
分析在自定义函数中所使用的变量。eg:纹理坐标、视角方向、反射方向等。
v2f_surf中包含一些其他需要的变量.eg:阴影纹理坐标、光照纹理坐标、逐顶点光照等。
-
生成顶点着色器
- 若定义顶点修改函数。1. 调用顶点修改函数修正顶点数据, 或填充自定义的Input结构体中的变量。2.分析顶点修改函数中修改的数据,需要时将修改结构存储到v2f_surf。
- 计算v2f_surf中其他生成的变量值。
顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理的采样坐标等。
- 将v2f_surf传递给片元着色器。
-
生成片元着色器
- 调用v2f_surf中的对应变量填充到Input结构体
纹理坐标、视角方向等。
- 调用自定义的表面函数填充SurfaceOutput结构体。
- 调用光照函数得到初始颜色值。
使用内置Lamert / BlinnPhong光照函数Unity会自动计算动态全局光照,并添加到光照模型的计算中
- 进行其他颜色叠加
没有使用光照烘培,还会添加逐顶点光照的影响
- 若自定义了最后的颜色修改函数。unity调用它进行最后的颜色修改。
/******************************************************
*对模型进行膨胀
1.沿顶点法线方向扩张顶点位置
* NormalExtrusion.Shader
*****************************************************/
Shader "NormlalExtrusion"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1) //颜色
_MainTex("Main Tex", 2D)="white"{} //纹理贴图
_BumpMap("Normal Map", 2D) = "bump"{} //法线纹理
_Amount("Amout", Range(-0.5,0.5)) = 0.1
}
SubShader{
Tags{"RenderType"="Opaque"} //物体
LOD 300 //<300才渲染
CGPROGRAM
//自定义光照模型 修改顶点函数、最后颜色修改、添加阴影效果、不生成延迟渲染通道Pass、不生成meta通道
#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
#pragma target 3.0
fixed4 _Color;
sampler2D _MainTex;
samper2D _BumpMap;
half _Amount;
struct Input{
float2 uv_MainTex;
float2 uv_BumpMap;
};
void myvert(inout appdata_full v){
//对顶点进行伸缩
v.vertex.xyz += v.normal * _Amount;
}
void mycolor(Input IN, SurfaceOutput o, iout fixed4 color){
color *= _Color;
}
void surf (Input IN, iout 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 * (atten * NdotL);
c.a = s.Alpha;
return c;
}
ENDCG
}
Fallback "Legacy Shaders/Diffuse"
}