第四部分讲一下如何在2D屏幕空间步进光线。

http://casual-effects.blogspot.com/2014/08/screen-space-ray-tracing.html 中的代码感觉不太好理解,这里的代码是按照我自己的理解去重新实现的简单版,在效率上可能不如这个网址中的代码。

3D空间的光线步进

原本的实现中,我们得到光线后,将其在3D空间中进行步进,再投影到2D空间上。在投影过后,3D空间中均匀的采样点在2D空间中就不是均匀分布的了。



(图来自 http://casual-effects.blogspot.com/2014/08/screen-space-ray-tracing.html)

这种不均匀导致了采样效率的低下。特别是光线的z方向的步进较大时,这种不均匀采样更为严重。此时有大量的采样被消耗在同一个像素上,或者一大片区域只有少数几个采样,这种情形是很多见的。

为了改进这个情况,我们将算法改进为在2D的屏幕上,按照像素步进光线。

相比于原来的在3D空间中步进光线,再将光线投影回2D屏幕,采样对应点的深度,做相交检测;在2D空间中步进光线,我们可以免去投影回2D屏幕这一步骤,但是我们也无法知道2D屏幕上的点的深度是多少了。

解决办法很简单,我们用2D空间中的光线起点和终点(这两个点的z值是知道的)做插值,得到中间的点的z值即可。

另外需要注意的是,为了获取透视校正的插值,我们需要做的是用1/z做线性插值,而不是直接对z做插值,如果对光栅化有了解的话应该可以理解这一段。

首先我们将起点和终点投影到屏幕空间里。

				float4 H0 = mul(unity_CameraProjection, float4(start, 1));		//H0.xy / H0.w is in [-1,1]
float4 H1 = mul(unity_CameraProjection, float4(end, 1)); float2 screenP0 = H0.xy / H0.w;
float2 screenP1 = H1.xy / H1.w; //。屏幕空间的采样坐标。

接着我们算出两个点在屏幕上的距离,以及对应的采样步长。

                                float4 texelSize = _MainTex_TexelSize;
if (abs(dot(screenP1 - screenP0, screenP1 - screenP0)) < 1.0) {
screenP1 += texelSize.xy;
}
float2 deltaPixels = (screenP1 - screenP0) * texelSize.zw; //屏幕上两点的像素间隔。
float step; //线性插值的步长。
step = min( 1 / abs(deltaPixels.y), 1 / abs(deltaPixels.x)); // 使每次采样都会恰好间隔一个像素

这里的step是采样的步长。当step == 1时,一步就从start采样到end了。

step = min( 1 / abs(deltaPixels.y), 1 / abs(deltaPixels.x)); 使得每次步进都会在较长的轴上步进一个像素的距离

				step *= PIXEL_STRIDE;		//加大采样距离(加快插值进度)。
float sampleScaler = 1.0 - min(1.0, -start.z / 100);
step *= 1.0 + sampleScaler; //距离较近时(不容易采偏),插值进度更快

PIXEL_STRIDE是一个大于1的数,用于加大采样像素的间隔。

接着我们加入一个samplerScale。当一个像素实际距离镜头较远时,我们需要相对较小的采样步长(因为远处物体的像素要更少,需要更精确的采样),对于近处的物体我们可以用相对宽松的步长。

下面是采样的部分。


float interpolationCounter = step; //记录当前插值的进度。采样进度计数,大于等于1时采样就会结束。初始值为step可以避免一些奇怪的情况。 float oneOverzCurrent = 1 / start.z; //当前采样点的1/z,
float2 screenPCurrent = screenP0; //是当前采样点的屏幕坐标。 float dOneOverZCurrent = step * (1 / end.z - 1 / start.z); //1/z的每两个采样的差值
float2 dScreenPCurrent = step * (screenP1 - screenP0); //同上 oneOverzCurrent += jitter * dOneOverZCurrent;
screenPCurrent += jitter * dScreenPCurrent;
float intersect = 0;
float prevDepth = 1 / (oneOverzCurrent + 0.1 * dOneOverZCurrent) / -_ProjectionParams.z; //上一个采样的z值,用于线段求交;+ 0.1 * dOneOverZCurrent可以防止因为精度问题导致光线在起点自交的问题。
#if 1
UNITY_LOOP
for (int i = 1; i <= STEP_COUNT && interpolationCounter <= 1; i++) {
oneOverzCurrent += dOneOverZCurrent;
screenPCurrent += dScreenPCurrent;
interpolationCounter += step;
float screenPTrueDepth = 1 / oneOverzCurrent/ -_ProjectionParams.z; //求出当前光线终点实际的z值
if (RayIntersect(screenPTrueDepth,prevDepth, screenPCurrent)){ //求交
#if 1 //binary search
...
#endif
hitPixel = (screenPCurrent) / 2 + 0.5;
intersect = 1;
alpha *= 1 - (float)i / STEP_COUNT;
break;
}
prevDepth = screenPTrueDepth;
}

jitter是抖动值,可以优化最终效果。可以通过


float2 uv2 = i.uv * _MainTex_TexelSize.zw;
float c = (uv2.x + uv2.y) * 0.25;
float jitter = fmod(c,1.0);

计算得到。

其他的

到这里,实现SSR的核心方法应该就差不多了。剩下的就是一些通用的优化方法了,比如降采样、加模糊等等,就不再说了。

需要一提的是,这里的屏幕空间光线追踪,其实是一个很通用的算法,可以基于它实现其他的更多的需要光线追踪的效果。

另外,当场景中有必须用forward rendering渲染的物体存在时,会出现一些bug。具体原因是unity的绘制顺序是Deferred -> Foward -> Image Effect,我们在Image Effect绘制SSR时,此时屏幕上已经有Forward物体被绘制了,这时候根据GBuffer算出来的(虽然depth

buffer是正确的)反射颜色就会直接叠加到Forward物体上。

这个bug可以通过改用command buffer解决。官方的实现中,SSR效果处于Deferred rendering结束之后进行(AfterFinalPass),即Deferred -> SSR -> Forward。此时就不会出现奇怪的问题了。但是这样也导致了Forward Object无法被反射(因为计算反射的时候Forward物体还没被绘制)。

下一篇文章会讲一下,用这种光线追踪方法实现屏幕空间阴影。这种阴影作为一个后处理效果,效率极高,而且可以直接和Unity内置的Screen space shadow mask结合使用。

在Unity中实现屏幕空间反射Screen Space Reflection(4)的更多相关文章

  1. 在Unity中实现屏幕空间反射Screen Space Reflection(1)

    本篇文章我会介绍一下我自己在Unity中实现的SSR效果 出发点是理解SSR效果的原理,因此最终效果不是非常完美的(代码都是够用就行),但是从学习的角度来说足以学习到SSR中的核心算法. 如果对核心算 ...

  2. 在Unity中实现屏幕空间反射Screen Space Reflection(2)

    traceRay函数 在上一篇中,我们有如下签名的traceRay函数 bool traceRay(float3 start, float3 direction, out float2 hitPixe ...

  3. 在Unity中实现屏幕空间反射Screen Space Reflection(3)

    本篇讲一下相交检测的优化.有两个措施. 线段相交检测 之前的检测都是检测光线的终点是否在物体内.我们可以尝试检测光线的线段是否与物体相交. 比如说有一个非常薄的物体,光线差不多垂直于它的表面.如果用普 ...

  4. 高级屏幕空间反射: Screen Space Reflection (SSSR)

    SSSR进一步调优,对标寒霜级技术水平,实现方式为Direct3D 11+自主实现实时渲染引擎,方法为对比测试.实现已经有段时间了,还是简要更新下吧.以下画面中的SSSR效果全部采用1:4 resol ...

  5. 高级屏幕空间反射: Screen Space Reflection (SSR)

    自从CE3首倡SSR以来,发展至今,其质量与当年早已不能同日而语.不仅强调超越性的质量,而且强调超越性的性能.乘着周末有空撸了撸,以下是增强型实时SSR结果图.与我原来的SSR原始实现相比,新的增强型 ...

  6. screen space reflection/soft alpha test/

    http://www.crytek.com/cryengine/presentations/secrets-of-cryengine-3-graphics-technology 很多宝贝里面 不止题目 ...

  7. 在Unity中实现屏幕空间阴影(1)

    接着上篇文章,我们实现了SSR效果. 其中的在屏幕空间进行光线追踪的方法是通用的.借此我们再实现一种屏幕空间的效果,即屏幕空间阴影. 文中的图片来自Catlike coding http://catl ...

  8. 在Unity中实现屏幕空间阴影(2)

    参考文章: https://www.imgtec.com/blog/implementing-fast-ray-traced-soft-shadows-in-a-game-engine/ 完成的工程: ...

  9. 关于Unity中的屏幕适配

    一.Game视图的屏幕分辨率可以先自定义添加,供以后选择,以下是手游经常用到的分辨率: 1.1136X640,iPhone5 2.1920X1080,横屏,主流游戏都是这个分辨率 3.1080X192 ...

随机推荐

  1. ansible介绍和安装

    ansible是由 Python 编写的强大的配置管理解决方案,ansible 的特点就在于它的简洁与高效率 ansible与其他的配置管理工具不同点在于:不需要你在想要配置的每个节点上安装自己的组件 ...

  2. ASP.NET存储Session的StateServer

    由于公司要对服务器做个负载均衡,所以Web项目在两台前端服务器(web1.web2)各部署了一份.但是在项目中会用到session.当一开始在web1上登陆后,由于web1之后负载可能会变大,就有可能 ...

  3. PHP《将画布(canvas)图像保存成本地图片的方法》

    用PHP将网页上的Canvas图像保存到服务器上的方法 2014年6月27日 歪脖骇客 发表回复 8 在几年前HTML5还没有流行的时候,我们的项目经理曾经向我提出这样一个需求:让项目评审专家们在评审 ...

  4. php简易配置函数

    nilcms中:php简易配置函数. 文件位置:nc-admin/common.php /* * --------------------------------------------------- ...

  5. jQuery 获取和设置radio 和 checkbox 值的操作

    jquery 中的val(),可以取值也可赋值,表单元素中的radio和checkbox是比较常用的控件,下面说说对它们的取值和赋值的使用 1.取值 表单如下: <div class=" ...

  6. QoS专题-第3期-QoS实现之报文简单分类与标记

    QoS实现之报文简单分类与标记 上一期专题我们讲到,MQC中的流分类可以实现报文的分类,流行为可以对报文进行重标记,从而实现对流量的精细化差分服务.而优先级映射则可以根据802.1p优先级.DSCP优 ...

  7. 如何使用火狐下的两款接口测试工具RESTClient和HttpRequester发送post请求

    Chrome下有著名的Postman,那火狐也有它的左膀右臂,那就是RESTClient和HttpRequester.这两款工具都是火狐的插件,主要用来模拟发送HTTP请求,HTTP请求最常用的两种方 ...

  8. OpenFlow 消息

    消息类型 OpenFlow 的消息共分为三类: Controller-to-Switch 顾名思义,此类消息是由控制器主动发出 Features 用于获取交换机特性 Configuration 用于配 ...

  9. BZOJ1563/洛谷P1912 诗人小G 【四边形不等式优化dp】

    题目链接 洛谷P1912[原题,需输出方案] BZOJ1563[无SPJ,只需输出结果] 题解 四边形不等式 什么是四边形不等式? 一个定义域在整数上的函数\(val(i,j)\),满足对\(\for ...

  10. bzoj5118: Fib数列2(费马小定理+矩阵快速幂)

    题目大意:求$fib(2^n)$ 就是求fib矩阵的(2^n)次方%p,p是质数,根据费马小定理有 注意因为模数比较大会爆LL,得写快速乘法... #include<bits/stdc++.h& ...