目前进入DX11进阶篇,如果不出意外,应该会更新SSR(ScreenSpaceReflection),  ComputeShader的各种应用(如计算FFT,海洋渲染),引入PBR材质系统,TileBasedDefferedShading(分块延迟渲染管线)以及深入探讨TBDS下的优化,基于GPU的ParticleSystem,基于GPU的骨骼动画系统,多线程渲染(MultiThreadRendering)。最终应该会将上面各种实现实现一个demo,讲解整个渲染管线,说说GBuffer,Lighting,Shadow,后处理一堆东西是怎么组织的,当然都是自己的探索,没参考有多少其他先进图形引擎,因为 我主张的一种观点,先从头到尾做出自己的图形引擎,自己组织渲染管线,后面再去参考先进的引擎进行改进,这样会有对比,才知道差距(好吧,现在我感觉只是算法的拼凑,完全没有架构的思想),才能深刻体会图形渲染管线的精粹所在。

先看看程序整个的结构:

ScreenSpaceReflection(屏幕空间反射)的介绍与实现.

屏幕空间算法的家族有很多,如SSAO,SSShadow(屏幕空间阴影),SSR之类的,甚至是延迟渲染本身,其实本质上都是屏幕空间的算法,它们都是利用屏幕空间的信息来求出相应的结果(AO,Shadow,Reflect等等)。

说到SSR,其实原理就是反射的原理(有点废话),只不过是建立在屏幕空间上的反射,最终提取到的反射颜色信息都是来源于屏幕空间的颜色信息。

先看看反射是怎么来的:

看上面图,A点发出光线经过C点的反射进入人的眼睛,所以我们从C点看到了物体上的A像素。这里B点是人眼逆着光线看到A点像素的镜像,当然SSR算法不用上B点。

这里我们决定在相机空间进行上图的算法,上面图的眼睛就是相机的位置(在相机空间是原点),C点为反射面的像素位置,我们求出CA向量,也就是反射光线的方向。这里以C为原点,CA为方向的光线,与物体求交,得到像素A点在相机空间的位置,然后转换为屏幕空间(采样纹理空间),对屏幕RT进行采样,从而得到反射的像素值。

这里抛出两个问题:

(1)在相机空间中,我们的CA光线在相机空间C点开始往CA方向每次前进的长度为多少?

(2)我们如何判断CA光线与物体相交?

(1)在相机空间中,我们的CA光线在相机空间C点开始往CA方向每次前进的长度为多少?

说说第一个问题,我们相机空间每次前进的长度单位为多少?按我查找相关资料,SSR算法刚出来那会,在相机空间光线步进的距离是1.0,但是这里有个问题,因为我们是从相机空间进行光线求交,然后我们转化为屏幕空间进行采样获取相应的像素颜色值,这里问题是什么?问题在于我们在相机空间每次步进1.0,在屏幕空间就是相应步进1.0?答案是否定的,所以我们在相机空间的光线每次步进1.0,在转换为屏幕空间的时候,由于透视纠正的存在,屏幕空间并不是步进1.0,光栅化的时候相机空间坐标点并不成线性变换,那么这样造成了我们在屏幕空间提取所有光线求交的像素值的时候,出现下面图的左边情况:

那么我们怎么办呢?新办法:我们从屏幕空间进行光线步进1个单位,然后进行反透视纠正转为相机空间的步进距离,当然之后还得纠正回去。这里顺便说下,相机空间的坐标,方向量,UV属性,世界空间的坐标,法向量等等属性除以相机空间坐标的Z分量后是成线性关系的,这个过程其实就是光栅化,不懂的回去参考下我的“软光栅器实现”系列博客。软件光栅器六之透视纹理映射。

