渲染路径
渲染路径的作用是准备光照相关的属性,如果没有指定渲染路径,相关的属性例如_LightColor0,_WorldSpaceLightPos0将无法正常使用.
目前unity中主要使用前向渲染路径,延迟渲染路径,两种渲染路径. 本篇中主要使用前向渲染路径.
在pass中使用了标签Tags{ “LightMode” = “ForwardBase” }指定前向渲染路径.
另外还有一种标签Tags{ “LightMode” = “ForwardAdd” }也用来指定前向渲染路径
目前已知的区别是ForwardBase有阴影效果,ForwardAdd没有,其他区别暂时不清楚.
Tags{ "LightMode" = "ForwardBase" }
Tags{ "LightMode" = "ForwardAdd" }
多光源代码
我们以高光反射为例,写一个多光源的shader.
Shader "Unity/Custom/NewSurfaceShader" {
Properties{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)//漫反射颜色
_Specular("Specular", Color) = (1, 1, 1, 1)//高光反射颜色
_Gloss("Gloss", Range(8.0, 256)) = 20//光泽度
}
SubShader{
Pass{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase//在forwardBase中获取正确的光照变量
#pragma vertex vert//声明顶点处理方法
#pragma fragment frag//声明片段处理方法
#include "Lighting.cginc"//调用光照函数库
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;//顶点的模型空间位置
float3 normal : NORMAL;//模型空间法线
};
struct v2f {
float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标
float3 worldNormal : TEXCOORD0;//世界坐标系法线
float3 worldPos : TEXCOORD1;//世界坐标系坐标
};
v2f vert(a2v v) {
v2f f;
f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
return f;
}
fixed4 frag(v2f f) : SV_Target{
fixed3 worldNormal = normalize(f.worldNormal);//单位化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//获取光照方向 并单位化
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光颜色
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));//漫反射颜色
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);//朝向相机的方向
fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass{
Tags{ "LightMode" = "ForwardAdd" }
Blend One One//开启混合 我们希望forwardAdd增加额外的光照效果,并不是覆盖原效果
CGPROGRAM
#pragma multi_compile_fwdadd//用于在forwardAdd中获取正确的光照变量
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f f;
f.pos = UnityObjectToClipPos(v.vertex);
f.worldNormal = UnityObjectToWorldNormal(v.normal);
f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return f;
}
fixed4 frag(v2f f) : SV_Target{
fixed3 worldNormal = normalize(f.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT//根据是否使用平行光 决定光照的方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - f.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT//根据光源 计算光照衰减
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(f.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(f.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果
光照衰减
在上述代码中,光照衰减的计算过程如下.
我们发现光照衰减的计算量太大了
#ifdef USING_DIRECTIONAL_LIGHT//根据光源 计算光照衰减
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
我们有两种方式优化光照衰减,
1.简化数学公式
缺点是效果不太好,偶尔有bug
float distance = length(_WorldSpaceLightPos0.xyz - v.worldPosition.xyz);//计算光源距离
atten=1/distance;//计算衰减
2.使用一张纹理计算光照衰减
Unity默认的方式,缺点是精度低,不直观
float3 lightcoord=mul(_LightMatrix0,float4 (v.worldPosition,1)).xyz;//将坐标转换到光源空间
fixed atten=tex2D(_LightTex,dot(lightcoord,lightcoord).rr).UNITY_ATTEN_CHANNEL;//用坐标模的平方对纹理进行采样,得到衰减值