UnityShader中级 复杂光照 Unity 渲染路径:Forward Deferred Legacy Vertex Lit Legacy Deferred
标签名
描述
Always
不管使用哪种渲染路径,该pass总会被渲染,但不会计算任何光照
ForwardBase
用于前向渲染 。该pass会计算环境光,最重要的平行光,逐顶点/SH光源和Lightmaps
ForwardAdd
用于前向渲染 。该pass会计算额外的逐像素光源,每个pass对应一个光源。
Deferred
用于延迟渲染 。该pass会渲染G缓冲(G-buffer)
ShadowCaster
把物体的深度信息渲染到阴影映射纹理或一张深度纹理中
PrepassBase
用于遗留的延迟渲染 。该pass会渲染法线和高光反射的指数部分。
PrepassFinal
用于遗留的延迟渲染 。该pass会通过合并纹理,光照和自发光来渲染得到最后颜色
Vertex, VertexLMRGBM和VertexLM
用于遗留的顶点照明渲染
在Shader对光照渲染模式进行设置 1 2 Tags{"LightMode" = "ForwardBase" }
注意:用空格进行不同标签的分割
ForwardBase向下兼容Deferred,但是不支持阴影
不同标签互不兼容
前向渲染 Unity 前向渲染:逐顶点,逐像素处理,球谐函数。
两种光源的区分
逐像素光源,光照更细腻,对性能要求更高,会对Shader的计算产生影响
就像是在顶点着色器中计算颜色一样,锯齿状的边缘会非常明显
多个平行光,挑一个最重要的来进行计算,如果剩下的平行光数量满足在 质量 中 对逐像素 光源数量的限制,并且在光源的RenderMode中设置的是Auto,那么剩下的平行就会自动成为 逐像素光源,然后放到ForwardAdd里面去计算,超过限制就成为逐顶点光源
逐像素光源:
1,当光源设置为Import时,是逐像素光源。(不受限制与质量设置里面pixel light count )(Forward Add)
2,光源为auto时,个数在pixel light count 以内的光源都是逐像素光源。(Forward Add)
3,光源为auto时,个数超过pixel light count ,那么按光源对物体影响重要程度排序后,前pixel light count个数的光源为逐像素光源。(Forward Add)
4,最重要的平行光为逐像素光源。(Forward Base)
逐顶点光源:(unity默认要求逐顶点光源不超过4个,超过的按SH光源处理,也就是球协函数)
1,当光源设置为NotImport时,是逐顶点光源。(Forward Base)
2,超过pixel light count 的光源为逐顶点光源。(Forward Base)
逐顶点光源计算 逐顶点光源计算中函数用到的参数可以在下载到的对应shader文件 UnityShaderVariables.cginc 中查找到
单纯在一个没有FallBack,仅仅实现了一个ForWardBase的Shader中进行计算,只会有一次DrawCall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifdef LIGHTMAP_OFF float3 shLight = ShadeSH9(float4(v.normal,1.0 )); o.vertexLight = shLight; #ifdef VERTEXLIGHT_ON float3 vertexLight = Shade4PointLights(unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0, unity_LightColor0.rgb,unity_LightColor1.rgb,unity_LightColor2.rgb,unity_LightColor3.rgb, unity_4LightAtten0, o.worldNormal,o.worldNormal); o.vertexLight += vertexLight; #endif #endif
每次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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 Shader "Unlit/11" { Properties { _Diffuse("Diffuse" ,Color) =(1 ,1 ,1 ,1 ) _Specular("Specular" ,Color) = (1 ,1 ,1 ,1 ) _Gloss("Gloss" ,Range(8.0 ,256 )) = 20 } SubShader { Tags { "RenderType" ="Opaque" } LOD 100 Pass { Tags{"LightMode" = "ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 vertex : SV_POSITION; float3 worldNormal :TEXCOORD0; float3 worldPos : TEXCOORD1; float3 vertexLight : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Diffuse; float4 _Specular; float _Gloss; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; #ifdef LIGHTMAP_OFF float3 shLight = ShadeSH9(float4(v.normal,1.0 )); o.vertexLight = shLight; #ifdef VERTEXLIGHT_ON float3 vertexLight = Shade4PointLights(unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0, unity_LightColor0.rgb,unity_LightColor1.rgb,unity_LightColor2.rgb,unity_LightColor3.rgb, unity_4LightAtten0, o.worldNormal,o.worldNormal); o.vertexLight += vertexLight; #endif #endif return o; } fixed4 frag (v2f i) : SV_Target { float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 normal = normalize(i.worldNormal); float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); float3 diffuseColor = _LightColor0.rgb * _Diffuse.rgb * (dot(lightDir,normal) * 0.5 + 0.5 ); float3 halfView = normalize(lightDir + viewDir); float3 specularColor = _LightColor0.rgb * _Specular.rgb * pow (max(0 ,dot(normal,halfView)),_Gloss); float3 resColor = environmentLightColor + (specularColor + diffuseColor) + i.vertexLight; return float4(resColor,1 ); } ENDCG } Pass { Tags{"LightMode" = "ForwardAdd" } Blend One One CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdadd #include "UnityCG.cginc" #include "AutoLight.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 vertex : SV_POSITION; float3 worldNormal :TEXCOORD0; float3 worldPos : TEXCOORD1; LIGHTING_COORDS(2 ,3 ) }; float4 _Diffuse; float4 _Specular; float _Gloss; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; TRANSFER_VERTEX_TO_FRAGMENT(o); return o; } fixed4 frag (v2f i) : SV_Target { float3 environmentLightColor = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 normal = normalize(i.worldNormal); float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); float3 diffuseColor = _LightColor0.rgb * _Diffuse.rgb * (dot(lightDir,normal) * 0.5 + 0.5 ); float3 halfView = normalize(lightDir + viewDir); float3 specularColor = _LightColor0.rgb * _Specular.rgb * pow (max(0 ,dot(normal,halfView)),_Gloss); float atten = LIGHT_ATTENUATION(i); float3 resColor = environmentLightColor + (specularColor + diffuseColor) * atten; return float4(resColor,1 ); } ENDCG } } }
前向渲染补充
1,ForwardAdd这个Pass需要和ForwardBase一起使用,否则会被Unity忽略掉(unity5.x),在新版本中,不会忽略,但是渲染会出错。
2,在Forward和Deferred渲染路径下,Forward的Pass均能被正常渲染。
3,ForwardAdd对每一个逐像素光源都会执行一次Pass,所以不要在ForwardAdd里面计算 unity_4LightPos[x,y,z]0中的数据。会重复计算。
在Frame Debugger,中的其他光源排序,按照光源的强度,大小和距离进行排序
延迟渲染 延迟渲染原理: 延迟渲染主要包含了两个Pass。在第一个Pass中,我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息存储到G缓冲区中。然后,在第二个Pass中,我们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫发射系数等,进行真正的光照计算。
延迟渲染缺点: 1,不支持真正的抗锯齿功能。
2,不能处理半透明物体。
3,对显卡有一定要求。如果要使用延迟渲染的话,显卡必须支持MRT、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
延迟渲染优点: 1,所有光都是逐像素光源。计算复杂度前向渲染 O(m*n),延迟渲染O(m+n)。
2,制作后处理等,可直接获取深度值。
1)第一个Pass用于渲染G缓冲。在这个Pass中,我们会把物体的漫反射颜色、高光发射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中。对于每个物体来说,这个Pass仅会执行一次。
2)第二个Pass用于计算真正的光照模型。这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
G缓冲 默认的G缓冲区(注意,不同Unity版本的渲染纹理存储内容会有所不同)包含了以下几个渲染纹理(Render Texture,RT)。
RT0:格式是ARGB32(每个通道8位),RGB通道用于存储漫反射颜色,A通道储存遮挡。
RT1:格式是ARGB32(每个通道8位),RGB通道用于存储高光反射颜色,A通道同于用于存储高光反射的指数部分。
RT2:格式是ARGB2101010,RGB通道用于存储世界空间法线,A通道没有被使用。
RT3:格式是ARGB2101010/ARGBHalf(每个通道16位),(低动态光照渲染/高动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲。
当在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard 光照模型。
实现 第一个Pass 在没有自定义Deferred第二个Pass的情况下,片元着色器frag中输出的每个元素都要按照给定的顺序,漫反射光,高光,法线,深度缓冲
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 _Gloss("Gloss" ,Range(8.0 ,256 )) = 8 struct DeferredOutData { float4 gBuffer0 : SV_TARGET0; float4 gBuffer1 : SV_TARGET1; float4 gBuffer2 : SV_TARGET2; float4 gBuffer3 : SV_TARGET3; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Diffuse; float4 _Specular; float _Gloss; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } DeferredOutData frag (v2f i) { DeferredOutData d; float3 color = tex2D(_MainTex,i.uv).rgb * _Diffuse.rgb; d.gBuffer0.rgb = color; d.gBuffer0.a =1 ; d.gBuffer1.rgb = _Specular.rgb; d.gBuffer1.a = _Gloss /256 ; d.gBuffer2 = float4(i.normal *0.5 + 0.5 ); d.gBuffer3 = float4(color,1 ); return d; }
在相机中出现需要进行的设置
屏幕后处理(第二个Pass) 就是延迟渲染的第二Pass,不需要进行深度写入
方法
将给定的裁剪空间坐标转换为屏幕空间坐标
1 2 3 4 5 6 7 inline float4 ComputeScreenPos (float4 pos) { float4 o = ComputeNonStereoScreenPos(pos); #if defined(UNITY_SINGLE_PASS_STEREO) o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w); #endif return o; }
在顶点着色器中使用,结果还不一个完整的屏幕空间坐标,需要后续在片元着色器中用xy分量除以zw分量(齐次除法)
投影空间不是线性的,插值是线性的
这个方法按照习惯会在顶点着色器中被调用,如果方法帮我们进行齐次除法,最后插值的结果就不会很准确,因此需要我们手动去片元着色器中操作
1 o.uv = ComputeScreenPos(o.vertex);
这一步之后还有很多操作需要被进行,这个时候拿到的uv是在屏幕空间的像素,延迟渲染的屏幕后处理进行的打光操作,打光操作需要在世界空间进行,因此需要把uv转换到齐次裁剪空间,再从齐次裁剪空间到世界空间
在第一次使用过程中,引入头文件的过程缺少一个 “ 结尾的标点符号,导致两个识别不出来两个Pass
1 2 3 #include "UnityCG.cginc" #include "UnityDeferredLibrary.cginc" #include "UnityGBuffer.cginc
完整代码
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 Shader "Unlit/12-Deferred" { Properties { } SubShader { Pass { ZWrite Off Blend [_SrcBlend] [_DstBlend] CGPROGRAM #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma multi_compile_lightpass #pragma exclude_renderers nomrt #include "UnityCG.cginc" #include "UnityDeferredLibrary.cginc" #include "UnityGBuffer.cginc" sampler2D _CameraGBufferTexture0; sampler2D _CameraGBufferTexture1; sampler2D _CameraGBufferTexture2; sampler2D _CameraGBufferTexture3; struct a2v { float4 vertex : POSITION; float3 normal :NORMAL; }; unity_v2f_deferred vert (a2v i) { unity_v2f_deferred o; o.pos = UnityObjectToClipPos(i.vertex); o.uv = ComputeScreenPos(o.pos); o.ray = UnityObjectToViewPos(i.vertex); o.ray = lerp(o.ray, i.normal, _LightAsQuad); return o; } float4 frag (unity_v2f_deferred i) : SV_Target { float2 uv = i.uv.xy / i.uv.w; float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,uv); depth = Linear01Depth(depth); float3 rayToFarPlane = i.ray * (_ProjectionParams.z / i.ray.z); float4 viewPos = float4(rayToFarPlane * depth,1 ); float3 worldPos = mul(unity_CameraToWorld,viewPos).xyz; float fadeDist = UnityComputeShadowFadeDistance(worldPos,viewPos.z); #if defined(SPOT) float3 toLight = _LightPos.xyz - worldPos; half3 lightDir = normalize(toLight); float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1 )); float atten = tex2Dbias(_LightTexture0,float4(uvCookie.xy / uvCookie.w,0 ,-8 )).w; atten *= uvCookie.w < 0 ; atten *= tex2D(_LightTextureB0,dot(toLight,toLight) * _LightPos.w).r; atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv); #elif defined(DIRECTIONAL) || defined(DIRECTIONAL_COOKIE) half3 lightDir = -_LightDir.xyz; float atten = 1.0 ; atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv); #if defined(DIRECTIONAL_COOKIE) float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1 )); atten *= tex2Dbias(_LightTexture0,float4(uvCookie.xy,0 ,-8 )).w; #endif #elif defined(POINT) || defined(POINT_COOKIE) float3 toLight = _LightPos.xyz - worldPos; half3 lightDir = normalize(toLight); float atten = tex2D(_LightTextureB0,dot(toLight,toLight) * _LightPos.w).r; atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv); #if defined(POINT_COOKIE) float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1 )); atten *= texCUBEbias(_LightTexture0,float4(uvCookie.xyz,-8 )).w; #endif #else half3 lightDir = 0 ; float atten = 0 ; #endif half3 lightColor = _LightColor.rgb * atten; half4 gbuffer0 = tex2D(_CameraGBufferTexture0, uv); half4 gbuffer1 = tex2D(_CameraGBufferTexture1, uv); half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv); half3 diffuseColor = gbuffer0.rgb; half3 specularColor = gbuffer1.rgb; float gloss = gbuffer1.a * 256 ; float3 worldNormal = normalize(gbuffer2.xyz * 2 - 1 ); float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); float3 halfView = normalize(viewDir + lightDir); half3 diffuse = lightColor * diffuseColor * (dot(worldNormal,lightDir) * 0.5 + 0.5 ); half3 specular = lightColor * specularColor * pow (max(0 ,dot(worldNormal,halfView)),gloss); return float4(diffuse + specular,1 ); } ENDCG } Pass { ZTest Always Cull Off Zwrite Off Stencil { ref[_stencilNonBackground] readMask[_StencilNonBackground] compback equal compfront equal } CGPROGRAM #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma exclude_renderers nomrt #include "UnityCG.cginc" sampler2D _LightBuffer; struct v2f { float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; }; v2f vert (float4 vertex : POSITION,float2 texcoord : TEXCOORD0) { v2f o; o.vertex = UnityObjectToClipPos(vertex); o.texcoord.xy = texcoord; #ifdef UNITY_SINGLE_PASS_STEREO o.texcoord = TransformStereoScreenSpaceTex(o.texcoord, 1.0 ); #endif return o; } float4 frag (v2f i) :SV_Target { return -log2(tex2D(_LightBuffer,i.texcoord)); } ENDCG } } }
1 2 3 4 5 6 7 8 float3 worldPos; float2 uv; half3 lightDir; float atten;float fadeDist;UnityDeferredCalculateLightParams(i,worldPos,uv,lightDir,atten,fadeDist);
这样的操作需要的前置cginc头文件是
1 #include "UnityDeferredLibrary.cginc"
模板测试 常用场景 通常在屏幕后处理和UI的Shader中用的比较多
需求场景 如果一个Shader本身有自己的深度需求和透明需求,这个时候还要控制某些东西渲染,某些东西不渲染,或者在某个区域渲染,在某个区域不渲染,这样的一个功能需求的时候,就会用到模板测试
说白了还是根据给出的条件来判断某个片元是否应该抛弃
概念 Stencil (模板测试/蒙版测试): 与深度测试,透明度测试类似,决定一个片元是否被扔掉。深度测试的比较数据在深度缓冲中,透明度测试的比较对象是颜色缓冲中的值,而模版测试的比较数据在Stencil中,并且模板测试要先于深度测试与透明度测试,在fragment函数之前就会执行模板测试。
Ref 就是参考值,当参数允许赋值时,会把参考值赋给当前像素
ReadMask 对当前参考值和已有值进行mask操作,默认值255,一般不用
WriteMask 写入Mask操作,默认值255,一般不用
Comp 比较方法。是拿Ref参考值和当前像素缓存上的值进行比较。默认值Always
Pass 当模版测试和深度测试都通过时,进行处理
Fail 当模版测试和深度测试都失败时,进行处理
ZFail 当模版测试通过而深度测试失败时,进行处理
Comp :
Always
Greater - 大于
GEqual - 大于等于
Less - 小于
LEqual - 小于等于
Equal - 等于
NotEqual - 不等于
Always - 永远通过
Never - 永远通不过
pass,Fail,ZFail:
Keep 保持(即不把参考值赋上去,直接不管)
Zero 归零
Replace 替换(拿参考值替代原有值)
IncrSat 值增加1,但不溢出,如果到255,就不再加
DecrSat 值减少1,但不溢出,值到0就不再减
Invert 反转所有位,如果1就会变成254
IncrWrap 值增加1,会溢出,所以255变成0
DecrWrap 值减少1,会溢出,所以0变成255
案例 写在Pass里面的不能被Unity识别,至少在设置窗口没显示
1 Tags{"LightMode" ="ForwardBase" "Queue" ="Geometry-1999" }
根据摄像机对物体的渲染顺序进行模板缓冲写入 根据自定义的 参考值 _Stencil_Ref,以及配合一下代码,实现反深度测试的效果
1 2 3 4 5 6 7 8 9 ZTest Off Stencil { Ref[_Stencil_Ref] Comp GEqual Pass Replace }
一个材质和Shader可以确定唯一的表现效果
可以使用多个材质配合一个Shader来实现Shader复用
根据渲染顺序来进行模板缓冲写入 充当背景的物体设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Properties { _MainTex ("Texture" , 2 D) = "white" {} _refVal("refVal" ,int ) = 1 } SubShader { Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry" } LOD 100 Pass { Stencil { Ref[_refVal] Pass Replace Fail Replace ZFail Replace }
需要穿透的物体设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Properties { _MainTex ("Texture" , 2 D) = "white" {} _refVal("refVal" ,int ) = 1 } SubShader { Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry+2" } LOD 100 Pass { Stencil { Ref[_refVal] Comp GEqual Pass Replace }
穿透物体设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Properties { _MainTex ("Texture" , 2 D) = "white" {} _refVal("refVal" ,int ) = 1 } SubShader { Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry+1" } ColorMask 0 LOD 100 Pass { Stencil { Ref[_refVal] Comp GEqual Pass Replace }
注意:在透视投影的情况下,这种形式下的挖洞需要有单独的背景或者游戏对象去支持,单纯的天空盒会是黑色
区域蒙版 实践效果参照Unity的UI组件Mask的效果,在给定区域内才能正常显示,超出范围会被裁剪
作为背景Stencil设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Properties { _MainTex ("Texture" , 2 D) = "white" {} _refVal("RefVal" ,int ) =1 } SubShader { Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry" } LOD 100 Pass { Stencil { Ref[_refVal] Comp Always Pass Replace }
需要被裁剪的Stencil设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Properties { _MainTex ("Texture" , 2 D) = "white" {} _refVal("RefVal" ,int ) =1 } SubShader { Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry+1" } LOD 100 Pass { Stencil { Ref[_refVal] Comp Equal }
光照衰减 Unity在内部使用一张名为_LightTexture0的纹理来计算光源衰减。我们通常只关心_LightTexture0对角线上的纹理颜色值,这些值表明了再光源空间中不同位置的点的衰减值。例如(0,0)点表明了与光源位置重合的点的衰减值,而(1,1)点表明了再光源空间中所关心的距离最远的点的衰减。
1 2 3 float3 lightCoord = mul(_LightMatrix0,float4(i.worldPos,1 )).xyz; fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
现将世界坐标与_LightMatrix0相乘得到在光源空间中的位置,用光源空间中顶点距离的平方来对纹理采样,然后,使用宏UINITY_ATTEN_CHANNEL来得到衰减纹理中的衰减值所在的分量,以得到最终的衰减值。
数学公式计算光照衰减
1 2 3 float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz); atten = 1.0 /distance;
对于点光源和方向光可以简单用这个来进行计算,区域光就不能使用,区域光的各个参数不可控制,一般来说都是使用Unity中自带的
阴影映射原理 Unity中阴影 1,Shadow Map:它会首先把摄像机位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是摄像机看不到的地方。
摄像机放置到光源位置上就会产生一个深度映射纹理,根据这个深度图区判断一个点是不是在阴影内
2,Screenspace Shadow Map:Unity首先会通过调用LightMode 为 ShadowCaster的Pass来得到可投射阴影是光源的阴影映射纹理以及摄像机的深度纹理。然后,根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明该表面虽然是可见的,但是却出于该光源的阴影中。通过这样的方式,阴影图就包含了屏幕空间中所有阴影的区域。如果我们想要一个物体接收来自其他物体的阴影,只需要在Shader中对阴影图进行采样。
总结来说就是分别在光源位置和摄像机位置产生一张阴影采样图,通过这两张图得到一个屏幕空间的阴影图。
阴影采样图的产生
1 Tags{"LightMode" = "ShadowCaster" }
就可以,一般在FallBack中就会存在一个阴影的Pass,不需要特别去写
一个物体接收来自其他物体的阴影,以及它向其他物体投射阴影是两个过程。
1,如果我们想要一个物体接收来自其他物体的阴影,就必须在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果。
2,如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在Unity 中,这个过程通过为该物体执行LightMode 为ShadowCaster 的 Pass 来实现的。如果使用了屏幕空间的投射映射技术,Unity还会使用这个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 Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #pragma multi_compile_shadowcaster #pragma multi_compile_instancing #include "UnityCG.cginc" struct v2f { V2F_SHADOW_CASTER; UNITY_VERTEX_OUTPUT_STEREO }; v2f vert ( appdata_base v ) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) return o; } float4 frag ( v2f i ) : SV_Target { SHADOW_CASTER_FRAGMENT(i) } ENDCG }
接受阴影 前向渲染手动配置接受阴影
头文件
1 #include "AutoLight.cginc"
v2f结构体
1 2 3 4 5 6 7 8 struct v2f { float4 pos : SV_POSITION; float3 worldNormal :TEXCOORD0; float3 worldPos : TEXCOORD1; float3 vertexLight : TEXCOORD2; SHADOW_COORDS(3 ) };
vert内容 需要放在顶点着色器最后面
1 2 TRANSFER_SHADOW(o); return o;
frag内容 1 2 3 4 5 6 7 8 9 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos); float3 resColor = environmentLightColor + (specularColor + diffuseColor) * atten; return float4(resColor,1 );
AlphaTest下的阴影 前置属性设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Properties { _MainTex("MainTex" ,2 D) = "white" {} _Color("Diffuse" ,Color) =(1 ,1 ,1 ,1 ) _Specular("Specular" ,Color) = (1 ,1 ,1 ,1 ) _Gloss("Gloss" ,Range(8.0 ,256 )) = 20 _Cutoff("CutOut" ,Range(0 ,1 )) = 0.1 } SubShader { Tags { "RenderType" ="TransparentCutOut" "IgnoreProjector" ="True" "Queue" = "AlphaTest" } LOD 100
在片元着色器中的输出格式 片元裁剪
1 2 3 4 5 6 7 float4 texColor = tex2D(_MainTex,i.uv); clip(texColor.a - _Cutoff); float3 diffuseColor = _LightColor0.rgb * _Color.rgb * (dot(lightDir,normal) * 0.5 + 0.5 ) * texColor;
混合输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos); float3 resColor = environmentLightColor + (diffuseColor + specularColor) * atten+ i.vertexLight; return float4(resColor,1 );
AlphaBlend阴影 Tags设置
1 Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "true" }
1 2 3 Tags{"LightMode" = "ForwardBase" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
可以让透明物体强制投射影子和接受影子
投射影子
接受影子
将渲染顺序限制在AlphaTest和Transparent之间