【Unity3D】阴影原理及应用

1 阴影原理

        光源照射到不透明物体上,会向该物体的后面投射阴影,如果阴影区域存在其他物体,这些物体不被光源照射的部分就需要渲染阴影。因此,我们可以将阴影的生成抽象出 2 个流程:物体投射阴影物体接收阴影

1.1 阴影相关开关

         1)开启 Light 组件渲染阴影

  • No Shadows:不渲染阴影
  • Hard Shadows:硬阴影(阴影边缘较清晰)
  • Soft Shadows:软阴影(阴影边缘较模糊)

         2)开启投射阴影 / 接收阴影

  • Cast Shadows:投射阴影,取值有 Off(关闭投射)、On(开启投射)、Two Sided(双面都可以投射阴影)、Shadows Only(只投射阴影,但物体不可见)
  • Receive Shadows:接收阴影

        注意:投射阴影和接收阴影开关可以独立控制,不相互依赖,因此,一个物体可以只投射阴影而不接收阴影,也可以只接收阴影而不投射阴影。

1.2 物体投射阴影

        1)阴影映射纹理

        物体的 MeshRenderer 组件开启了 Cast Shadows 后,Unity 会把该物体加入到光源的阴影映射纹理Shadow Map)的计算中。阴影映射纹理是一张深度图,它记录了从光源位置能看到的顶点的位置和深度,即将相机放在光源位置渲染的深度纹理。

        2)屏幕空间的阴影映射纹理

        Unity 5 中,Unity 使用了不同于这种传统的阴影采样技术,即屏幕空间的阴影映射技术Screenspace Shadow Map)。其原理是:先生成相机的深度纹理和灯光的阴影映射纹理,对于场景中任意一点,以其深度纹理的 x、y 值和阴影映射纹理的 z 值作为该点的屏幕空间阴影映射纹理。注意:Unity 并没有在所有平台上都使用了这种技术并,需要显卡支持 MRT,有些移动平台就不支持该特性。

        3)ShadowCaster Pass

        正常的渲染流程会调用 LightMode 标签为 ForwardBase 和 Additional 的 Pass,而深度映射纹理是一张深度图,不需要渲染顶点颜色,为节省性能,Unity 设计了 LightMode 为 ShadowCaster 的 Pass 专门用于渲染阴影纹理。如果当前 Shader 文件中没有 LightMode 为 ShadowCaster 的 Pass,就去 Fallback 指定的 Shader 中继续寻找,如果仍没有找到,就继续去 Fallback 里寻找,直到找到了 ShadowCaster Pass 或没有 Fallback,如果最后找到了,Unity 会使用该 Pass 更新光源的阴影映射纹理。

        4)渲染阴影映射纹理

        对于阴影映射纹理的渲染,Unity 封装得较好,用户只需要在 Shader 的 Fallback 中指定 Diffuse、Specular 或 VertexLit 等,如下:

Fallback "Specular"

        内置的 Specular 中没有 ShadowCaster Pass,其 Fallback 为 VertexLit(Shader 见:Unity Editor 安装目录下的 Editor\Data\built-in-shaders-xxx\Shaders\DefaultResourcesExtra\Normal-VertexLit.shader 文件,built-in-shaders),VertexLit 中有 ShadowCaster Pass,如下:

// Pass to render object as a shadow caster
Pass {
	Name "ShadowCaster"
	Tags { "LightMode" = "ShadowCaster" }

	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	#pragma multi_compile_shadowcaster
	#include "UnityCG.cginc"

	struct v2f {
		V2F_SHADOW_CASTER;
	};

	v2f vert( appdata_base v )
	{
		v2f o;
		TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
		return o;
	}

	float4 frag( v2f i ) : SV_Target
	{
		SHADOW_CASTER_FRAGMENT(i)
	}
	ENDCG
}

1.3 物体接收阴影

        对于模型的任意顶点,使用该点对应的阴影灰度值(可以从屏幕空间的阴影映射纹理中获取)乘以光照纹理,得到的就是该点最终要显示的纹理。阴影灰度值的计算如下:

