【Shader进阶】SubShader块标签Tags——ForceNoShadowCasting属性和阴影投射ShadowCaster

本文详细解析Unity Shader中阴影投射的实现原理,包括Pass块标签、宏定义使用、光照模式、顶点变换及纹理采样等关键步骤,深入探讨传统阴影映射与屏幕空间阴影技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/SubShaderTagsTest"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,0,0,1)
	}
		SubShader
		{
			Tags
			{
				//我希望下面的这些字符串,全部变成宏定义,并且能够由IDE智能提示 如何做?
				//因为可能由于拼写错误导致无效,而又不会报错...
				"Queue" = "Geometry"
				"RenderType" = "Opaque"
				"DisableBatching" = "False"
				"ForceNoShadowCasting" = "True" //无阴影测试(成功)
				"IgnoreProjector" = "True"
				"CanUseSpriteAtlas" = "True"
				"PreviewType" = "Plane"
			}
			LOD 100

			//阴影投射Pass
			Pass
			{
				Tags
				{
					"LightMode" = "ShadowCaster" //1.
				}
				CGPROGRAM
			
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile_shadowcaster //2.
				
				#include "UnityCG.cginc"
				//appdata根据TRANSFER_SHADOW_CASTER_NORMALOFFSET宏来设置,我的情况需要下面3种输入,具体看UnityCG.cginc的TRANSFER_SHADOW_CASTER_NORMALOFFSET宏需求
				//若发现阴影投射无效或报错,可能是这里出问题了,具体信息博客说明
				struct appdata
				{
					float4 vertex : POSITION;					
					float4 texcoord :TEXCOORD0; //vert时需要
					float3 normal : NORMAL;
				};
				struct v2f {
					V2F_SHADOW_CASTER;//3.
				};
				v2f vert(appdata v) {
					v2f o;
					TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) //4.
					return o;
				}
				fixed4 frag(v2f i) :SV_Target {
					SHADOW_CASTER_FRAGMENT(i) //5.
				}
				ENDCG
			}

			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				#include "UnityCG.cginc"

				struct appdata
				{
					float4 vertex : POSITION;
					float2 uv : TEXCOORD0;
					float3 normal : NORMAL;
				};

				struct v2f
				{
					float2 uv : TEXCOORD0;		
					float4 vertex : SV_POSITION;
					float3 normal : NORMAL;
					float4 pos : TEXCOORD1;
				};

				fixed4 _Color;
				sampler2D _MainTex;
				float4 _MainTex_ST;

				v2f vert(appdata v)
				{
					v2f o;
					o.vertex = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);					
					o.normal = UnityObjectToWorldNormal(v.normal);
					o.pos = mul(unity_ObjectToWorld, v.vertex);
					return o;
				}
				//漫反射
				fixed4 frag(v2f i) : SV_Target
				{
					// sample the texture
					fixed4 col = tex2D(_MainTex, i.uv);
					fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.pos));
					fixed3 normal = normalize(i.normal);
					col = col * dot(lightDir, normal);
					return col * _Color;
				}
			ENDCG
		}
	}
		//FallBack "Diffuse" //这个Diffuse自带ShadowCaster的Pass 先注释掉自己实现了一个ShadowCaster(纯属玩玩)
}

关于代码中标注的阴影投射说明:
1、Tags { "LightMode" = "ShadowCaster" }  标明该Pass块用于阴影投射功能,专属Pass块的标签属性
2、 #pragma multi_compile_shadowcaster (尚未清楚,但必要)
3、v2f结构体中的V2F_SHADOW_CASTER宏,具体情况如下,其中UNITY_POSITION(pos)宏还未知在啥.cginc中可自行查找

// Declare all data needed for shadow caster pass output (any shadow directions/depths/distances as needed),
// plus clip space position.
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS UNITY_POSITION(pos)

#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    // Rendering into point light (cubemap) shadows
    #define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
#else
    // Rendering into directional or spot light shadows
    #define V2F_SHADOW_CASTER_NOPOS
    // Let embedding code know that V2F_SHADOW_CASTER_NOPOS is empty; so that it can workaround
    // empty structs that could possibly be produced.
#endif

4. TRANSFER_SHADOW_CASTER_NORMALOFFSET宏(我的情况恰好属于else情况所以会有normal,若你的情况满足if条件则不需要normal)

// Vertex shader part, with support for normal offset shadows. Requires
// position and normal to be present in the vertex input.
#define TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) TRANSFER_SHADOW_CASTER_NOPOS(o,o.pos)

#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
#else
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
        opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
        opos = UnityApplyLinearShadowBias(opos);
#endif

5. SHADOW_CASTER_FRAGMENT宏

#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    #define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
    #define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif

具体这些宏在干什么,目前还未了解,根据已了解知识来看是在进行如下操作。(注意UnityShader并不完整,有很多操作都在编译成真正的Shader代码,如 OpenGL的GLSL(OpenGL Shading Language) 或 微软DirectX的HLSL(High Level Shading Language) 或 NVIDIA与微软合作制作的CG(C for Graphic)

https://blog.youkuaiyun.com/qq_39574690/article/details/99766585
Shader从阴影图根据阴影纹理坐标采样阴影值,再使用该阴影值对输出颜色进行相乘,最终表现出阴影效果。

阴影图是一张二维纹理,存储着场景上物体的阴影部分。

阴影图有如下两种方法生成:

①传统方法:Unity首先把摄像机放到光源位置上,调用LightMode为ShadowCaster的Pass来进行渲染出光源空间下的阴影映射纹理(实际上是一个表面深度纹理),之后在正常渲染的Pass中将顶点位置变换到光源空间下,将这个光源空间下的顶点坐标xy分量作为纹理坐标xy分量从光源空间下的阴影映射纹理采样出深度值,再与该Pass处理的顶点或片元深度值进行比较,如果取出来的深度值比自身深度值更小,说明当前处理的顶点或片元处于阴影中,至于阴影值是多少,书上没解释明白,我也没弄明白,具体还要看源码看它是怎么的算法。(问题1)

②屏幕空间的阴影映射技术:Unity5及之后,需显卡支持MRT。也是在LightMode为ShadowCaster的Pass中进行渲染出屏幕空间下的阴影投射纹理,同时Unity会计算出摄像机的表面深度纹理,同一纹理坐标上,若阴影映射纹理的深度值比摄像机的表面深度纹理的深度值更小,说明该Pass处理的顶点或片元处于阴影中,将该纹理坐标上的阴影映射纹理的深度值赋值到阴影图相应坐标上。所以摄像机的表面深度纹理实际上是从阴影映射纹理中选出那些真正的阴影部分,成为所谓的“阴影图”。最终,我们在正常的Pass中,通过将顶点坐标转屏幕空间下,然后根据这个坐标来从阴影图采样阴影值,之后你都懂了。

在总结到这里,不妨猜想一下,问题1中的阴影值是多少?应该是阴影映射纹理的深度值,因为实际上无论①和②真正的阴影都是从这个阴影映射纹理而来的,②只不过是多了一个过滤,①的情况有点复杂,因为我们看的视角是屏幕空间的,而阴影映射纹理是光源空间的,假设光源和摄像机是在同一个位置那还好理解,如果是不同位置,就有点难以理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值