nscreen可以连接电视的游戏机游戏

游戏后期特效第四发 -- 屏幕空间环境光遮蔽(SSAO)a year ago
到此我们发现了一个问题: 上面求得的AO结果非常不理想. 图中有非常明显的条带状阴影, 给人的感觉像是在图上轻轻地抹了一层均匀的油漆. 产生这种现象的原因很简单 - 为了满足实时渲染的性能要求, 我们必须限制采样点的数目.
但是, 对于这种现象我们有一个Trick ---- 可以引入噪声, 将每个采样点以原点法线方向为旋转轴旋转随机的角度. 这样的新采样点会变得极其不规则, 更加离散化. 将低频的条纹转化成高频的噪声. half3 randomVector = tex2D ( _RandomTexture, i.uv_random ).xyz * 2.0 - 1.0;
half3 randomDirection = reflect ( RandomSample[ ii ], randomVector );
上图为引入随机噪声后的采样结果. 我们发现"油漆"变成了"沙子". 2. 模糊/滤波"油漆"好还是"沙子"好? 都不好!
"油漆"显得平淡无奇, "沙子"让人眼花缭乱. 中国人讲究中庸之道, 也就是说 ---- 我们需要一个"中频"的AO!
在此介绍两种方法. 第一种方法是直接模糊. 比较常用的是高斯模糊. 关于高斯模糊的资料有很多, 本文不再赘述.
第二种方法在采样原理上和高斯模糊别无二致, 只是采样系数由静态变为动态: 原点与采样点的UV坐标距离, 法线和深度关系共同决定采样系数, 距离越远采样系数越小, 法线和深度的差距越大则采样系数也越大. 这样的模糊使得结果更加趋近于中频, 进一步减弱了闪烁(Flickering)的效果. 3. 与Color Buffer混合.
一般加入Gamma Correction使得阴影更有层次感, 即最终结果为: tex2D ( _MainTex, i.uv ) * pow ( ( 1 - occlusion ), 2.2 );
上图为SSAO处理后的最终结果. SSAO的问题与优化策略
SSAO技术的基本原理已经介绍完了, 下面我们来谈谈SSAO可能遇到的问题, 以及相应的解决方案: 1. 采样块的问题上文的SSAO实现方案其实是假定了使用Deferred Rendering, 深度和法线都可以非常容易得获取到, 因此我们的半球形采样块可以沿着顶点的法线方向摆放. 但是如果获取法线比较困难, 我们可以将半球形退化成球形, 这也正是2007年Crysis中SSAO的实现方案.
如上图所示, 采样块的摆放与法线无关, 采样点遍布整个球形中. 这种方法的效果自然不如半球形采样块. 2. 重复计算的问题
SSAO最为耗时的操作是模拟多个采样点并计算其AO贡献值, 因此我们应该想办法避免重复计算, 尽量使用以前的结果. 这里可以使用Reverse Reprojection(反向二次投影), 保存上一帧的AO计算信息, 使得当前帧中相对上一帧没有变化的点可以利用旧的AO信息, 避免重复计算. 这种方式称为SSAO with Temporal Coherence(时间相干性), 简称为TSSAO. 具体的实现方式将在下文中进行阐述. 3. 滤波与精确计算之间的矛盾
这也是一个Trade Off ---- 反正运算结果都是要套用滤波来过滤掉噪点, 那么最开始计算的时候就可以想办法在保证质量不受太大影响的前提下, 尽量提升效率. 举一个例子: 假设有两个面饼师傅, 第一个师傅的工作是揉出来5个直径为1.2cm的面球, 第二个师傅的工作是把这5个面球放在一起拍成一个面饼.
我们不讨论这种工作方式是否合理, 只是在此情况下第一个师傅确实不用对1.2cm吹毛求疵, 只要差不多就行了.
明白了这个道理, 就会发现可以在计算AO的时候使用降采样, 这样能成平方倍地降低SSAO的时间复杂度. 关于降采样网上有很多资料, 我的专栏也对此有过介绍. TSSAO1. Reverse Reprojection
交替使用两张Render Texture, 一张代表当前帧, 另一张代表上一帧. 对于当前帧上任何一个Pixel都可以根据其UV坐标重建其世界坐标, 然后根据上一帧的View-Projection矩阵的逆矩阵来转化成上一帧的相应UV坐标. 如果两帧上对应的Pixel的Depth与世界坐标差距不大, 那么当前帧就可以利用上一帧对应Pixel的信息, 免去重复计算.
对于静态场景我们有如下公式:
t表示NDC坐标, P表示Projection矩阵, V表示View矩阵. inline float4 UV2WorldPos(float2 uv, float4x4 iv)
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
float2 invClipSize = float2(_CurP._11, _CurP._22);
float2 spos = (uv * 2 - 1) / invClipSize;
float depthEye = LinearEyeDepth(depth);
float3 vpos = float3(spos, -1) * depthEye;
return mul(iv, float4(vpos, 1.0));
inline float2 WorldPos2UV(float4 worldPos, float4x4 vp)
float4 vpos = mul(vp, worldPos);
float2 projOffset = float2(_CurP._13, _CurP._23);
float2 screenPos = vpos.xy / vpos.w + projOffset;
return 0.5 * screenPos + 0.5;
inline float2 GetMotionVector(float2 uv)
float4 worldPos = UV2WorldPos(uv, _CurIV);
float2 curUv = WorldPos2UV(worldPos, _CurVP);
float2 preUv = WorldPos2UV(worldPos, _PrevVP);
curUv - preUv;
但是对于动态场景上述公式不再成立. 因为SSAO要做到Scene Independent, 同时还必须记录前后两帧的关联, 因此在此我们可以用一个Render Texture单独记录每个像素在前一帧与当前帧的世界坐标的差. 具体的原因详见下文. 2. 新的AO计算公式
在TSSAO中我们使用两个Render Texture分别作为上一帧和当前帧的AO Buffer. 首先计算当前帧的AO贡献值: 表示的是之前已经计算过的采样点数量, k表示每一帧应该计算的采样点数量.
随后, 利用当前帧和上一帧的AO贡献值共同计算当前帧的AO值:
最后, 更新的值:
在此说明一下为什么要设置n的上限. 这里主要有两个原因, 第一是如果之前的计算结果不老化, 当前帧的AO贡献值会越来越小, 算法的反应会越来越慢. 第二是反向二次投影本身是有误差的, 随着投影次数的增加误差会变得非常大, 因此必须限制被使用的结果数量, 适当舍弃掉过老的数据. 3. 检测不合法像素
很容易想到的一个判定条件是深度检测: 如果新旧两个像素的深度差距过大, 那么说明场景 已经改变, 当前像素的AO值已经不正确, 必须全部舍弃. 相对深度关系检测条件如下:
但是, SSAO考虑的不仅仅是当前点, 还有它周边的环境. 举一个例子: 在一个静态的地面上放置着一个动态的立方体. 这个立方体随着时间不规则运动. 地面与立方体地面棱边的外交界处的AO值自然明显高出地面上其他点的值, 但是立方体的移动会使得地面上相应区域的AO值不再有效 ---- 虽然地面没有在动, 地面上的点能够通过深度关系检测.
这里我们用到了另一个Trick ---- 我们在计算当前帧AO的循环中可以同时做出以下判定:
只要有一个采样点不满足上述条件, 则说明其对应原点的周边环境已经发生改变, 其AO值自然也应该重新计算. 对于的计算方法也很简单: 根据和在第一步中记录的世界坐标的差就可以直接得出了.
只要像素被判定为不合法, 则其会被重置为0, 即之前的所有AO运算结果全部舍弃. int ww =
if ( abs ( 1 - texelDepth / preDepth ) &= EPS )
float scale = _Parameters.x /
for ( int ii = 0; ii & _SampleC ii++ )
float3 randomDirection = RAND_SAMPLES [ w + ii ];
randomDirection *= -sign ( dot ( texelNormal, randomDirection ) );
randomDirection += texelNormal * 0.3;
float2 uv_offset = randomDirection.xy *
float randomDepth = texelDepth - randomDirection.z * _Parameters.x;
float sampleD
float3 sampleN
GetDepthNormal ( i.uv + uv_offset, sampleDepth, sampleNormal );
sampleDepth *= _ProjectionParams.z;
float diff = randomDepth - sampleD
if ( diff & _Parameters.y )
occlusion += saturate ( cos ( dot ( randomDirection, texelNormal ) ) ) * pow ( 1 - diff, _Parameters.z );
if ( abs ( randomDirection - abs ( randomDirection + GetMotionVector ( i.uv + uv_offset ) - GetMotionVector ( i.uv ) - texelPosition ) ) &= EPS )
可以在SSAO的滤波基础上加上收敛度的条件. 收敛度定义为:
收敛度越大, 说明当前像素越为"安全", 随着时间的改变越小, 因此采样系数也越大. Selective Temporal Filtering (STF)
这项技术应用在了BattleField3中. 首先它是基于TSSAO的, 不同的是其AO计算方式: 在当前帧的AO贡献值和历史数据中间做插值.
这样做带来的一个小问题就是"老化" ---- 旧的数据不能及时清理出去, 这就导致场景移动比较快的时候, AO Buffer会存在鬼影的问题.
但是一个更为严重的问题是"闪烁" ---- BattleField3的场景中有大量花草树木, 树叶的晃动使得大量像素被频繁检测为失效, 重新计算AO, 这与未失效的部分构成了鲜明的对比.
DICE的解决方案非常Trick: 他们发现存在鬼影的像素和存在闪烁的像素是互斥的. 因此他们想办法甄别这两种像素, 并对于可能产生鬼影的像素关掉Temporal Filtering. 因此这项技术被称为Selective Temporal Filtering.
具体的方法是检测连续性: 对于任何一个Pixel, 连续在x或y方向选择两个像素, 判断这三个像素的深度是否连续. 如果连续则可能产生鬼影, 否则可能产生闪烁.
最后将所有闪烁的像素按照4x4放大, 圈定进行Temporal Filter的区域: 后记
SSAO原理并不复杂, 只是在实际应用场景中会有各种各样的Trick以应对个性化的需要. 文中主要讲解了SSAO的基本原理与TSSAO的优化原理, 并举了BattleField3的STF为例. 引用Nehab, Sander, Lawrence, Tatarchuk, Isidoro.
Accelerating Real-Time Shading with Reverse Reprojection Caching. In ACM
SIGGRAPH/Eurographics Symposium on Graphics Hardware 2007.Mattausch, Oliver, Daniel Scherzer, and Michael Wimmer. "High‐Quality Screen‐Space Ambient Occlusion using Temporal Coherence." Computer Graphics Forum. Vol. 29. No. 8. Blackwell Publishing Ltd, 2010. BAVOIL L., SAINZ M.: Multi-layer dual-resolution
screen-space ambient occlusion. In SIGGRAPH ’09: SIGGRAPH
2009: Talks (New York, NY, USA, 2009), ACM, pp. 1–1. 3, 7Screen space ambient occlusion. (2016, December 29). In Wikipedia, The Free Encyclopedia. Retrieved 19:24, January 28, 2017, from Bavoil, L., & Sainz, M. (2008). Screen space ambient occlusion. NVIDIA developer information: . nvidia. com, 6.赞赏2 人赞赏187收藏分享举报文章被以下专栏收录分享各种Shader的实现思路和源代码推荐阅读{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[{&sourceColumn&:{&lastUpdated&:,&description&:&&,&permission&:&COLUMN_PUBLIC&,&memberId&:,&contributePermission&:&COLUMN_PUBLIC&,&translatedCommentPermission&:&all&,&canManage&:true,&intro&:&分享各种Shader的实现思路和源代码&,&urlToken&:&MeowShader&,&id&:23061,&imagePath&:&v2-a6fcf1225bc73.jpg&,&slug&:&MeowShader&,&applyReason&:&0&,&name&:&大萌喵的Shader相册&,&title&:&大萌喵的Shader相册&,&url&:&https:\u002F\\u002FMeowShader&,&commentPermission&:&COLUMN_ALL_CAN_COMMENT&,&canPost&:true,&created&:,&state&:&COLUMN_NORMAL&,&followers&:1437,&avatar&:{&id&:&v2-a6fcf1225bc73&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&activateAuthorRequested&:false,&following&:false,&imageUrl&:&https:\u002F\\u002Fv2-a6fcf1225bc73_l.jpg&,&articlesCount&:5},&state&:&accepted&,&targetPost&:{&titleImage&:&https:\u002F\\u002Fv2-c75b6b9214aed4b210b2b_r.jpg&,&lastUpdated&:,&imagePath&:&v2-c75b6b9214aed4b210b2b.png&,&permission&:&ARTICLE_PUBLIC&,&topics&:[,6199],&summary&:&写在前面 专栏断更了这么久, 实在惭愧. 这段时间又是期末考试又是回家过节, 实在是没时间整理干货来分享. 之前说好的不及时更新就赔钱, @Cathy Chen童鞋已经拿到10大洋的红包了. 不过从今天开始我就有大把的时间来继续研究图形学啦, 因此会保证更新速度的 ~…&,&copyPermission&:&ARTICLE_COPYABLE&,&translatedCommentPermission&:&all&,&likes&:0,&origAuthorId&:0,&publishedTime&:&T03:26:43+08:00&,&sourceUrl&:&&,&urlToken&:,&id&:2201896,&withContent&:false,&slug&:,&bigTitleImage&:true,&title&:&游戏后期特效第四发 -- 屏幕空间环境光遮蔽(SSAO)&,&url&:&\u002Fp\u002F&,&commentPermission&:&ARTICLE_ALL_CAN_COMMENT&,&snapshotUrl&:&&,&created&:,&comments&:0,&columnId&:23061,&content&:&&,&parentId&:0,&state&:&ARTICLE_PUBLISHED&,&imageUrl&:&https:\u002F\\u002Fv2-c75b6b9214aed4b210b2b_r.jpg&,&author&:{&bio&:&技术美术里最萌的那只&,&isFollowing&:false,&hash&:&f3e81b8aacbfce6e5fd464cf46cfd2a2&,&uid&:676600,&isOrg&:false,&slug&:&yu-jing-ping-64&,&isFollowed&:false,&description&:&在程序员里算是会画画的;在设计师里算是会敲代码的. \n微信: yujingping95\nEmail: \nWeb: http:\u002F\u002Falphamistral.github.io\nRTX: joshuayu&,&name&:&音速键盘猫&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fyu-jing-ping-64&,&avatar&:{&id&:&v2-de4d60d68738b16dcbff83de89ff6ea1&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&memberId&:,&excerptTitle&:&&,&voteType&:&ARTICLE_VOTE_CLEAR&},&id&:534442}],&title&:&游戏后期特效第四发 -- 屏幕空间环境光遮蔽(SSAO)&,&author&:&yu-jing-ping-64&,&content&:&\u003Ch2\u003E写在前面\u003C\u002Fh2\u003E\u003Cp\u003E
专栏断更了这么久, 实在惭愧. 这段时间又是期末考试又是回家过节, 实在是没时间整理干货来分享. 之前说好的不及时更新就赔钱, @Cathy Chen童鞋已经拿到10大洋的红包了. \u003C\u002Fp\u003E\u003Cp\u003E
不过从今天开始我就有大把的时间来继续研究图形学啦, 因此会保证更新速度的 ~ \u003C\u002Fp\u003E\u003Cp\u003E
延续了我对Depth Buffer的一贯兴趣, 本文将介绍\u003Cb\u003ESSAO\u003C\u002Fb\u003E的基本原理及包括\u003Cb\u003ETemporal Coherence SSAO, Selective Temporal Filtering SSAO\u003C\u002Fb\u003E在内的优化算法. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E何为\&SSAO\&\u003C\u002Fh2\u003E\u003Cp\u003E
Screen Space Ambient Occlusion (以下简称SSAO), 屏幕空间环境光遮蔽. 在具体介绍SSAO之前, 本文先介绍更加广义的Ambient Occlusion (AO). \u003C\u002Fp\u003E\u003Cp\u003E
简单来说, Ambient Occlusion(以下简称\&AO\&)是一种基于全局照明中的环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的\u003Cb\u003E光照强度系数\u003C\u002Fb\u003E的算法. AO描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比, 因此使得渲染的结果更加富有层次感, 对比度更高. \u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-40edd773a01ffb69399c75d_b.jpg\& data-rawwidth=\&652\& data-rawheight=\&315\& class=\&origin_image zh-lightbox-thumb\& width=\&652\& data-original=\&https:\u002F\\u002Fv2-40edd773a01ffb69399c75d_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='652'%20height='315'&&\u002Fsvg&\& data-rawwidth=\&652\& data-rawheight=\&315\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&652\& data-original=\&https:\u002F\\u002Fv2-40edd773a01ffb69399c75d_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-40edd773a01ffb69399c75d_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E
图片来自Wiki. 因为老人的皱纹处对外界暴露的部分较少, 使用AO后被遮蔽的部分较多, 渲染后显得更加暗一些, 增加了皱纹的层次感和质感. \u003C\u002Fp\u003E\u003Cp\u003E
AO的计算公式如下: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=AO%28p%2C+n_%7Bp%7D+%29+%3D+%5Cfrac%7B1%7D%7B%5Cpi+%7D+%5Cint_%7B%5COmega+%7D%5E%7B%7D+V%28p%2C+%5Comega+%29max%28n_%7Bp%7D%2A%5Comega%2C+0%29d%5Comega\& alt=\&AO(p, n_{p} ) = \\frac{1}{\\pi } \\int_{\\Omega }^{} V(p, \\omega )max(n_{p}*\\omega, 0)d\\omega\& eeimg=\&1\&\u003E\u003Cp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=n_%7Bp%7D+\& alt=\&n_{p} \& eeimg=\&1\&\u003E代表点\u003Cimg src=\&http:\u002F\\u002Fequation?tex=p\& alt=\&p\& eeimg=\&1\&\u003E的法线, \u003Cimg src=\&http:\u002F\\u002Fequation?tex=%5Comega+\& alt=\&\\omega \& eeimg=\&1\&\u003E代表点\u003Cimg src=\&http:\u002F\\u002Fequation?tex=p\& alt=\&p\& eeimg=\&1\&\u003E切平面正方向的任意单位向量, \u003Cimg src=\&http:\u002F\\u002Fequation?tex=V%28p%2C+%5Comega+%29\& alt=\&V(p, \\omega )\& eeimg=\&1\&\u003E是可见函数, 如果点\u003Cimg src=\&http:\u002F\\u002Fequation?tex=p\& alt=\&p\& eeimg=\&1\&\u003E在\u003Cimg src=\&http:\u002F\\u002Fequation?tex=%5Comega+\& alt=\&\\omega \& eeimg=\&1\&\u003E方向被遮挡则为1, 否则为0. \u003C\u002Fp\u003E\u003Cp\u003E
由此可见, 计算AO系数是一个颇为昂贵的操作. 一般离线渲染器都会采用Ray-Tracing(光线追踪)或是简化的Ray-Marching(所谓光线行进)算法, 模拟若干条射线以计算遮蔽百分比. 很明显这种方式不可能应用到实时图形渲染中. 尽管目前有一些实时计算AO的新技术, 但是其性能距离普及还有很长的路要走. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-17b9e68effc23f3bd170ebefd94b9c9d_b.jpg\& data-rawwidth=\&838\& data-rawheight=\&536\& class=\&origin_image zh-lightbox-thumb\& width=\&838\& data-original=\&https:\u002F\\u002Fv2-17b9e68effc23f3bd170ebefd94b9c9d_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='838'%20height='536'&&\u002Fsvg&\& data-rawwidth=\&838\& data-rawheight=\&536\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&838\& data-original=\&https:\u002F\\u002Fv2-17b9e68effc23f3bd170ebefd94b9c9d_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-17b9e68effc23f3bd170ebefd94b9c9d_b.jpg\&\u003E\u003C\u002Ffigure\u003E
上图为基于Ray-Tracing的AO计算模型. 红色的射线表示V = 1, 绿色的射线表示V = 0. \u003C\u002Fp\u003E\u003Cp\u003E
那么我们能否\u003Cb\u003ETrade Off\u003C\u002Fb\u003E, 用差一点的渲染结果来获得更高的运行效率呢? 答案是肯定的, 而且方法还远不止一种. 本文将重点放在SSAO上. \u003C\u002Fp\u003E\u003Cp\u003E
顾名思义, \&Screen Space\&意味着SSAO并不是场景的预处理, 而是屏幕后期处理. 其原理是在片元着色器中对于屏幕上的每个像素模拟若干个位置随机的采样点, 用被遮蔽的采样点数量百分比来\u003Cb\u003E近似\u003C\u002Fb\u003E表示光照强度系数. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003ESSAO的实现\u003C\u002Fh2\u003E\u003Cp\u003E
SSAO的实现可分为三个步骤: 计算AO, 模糊\u002F滤波, 与Color Buffer混合. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1. 计算AO\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E计算AO的核心问题在于\u003Cb\u003E如何取采样点\u003C\u002Fb\u003E并判断\u003Cb\u003E这些采样点是否被遮蔽\u003C\u002Fb\u003E. 我们首先解决第一个问题. 在此我们使用一种指向法线方向的半球形采样块(Sample Kernel), 并在采样块中生成采样点. \u003Cb\u003E距离原点越远的点, AO贡献越小\u003C\u002Fb\u003E. 采样块如下图所示: \u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-0e8ef56914ab7bdedebbbd23_b.jpg\& data-rawwidth=\&380\& data-rawheight=\&218\& class=\&content_image\& width=\&380\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='380'%20height='218'&&\u002Fsvg&\& data-rawwidth=\&380\& data-rawheight=\&218\& class=\&content_image lazy\& width=\&380\& data-actualsrc=\&https:\u002F\\u002Fv2-0e8ef56914ab7bdedebbbd23_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cbr\u003E\u003Cp\u003E
那么我们转入第二个问题: 如何判断下图中的采样点遮蔽情况呢? \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-eab494f8c71bf1fec82bd9_b.jpg\& data-rawwidth=\&352\& data-rawheight=\&184\& class=\&content_image\& width=\&352\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='352'%20height='184'&&\u002Fsvg&\& data-rawwidth=\&352\& data-rawheight=\&184\& class=\&content_image lazy\& width=\&352\& data-actualsrc=\&https:\u002F\\u002Fv2-eab494f8c71bf1fec82bd9_b.jpg\&\u003E\u003C\u002Ffigure\u003E
(涂黑的点处在几何体表面内部, 因此判定为被遮蔽)\u003C\u002Fp\u003E\u003Cp\u003E
一种方法是将采样点全部投影到View Plane上, 相当于获取采样点的UV坐标, 并同时获取Depth Buffer中该UV坐标处的深度值. 随后比较采样点的深度和场景中该点的深度. 如果采样点的深度更大, 说明其被场景遮蔽. 最终将所有采样点的AO贡献求和, 即是该点的AO值. 计算公式如下: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=AO_%7Bn%7D%28p%29+%3D+%5Cfrac%7B1%7D%7Bn%7DC%28p%2C+s_%7Bi%7D+%29++\& alt=\&AO_{n}(p) = \\frac{1}{n}C(p, s_{i} )
\& eeimg=\&1\&\u003E\u003Cbr\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=C%28p%2C+s_%7Bi%7D+%29+%3D+V%28p%2C+s_%7Bi%7D+%29cos%28s_%7Bi%7D+-+p%2C+n_%7Bp%7D++%29D%28s_%7Bi%7D+-+p+%29\& alt=\&C(p, s_{i} ) = V(p, s_{i} )cos(s_{i} - p, n_{p}
)D(s_{i} - p )\& eeimg=\&1\&\u003E\u003Cbr\u003E\u003Cp\u003E
其中, 函数V前面已经介绍过. 函数D是一个[0, 1]之间的单调递减函数, 距离原点越近的采样点对AO的贡献越大. 一般使用指数函数. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-e53ec439e47cb72ef75ce_b.jpg\& data-rawwidth=\&1336\& data-rawheight=\&835\& class=\&origin_image zh-lightbox-thumb\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-e53ec439e47cb72ef75ce_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1336'%20height='835'&&\u002Fsvg&\& data-rawwidth=\&1336\& data-rawheight=\&835\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-e53ec439e47cb72ef75ce_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-e53ec439e47cb72ef75ce_b.jpg\&\u003E\u003C\u002Ffigure\u003E上图为原图\u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-425f9d8a84d9fa052ba3f7ace46a1d1a_b.jpg\& data-rawwidth=\&2880\& data-rawheight=\&1800\& class=\&origin_image zh-lightbox-thumb\& width=\&2880\& data-original=\&https:\u002F\\u002Fv2-425f9d8a84d9fa052ba3f7ace46a1d1a_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='2880'%20height='1800'&&\u002Fsvg&\& data-rawwidth=\&2880\& data-rawheight=\&1800\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&2880\& data-original=\&https:\u002F\\u002Fv2-425f9d8a84d9fa052ba3f7ace46a1d1a_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-425f9d8a84d9fa052ba3f7ace46a1d1a_b.jpg\&\u003E\u003C\u002Ffigure\u003E上图为求得的AO值. 颜色越深代表AO越大. \u003C\u002Fp\u003E\u003Cp\u003E以下是循环采样部分代码(ii为循环变量): \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-glsl\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ehalf3\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDirection\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ERandomSample\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eii\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Euv_offset\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDirection\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Exy\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Escale\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003Efloat\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDepth\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Edepth\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDirection\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ez\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_Radius\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003Efloat\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EsampleDepth\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Efloat3\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EsampleNormal\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&n\&\u003EDecodeDepthNormal\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Etex2D\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_CameraDepthNormalsTexture\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ei\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Euv_offset\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E),\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EsampleDepth\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EsampleNormal\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&n\&\u003EsampleDepth\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_ProjectionParams\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ez\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003Efloat\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ediff\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E
\u003Cspan class=\&n\&\u003Esaturate\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDepth\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EsampleDepth\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Eif\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ediff\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E&\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_ZDiff\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003EocclusionAmount\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E+=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epow\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ediff\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_Attenuation\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E
到此我们发现了一个问题: 上面求得的AO结果非常不理想. 图中有非常明显的条带状阴影, 给人的感觉像是在图上轻轻地抹了一层均匀的\u003Cb\u003E\u003Ci\u003E\u003Cu\u003E油漆\u003C\u002Fu\u003E\u003C\u002Fi\u003E\u003C\u002Fb\u003E. 产生这种现象的原因很简单 - \u003Cb\u003E为了满足实时渲染的性能要求, 我们必须限制采样点的数目. \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
但是, 对于这种现象我们有一个Trick ---- 可以引入\u003Cb\u003E噪声\u003C\u002Fb\u003E, 将每个采样点以原点法线方向为旋转轴旋转随机的角度. 这样的新采样点会变得极其不规则, 更加离散化. \u003Cb\u003E将低频的条纹转化成高频的噪声. \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-glsl\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ehalf3\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomVector\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Etex2D\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_RandomTexture\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ei\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Euv_random\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E).\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Exyz\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E2.0\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E1.0\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Ehalf3\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomDirection\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ereflect\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ERandomSample\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eii\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E],\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ErandomVector\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-985dfe06f533bc25ee3ce3_b.jpg\& data-rawwidth=\&2880\& data-rawheight=\&1800\& class=\&origin_image zh-lightbox-thumb\& width=\&2880\& data-original=\&https:\u002F\\u002Fv2-985dfe06f533bc25ee3ce3_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='2880'%20height='1800'&&\u002Fsvg&\& data-rawwidth=\&2880\& data-rawheight=\&1800\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&2880\& data-original=\&https:\u002F\\u002Fv2-985dfe06f533bc25ee3ce3_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-985dfe06f533bc25ee3ce3_b.jpg\&\u003E\u003C\u002Ffigure\u003E上图为引入随机噪声后的采样结果. 我们发现\&油漆\&变成了\&\u003Cb\u003E\u003Ci\u003E\u003Cu\u003E沙子\u003C\u002Fu\u003E\u003C\u002Fi\u003E\u003C\u002Fb\u003E\&. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E2. 模糊\u002F滤波\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Ci\u003E\&油漆\&好还是\&沙子\&好? \u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Ci\u003E都不好! \u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E
\&油漆\&显得平淡无奇, \&沙子\&让人眼花缭乱. 中国人讲究中庸之道, 也就是说 ---- \u003Cb\u003E我们需要一个\&中频\&的AO! \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
在此介绍两种方法. 第一种方法是直接模糊. 比较常用的是高斯模糊. 关于高斯模糊的资料有很多, 本文不再赘述. \u003C\u002Fp\u003E\u003Cp\u003E
第二种方法在采样原理上和高斯模糊别无二致, 只是采样系数由静态变为动态: 原点与采样点的UV坐标距离, 法线和深度关系共同决定采样系数, 距离越远采样系数越小, 法线和深度的差距越大则采样系数也越大. 这样的模糊使得结果更加趋近于中频, 进一步减弱了闪烁(Flickering)的效果. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E3. 与Color Buffer混合. \u003C\u002Fp\u003E\u003Cp\u003E
一般加入Gamma Correction使得阴影更有层次感, 即最终结果为: \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-glsl\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Etex2D\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_MainTex\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ei\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epow\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eocclusion\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E),\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E2.2\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-e2a74d972de2e725fa22b2a_b.jpg\& data-rawwidth=\&1336\& data-rawheight=\&835\& class=\&origin_image zh-lightbox-thumb\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-e2a74d972de2e725fa22b2a_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1336'%20height='835'&&\u002Fsvg&\& data-rawwidth=\&1336\& data-rawheight=\&835\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-e2a74d972de2e725fa22b2a_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-e2a74d972de2e725fa22b2a_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cp\u003E
上图为SSAO处理后的最终结果. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003ESSAO的问题与优化策略\u003C\u002Fh2\u003E\u003Cp\u003E
SSAO技术的基本原理已经介绍完了, 下面我们来谈谈SSAO可能遇到的问题, 以及相应的解决方案: \u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1. 采样块的问题\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E上文的SSAO实现方案其实是假定了使用Deferred Rendering, 深度和法线都可以非常容易得获取到, 因此我们的半球形采样块可以沿着顶点的法线方向摆放. 但是如果获取法线比较困难, 我们可以将半球形退化成球形, 这也正是2007年\u003Ci\u003ECrysis\u003C\u002Fi\u003E中SSAO的实现方案. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-ee4a82aa0d9fc507c413_b.jpg\& data-rawwidth=\&352\& data-rawheight=\&184\& class=\&content_image\& width=\&352\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='352'%20height='184'&&\u002Fsvg&\& data-rawwidth=\&352\& data-rawheight=\&184\& class=\&content_image lazy\& width=\&352\& data-actualsrc=\&https:\u002F\\u002Fv2-ee4a82aa0d9fc507c413_b.jpg\&\u003E\u003C\u002Ffigure\u003E
如上图所示, 采样块的摆放与法线无关, 采样点遍布整个球形中. 这种方法的效果自然不如半球形采样块. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E2. 重复计算的问题\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
SSAO最为耗时的操作是模拟多个采样点并计算其AO贡献值, 因此我们应该想办法避免重复计算, 尽量使用以前的结果. 这里可以使用\u003Cb\u003EReverse Reprojection\u003C\u002Fb\u003E(反向二次投影), 保存上一帧的AO计算信息, 使得当前帧中相对上一帧没有变化的点可以利用旧的AO信息, 避免重复计算. 这种方式称为SSAO with Temporal Coherence(\u003Cb\u003E时间相干性\u003C\u002Fb\u003E), 简称为\u003Cb\u003E\u003Cu\u003ETSSAO\u003C\u002Fu\u003E\u003C\u002Fb\u003E. 具体的实现方式将在下文中进行阐述. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E3. 滤波与精确计算之间的矛盾\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
这也是一个Trade Off ---- 反正运算结果都是要套用滤波来过滤掉噪点, 那么最开始计算的时候就可以想办法在保证质量不受太大影响的前提下, 尽量提升效率. 举一个例子: \u003C\u002Fp\u003E\u003Cblockquote\u003E假设有两个面饼师傅, 第一个师傅的工作是揉出来5个直径为1.2cm的面球, 第二个师傅的工作是把这5个面球放在一起拍成一个面饼. \u003C\u002Fblockquote\u003E\u003Cp\u003E
我们不讨论这种工作方式是否合理, 只是在此情况下第一个师傅确实不用对1.2cm吹毛求疵, 只要差不多就行了. \u003C\u002Fp\u003E\u003Cp\u003E
明白了这个道理, 就会发现可以在计算AO的时候使用\u003Cb\u003E\u003Cu\u003E降采样\u003C\u002Fu\u003E\u003C\u002Fb\u003E, 这样能成平方倍地降低SSAO的时间复杂度. 关于降采样网上有很多资料, 我的专栏\u003Ca href=\&https:\u002F\\u002Fp\u002F?refer=MeowShader\& class=\&internal\&\u003E第一篇文章\u003C\u002Fa\u003E也对此有过介绍. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003ETSSAO\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003E1. Reverse Reprojection\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
交替使用两张Render Texture, 一张代表当前帧, 另一张代表上一帧. 对于当前帧上任何一个Pixel都可以根据其UV坐标重建其世界坐标, 然后根据上一帧的View-Projection矩阵的逆矩阵来转化成上一帧的相应UV坐标. 如果两帧上对应的Pixel的Depth与世界坐标差距不大, 那么当前帧就可以利用上一帧对应Pixel的信息, 免去重复计算. \u003C\u002Fp\u003E\u003Cp\u003E
对于静态场景我们有如下公式: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=t_%7Bold_%7Bx%5E%7B%27%7D%2C+y%5E%7B%27%7D%2C+z%5E%7B%27%7D+%7D+%7D+%3D+P_%7Bold%7DV_%7Bold%7DV_%7Bnew%7D%5E%7B-1%7D+P_%7Bnew%7D%5E%7B-1%7D+t_%7Bnew_%7Bx%2C+y%2C+z+%7D+%7D\& alt=\&t_{old_{x^{'}, y^{'}, z^{'} } } = P_{old}V_{old}V_{new}^{-1} P_{new}^{-1} t_{new_{x, y, z } }\& eeimg=\&1\&\u003E\u003Cbr\u003E\u003Cp\u003E
t表示NDC坐标, P表示Projection矩阵, V表示View矩阵. \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-glsl\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&k\&\u003Einline\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EUV2WorldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat4x4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eiv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&k\&\u003Efloat\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Edepth\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ESAMPLE_DEPTH_TEXTURE\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_CameraDepthTexture\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\t\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EinvClipSize\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_CurP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_11\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_CurP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_22\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Espos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E2\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E\u002F\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EinvClipSize\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&k\&\u003Efloat\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EdepthEye\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ELinearEyeDepth\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Edepth\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\t\n
\u003Cspan class=\&n\&\u003Efloat3\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evpos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat3\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Espos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EdepthEye\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Emul\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eiv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat4\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Evpos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E1.0\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E));\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003Einline\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EWorldPos2UV\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Efloat4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EworldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat4x4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evp\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evpos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Emul\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Evp\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EworldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EprojOffset\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_CurP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_13\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_CurP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003E_23\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EscreenPos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evpos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Exy\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E\u002F\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evpos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ew\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EprojOffset\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E0.5\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EscreenPos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&mf\&\u003E0.5\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003Einline\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EGetMotionVector\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat4\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EworldPos\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EUV2WorldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Euv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_CurIV\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EcurUv\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EWorldPos2UV\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EworldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_CurVP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&n\&\u003Efloat2\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EpreUv\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EWorldPos2UV\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EworldPos\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003E_PrevVP\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\t\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E
\u003Cspan class=\&n\&\u003EcurUv\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EpreUv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cbr\u003E\u003Cp\u003E
但是对于动态场景上述公式不再成立. 因为SSAO要做到Scene Independent, 同时还必须记录前后两帧的关联, 因此在此我们可以用一个Render Texture单独记录每个像素在前一帧与当前帧的世界坐标的差. 具体的原因详见下文. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E2. 新的AO计算公式\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
在TSSAO中我们使用两个Render Texture分别作为上一帧和当前帧的AO Buffer. 首先计算当前帧的AO贡献值: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=C_%7Bt+%2B+1%7D+%28p%29+%3D+%5Cfrac%7B1%7D%7Bk%7D+%5Csum_%7Bi+%3D+n_%7Bt%7D%28p%29+%2B+1+%7D%5E%7Bn_%7Bt%7D%28p%29+%2B+k%7D%7BC%28p%2C+s_%7Bi%7D%29%7D\& alt=\&C_{t + 1} (p) = \\frac{1}{k} \\sum_{i = n_{t}(p) + 1 }^{n_{t}(p) + k}{C(p, s_{i})}\& eeimg=\&1\&\u003E\u003Cp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=n_%7Bt%7D%28p%29\& alt=\&n_{t}(p)\& eeimg=\&1\&\u003E表示的是之前已经计算过的采样点数量, k表示每一帧应该计算的采样点数量. \u003C\u002Fp\u003E\u003Cp\u003E
随后, 利用当前帧和上一帧的AO贡献值共同计算当前帧的AO值: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=AO_%7Bt%2B1%7D%28p%29+%3D+%5Cfrac%7Bn_%7Bt%7D%28p%29AO_%7Bt%7D%28p_%7Bold%7D%29+%2B+kC_%7Bt%2B1%7D%28p%29%7D%7Bn_%7Bt%7D%28p%29%2Bk%7D+\& alt=\&AO_{t+1}(p) = \\frac{n_{t}(p)AO_{t}(p_{old}) + kC_{t+1}(p)}{n_{t}(p)+k} \& eeimg=\&1\&\u003E\u003Cbr\u003E\u003Cp\u003E
最后, 更新\u003Cimg src=\&http:\u002F\\u002Fequation?tex=n_%7Bt%7D%28p%29\& alt=\&n_{t}(p)\& eeimg=\&1\&\u003E的值: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=n_%7Bt%2B1%7D%28p%29%3Dmin%28n_%7Bt%7D%28p%29%2Bk%2C+n_%7Bmax%7D%29\& alt=\&n_{t+1}(p)=min(n_{t}(p)+k, n_{max})\& eeimg=\&1\&\u003E\u003Cp\u003E
在此说明一下为什么要设置n的上限. 这里主要有两个原因, 第一是如果之前的计算结果不老化, 当前帧的AO贡献值会越来越小, 算法的反应会越来越慢. 第二是反向二次投影本身是有误差的, 随着投影次数的增加误差会变得非常大, 因此必须限制被使用的结果数量, 适当舍弃掉过老的数据. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E3. 检测不合法像素\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
很容易想到的一个判定条件是深度检测: 如果新旧两个像素的深度差距过大, 那么说明场景 已经改变, 当前像素的AO值已经不正确, 必须全部舍弃. 相对深度关系检测条件如下: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=%7C1+-+%5Cfrac%7Bd_%7Bnew%7D%7D%7Bd_%7Bold%7D%7D+%7C+%3C+%5Cvarepsilon+\& alt=\&|1 - \\frac{d_{new}}{d_{old}} | & \\varepsilon \& eeimg=\&1\&\u003E\u003Cp\u003E
但是, SSAO考虑的不仅仅是当前点, 还有它周边的\u003Cb\u003E环境\u003C\u002Fb\u003E. 举一个例子: 在一个静态的地面上放置着一个动态的立方体. 这个立方体随着时间不规则运动. 地面与立方体地面棱边的外交界处的AO值自然明显高出地面上其他点的值, 但是立方体的移动会使得地面上相应区域的AO值不再有效 ---- 虽然地面没有在动, 地面上的点能够通过深度关系检测. \u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-f384c75ce3b305bece332_b.jpg\& data-rawwidth=\&400\& data-rawheight=\&308\& class=\&content_image\& width=\&400\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='400'%20height='308'&&\u002Fsvg&\& data-rawwidth=\&400\& data-rawheight=\&308\& class=\&content_image lazy\& width=\&400\& data-actualsrc=\&https:\u002F\\u002Fv2-f384c75ce3b305bece332_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cbr\u003E\u003Cp\u003E
这里我们用到了另一个Trick ---- 我们在计算当前帧AO的循环中可以同时做出以下判定: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=%7C+%7C+s_%7Bi%7D+-+p+%7C+-+%7C+s_%7Bi_%7Bold%7D%7D+-+p_%7Bold%7D+%7C+%7C+%3C+%5Cvarepsilon+\& alt=\&| | s_{i} - p | - | s_{i_{old}} - p_{old} | | & \\varepsilon \& eeimg=\&1\&\u003E\u003Cp\u003E
只要有一个采样点不满足上述条件, 则说明其对应原点的周边环境已经发生改变, 其AO值自然也应该重新计算. 对于\u003Cimg src=\&http:\u002F\\u002Fequation?tex=S_%7Bi_%7Bold%7D%7D\& alt=\&S_{i_{old}}\& eeimg=\&1\&\u003E的计算方法也很简单: 根据\u003Cimg src=\&http:\u002F\\u002Fequation?tex=s_%7Bi%7D\& alt=\&s_{i}\& eeimg=\&1\&\u003E和在第一步中记录的世界坐标的差就可以直接得出了. \u003C\u002Fp\u003E\u003Cp\u003E
只要像素被判定为不合法, 则其\u003Cimg src=\&http:\u002F\\u002Fequation?tex=n_%7Bt%7D%28p%29\& alt=\&n_{t}(p)\& eeimg=\&1\&\u003E会被重置为0, 即之前的所有AO运算结果全部舍弃. \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eint ww =\n\nif ( abs ( 1 - texelDepth \u002F preDepth ) &= EPS )\n\tww = 0;\n\nfloat scale = _Parameters.x \u002F\n\nfor ( int ii = 0; ii & _SampleC ii++ )\n{\n\tfloat3 randomDirection = RAND_SAMPLES [ w + ii ];\n\trandomDirection *= -sign ( dot ( texelNormal, randomDirection ) );\n\trandomDirection += texelNormal * 0.3;\n\n\tfloat2 uv_offset = randomDirection.xy *\n\n\tfloat randomDepth = texelDepth - randomDirection.z * _Parameters.x;\n\n\tfloat sampleD\n\tfloat3 sampleN\n\n\tGetDepthNormal ( i.uv + uv_offset, sampleDepth, sampleNormal );\n\n\tsampleDepth *= _ProjectionParams.z;\n\n\tfloat diff = randomDepth - sampleD\n\n\tif ( diff & _Parameters.y )\n\t\tocclusion += saturate ( cos ( dot ( randomDirection, texelNormal ) ) ) * pow ( 1 - diff, _Parameters.z );\n\n\tif ( abs ( randomDirection - abs ( randomDirection + GetMotionVector ( i.uv + uv_offset ) - GetMotionVector ( i.uv ) - texelPosition ) ) &= EPS )\n}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cb\u003E4. 滤波\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
可以在SSAO的滤波基础上加上收敛度的条件. 收敛度定义为: \u003C\u002Fp\u003E\u003Cimg src=\&http:\u002F\\u002Fequation?tex=conv+%3D+min+%28n_%7Bt%7D%28p%29%2C+n_%7Bmax%7D%29+%2F+n_%7Bmax%7D\& alt=\&conv = min (n_{t}(p), n_{max}) \u002F n_{max}\& eeimg=\&1\&\u003E\u003Cp\u003E
收敛度越大, 说明当前像素越为\&安全\&, 随着时间的改变越小, 因此采样系数也越大. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003ESelective Temporal Filtering (STF)\u003C\u002Fh2\u003E\u003Cp\u003E
这项技术应用在了BattleField3中. 首先它是基于TSSAO的, 不同的是其AO计算方式: 在当前帧的AO贡献值和历史数据中间做插值. \u003C\u002Fp\u003E\u003Cp\u003E
这样做带来的一个小问题就是\&老化\& ---- 旧的数据不能及时清理出去, 这就导致场景移动比较快的时候, AO Buffer会存在鬼影的问题. \u003C\u002Fp\u003E\u003Cp\u003E
但是一个更为严重的问题是\&闪烁\& ---- BattleField3的场景中有大量花草树木, 树叶的晃动使得大量像素被频繁检测为失效, 重新计算AO, 这与未失效的部分构成了鲜明的对比. \u003C\u002Fp\u003E\u003Cp\u003E
DICE的解决方案非常Trick: 他们发现存在鬼影的像素和存在闪烁的像素是互斥的. 因此他们想办法甄别这两种像素, 并对于可能产生鬼影的像素关掉Temporal Filtering. 因此这项技术被称为Selective Temporal Filtering. \u003C\u002Fp\u003E\u003Cp\u003E
具体的方法是检测连续性: 对于任何一个Pixel, 连续在x或y方向选择两个像素, 判断这三个像素的深度是否连续. 如果连续则可能产生鬼影, 否则可能产生闪烁. \u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-50abf7a4efaf_b.jpg\& data-rawwidth=\&1366\& data-rawheight=\&776\& class=\&origin_image zh-lightbox-thumb\& width=\&1366\& data-original=\&https:\u002F\\u002Fv2-50abf7a4efaf_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1366'%20height='776'&&\u002Fsvg&\& data-rawwidth=\&1366\& data-rawheight=\&776\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1366\& data-original=\&https:\u002F\\u002Fv2-50abf7a4efaf_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-50abf7a4efaf_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cp\u003E\u003Cbr\u003E
最后将所有闪烁的像素按照4x4放大, 圈定进行Temporal Filter的区域: \u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-98fc149a1cef5bbcaf53_b.jpg\& data-rawwidth=\&1336\& data-rawheight=\&753\& class=\&origin_image zh-lightbox-thumb\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-98fc149a1cef5bbcaf53_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1336'%20height='753'&&\u002Fsvg&\& data-rawwidth=\&1336\& data-rawheight=\&753\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-98fc149a1cef5bbcaf53_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-98fc149a1cef5bbcaf53_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-a0a41d5ad7facb62aa15_b.jpg\& data-rawwidth=\&1336\& data-rawheight=\&751\& class=\&origin_image zh-lightbox-thumb\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-a0a41d5ad7facb62aa15_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1336'%20height='751'&&\u002Fsvg&\& data-rawwidth=\&1336\& data-rawheight=\&751\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1336\& data-original=\&https:\u002F\\u002Fv2-a0a41d5ad7facb62aa15_r.jpg\& data-actualsrc=\&https:\u002F\\u002Fv2-a0a41d5ad7facb62aa15_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cbr\u003E\u003Ch2\u003E后记\u003C\u002Fh2\u003E\u003Cp\u003E
SSAO原理并不复杂, 只是在实际应用场景中会有各种各样的Trick以应对个性化的需要. 文中主要讲解了SSAO的基本原理与TSSAO的优化原理, 并举了BattleField3的STF为例. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E引用\u003C\u002Fh2\u003E\u003Cbr\u003E\u003Cp\u003ENehab, Sander, Lawrence, Tatarchuk, Isidoro.\nAccelerating Real-Time Shading with Reverse Reprojection Caching. In ACM\nSIGGRAPH\u002FEurographics Symposium on Graphics Hardware 2007.\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003EMattausch, Oliver, Daniel Scherzer, and Michael Wimmer. \&High‐Quality Screen‐Space Ambient Occlusion using Temporal Coherence.\& \u003Ci\u003EComputer Graphics Forum\u003C\u002Fi\u003E. Vol. 29. No. 8. Blackwell Publishing Ltd, 2010. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003EBAVOIL L., SAINZ M.: Multi-layer dual-resolution\nscreen-space ambient occlusion. In SIGGRAPH ’09: SIGGRAPH\n2009: Talks (New York, NY, USA, 2009), ACM, pp. 1–1. 3, 7\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003EScreen space ambient occlusion. (2016, December 29). In \u003Ci\u003EWikipedia, The Free Encyclopedia\u003C\u002Fi\u003E. Retrieved 19:24, January 28, 2017, from \u003Ca class=\& wrap external\& href=\&http:\u002F\\u002F?target=https%3A\u002F\u002Fen.wikipedia.org\u002Fw\u002Findex.php%3Ftitle%3DScreen_space_ambient_occlusion%26oldid%3D\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EScreen space ambient occlusion\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003EBavoil, L., & Sainz, M. (2008). Screen space ambient occlusion. \u003Ci\u003ENVIDIA developer information: \u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fdevelopers\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Edevelopers\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E. nvidia. com\u003C\u002Fi\u003E, \u003Ci\u003E6\u003C\u002Fi\u003E.\u003C\u002Fp\u003E&,&updated&:new Date(&T19:26:43.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:15,&collapsedCount&:0,&likeCount&:187,&state&:&published&,&isLiked&:false,&slug&:&&,&lastestTipjarors&:[{&isFollowed&:false,&name&:&Rapperswil&,&headline&:&&,&avatarUrl&:&https:\u002F\\u002Fda8e974dc_s.jpg&,&isFollowing&:false,&type&:&people&,&slug&:&rapperswil&,&bio&:&~~&,&hash&:&dd25f025eba3cf89a4ee&,&uid&:12,&isOrg&:false,&description&:&&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Frapperswil&,&avatar&:{&id&:&da8e974dc&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&isFollowed&:false,&name&:&于归功于&,&headline&:&&,&avatarUrl&:&https:\u002F\\u002F_s.jpg&,&isFollowing&:false,&type&:&people&,&slug&:&yu-gui-gong-yu&,&bio&:&托福,sat,ap&,&hash&:&bab853cde11ca72bd31f5&,&uid&:56,&isOrg&:false,&description&:&&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fyu-gui-gong-yu&,&avatar&:{&id&:&&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&isTitleImageFullScreen&:true,&rating&:&none&,&titleImage&:&https:\u002F\\u002Fv2-c75b6b9214aed4b210b2b_r.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&计算机图形学&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&着色器&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&Unity(游戏引擎)&}],&adminClosedComment&:false,&titleImageSize&:{&width&:1728,&height&:1080},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&column&:{&slug&:&MeowShader&,&name&:&大萌喵的Shader相册&},&tipjarState&:&activated&,&tipjarTagLine&:&猫粮&,&sourceUrl&:&&,&pageCommentsCount&:15,&tipjarorCount&:2,&annotationAction&:[],&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T03:26:43+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&程序员&,&isFollowing&:false,&hash&:&609a36eed798c&,&uid&:68,&isOrg&:false,&slug&:&ye-wu&,&isFollowed&:false,&description&:&&,&name&:&夜舞&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fye-wu&,&avatar&:{&id&:&da8e974dc&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&&,&isFollowing&:false,&hash&:&b8de15f0ee1c4dbf1669e8&,&uid&:04,&isOrg&:false,&slug&:&zhang-meng-47-24&,&isFollowed&:false,&description&:&喜欢hxx\u002F然后是数学&,&name&:&逍遥雪球兔&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fzhang-meng-47-24&,&avatar&:{&id&:&v2-69a5c51c21a30efc1f49fb3&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&游戏研发工程师\u002F独立游戏制作者&,&isFollowing&:false,&hash&:&683cdeecabefc&,&uid&:68,&isOrg&:false,&slug&:&cz-shi&,&isFollowed&:false,&description&:&&,&name&:&星辰子&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fcz-shi&,&avatar&:{&id&:&9c6fbbd47cbb120c73dd&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&教练,我想做游戏&,&isFollowing&:false,&hash&:&d62c875b0b8b9f49d7b8d&,&uid&:68,&isOrg&:false,&slug&:&luo-ke-88-45&,&isFollowed&:false,&description&:&一个疯狂的游戏开发者&,&name&:&洛克&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fluo-ke-88-45&,&avatar&:{&id&:&v2-24cfb227b0c5ae&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&skate or die&,&isFollowing&:false,&hash&:&542292ffdd1&,&uid&:645700,&isOrg&:false,&slug&:&da-wen-xi-12-48&,&isFollowed&:false,&description&:&&,&name&:&65daysofstatic&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fda-wen-xi-12-48&,&avatar&:{&id&:&v2-df3a287f8d94e0518dc3&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&summary&:&\u003Cimg src=\&http:\u002F\\u002Fv2-0e8ef56914ab7bdedebbbd23_200x112.png\& data-rawwidth=\&380\& data-rawheight=\&218\& class=\&origin_image inline-img zh-lightbox-thumb\& data-original=\&http:\u002F\\u002Fv2-0e8ef56914ab7bdedebbbd23_r.png\&\u003E写在前面 专栏断更了这么久, 实在惭愧. 这段时间又是期末考试又是回家过节, 实在是没时间整理干货来分享. 之前说好的不及时更新就赔钱, @Cathy Chen童鞋已经拿到10大洋的红包了. 不过从今天开始我就有大把的时间来继续研究图形学啦, 因此会保证更新速度的 ~…&,&reviewingCommentsCount&:0,&meta&:{&previous&:{&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\\u002F50\u002Fv2-619a5b9d4a1d8d83e4cd_xl.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&计算机图形学&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&Unity(游戏引擎)&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&着色器&}],&adminClosedComment&:false,&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&author&:{&bio&:&技术美术里最萌的那只&,&isFollowing&:false,&hash&:&f3e81b8aacbfce6e5fd464cf46cfd2a2&,&uid&:676600,&isOrg&:false,&slug&:&yu-jing-ping-64&,&isFollowed&:false,&description&:&在程序员里算是会画画的;在设计师里算是会敲代码的. \n微信: yujingping95\nEmail: \nWeb: http:\u002F\u002Falphamistral.github.io\nRTX: joshuayu&,&name&:&音速键盘猫&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fyu-jing-ping-64&,&avatar&:{&id&:&v2-de4d60d68738b16dcbff83de89ff6ea1&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&column&:{&slug&:&MeowShader&,&name&:&大萌喵的Shader相册&},&content&:&\u003Ch2\u003E开始之前\u003C\u002Fh2\u003E\u003Cp\u003E
在之前的文章中有前辈提到大萌喵的写作风格的问题, 对此大萌喵感到抱歉和惭愧. 以后在谈技术的正文当中不会出现卖萌耍宝的废话啦 ~ 谢谢各位的批评指正, 大萌喵还是个大三学生, 非常热切地希望与前辈多交流. \u003C\u002Fp\u003E\u003Cp\u003E
上一次的文章中介绍的是相交高亮效果, 用到了DepthBuffer. 然后大萌喵就对DepthBuffer着迷的一发不可收拾 ... 以后几期更新的特效也都会和DepthBuffer有关 ~ \u003C\u002Fp\u003E\u003Cp\u003E
过去的一周比较忙碌, 因此更新不及时啦. 首位举报我没有按时更新的童鞋已经获得了10块大洋的奖励哈哈哈. 希望大家多多监督我哦 ~ \u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E
废话.Stop();\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E垂直雾(Vertical Fog)是个啥\u003C\u002Fh2\u003E\u003Cp\u003E
想必大家都知道雾特效, 一般来讲, 距离摄像机越远的点, 其受到雾特效的影响会越为严重. 这是最为常见的雾特效. \u003C\u002Fp\u003E\u003Cp\u003E
但是还有一种雾, 在某一点的浓度与其和观察点间的距离关系似乎并不大, 而与其世界位置坐标有非常紧密的联系. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-fdbca55bb42fdb4c70baaf_b.jpg\& data-rawwidth=\&858\& data-rawheight=\&536\& class=\&origin_image zh-lightbox-thumb\& width=\&858\& data-original=\&https:\u002F\\u002Fv2-fdbca55bb42fdb4c70baaf_r.jpg\&\u003E\u003C\u002Ffigure\u003E
大萌喵不太懂物理, 姑且理解为雾霾因自身受到的重力而产生了沉积作用, 使得距离地面较近的区域雾浓度特别高, 但是一旦高过某个阈值, 浓度则开始急剧下降. \u003C\u002Fp\u003E\u003Cp\u003E
以上属于大萌喵的不懂装懂辣鸡解释. 如果某位大侠能够科学地解释一下, 大萌喵感激不尽. \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-cc385e90fa905b203377_b.jpg\& data-rawwidth=\&610\& data-rawheight=\&407\& class=\&origin_image zh-lightbox-thumb\& width=\&610\& data-original=\&https:\u002F\\u002Fv2-cc385e90fa905b203377_r.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-357b899e952f59d7f00a88ba_b.jpg\& data-rawwidth=\&964\& data-rawheight=\&638\& class=\&origin_image zh-lightbox-thumb\& width=\&964\& data-original=\&https:\u002F\\u002Fv2-357b899e952f59d7f00a88ba_r.jpg\&\u003E\u003C\u002Ffigure\u003E
以上三张照片全部来自谷歌图片, 摄于Dubai. \u003C\u002Fp\u003E\u003Cp\u003E
知道了Vertical Fog是什么东西之后, 我们就需要知道这个特效\u003Cb\u003E有什么用\u003C\u002Fb\u003E. \u003C\u002Fp\u003E\u003Cp\u003E
在很多Top-Down类型的游戏(比如LOL, Dota, Space Marshall)中想要加入雾特效的话, 使用传统的基于距离和深度的雾特效会导致效果失真. 这是因为Top-Down的视角是比较广的, 简单粗暴地糊上一层雾会导致许多距离摄像机较远, 但又很重要的部分被渲染为白茫茫的雾. 与此相反, 我们只希望在放置GameObject的那一层产生比较集中的雾, 因此可以考虑基于每一个点的世界坐标决定其雾的浓度. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E所以说我们要干啥\u003C\u002Fh2\u003E\u003Cp\u003E
实现基于Image Effect的Vertical Fog效果. 当然了思路是相通的, 如果想要局部地添加雾特效, 也可以将类似的着色器特效应用于模型上, 然后注意调整模型的Blend与ZTest就好了. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E想看懂这篇文章, 我得知道啥\u003C\u002Fh2\u003E\u003Cp\u003E
需要知道DepthBuffer或Unity的CameraDepthTexture. 对点元着色器与片元着色器有一定了解. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E看完这篇文章, 我能知道啥\u003C\u002Fh2\u003E\u003Cp\u003E
你将知道如何在片元着色器中重新构建每一个像素点的世界坐标. (Reconstruct world position from Depth Buffer in Pixel Shader), 如何实现垂直雾特效. \u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E首先, 假设我们什么都不知道\u003C\u002Fh2\u003E\u003Cp\u003E
恩, 假设我们什么都不知道, 只有一个处理前的图和处理后的效果图: \u003C\u002Fp\u003E\u003Cp\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-f81c535bd1da0bffb5f2e_b.jpg\& data-rawwidth=\&1280\& data-rawheight=\&1202\& class=\&origin_image zh-lightbox-thumb\& width=\&1280\& data-original=\&https:\u002F\\u

我要回帖

更多关于 可以连接电视的游戏机 的文章

 

随机推荐