平心在线:GraphicsLab Project 之 Screen Space Planar Reflection

时间:3个月前   阅读:33

作者:i_dovelemon

日期:2020-06-23

主题:Screen Space Planar Reflection, Compute Shader

弁言

        前段时间,同事发来一篇讲述特化版本的 Screen Space Reflection 实现 Planar Reflection 的文章。出于好奇,实验了下,看看效果若何。如下是现在实现出来的基础版本的效果:

 原理

        对于上图来说, Water Plane 示意水面,上半部分为现实场景的山体,下半部分为以水面为镜像举行反射之后的山体效果。

        对于山体上某一个点(图中白色点)来说,它对应的镜像点为黄色点。

        我们可以从 Screen Position 以及 Depth Texture 信息,盘算出来白点的世界坐标位置 WorldPosition

        然后可以以 Water Plane 所在的平面临该 WorldPosition 作镜像操作,获得 ReflectionPosition

        获得 ReflectionPosition 之后,我们就能够盘算出来 ReflectionPostion 所对应的屏幕坐标 Reflection Screen Position

        凭据前面的操作,我们就可以知道,此时 Reflection Screen Position 所反射的颜色即为 Screen Positon 所示意的颜色。

        基础原理十分简朴,然则现实实现的时刻,会发现有许多问题。接下里逐一讲述。

问题

闪灼

        凭据上面的原理,可以想到,有多个像素可能会被反射到相同的位置,如下图所示:

         这样由于 GPU 执行顺序的不确定性,就会导致画面泛起闪灼,如下所示:

        针对这样的问题,我们现实需要的反射点是最近的反射点。可以思量使用 HLSL 中提供的 InterlockedMin/InterlockedMax (参考[1],[2]) 之类的指令,在写入数据时举行巨细对照,从而实现保留最近反射点的功效。

        前面的指令虽然能够实现巨细对照,以此举行排序。然则凭据前面的形貌,我们现实保留的是反射点的颜色。没有办法只凭据颜色举行排序,以是我们需要保留其他便于排序的信息,这里选择使用反射点的 Screen Position。而且根据如下方式举行编码,从而实现获取最近反射点的效果:

                        uint2 SrcPosPixel = uint2(DepthPos.x, DepthPos.y);
                        uint2 ReflPosPixel = ReflPosUV * uint2(ReflectWidth, ReflectHeight);

                        int Hash = SrcPosPixel.y << 16 | SrcPosPixel.x;
                        int dotCare = 0;
                        InterlockedMin(HashResult[ReflPosPixel], Hash, dotCare);
Encode and Sort

孔洞

        凭据先前算法的形貌,我们知道,我们先要凭据 Depth 信息和 Screen Position 信息盘算出 World Positon,然后镜像之后,在转化为新的屏幕坐标。在这一系列操作中,由于数值盘算的不精确性,导致有些地方没有存储到有用的反射点位置信息,从而导致最终显示时画面上有孔洞的情形,如下图所示:

        幸运的是,从效果看这些孔洞并不会群集在一起,形成大块的黑块。对于这种情形,我们只要在天生反射贴图的时刻,检测到没有保留有用位置信息时,遍历下周围的像素,寻找到一个拥有有用像素的值即可解决这个问题,如下代码所示:

        uint Hash = HashTexture[id.xy].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x, id.y + 1)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x, id.y - 1)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x + 1, id.y)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x - 1, id.y)].x;

        if (Hash != 0x0FFFFFFF)
        {
            uint x = Hash & 0xFFFF;
            uint y = Hash >> 16;
            ReflectionTexture[id.xy] = ColorTexture[uint2(x, y)];
        }
        else
        {
            ReflectionTexture[id.xy] = float4(0.0f, 0.0f, 0.0f, 0.0f);
        }
Hole

        如下是修正孔洞之后的效果:

实现

        本文的代码是使用 Unity 实现的,实现起来对照简朴。对照坑的地方在于 Unity 内里获取 Projection Matrix 要通过 GL.GetGPUProjectionMatrix (文献[3]) 转化一下才气酿成通报到 GPU 上用于渲染的投影矩阵。如下是功效焦点的 Compute Shader 代码:

// Each #kernel tells which function to compile; you can have many kernels
#pragma enable_d3d11_debug_symbols
#pragma kernel SSPRClear_Main
#pragma kernel SSPRHash_Main
#pragma kernel SSPRResolve_Main

//-----------------------------------------------------------------
float4x4 VPMatrix;
float4x4 InvVPMatrix;
uint Width;
uint Height;
uint ReflectWidth;
uint ReflectHeight;

