Unity中的反射


CubeMap采樣

Unity提供了Unity_GlossyEnvironment函數來對cubemap進行采樣。該函數的實現如下:

half3 Unity_GlossyEnvironment (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn)
{
    half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ;

// TODO: CAUTION: remap from Morten may work only with offline convolution, see impact with runtime convolution!
// For now disabled
#if 0
    float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the real roughness parameter
    const float fEps = 1.192092896e-07F;        // smallest such that 1.0+FLT_EPSILON != 1.0  (+1e-4h is NOT good here. is visibly very wrong)
    float n =  (2.0/max(fEps, m*m))-2.0;        // remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf

    n /= 4;                                     // remap from n_dot_h formulatino to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html

    perceptualRoughness = pow( 2/(n+2), 0.25);      // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness)
#else
    // MM: came up with a surprisingly close approximation to what the #if 0'ed out code above does.
    perceptualRoughness = perceptualRoughness*(1.7 - 0.7*perceptualRoughness);
#endif


    half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness);
    half3 R = glossIn.reflUVW;
    half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip);

    return DecodeHDR(rgbm, hdr);
}

UNITY_ARGS_TEXCUBE宏是一個用來定義cubemap作為函數參數的宏,用於函數的聲明,我們在調用函數時需要相應地使用UNITY_PASS_TEXCUBE宏進行cubmap參數傳遞。hdr參數用於當cubemap中包含hdr顏色時,需要將hdr轉換到rgb顏色,一般直接傳unity_SpecCube0_HDR即可。Unity_GlossyEnvironmentData是unity定義的一個數據結構,我們需要設置它的roughness和reflUVW屬性,roughness就是材質的粗糙程度,越粗糙物體的反射越模糊;reflUVW就是反射向量,用於采樣cubemap。我們可以這樣調用該函數:

float3 reflectionDir = reflect(-viewDir, i.normal);
Unity_GlossyEnvironmentData envData;
envData.roughness = 1 - _Smoothness;
envData.reflUVW = reflectionDir;
float3 specular = Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData);

該函數首先對roughness進行變換,因為roughness和cubemap的mipmap level並非是線性關系,Unity使用了一個近似的公式來進行模擬:

\[r = 1.7r - 0.7r^2 \]

因為roughness越大,物體的反射越模糊,這就類似於采樣的cubemap的mipmap level越高。得到對應的mipmap level之后,我們就可以用UNITY_SAMPLE_TEXCUBE_LOD對cubemap進行采樣,得到采樣的結果。如果是hdr格式,進一步轉換到rgb格式。

box projection

使用Unity提供的反射探針,我們可以方便地實現反射的效果。如果要顯示反射效果的物體是會移動的,我們需要在反射探針中勾選Box Projection,這樣反射探針的box會隨着物體移動而移動,從而只用一個反射探針,也可以實現不同的反射效果。同樣地,unity提供了BoxProjectedCubemapDirection函數來方便我們計算box projection下的反射向量:

inline float3 BoxProjectedCubemapDirection (float3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax)
{
    // Do we have a valid reflection probe?
    UNITY_BRANCH
    if (cubemapCenter.w > 0.0)
    {
        float3 nrdir = normalize(worldRefl);

        #if 1
            float3 rbmax = (boxMax.xyz - worldPos) / nrdir;
            float3 rbmin = (boxMin.xyz - worldPos) / nrdir;

            float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;

        #else // Optimized version
            float3 rbmax = (boxMax.xyz - worldPos);
            float3 rbmin = (boxMin.xyz - worldPos);

            float3 select = step (float3(0,0,0), nrdir);
            float3 rbminmax = lerp (rbmax, rbmin, select);
            rbminmax /= nrdir;
        #endif

        float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

        worldPos -= cubemapCenter.xyz;
        worldRefl = worldPos + nrdir * fa;
    }
    return worldRefl;
}

worldRefl即為世界空間的反射向量,worldPos即為要計算的點的世界坐標,cubemapCenter是反射探針的坐標,boxMin和boxMax是反射探針包圍盒的最小最大坐標。我們可以這樣調用BoxProjectedCubemapDirection:

float3 reflectionDir = reflect(-viewDir, i.normal);
float3 reflUVW = BoxProjectedCubemapDirection(reflectionDir, i.worldPos, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);

繼續看函數內部實現,這里有個條件判斷,當cubemapCenter.w > 0時,說明反射探針啟用了box projection,我們只需要在啟用的情況下進行計算。

在啟用box projection時,傳入的worldRefl向量並非恰好是我們對cubemap采樣的向量,如圖所示,I為當前點,C為反射探針的中心點,P為cubemap的采樣點,我們要求的就是向量W

首先,我們需要根據cubemap采樣的原理,計算出向量U。向量U的方向與worldRefl向量一致,長度為與最近的包圍盒的面相交的點的距離。容易知道,六個面到點I的距離可以表示為兩組三維向量:

            float3 rbmax = (boxMax.xyz - worldPos);
            float3 rbmin = (boxMin.xyz - worldPos);

然后根據歸一化后的worldRefl向量,也就是worldRefl向量的方向,可以得到沿該方向去,到達六個面所需要的時間:

			float3 nrdir = normalize(worldRefl);
			float3 rbmax = (boxMax.xyz - worldPos) / nrdir;
             float3 rbmin = (boxMin.xyz - worldPos) / nrdir;

我們要求的所需要的最短時間必然是大於0的時間中的最小值:

			float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;
			float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

進而,可以求出向量U

			float3 u = nrdir * fa;

向量V的值很顯然就是兩個點世界坐標的差值:

			float3 v = cubemapCenter.xyz - worldPos;

那么,向量W = U - V為:

			float3 w = nrdir * fa + worldPos - cubemapCenter.xyz;
反射探針插值

Unity允許讓我們對兩個反射探針采樣的值進行插值融合,得到一個過渡的效果。Unity提供了UNITY_SPECCUBE_BLENDING宏來判斷當前平台是否支持反射探針融合。另外,unity_SpecCube0_BoxMin的w分量存儲了第一個反射探針所占的權重,如果權重比較大,我們的實現代碼就可以忽略第二個反射探針的存在,避免不必要的性能開銷:

		#if UNITY_SPECCUBE_BLENDING
			float interpolator = unity_SpecCube0_BoxMin.w;
			UNITY_BRANCH
			if (interpolator < 0.99999) {
				float3 probe1 = Unity_GlossyEnvironment(
					UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
					unity_SpecCube0_HDR, envData
				);
				float3 specular = lerp(probe1, probe0, interpolator);
			}
			else {
				float3 specular = probe0;
			}
		#else
			float3 specular = probe0;
		#endif
多次反射

為了實現鏡中鏡之類的效果,Unity支持對反射探針的cubemap進行多次渲染,這樣就可以把反射的信息也渲染到cubemap中。相關的設置在Window/Rendering/Lighting Settings中:

Reference

[1] Reflections

如果你覺得我的文章有幫助,歡迎關注我的微信公眾號(大齡社畜的游戲開發之路-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM