Screen Space Contact Shadow Cover

使用Compute Shader计算屏幕空间接触阴影

屏幕空间接触阴影 屏幕空间接触阴影是用来解决普通的阴影贴图精度不够的问题而提出来的一种通过深度图在屏幕空间计算阴影的方法。索尼的Bend Studio的Graham Aldridge在Sigraph 2023的索尼创作者大会上,介绍了往日不再(Days Gone)中计算屏幕空间接触阴影的方式,这里可以找到演示文稿和参考代码。 本篇文章相当于是Radial Dispatch系列的第三篇文章了,与上一篇文章一样,这篇文章是基于径向分派Compute Shader中相关算法的实际应用,具体的缓存方式也可以参考上一篇文章使用Group Shared Memory加速径向模糊,这里就不再赘述了。实际上我发现了这样计算接触阴影的一个缺陷,就是不太好计算软阴影了,由于缓存的限制,随机采样只能在一个很小的范围内分布,基本上用不上了。由于使用的是屏幕空间的深度图的信息,加上厚度检测之后很容易出现漏面的问题,封面中的瑕疵也有一部分是来自于我的Relaxed Cone Step Mapping本身深度值的瑕疵,屏幕上半部分的阴影就好很多。这就当作是一个Proof of Concept吧,之后有机会的话再回来优化优化。 本文使用的是Unity 2022.3.21f1,URP版本是14.0.10。 具体的代码 ContactShadowComputeShader.compute 核心的代码来自于前一篇文章径向分派Compute Shader。前一篇文章在循环中是通过统一步长进行采样的,会采样到四个像素中间因此需要双线性插值,这次我们固定水平或者竖直方向的步长为一个像素,这样我们只需要在一个方向上进行线性插值了。由于深度和颜色信息是两种不同的信息,我们仅对距离很近的深度进行线性插值,对于距离较远的两个深度值,我们使用离采样点最近像素的深度值。至于如何判断深度远近,我使用了和屏幕空间反射中相同的_ThicknessParams,默认物体的厚度为linearSampleDepth * _Thickness.y + _Thickness.x。 #pragma kernel ContactShadowPoint #pragma kernel ClearMain // #pragma warning(disable: 3556) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #define THREAD_COUNT 128 Texture2D<float4> _ColorTex; Texture2D<float> _CameraDepthTexture; RWTexture2D<float4> _RW_TargetTex; float3 _LightPosWS; float4 _LightPosCS; float2 _LightPosSS; float2 _ThicknessParams; float4 _TextureSize; float _LightRange; float _Debug; struct DispatchParams { int2 offset; int count; int stride; int xMajor; }; StructuredBuffer<DispatchParams> _DispatchData; int GetDispatchType(int index, out int dispatchIndex, out DispatchParams dispatchParams) { for (int i=0; i<8; ++i) { dispatchParams = _DispatchData[i]; dispatchIndex = dispatchParams....

April 20, 2024 · zznewclear13
Screen Space Reflection Cover

Screen Space Reflection

Screen Space Reflection While screen space reflection (SSR) is a well-known effect, this article aims to introduce a unique method for calculating screen space reflections – one that I haven’t encountered online before. Many online tutorials cover screen space reflection already, and most of them follow a similar process: calculate the reflection direction in world space, use a mostly uniform step size to traverse the world space, and for each step, compute the normalized device coordinates (NDC)....

March 9, 2024 · zznewclear13
Screen Space Reflection Cover

屏幕空间反射