//初始化反射的颜色float4 reflectColor = float4(0.0, 0.0, 0.0, 0.0f);float2 screenSize = texSize(DiffuseTex);float2 texcoord = outa.Tex;float3 viewPos = ViewPosTex.Sample(SampleClampPoint, outa.Tex).xyz;float3 viewNormal= ViewNormalTex.Sample(SampleClampPoint, outa.Tex).xyz;float t = 1;int2 origin = texcoord * screenSize;int2 coord;//像素在相机空间的位置(光线起点)和法线float3 v0 = viewPos;float3 vsNormal = viewNormal;//相机到像素的方向float3 eyeToPixel = normalize(v0);//光线反射的方向float3 reflRay = normalize(reflect(eyeToPixel, vsNormal));//反射光线终点float3 v1 = v0 + reflRay * farPlane;//屏幕空间的坐标float4 p0 = mul(float4(v0, 1.0), Proj);float4 p1 = mul(float4(v1, 1.0), Proj);//这里参考软光栅器 纹理坐标 世界空间坐标的插值原理(透视纠正)//w为相机空间的Z值float k0 = 1.0 / p0.w;float k1 = 1.0 / p1.w;p0 *= k0;p1 *= k1;v0 *= k0;v1 *= k1;p0.xy = NormalizedDeviceCoordToScreenCoord(p0.xy, screenSize.xy);p1.xy = NormalizedDeviceCoordToScreenCoord(p1.xy, screenSize.xy);//保证屏幕空间的光线起始点终点至少一个单位长度float ds = distanceSquared(p1.xy, p0.xy);p1 += ds < 0.0001 ? 0.01 : 0.0;float divisions = length(p1.xy - p0.xy);float3 dV = (v1 - v0) / divisions;float dK = (k1 - k0) / divisions;float2 traceDir = (p1 - p0) / divisions;float maxSteps = min(divisions, MAX_STEPS);

(2)我们如何判断CA光线与物体相交?

我们在相机空间每次步进的时候,转为换屏幕空间(采样纹理空间),对DepthBuffer进行采样,然后就得到深度值,进行空间转换变为相机空间的深度,然后拿相机空间光线当前位置的Z值与这个相机空间的深度进行比较(略微大于等于)就行了。如下图所示:

if ((curDepth > storeDepth) && ((curDepth - storeDepth) <= 0.1)){reflectColor = DiffuseTex.SampleLevel(SampleClampPoint, texcoord, 0);   reflectColor.a = 0.4;break;    }

所有代码如下所示:

