赞
踩
最常使用的光源属性:
这些属性和光源的几何定义息息相关。
光源类型 | 位置 | 方向 | 颜色和强度 | 衰减 |
---|---|---|---|---|
Unity代码 | _WorldSpaceLightPos0 | _LightColor0 | unity_WorldToLight 、_LightTexture0 、UNITY_ATTEN_CHANNEL | |
平行光 | × | Transform的Rotation属性 | 面板控制 | 1.0(无衰减) |
点光源 | Transform的Position属性 | 点光源的位置减去某点的位置 | 面板控制 | 点光源球心处的光照强度最强,球体边界处的最弱,值为0 |
聚光灯 | Transform的Position属性 | 聚光灯的位置减去某点的位置 | 面板控制 | 锥形的顶点处光照强度最强,在锥形的边界处强度为0 |
让场景中可以产生阴影:
我们首先需要让平行光可以收集阴影信息。这需要在光源的Light组件中开启阴影。
让物体投射或接收阴影:
在Unity中,我们可以选择是否让一个物体投射或接收阴影。这是通过设置Mesh Renderer组件中的Cast Shadows和Receive Shadows属性来实现的。
一个物体接收来自其他物体的阴影,以及它向其他物体投射阴影是两个过程。
执行LightMode为ShadowCaster的Pass
来实现的。如果使用了屏幕空间的投影映射技术,Unity还会使用这个Pass产生一张摄像机的深度纹理。对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果
。我们的shader中没有定义这样一个Pass,但我们为它的Fallback指定了一个用于回调Unity Shader,即内置的Specular。虽然Specular本身也没有包含这样一个Pass,但是由于它的Fallback调用了VertexLit,它会继续回调,并最终回调到内置的VertexLit。
我们可以不依赖Fallback,而自行在SubShader中定义自己的LightMode为ShadowCaster的Pass。这种自定义的Pass可以让我们更加灵活地控制阴影的产生。但由于这个Pass的功能通常是可以在多个Unity Shader间通用的,因此直接Fallback是一个更加方便的用法。
在默认情况下,我们在计算光源的阴影映射纹理时会剔除掉物体的背面。但对于内置的平面来说,它只有一个面,因此在本例中当计算阴影映射纹理时,由于右侧的平面在光源空间下没有任何正面(frontface),因此就不会添加到阴影映射纹理中。我们可以将Cast Shadows设置为Two Sided来允许对物体的所有面都计算阴影信息。
实战:
以下shader代码是渲染一个不透明物体在平行光、点光源下的阴影:
Shader "ShaderBook/Chapter9/ForwardRender" { Properties { ...... } SubShader { Tags { "RenderType"="Opaque" } Pass { // 环境光 & 第一个逐像素光照 (平行光) Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" //计算阴影时所用的宏都是在这个文件中声明 #include "AutoLight.cginc" ...... struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; //【阴影一】声明一个用于对阴影纹理采样的坐标:_ShadowCoord SHADOW_COORDS(2) //需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2 }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //【阴影二】在顶点着色器中计算上一步中声明的阴影纹理坐标 //根据平台不同而有所差异:把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中 //TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标 //a2v结构体中的顶点坐标变量名必须是vertex. //顶点着色器的输出结构体v2f必须命名为o,且v2f中的顶点位置变量必须命名为pos TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_Target { //环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //平行光的漫反射 fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); //平行光的高光反射 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); //平行光的光照衰减 fixed atten = 1.0; //【阴影三】使用_ShadowCoord对相关的纹理进行采样,得到阴影信息 fixed shadow = SHADOW_ATTENUATION(i); return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0); } ENDCG } Pass { // 其他逐像素光照 Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM #pragma multi_compile_fwdadd #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "AutoLight.cginc" ...... v2f vert(a2v v) { ...... } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); //【不同光源的光照方向】 #ifdef USING_DIRECTIONAL_LIGHT //如果当前前向渲染Pass处理的光源类型是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); #endif //_LightColor0:该pass处理的逐像素光源的颜色(已经是颜色和强度的乘积了) fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.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) //得到光源空间下的坐标:unity_WorldToLight(从世界空间到光源空间的变换矩阵) float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; //使用这个坐标的模的平方对衰减纹理进行采样得到衰减值 //使用宏UNITY_ATTEN_CHANNEL来得到衰减纹理中衰减值所在的分量 fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #elif defined (SPOT) //得到光源空间下的坐标:unity_WorldToLight(从世界空间到光源空间的变换矩阵) 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 return fixed4((diffuse + specular) * atten, 1.0); } ENDCG } } FallBack "Specular" }
UNITY_LIGHT_ATTENUATION
是Unity内置的用于计算光照衰减和阴影的宏。Unity针对不同光源类型、是否启用cookie等不同情况声明了多个版本的UNITY_LIGHT_ATTENUATION。
它接受3个参数:
实战:
以下代码在base pass和add pass中都计算阴影。在add pass中不用再分情况计算衰减了。
Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" { Properties { ...... } SubShader { ...... Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM ...... fixed4 frag(v2f i) : SV_Target { ...... // UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); return fixed4(ambient + (diffuse + specular) * atten, 1.0); } ENDCG } Pass { // Pass for other pixel lights Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM #pragma multi_compile_fwdadd_fullshadows ...... struct v2f { ...... SHADOW_COORDS(2) }; v2f vert(a2v v) { ...... TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); // UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); return fixed4((diffuse + specular) * atten, 1.0); } ENDCG } } FallBack "Specular" }
AlphaTest
实战:在原AlphaTest代码的基础上添加了阴影
Shader "ShaderBook/Chapter8/AlphaTest" { Properties { ...... } SubShader { ...... Pass { Tags { "LightMode"="ForwardBase" } Cull Off CGPROGRAM #pragma multi_compile_fwdbase ...... #include "AutoLight.cginc" ...... struct v2f { ....... //(使用TEXCOORD0、TEXCOORD1和TEXCOORD2修饰的变量) //由于我们已经占用了3 个插值寄存器,因此SHADOW_COORDS中传入的参数是3 //阴影纹理坐标将占用第四个插值寄存器TEXCOORD3 SHADOW_COORDS(3) }; v2f vert(a2v v) { ...... TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_Target { UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); return fixed4(ambient + diffuse * atten, 1.0); } ENDCG } } //Transparent/Cutout/VertexLit中计算透明度测试时,使用了名为_Cutoff的属性来进行透明度测试 //因此,这要求我们的Shader中也必须提供名为_Cutoff的属性 FallBack "Transparent/Cutout/VertexLit" }
我们可以将正方体的Mesh Renderer组件中的Cast Shadows属性设置为Two Sided,强制Unity在计算阴影映射纹理时计算所有面的深度信息。
AlphaBlend
由于透明度混合需要关闭深度写入,由此带来的问题也影响了阴影的生成。总体来说,要想为这些半透明物体产生正确的阴影,需要在每个光源空间下仍然严格按照从后往前的顺序进行渲染,这会让阴影处理变得非常复杂,而且也会影响性能。因此,在Unity中,所有内置的半透明Shader是不会产生任何阴影效果的。
当然,我们可以使用一些dirty trick来强制为半透明物体生成阴影,这可以通过把它们的Fallback设置为VertexLit、Diffuse这些不透明物体使用的Unity Shader,这样Unity就会在它的Fallback找到一个阴影投射的Pass。然后,我们可以通过物体的Mesh Renderer组件上的Cast Shadows和Receive Shadows选项来控制是否需要向其他物体投射或接收阴影。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。