struct v2f {
    float4 pos : SV_POSITION; // 裁剪空间顶点坐标
    float3 normal: Normal; // 世界空间顶点法线向量
    float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
	SHADOW_COORDS(1) // 声明一个用于阴影纹理采样的uv坐标, 入参是下一个可用的插值寄存器的索引(此处下一个可用插值寄存器是TEXCOORD1, 其索引为1)
};

v2f vert(a2v v) {
	v2f o;
	...
	TRANSFER_SHADOW(o); // 计算阴影纹理uv坐标
	return o;
}

fixed4 frag(v2f i) : SV_Target {
	...
	// fixed shadow = SHADOW_ATTENUATION(i); // 计算阴影灰度
	// return fixed4(ambient + (diffuse + specular) * shadow, 1);
	UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // 计算衰减因子和阴影灰度的乘积
	return fixed4(ambient + (diffuse + specular) * atten, 1);
}

        说明:上述宏的定义在 AutoLight.cginc 文件中;这些宏中使用了上下文中部分变量进行相关计算(如:v2f 中的 pos),因此 pos 的名称不能改变。

2 阴影应用

        Shadow.Shader

Shader "MyShader/Shadow" {
	Properties {
		_ModelColor ("Model Color", Color) = (1, 1, 1, 1) // 模型颜色
		_Specular ("Specular Color", Color) = (1, 1, 1, 1) // 镜面反射颜色
		_Gloss ("Gloss", Range(8.0, 256)) = 20 // 镜面反射光泽度
	}
 
	SubShader {
		Tags { "RenderType"="Opaque" }

		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			#pragma multi_compile_fwdbase
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _ModelColor; // 模型颜色
			fixed4 _Specular; // 镜面反射颜色
			float _Gloss; // 镜面反射光泽度
			
			struct a2v {
				float4 vertex : POSITION; // 模型空间顶点坐标
				half3 normal: NORMAL; // 模型空间顶点法线向量
			};
			
			struct v2f {
				float4 pos : SV_POSITION; // 裁剪空间顶点坐标
				float3 normal : Normal; // 世界空间顶点法线向量
                float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
				SHADOW_COORDS(1)
			};
 
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
				o.normal = UnityObjectToWorldNormal(v.normal); // 将模型空间法线向量变换到世界空间
				o.worldPos = mul(unity_ObjectToWorld, v.vertex); // 将模型空间顶点坐标变换到世界空间
				TRANSFER_SHADOW(o);
				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				fixed3 normal = normalize(i.normal); // 世界空间法线向量
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 世界空间灯光向量
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); // 世界空间观察向量
				fixed3 halfDir = normalize(lightDir + viewDir); // 半向量
				fixed3 albedo = _ModelColor; // 模型自身颜色
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo; // 环境光
				fixed3 diffuse = _LightColor0 * albedo * max(0, dot(normal, lightDir)); // 漫反射光
				fixed3 specular = _LightColor0 * _Specular * pow(max(0, dot(normal, halfDir)), _Gloss); // 镜面反射光(Blinn Phong光照模型)
				// fixed shadow = SHADOW_ATTENUATION(i); // 计算阴影灰度
				// return fixed4(ambient + (diffuse + specular) * shadow, 1);
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // 计算衰减因子和阴影灰度的乘积
				return fixed4(ambient + (diffuse + specular) * atten, 1);
			}
			
			ENDCG
		}
	}
 
	FallBack "Specular"
}

        运行效果:

3 帧调试器查看阴影绘制过程

        通过 Window → Analysis → Frame Debug 打开帧调试器,如下:

         渲染对象主要有:Camera DepthTexture(相机深度纹理)、ShadowMap(阴影映射纹理)、Screenspace ShadowMap(屏幕空间阴影映射纹理)、TempBuffer(帧缓冲区)。

        1)Camera DepthTexture

        2)ShadowMap

        3)Screenspace ShadowMap

        4)TempBuffer

猜你喜欢

转载自blog.csdn.net/m0_37602827/article/details/129804783
今日推荐