Texture2D<float4> DiffuseTex:register(t0);
Texture2D<float4> FrontDepthTex:register(t1);
Texture2D<float4> BackDepthTex:register(t2);
Texture2D<float4> ViewPosTex:register(t3);
Texture2D<float4> ViewNormalTex:register(t4);
SamplerState SampleWrapLinear:register(s0);
SamplerState SampleClampPoint:register(s1);#define MAX_STEPS 500cbuffer CBMatrix:register(b0)
{matrix World;matrix View;matrix Proj;matrix WorldInvTranspose;float3 cameraPos;float pad1;float4 dirLightColor;float3 dirLightDir;float pad2;float3 ambientLight;float pad3;
};cbuffer CBSSR:register(b1)
{float farPlane;float nearPlane;float2 perspectiveValues;
};struct VertexIn
{float3 Pos:POSITION;float2 Tex:TEXCOORD;
};struct VertexOut
{float4 Pos:SV_POSITION;float2 Tex:TEXCOORD0;
};float DepthBufferConvertToViewDepth(float depth)
{float viewDepth = perspectiveValues.x / (depth + perspectiveValues.y);return viewDepth;
};float2 texSize(Texture2D tex)
{uint texWidth, texHeight;tex.GetDimensions(texWidth, texHeight);return float2(texWidth, texHeight);
}//转换为屏幕空间坐标
float2 NormalizedDeviceCoordToScreenCoord(float2 ndc, float2 screenSize)
{float2 screenCoord;screenCoord.x = screenSize.x * (0.5 * ndc.x + 0.5);screenCoord.y = screenSize.y * (-0.5 * ndc.y + 0.5);return screenCoord;
}float distanceSquared(float2 a, float2 b)
{a -= b;return dot(a, a);
}VertexOut VS(VertexIn ina)
{VertexOut outa;outa.Pos = float4(ina.Pos.xy,1.0f,1.0f);outa.Tex = ina.Tex;return outa;
}float4 PS(VertexOut outa) : SV_Target
{//初始化反射的颜色float4 reflectColor = float4(0.0, 0.0, 0.0, 0.0f);float2 screenSize = texSize(DiffuseTex);float2 texcoord = outa.Tex;float3 viewPos = ViewPosTex.Sample(SampleClampPoint, outa.Tex).xyz;float3 viewNormal= ViewNormalTex.Sample(SampleClampPoint, outa.Tex).xyz;float t = 1;int2 origin = texcoord * screenSize;int2 coord;//像素在相机空间的位置(光线起点)和法线float3 v0 = viewPos;float3 vsNormal = viewNormal;//相机到像素的方向float3 eyeToPixel = normalize(v0);//光线反射的方向float3 reflRay = normalize(reflect(eyeToPixel, vsNormal));//反射光线终点float3 v1 = v0 + reflRay * farPlane;//屏幕空间的坐标float4 p0 = mul(float4(v0, 1.0), Proj);float4 p1 = mul(float4(v1, 1.0), Proj);//这里参考软光栅器 纹理坐标 世界空间坐标的插值原理(透视纠正)//w为相机空间的Z值float k0 = 1.0 / p0.w;float k1 = 1.0 / p1.w;p0 *= k0;p1 *= k1;v0 *= k0;v1 *= k1;p0.xy = NormalizedDeviceCoordToScreenCoord(p0.xy, screenSize.xy);p1.xy = NormalizedDeviceCoordToScreenCoord(p1.xy, screenSize.xy);//保证屏幕空间的光线起始点终点至少一个单位长度float ds = distanceSquared(p1.xy, p0.xy);p1 += ds < 0.0001 ? 0.01 : 0.0;float divisions = length(p1.xy - p0.xy);float3 dV = (v1 - v0) / divisions;float dK = (k1 - k0) / divisions;float2 traceDir = (p1 - p0) / divisions;float maxSteps = min(divisions, MAX_STEPS);while (t < maxSteps){coord = origin + traceDir * t;if (coord.x > screenSize.x || coord.y > screenSize.y || coord.x < 0 || coord.y < 0){break;}float curDepth = (v0 + dV * t).z;float k = k0 + dK * t;curDepth /= k;texcoord = float2(coord) / screenSize;float storeFrontDepth = FrontDepthTex.SampleLevel(SampleClampPoint, texcoord, 0).r;storeFrontDepth = DepthBufferConvertToViewDepth(storeFrontDepth);float storeBackDepth = BackDepthTex.SampleLevel(SampleClampPoint, texcoord, 0).r;storeBackDepth = DepthBufferConvertToViewDepth(storeBackDepth);if ((curDepth >= storeFrontDepth) && ((curDepth - storeFrontDepth) <= 0.1)){reflectColor = DiffuseTex.SampleLevel(SampleClampPoint, texcoord, 0);  reflectColor.a = 0.4;break;    }t++;}return reflectColor;
}

运行截图:

好吧,SSR效果很差,原因在哪?因为我们没考虑物体的厚度,我们是通过

(curDepth >= storeFrontDepth) && ((curDepth - storeFrontDepth) <= 0.1)

来判定深度差不多来光线求交的,但是我们这里只考虑薄的物体,而厚的物体下,DepthBuffer存储的深度为最前面像素的深度,因此我们在光线与厚的物体求交会出现问题。

