• 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

    如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

  • 相关阅读:
    (个人题目)作业 题解
    P2618 数字工程
    P6394 樱花,还有你
    USACO08FEB Making the Grade G
    USACO13NOV Pogo-Cow S
    CSP2019 树上的数
    JSOI2018 潜入行动
    NOIP2017 宝藏
    SNOI2017 炸弹
    【洛谷】【最小生成树】P1195 口袋的天空
  • 原文地址:https://www.cnblogs.com/back-to-the-past/p/15260709.html
Copyright © 2020-2023  润新知