GraphicsLab Project 之 Screen Space Planar Reflection

作者: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 << | SrcPosPixel.x;
int dotCare = ;
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 + )].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x, id.y - )].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x + , id.y)].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x - , id.y)].x; if (Hash != 0x0FFFFFFF)
{
uint x = Hash & 0xFFFF;
uint y = Hash >> ;
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(, , )]
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(, , )]
void SSPRHash_Main(uint3 id : SV_DispatchThreadID)
{
for (uint i = ; i < DownSampleFactor; i++)
{
for (uint j = ; 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, )).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 << | SrcPosPixel.x;
int dotCare = ;
InterlockedMin(HashResult[ReflPosPixel], Hash, dotCare);
}
}
}
}
}
} //------------------------------------------------------------------------------
Texture2D<int> HashTexture;
Texture2D<float4> ColorTexture;
RWTexture2D<float4> ReflectionTexture; [numthreads(, , )]
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 + )].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x, id.y - )].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x + , id.y)].x;
if (Hash == 0x0FFFFFFF)
Hash = HashTexture[uint2(id.x - , id.y)].x; if (Hash != 0x0FFFFFFF)
{
uint x = Hash & 0xFFFF;
uint y = Hash >> ;
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
参考文献
[4] Screen Space Planar Reflection
[5] Optimized Pixel Projected Reflections for Planar Reflectors
GraphicsLab Project 之 Screen Space Planar Reflection的更多相关文章
- GraphicsLab Project学习项目
作者:i_dovelemon 日期:2016 / 05 / 30 主题:3D,Graphics 引言 进公司以来,主要在学习的就是如何保证代码的质量,以前热爱的图形学也放置了.但是,作为游戏程序员,特 ...
- screen space reflection/soft alpha test/
http://www.crytek.com/cryengine/presentations/secrets-of-cryengine-3-graphics-technology 很多宝贝里面 不止题目 ...
- 在Unity中实现屏幕空间反射Screen Space Reflection(4)
第四部分讲一下如何在2D屏幕空间步进光线. http://casual-effects.blogspot.com/2014/08/screen-space-ray-tracing.html 中的代码感 ...
- 在Unity中实现屏幕空间反射Screen Space Reflection(2)
traceRay函数 在上一篇中,我们有如下签名的traceRay函数 bool traceRay(float3 start, float3 direction, out float2 hitPixe ...
- GraphicsLab Project之Diffuse Irradiance Environment Map
作者:i_dovelemon 日期:2020-01-04 主题:Rendering Equation,Irradiance Environment Map,Spherical Harmonic 引言 ...
- 基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)
目录 Reflective Shadow Maps(RSM) RSM 的重要性采样 RSM 的应用与缺陷 Screen Space Ambient Occulsion(SSAO) SSAO Blur ...
- screen space shadowmap unity
unity用到了screen space shadow map 1.camera 在light pos 生成depth1 2.screen space depth2 3.根据depth1 depth2 ...
- GraphicsLab Project之辉光(Glare,Glow)效果 【转】
作者:i_dovelemon 日期:2016 / 07 / 02 来源:CSDN 主题:Render to Texture, Post process, Glare, Glow, Multi-pass ...
- updatedepthtexture 和 screen space shadow 开关
2018.0.3f 里面directional light开了shadow 就会有一张updatedepth 如果距离远 没有阴影就没有shadow pass 但是updatedepth没有关掉 管线 ...
随机推荐
- GTA5侠盗猎车5中文版破解版绿色版汉化版迅雷下载地址种子实测可用
GTA5(侠盗猎车5)中文版下载地址(实测可用) 迅雷下载地址:https://www.90pan.com/b1548988 一定要关闭安全软件并且加入白名单 实测通过,关闭杀毒软件可以完美运行,最好 ...
- Java实现 LeetCode 695 岛屿的最大面积(DFS)
695. 岛屿的最大面积 给定一个包含了一些 0 和 1 的非空二维数组 grid . 一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相 ...
- Java实现 LeetCode 650 只有两个键的键盘(递归 || 数学)
650. 只有两个键的键盘 最初在一个记事本上只有一个字符 'A'.你每次可以对这个记事本进行两种操作: Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的). ...
- Java实现 LeetCode 495 提莫攻击
495. 提莫攻击 在<英雄联盟>的世界中,有一个叫 "提莫" 的英雄,他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态.现在,给出提莫对艾希的攻击时间序列和 ...
- Java实现蓝桥杯 历届试题 k倍区间
历届试题 k倍区间 时间限制:2.0s 内存限制:256.0MB 问题描述 给定一个长度为N的数列,A1, A2, - AN,如果其中一段连续的子序列Ai, Ai+1, - Aj(i <= j) ...
- java实现 洛谷 P1427 小鱼的数字游戏
题目描述 小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字(长度不一定,以0结束,最多不超过100个,数字不超过2^32-1),记住了然后反着念出来(表示结束的数字0就不要念出来了).这对小鱼的 ...
- java算法集训结果填空题练习1
1 空瓶换汽水 浪费可耻,节约光荣.饮料店节日搞活动:不用付费,用3个某饮料的空瓶就可以换一瓶该饮料.刚好小明前两天买了2瓶该饮料喝完了,瓶子还在.他耍了个小聪明,向老板借了一个空瓶,凑成3个,换了一 ...
- Java实现 蓝桥杯 算法提高金属采集
问题描述 人类在火星上发现了一种新的金属!这些金属分布在一些奇怪的地方,不妨叫它节点好了.一些节点之间有道路相连,所有的节点和道路形成了一棵树.一共有 n 个节点,这些节点被编号为 1~n .人类将 ...
- Java实现第十届蓝桥杯求和
试题 A: 求和 本题总分:5 分 [问题描述] 小明对数位中含有 2.0.1.9 的数字很感兴趣,在 1 到 40 中这样的数包 括 1.2.9.10 至 32.39 和 40,共 28 个,他们的 ...
- JSP+SSM+Mysql实现的学生成绩管理系统
项目简介 项目来源于:https://gitee.com/z77z/StuSystem 本系统是基于JSP+SSM+Mysql实现的学生成绩管理系统.主要实现的功能有教师管理.学生管理.课程管理.学生 ...