1. 程式人生 > 其它 >Unity投射陰影--V2F_SHADOW_CASTER,TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),SHADOW_CASTER_FRAGMENT(i)三部分

Unity投射陰影--V2F_SHADOW_CASTER,TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),SHADOW_CASTER_FRAGMENT(i)三部分

技術標籤:Unity Shader

作用:讓物體能夠投射陰影

原理:返回裁剪空間的座標,寫入depth buff,讓攝像機在光源空間渲染出深度圖,再進一步渲染出螢幕陰影紋理(某些平臺並沒有這一步驟https://blog.csdn.net/zengjunjie59/article/details/111356636),最後在光照pass裡面對紋理進行取樣。

例子:

Pass 
{
Tags 
{
    "LightMode" = "ShadowCaster"
}

CGPROGRAM
    #pragma target 3.0
    
    // 定義相關key,具體看https://blog.csdn.net/zengjunjie59/article/details/111404824
    #pragma multi_compile_shadowcaster
    

    #include "CustomShadows.cginc"
    #pragma vertex MyShadowVertexProgram
    #pragma fragment MyShadowFragmentProgram
ENDCG
}
#if !defined(CUSTOM_SHADOWS_INCLUDE)
#define CUSTOM_SHADOWS_INCLUDE

#include "UnityCG.cginc"


//如果直接呼叫unity自帶的巨集,則可以這樣寫
struct Interpolators{
	V2F_SHADOW_CASTER;
};

Interpolators MyShadowVertexProgram(appdata_base v){
	Interpolators i;
	TRANSFER_SHADOW_CASTER_NORMALOFFSET(i);
	return i;
}

float4 MyShadowFragmentProgram(Interpolators i): SV_TARGET {
	SHADOW_CASTER_FRAGMENT(i);
}

#endif

首先看第一個巨集:V2F_SHADOW_CASTER

原始碼:

// On D3D reading screen space coordinates from fragment shader requires SM3.0
#define UNITY_POSITION(pos) float4 pos : SV_POSITION

#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.
    #define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
#endif


// 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)
    float4 pos : SV_POSITION   // 用於儲存裁剪座標
    float3 vec : TEXCOORD0     // 用於儲存光源到頂點的世界空間方向向量
#else
    float4 pos : SV_POSITION
#endif

頂點著色器裡用的巨集:TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

原始碼:

#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
    // 轉換到才裁剪空間齊次座標,並執行頂點法向偏差
    // 便宜裁剪齊次座標的z值,並返回頂點裁剪座標點
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
        opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
        opos = UnityApplyLinearShadowBias(opos);
#endif


// 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)

片元著色器裡用的巨集:SHADOW_CASTER_FRAGMENT(i)

原始碼:


#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    // _LightPositionRange.w是1/Range, Range是光照範圍
    // UnityEncodeCubeShadowDepth是如果檢測到不能用浮點數則把float值的不同位存到rgba各個分量中
    // 整段算的就是光源空間下頂點的深度,值域[0, 1], 期間也用了z值偏差(+unity_LightShadowBias.x)
    #define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
    #define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif

最後:

有個小疑問:點光源,定義SHADOWS_CUBE之後,在SHADOW_CASTER_FRAGMENT巨集裡的第一個if分支下直接返回0效果並沒有什麼區別,猜測是某些平臺會有用。有知道的人,還望指教指教