看上面图,这种情况下我们是拿B点相机空间的深度减去A点相机空间的深度,也就是AB的距离,毫无疑问是大于0.1的,也就是我们刚开始只考虑到薄的物体,没考虑到厚的物体,因此我们造成了反射的像素丢失。那么我们怎么办?很简单,我们得同时知道整个场景前面的DepthBuffer和背面的DepthBuffer. 也就是CullBackFace设置下的DepthBuffer和CullFrontFace设置下的DepthBuffer。如下所示:

CullBackFace设置下的DepthBuffer(前面的深度):(g,b通道为0,R通道大,因此红色,并且越红代表 越深)

CullFrontFace设置下的DepthBuffer(背后的深度):

则物体在厚度为:

storeBackDepth - storeFrontDepth

最终SSR Shader 如下所示:

Texture2D<float4> DiffuseTex:register(t0);
Texture2D<float4> FrontDepthTex:register(t1);
Texture2D<float4> BackDepthTex:register(t2);
Texture2D<float4> ViewPosTex:register(t3);
Texture2D<float4> ViewNormalTex:register(t4);
SamplerState SampleWrapLinear:register(s0);
SamplerState SampleClampPoint:register(s1);#define MAX_STEPS 500cbuffer CBMatrix:register(b0)
{matrix World;matrix View;matrix Proj;matrix WorldInvTranspose;float3 cameraPos;float pad1;float4 dirLightColor;float3 dirLightDir;float pad2;float3 ambientLight;float pad3;
};cbuffer CBSSR:register(b1)
{float farPlane;float nearPlane;float2 perspectiveValues;
};struct VertexIn
{float3 Pos:POSITION;float2 Tex:TEXCOORD;
};struct VertexOut
{float4 Pos:SV_POSITION;float2 Tex:TEXCOORD0;
};float DepthBufferConvertToViewDepth(float depth)
{float viewDepth = perspectiveValues.x / (depth + perspectiveValues.y);return viewDepth;
};float2 texSize(Texture2D tex)
{uint texWidth, texHeight;tex.GetDimensions(texWidth, texHeight);return float2(texWidth, texHeight);
}//转换为屏幕空间坐标
float2 NormalizedDeviceCoordToScreenCoord(float2 ndc, float2 screenSize)
{float2 screenCoord;screenCoord.x = screenSize.x * (0.5 * ndc.x + 0.5);screenCoord.y = screenSize.y * (-0.5 * ndc.y + 0.5);return screenCoord;
}float distanceSquared(float2 a, float2 b)
{a -= b;return dot(a, a);
}VertexOut VS(VertexIn ina)
{VertexOut outa;outa.Pos = float4(ina.Pos.xy,1.0f,1.0f);outa.Tex = ina.Tex;return outa;
}float4 PS(VertexOut outa) : SV_Target
{//初始化反射的颜色float4 reflectColor = float4(0.0, 0.0, 0.0, 0.0f);float2 screenSize = texSize(DiffuseTex);float2 texcoord = outa.Tex;float3 viewPos = ViewPosTex.Sample(SampleClampPoint, outa.Tex).xyz;float3 viewNormal= ViewNormalTex.Sample(SampleClampPoint, outa.Tex).xyz;float t = 1;int2 origin = texcoord * screenSize;int2 coord;//像素在相机空间的位置(光线起点)和法线float3 v0 = viewPos;float3 vsNormal = viewNormal;//相机到像素的方向float3 eyeToPixel = normalize(v0);//光线反射的方向float3 reflRay = normalize(reflect(eyeToPixel, vsNormal));//反射光线终点float3 v1 = v0 + reflRay * farPlane;//屏幕空间的坐标float4 p0 = mul(float4(v0, 1.0), Proj);float4 p1 = mul(float4(v1, 1.0), Proj);//这里参考软光栅器 纹理坐标 世界空间坐标的插值原理(透视纠正)//w为相机空间的Z值float k0 = 1.0 / p0.w;float k1 = 1.0 / p1.w;p0 *= k0;p1 *= k1;v0 *= k0;v1 *= k1;p0.xy = NormalizedDeviceCoordToScreenCoord(p0.xy, screenSize.xy);p1.xy = NormalizedDeviceCoordToScreenCoord(p1.xy, screenSize.xy);//保证屏幕空间的光线起始点终点至少一个单位长度float ds = distanceSquared(p1.xy, p0.xy);p1 += ds < 0.0001 ? 0.01 : 0.0;float divisions = length(p1.xy - p0.xy);float3 dV = (v1 - v0) / divisions;float dK = (k1 - k0) / divisions;float2 traceDir = (p1 - p0) / divisions;float maxSteps = min(divisions, MAX_STEPS);while (t < maxSteps){coord = origin + traceDir * t;if (coord.x > screenSize.x || coord.y > screenSize.y || coord.x < 0 || coord.y < 0){break;}float curDepth = (v0 + dV * t).z;float k = k0 + dK * t;curDepth /= k;texcoord = float2(coord) / screenSize;float storeFrontDepth = FrontDepthTex.SampleLevel(SampleClampPoint, texcoord, 0).r;storeFrontDepth = DepthBufferConvertToViewDepth(storeFrontDepth);float storeBackDepth = BackDepthTex.SampleLevel(SampleClampPoint, texcoord, 0).r;storeBackDepth = DepthBufferConvertToViewDepth(storeBackDepth);if ((curDepth > storeFrontDepth) && ((curDepth - storeFrontDepth) <= (storeBackDepth - storeFrontDepth))){reflectColor = DiffuseTex.SampleLevel(SampleClampPoint, texcoord, 0); reflectColor.a = 0.4;break;    }t++;}return reflectColor;
}

