详解Unity3D Shader之Shader Lab框架
笔者介绍
姜雪伟,泰课海洋老师
网上已有诸多关于Shader的教程,本文不再赘述基础知识,而是直接聚焦重点。为帮助读者理解Unity3D引擎内部对于Shader加载的实现原理,本文将结合C++底层代码,深入剖析Unity3D中Shader的编写。
Unity3D中Shader的基本结构
在Unity3D的每个Shader里,都包含SubShader代码段。以下是一个示例:
SubShader {
Pass {
// 不写入深度缓冲区
ZWrite off
// 设置Alpha混合
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _Color;
struct v2f {
float4 pos:SV_POSITION;
float4 texcoord : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.texcoord = v.texcoord;
return o;
}
half4 frag(v2f i):COLOR0 {
half4 col = _Color * tex2D(_MainTex, i.texcoord.xy);
return col;
}
ENDCG
}
pass {
// 此处可添加具体代码
}
}
SubShader {
Pass {
// 此处可添加具体代码
}
}
SubShader中的代码段是核心程序,其中包含Pass通道。Pass通道定义了输出结构体、处理顶点函数和处理片段函数。在一个Shader中可以包含多个SubShader,这些SubShader能够根据硬件情况自行适配。
结合DirectX的Shader代码分析
接下来,我们结合DirectX中的Shader代码,深入分析Unity3D中的Shader。以下是DirectX的Shader代码:
//--------------------------------------------------------------
// 全局变量
//--------------------------------------------------------------
float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecLightDir;
float4 vecEye;
float4 materialAmbient;
float4 materialDiffuse;
float4 materialSpecular;
//-------------------------------------------------------------
// 顶点渲染器输出结构
//-------------------------------------------------------------
struct VS_OUTPUT {
float4 Pos : POSITION;
float4 Color : COLOR;
};
//-------------------------------------------------------------
// 顶点渲染器
//-------------------------------------------------------------
VS_OUTPUT VS( float4 Pos: POSITION, float3 Normal: NORMAL, uniform bool bEnableSelfShadow ) {
VS_OUTPUT Out = (VS_OUTPUT) 0;
// 顶点坐标变换
Out.Pos = mul(Pos, matWorldViewProj);
// 单位化光照方向向量
float3 LightDir = normalize( vecLightDir );
// 计算观察方向
float3 PosWorld = normalize( mul(Pos, matWorld) );
float3 ViewDir = normalize( vecEye - PosWorld );
// 计算法向量方向和漫反射强度
float3 NormalWorld = normalize( mul(Normal, matWorld) );
float4 diff = saturate( dot(NormalWorld, LightDir) );
// 计算反射光方向( R = 2 * (N.L) * N - L)和镜面反射强度
float3 Reflect = normalize( 2 * diff * NormalWorld - LightDir );
float4 specu = pow( saturate(dot(Reflect, ViewDir)), 0.5 );
// 各种光的颜色
float4 ambientColor = { 0.1f, 0.0f, 0.0f, 1.0f};
float4 diffuseColor = { 1.0f, 0.0f, 0.0f, 1.0f};
float4 specularColor = { 1.0f, 0.0f, 0.0f, 1.0f};
// 计算顶点颜色
if (bEnableSelfShadow) // 启用自阴影
{
float shadow = saturate(4 * diff);
// 计算顶点颜色 = Ambient + Shadow * ( Diffuse + Specular )
Out.Color = ambientColor * materialAmbient + shadow *
(diffuseColor * materialDiffuse * diff +
specularColor * specu * materialSpecular);
}
else // 禁用自阴影
{
Out.Color = ambientColor * materialAmbient +
diffuseColor * materialDiffuse * diff +
specularColor * specu * materialSpecular;
}
return Out;
}
//--------------------------------------------------------------
// 技术
//--------------------------------------------------------------
technique TShaderSelfShadow {
pass P0 {
VertexShader = compile vs_2_0 VS(true);
}
}
technique TShaderNoSelfShadow {
pass P0 {
VertexShader = compile vs_2_0 VS(false);
}
}
上述Shader定义了输出结构体,这与Unity3D中定义的结构体颇为相似。DirectX中的Shader定义了顶点渲染器VS函数:
VS_OUTPUT VS( float4 Pos: POSITION, float3 Normal: NORMAL, uniform bool bEnableSelfShadow )
其中,VS函数结合了顶点着色器和片段着色器的功能。此外,它还定义了technique,在technique中包含pass通道。这里的technique类似于Unity3D中的SubShader,pass通道类似于SubShader中的pass通道。
由于Unity引擎内部的实现细节不可见,我们以DirectX中的C++代码为例,来揭示引擎内部的实现原理。以下是完整的C++代码(此处原文档代码未完整给出,后续可补充完整):
// 此处应补充完整的C++代码
通过以上分析,我们可以更深入地理解Unity3D Shader的实现原理以及与DirectX Shader的关联,从而更好地进行Shader的开发和优化。