屏幕空间反射 屏幕空间反射也是一个老生常谈的效果了,但正如本博客的宗旨,要从千篇一律中脱颖而出,这篇文章也将介绍与众不同的,至少我在网上没有见到过的计算屏幕空间反射的方法。 网上有很多很多的屏幕空间反射的教程,绝大部分的流程是这样的:计算世界空间的反射方向,使用一个大部分情况下是统一的步长在世界空间中步进,对于每一次步进,计算标准化设备空间的坐标,将当前的深度和深度图进行比较,如果在深度图之后,认为发生了交叉,采样当前点的颜色值并返回。这种方法能看到很多很多看上去非常完美的反射效果,但几乎没有人会提及所需要的步进次数,因为它往往高得惊人,关于这点我们后续还会谈到。而且对于不同远近的物体,想要达到比较好反射效果,其需要的步长往往是不同的,也很少有人去做这方面的思考。稍好一些的会考虑在交叉之后做几次二分法查找,这样能够让一段一段的反射后的颜色带上下颠倒,使画面看上去更加连贯,后面也能看到对比。还有一些会考虑在计算标准化设备空间的坐标后,根据坐标和[-1, 1]之间的大小关系,提前结束步进或是对反射的颜色和环境反射进行插值。目前看来最好的步进方法,是预先计算Hierarchical ZBuffer,通过对更高LOD步进的方法,使用更少的步进次数达到同样的步进效果,但是Hierarchical ZBuffer并不是一个所有项目都能有的特性。 网上能找到的最有用的教程,是Morgan McGuire写的Screen Space Ray Tracing。在他的这篇文章中也提到了为什么在世界空间中步进是不好的,因为世界空间步进的位置在经过透视变换后,很有可能在屏幕空间中没多大变化,也就导致了世界空间步进需要更多的次数来达到较好的反射效果。在这篇文章中展示了一个非常好的方法,计算裁剪空间和屏幕空间的起点和终点的坐标,通过对裁剪空间的z、裁剪空间的1/w、屏幕空间的xy进行线性插值,省去了每一次步进所需要的矩阵运算,十分值得使用。 本文的目标是,在一个Shader中使用尽量少的步进次数得到正确的反射颜色。随机采样、模糊、菲涅尔效应之类的不在本文的考虑范围之内。本文仅考虑Windows平台下DX11的Shader,这样能省去很多的平台适配的代码,使用的Unity版本是Unity 2022.3.21f1,在文章的最后会附上最终的Shader代码。 反射的计算 参数的选择 计算反射基本上只需要三个参数,一个是Max Distance,只考虑距离反射点一定范围内的物体带来的反射,一个是Step Count,更多的步进次数带来更精确的反射,同时也增加性能消耗,最后一个是Thickness Params,对于一个物体,默认其厚度为depth * _Thickness.y + _Thickness.x,这样当射线经过物体背面时不会被认为发生了交叉。 深度比较 步进的时候比较什么深度也是一个值得思考的问题。将步进的深度记为rayDepth,将采样获得的深度记为sampleDepth,一个很简单的想法在标准化设备空间进行比较,因为直接采样深度图就能获取到标准化设备空间的深度值,当rayDepth < sampleDepth的时候,射线和场景发生了交叉。又或是对实际的深度进行比较,这样能够指定一个厚度,当深度的差大于厚度时,认为射线从场景物体的后面穿了过去并没有发生交叉,当rayDepth > sampleDepth && rayDepth < sampleDepth + thickness的时候,射线和场景发生了交叉。此外裁剪空间的Z分量也能用来判断是否发生了交叉,这里不再赘述。深度图的采样方式则应该使用PointClamp的方式,使用线性插值的话在一前一后的两个面的边缘很可能会被认为发生了交叉,导致画面上有不少的小点,除非另外有一张标记物体边缘的贴图可以用来排除掉这部分的交叉点。 光线步进 伪代码很简单: 记k0、k1分别是步进起点和终点的裁剪空间坐标的w分量的倒数。 记q0、q1分别是步进起点和终点的裁剪空间坐标的xyz分量。 记p0、p1分别是步进起点和终点的标准化设备空间坐标的xy分量。 记w是一个在(0, 1)之间按照1.0f/_StepCount递增的变量。 对每一次步进,更新w的值,并对上面的三组分量线性插值得到k、q、p。 使用q.z * k获得rayDepth,使用p采样深度图获得sampleDepth。 如果rayDepth < sampleDepth,射线和场景发生了交叉,跳出循环,返回p。 使用p采样颜色图,获得反射的颜色。 效果是这样的(步进次数为32次): 看上去非常糟糕,最明显的是拉扯的效果。它主要有两个产生的原因:一是我们并没有使用厚度来判断射线是否从物体的背面穿过,这导致了悬空的物体下方会有很长的拉扯;二是我们并没有对超出屏幕范围的位置进行限制,这导致了我们使用屏幕外的坐标采样深度图但返回了Clamp之后的深度值。 厚度检测 为了解决上面的厚度问题,我们新增了一个方法由于判断步进的位置是否在物体后面。我们需要使用的是距离相机的线性深度linearRayDepth和linearSampleDepth。上文说到我们使用linearSampleDepth * _Thickness.y + _Thickness.x来作为一个场景中一个物体的厚度,我们只需要判断(linearRayDepth-linearSampleDepth-_Thickness.x) / linearSampleDepth和_Thickness.y的大小即可,如果前式大于后式,则表明射线从物体后面穿过。 float getThicknessDiff(float diff, float linearSampleDepth, float2 thicknessParams) { return (diff - thicknessParams....

March 9, 2024 · zznewclear13
Ground Truth Ambient Occlusion Cover

Unity使用ComputeShader计算GTAO

环境光遮蔽 环境光遮蔽,在很久很久以前玩刺客信条的时候就看到过这个词语,但是并不懂什么意思,本着画质拉满的原则总是会勾选这个选项。后来才知道环境光遮蔽翻译自Ambient Occlusion(还真是直白的翻译),用来表现角落里阴暗的效果。 环境光遮蔽作用在光线计算的间接光照的阶段,由于光栅化渲染的局限性,间接光照往往分为漫反射间接光照和高光间接光照,因此环境光遮蔽也分漫反射和高光两种,这里暂时只讨论作用于漫反射间接光照的漫反射环境光遮蔽。而又由于前向渲染的局限性,屏幕空间的环境光遮蔽不分差别地作用于直接光照和间接光照,因此其强度还需要特别地留意。 Ground Truth Ambient Occlusion是Jorge Jimenez在他的文章Practical Real-Time Strategies for Accurate Indirect Occlusion中介绍的一种在主机上能够符合事实环境光遮蔽效果的一种屏幕空间环境光遮蔽的算法。我认为这个算法相较于其他的环境光遮蔽的算法最大的优点是,暗部够暗,在很窄的缝隙中能够很黑很黑,这是别的算法做不到的。 本文极大地参考了英特尔的XeGTAO开源代码。 具体的操作 这篇文章着重要讲的是使用Compute Shader来加速计算的操作方式,因此不会具体涉及到GTAO算法本身,感兴趣的话可以去SIGGRAPH 2016 Course上阅读GTAO的ppt。 GTAO的计算需要视空间法线和深度两个数据,如果是延迟管线的话能轻易得拿到所有数据,但对于前向渲染来说,需要从深度数据还原出视空间法线。正好我之前的文章介绍了一些从深度图计算视空间法线的方法。但在原有文章的基础上,我们还能使用Group Shared Memory对采样数进行一系列的优化。 由于GTAO相对来说算是比较低频的信息,我们可以考虑使用下采样的方式只用半分辨率甚至是更低的分辨率来计算GTAO。这里使用的方法是对NxN大小的区域,每一帧只取一个采样点,最后通过TAA来进行混合。 GTAO本身的采样数也能使用时空噪声来生成较少的采样点,最后通过TAA来进行混合。但是实际使用中发现,如果使用较多的时间混合,当场景中的物体发生移动之后,会露出一部分白色的画面,和较深的AO有比较明显的对比,因此考虑尽量多地使用空间的混合。 得益于Group Shared Memory,可以在非常大的范围内进行空间的混合,这里使用高斯模糊的方式进行混合,能够尽量保持暗部较暗的颜色。如果直接对所有的采样进行平均的话,会导致暗部变得很亮,失去了GTAO最出众的优点。对水平和竖直方向做两次高斯模糊的话,由于本身还会根据深度和法线算出额外的几何上的权重,在下采样程度较大的时候会产生比较明显的瑕疵,可以用全分辨率的深度图和法线来解决,但这会带来额外的采样。 在高斯模糊的阶段,由于模糊是作用在低分辨率的图像上的,在我们的上采样的操作中,还需要根据上采样的位置进行双线性插值(实际上只要一个方向线性插值就好了)。 Render Texture的精度上,GTAO最后的值可以用8位通道来储存,如果不需要额外的视空间法线的话,可以把GTAO值和24位的深度一起存到RGBA32的RT中。这里就偷懒使用R16G16B16A16_SFloat来储存了。 如此一来整个路线图就比较清晰了 下采样深度图获取深度数据 使用深度图计算视空间的法线,或者从G Buffer直接获取法线数据 使用深度图和法线计算GTAO的值 横向上采样,计算水平高斯模糊后的GTAO的值 纵向上采样,计算垂直高斯模糊后的GTAO的值 相关代码和说明 GTAOComputeShader.compute 重中之重就是Compute Shader了。分了四个Kernel:第一个计算GTAO的值,同时还储存了深度图和法线(除了直接储存法线的两个分量,也可以Encode成八面体来储存);第二个和第三个分别是水平和竖直方向的模糊;最后一个用来可视化,实际项目中可以不用这个。 和XeGTAO不同的是,我增加了一个USE_AVERAGE_COS的宏,正常是在每一个Slice中选择最大的cos值,但是考虑到场景中有栅格这样的物体,在时空混合程度不是很大的时候,可以计算cos的平均值来降低栅格对GTAO的影响(也就是减弱了噪声),这个宏完全可以不用开启。 本文为了尽量多的使用空间混合(亦即不使用时间混合),在XeGTAO的时空平均噪波中限制了时间的参数为13,这样GTAO就不会随着时间而变化了,实际上可以传入_FrameIndex充分利用时空噪波的优势。 主要是用groupIndex来储存和读取Group Shared Memory,每个点至多采样两次。计算法线时会采样5x5的区域,因此NORMAL_FROM_DEPTH_PIXEL_RANGE的值是2;计算模糊时既有高斯模糊的采样,还有后续手动线性插值的采样,所以CACHED_AO_NORMAL_DEPTH_FOR_BLUR_SIZE会有两者之和。线性插值还需要注意subpixelBias对线性插值的权重产生的影响。 本文使用了宽度为29的高斯核,可以在demofox的网站上轻松的计算很大的高斯核。 可能会有报寄存器使用数量超过限制的问题,感觉是const array和循环导致的,不过reimport之后就不会报这个警告了。 #pragma kernel GTAOMain #pragma kernel BlurHorizontalMain #pragma kernel BlurVerticalMain #pragma kernel VisualizeMain #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" Texture2D<float4> _ColorTexture; Texture2D<float> _DepthTexture; Texture2D<float4> _GTAOTexture; RWTexture2D<float4> _RW_NormalTexture; RWTexture2D<float4> _RW_GTAOTexture; RWTexture2D<float4> _RW_BlurTexture; RWTexture2D<float4> _RW_VisualizeTexture; SamplerState sampler_LinearClamp; SamplerState sampler_PointClamp; //region Parameters uint _FrameIndex; uint _DownsamplingFactor; float _Intensity; float _SampleRadius; float _DistributionPower; float _FalloffRange; float2 _HeightFogFalloff; float4 _TextureSize; float4 _TAAOffsets; //endregion //region Pre-defined Marcos #define SQRT2_2 0....

December 2, 2022 · zznewclear13
zznewclear13 技术美术 图形学 个人博客 technical art computer graphics