运行效果:

ScreenSpaceReflection(屏幕空间反射)的缺点与改进:

(1)因为是提取屏幕空间的像素值,因此不能反射屏幕外的物体。算是很致命的缺点了。(题外话:其实SS(屏幕空间)系列家族的算法都有类似的致命缺点,所以DXR RealTime RayTrace才是未来)。因此竖直的反射面,球面这些反射屏幕外物体多的表面不适合用SSR,像图中的较为水平的面(水面,江河,地面)采用SSR比较适合。如下图,部分反射信息丢失了。

(2)消耗高,由于可能求一个反射像素就步进几百步,消耗过大,这可以通过 BinarySearch和jitter来优化。以后有空讲解下原理。

(3)不支持粗糙表面的反射,显示中的表面不是理想完全光滑的表面,经常是经过反射变得有些扭曲模糊,后面有空再讲解了。

源码链接:

https://github.com/2047241149/SDEngine

参考资料:

[1]http://jcgt.org/published/0003/04/04/

Directx11进阶之ScreenSpaceReflection(SSR)屏幕空间反射(1)相关推荐

  1. Unity Shader学习:SSR屏幕空间反射

    Unity Shader学习:SSR屏幕空间反射 本文在前向渲染模式下实现,延迟渲染更适合SSR,这里只简单的实现下,未作更深入的优化. 思路:沿视线和法线的反射向量步进光线,判断打到物体(这里用的是 ...

  2. 图形学基础|屏幕空间反射(SSR)

    图形学基础|屏幕空间反射(SSR) 文章目录 图形学基础|屏幕空间反射(SSR) 一.前言 二.反射技术概述 2.1 环境贴图反射 2.2 IBL反射 2.3 平面反射(Planar Reflecti ...

  3. Directx11进阶教程之Cluster Based Deffered Shading

    前言 很多游戏中存在大量的点光源(PointLight),环境艺术家为了让游戏模拟现实的氛围,一个场景下放下上千个点光源(PointLight)毫不奇怪. 在上一章中  Directx11进阶教程之T ...

  4. 从零开始编写minecraft光影包(9)高级水面绘制 反射与屏幕空间反射

    完整资源: 我的Github地址 前情提要: 从0开始编写minecraft光影包(0)GLSL,坐标系,光影包结构介绍 从零开始编写minecraft光影包(1)基础阴影绘制 从零开始编写minec ...

  5. UnityShader——屏幕空间反射(一)

    从Crytek提出SSR后,基本上成了各种大作标配,而自己不知道有意还是无意居然也一直没有自己写过SSR实现,正好最近有点时间,自己写了写试了试,啃了啃别人的源码,博客内容主要是自己在学习过程中的一些 ...

  6. DirectX11 With Windows SDK--32 SSAO(屏幕空间环境光遮蔽)

    前言 由于性能的限制,实时光照模型往往会忽略间接光因素(即场景中其他物体所反弹的光线).但在现实生活中,大部分光照其实是间接光.在第7章里面的光照方程里面引入了环境光项: C a = A L ⊗ m ...

  7. Directx11进阶教程之Tiled Based Deffered Shading

    前言 很多游戏中存在大量的点光源(PointLight),环境艺术家为了让游戏模拟现实的氛围,一个场景下放下上千个点光源(PointLight)毫不奇怪. 下面介绍下传统的渲染管线大量点光源的表现. ...

  8. DirectX11进阶5_硬件实例化与视锥体裁剪及鼠标拾取交互

    一.硬件实例化(Hardware Instancing) 硬件实例化指的是在场景中绘制同一个物体多次,但是是以不同的位置.旋转.缩放.材质以及纹理来绘制(比如一棵树可能会被多次使用以构建出一片森林). ...

  9. Directx11进阶教程之CascadeShadowMap(层级阴影)(上)

    好久没写博客了,紧接着前面的博客,不过这次读取纹理的借口换为了DXUT框架里面的接口. 程序结构 如果没有学会ShadowMap,PCF软阴影原理的建议回去回顾下面几个教程: Directx11教程三 ...

最新文章

  1. git是航空母舰:ScrumBoard
  2. 六,ESP8266 TCP Client(基于Lua脚本语言)
  3. python解析mht文件_php解析mht文件转换成html的方法
  4. 关于php的梗儿_php几个不起眼儿的小技巧
  5. 联想服务器RD450 配置RAID5阵列图文方法
  6. spring boot--拦截器实现
  7. docker学习-运行第一个docker镜像hello world
  8. RH413-测试文件的特殊权限
  9. Mac 系统如何修改python的IDLE默认模块导入路径。
  10. dotnetfx40lp 不能安装在d盘_使用小白一键重装系统给电脑安装双系统教程
  11. 把生活过的像模像样已经很不容易
  12. 惠普服务器做虚拟化,节省成本立竿见影 惠普虚拟化技术详解
  13. js压缩文件或文件夹
  14. Spring AOP术语:连接点和切点的区别。
  15. Win10正式版微软官方原版ISO系统镜像下载大全
  16. nginx: [warn] conflicting server name “xxx“ on 0.0.0.0:80解决
  17. 什么是借、贷、来账、往账、挂账、贷记、借记
  18. 饥荒服务器账号问题,求救,游侠平台的 饥荒服务器问题 请教高手指教下
  19. 对MYSQL的explain中extra using where的理解
  20. 拣货单Picket Ticket

热门文章

  1. 每天读一遍,坚持27天,你的英语水平就可以达到跟美国人交流
  2. Mac下基于Homebrew的Rattle安装和对于权威安装文档的一点更新建议
  3. window 7 计算机配置文件,怎么用WIN7转移整台老电脑配置与文件到新电脑
  4. Linux下zip格式文件的解压缩与压缩操作命令详解
  5. win7怎么允许计算机访问,win7系统设置允许或拒绝从网络中访问本地电脑的操作方法...
  6. 摄像头未能创建视频预览的解决方法
  7. Python 爬取任意指定城市的天气预报,总共1万字❤️解决重要的“任意指定城市”的问题哦
  8. 背单词系统Python
  9. FLAC3D可视化后处理matlab,FLAC3D模型应力的Surfer可视化后处理.pdf
  10. 如何选择合适的自动化测试工具?