//--------------------------------------------------------------------
RWTexture2D<int> ClearHashTexture;

[numthreads(8, 8, 1)]
void SSPRClear_Main(uint3 id : SV_DispatchThreadID)
{
    if (id.x < ReflectWidth && id.y < ReflectHeight)
    {
        ClearHashTexture[id.xy] = 0x0FFFFFFF;
    }
}

//---------------------------------------------------------------
Texture2D<float> DepthTex;
RWTexture2D<int> HashResult;

#define DownSampleFactor (1)

float3 Unproject(float3 clip)
{
    float4 clipW = float4(clip, 1.0f);
    clipW = mul(InvVPMatrix, clipW);
    clipW.xyz = clipW.xyz / clipW.w;
    return clipW.xyz;
}

float2 Project(float3 world)
{
    float4 worldW = float4(world, 1.0f);
    worldW = mul(VPMatrix, worldW);
    worldW.xy = worldW.xy / worldW.w;
    worldW.xy = (worldW.xy + float2(1.0f, 1.0f)) / 2.0f;
    return worldW.xy;
}

[numthreads(8, 8, 1)]
void SSPRHash_Main(uint3 id : SV_DispatchThreadID)
{
    for (uint i = 0; i < DownSampleFactor; i++)
    {
        for (uint j = 0; j < DownSampleFactor; j++)
        {
            uint2 DepthPos = uint2(id.x * DownSampleFactor + i, id.y * DownSampleFactor + j);
            if (DepthPos.x < Width && DepthPos.y < Height)
            {
                float depth = DepthTex.Load(int3(DepthPos.x, DepthPos.y, 0)).x;

                if (depth > 0.0f)
                {
                    float2 uv = (DepthPos.xy * 1.0f) / float2(Width, Height);
                    uv = uv * 2.0f - float2(1.0f, 1.0f);
                    uv.y = -uv.y;

                    float3 PosWS = Unproject(float3(uv, depth));

                    if (PosWS.y > 0.0f)
                    {
                        float3 ReflPosWS = float3(PosWS.x, -PosWS.y, PosWS.z);
                        float2 ReflPosUV = Project(ReflPosWS);

                        uint2 SrcPosPixel = uint2(DepthPos.x, DepthPos.y);
                        uint2 ReflPosPixel = ReflPosUV * uint2(ReflectWidth, ReflectHeight);

                        int Hash = SrcPosPixel.y << 16 | SrcPosPixel.x;
                        int dotCare = 0;
                        InterlockedMin(HashResult[ReflPosPixel], Hash, dotCare);
                    }
                }
            }
        }
    }
}

//------------------------------------------------------------------------------
Texture2D<int> HashTexture;
Texture2D<float4> ColorTexture;
RWTexture2D<float4> ReflectionTexture;

[numthreads(8, 8, 1)]
void SSPRResolve_Main(uint3 id : SV_DispatchThreadID)
{
    if (id.x < ReflectWidth && id.y < ReflectHeight)
    {
        uint Hash = HashTexture[id.xy].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x, id.y + 1)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x, id.y - 1)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x + 1, id.y)].x;
        if (Hash == 0x0FFFFFFF)
            Hash = HashTexture[uint2(id.x - 1, id.y)].x;

        if (Hash != 0x0FFFFFFF)
        {
            uint x = Hash & 0xFFFF;
            uint y = Hash >> 16;
            ReflectionTexture[id.xy] = ColorTexture[uint2(x, y)];
        }
        else
        {
            ReflectionTexture[id.xy] = float4(0.0f, 0.0f, 0.0f, 0.0f);
        }
    }
}
ScreenSpacePlanarReflection

结论

        本文只是探索这个方式的可能性,加倍庞大的实现,加倍高效的优化可以参考文献[4][5],这也是本文主要参考的工具。

        相比于传统的绘制场景双方的方式来说,这个方案的性能加倍高效,同时也没有 SSR 那样的高需求。在条件知足的情形下,使用该方案能够带来显著的效果提升,推荐可以实验。

        完整代码在这里:https://github.com/idovelemon/UnityProj/tree/master/ScreenSpacePlanarReflection

参考文献

[1] HLSL-InterlockedMax

[2] HLSL-InterlockedMin

[3] GL.GetGPUProjectionMatrix

[4] Screen Space Planar Reflection

[5] Optimized Pixel Projected Reflections for Planar Reflectors

,

Allbet注册

欢迎进入Allbet注册(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

上一篇:欧博网址:英超综合:谢菲联逆转胜 曼城竞赛因风暴作废

下一篇:欧博手机版下载:海霞:未成年人珍〖爱不应〗留下“隐秘的角落”,这些责任要压实!