最近在学习SSAO,网上参考了一些资料,不过感觉还是Unity例子里面的写的更好一些,简单分享一下源码的学习心得。

  先贴一下网上的两篇不错的资料:https://blog.csdn.net/puppet_master/article/details/82929708https://www.bilibili.com/video/BV16q4y1U7S3?p=2

  SSAO,我以前在Games202的GI相关学习中记录过,之前的想法就是首先把间接光看做一个常量,但是对于每个点来说接受到的间接光程度是不一样的,对这个点到附近进行采样,看附近的点将其遮挡的比例,遮挡比例越大,这个间接光常量的贡献就越少,这个思路应该是没什么问题的,不过这样的话,整个画面都会变亮,实际实践中往往反过来,所有点都会减去一个常量,来让画面变暗,被遮挡比例越大,减去的百分比就越大,最后的画面就会变得更暗一些,使得画面变得更加真实。

  顺带一提,关于这个采样点是否被遮挡,可以简单的用该点的位置深度和相机下的实际深度作比较来近似,如果深度比实际深度大,说明被挡住了,有点类似于ShadowMapping里面的做法;同时,很多资料都会提到,我们只需要采样法线半球的点即可。

  下面贴一下Unity例子的效果:

  原图:

 

 

   SSAO后:

 

 

   可以很明显的看到,类似于墙角天花板的灯后面都带了阴影,使得画面变得更加细致更加饱满。

  下面简单说一下SSAO shader的原理,重点在frag中,对于一个像素,我们可以得到其view空间下的法线,然后使用一个随机的采样方向,得到这个采样点的位置和其深度,做一下比较,根据前文提到的,计算是否被遮挡,是的话就贡献AO值;同时需要注意一点,深度差距过大的是不能贡献AO值的,例如上图中左边的一个Cube,是不能给后面的墙附近贡献AO值的,一般资料都是会做一个range check,Unity中比较简单的用saturate来做了这点,即深度差超过1m的被忽略,感觉这个实际上可以用一个参数来控制,不知道为啥unity没做,可能觉得没必要吧。

  贴一下Unity的代码,有些细节做了一些注释:

 1 half frag_ao (v2f_ao i, int sampleCount, float3 samples[INPUT_SAMPLE_COUNT])
 2 {
 3     // read random normal from noise texture
 4     half3 randN = tex2D (_RandomTexture, i.uvr).xyz * 2.0 - 1.0;    
 5     
 6     // read scene depth/normal
 7     float4 depthnormal = tex2D (_CameraDepthNormalsTexture, i.uv);
 8     float3 viewNorm;
 9     float depth;
10     DecodeDepthNormal (depthnormal, depth, viewNorm);
11     // zzy: https://blog.csdn.net/ak47007tiger/article/details/102658128
12     // depth是[0, 1],下面的z是far plane
13     depth *= _ProjectionParams.z;
14     // zzy: _Params.x ==> SSAO radius
15     float scale = _Params.x / depth;
16 
17     // accumulated occlusion factor
18     float occ = 0.0;
19     for (int s = 0; s < sampleCount; ++s)
20     {
21         // Reflect sample direction around a random vector
22         half3 randomDir = reflect(samples[s], randN);
23         
24         // Make it point to the upper hemisphere
25         half flip = (dot(viewNorm,randomDir)<0) ? 1.0 : -1.0;
26         randomDir *= -flip;
27         // Add a bit of normal to reduce self shadowing
28         randomDir += viewNorm * 0.3;
29         
30         float2 offset = randomDir.xy * scale;
31         float sD = depth - (randomDir.z * _Params.x); // zzy: 这个随机点的位置
32 
33         // Sample depth at offset location
34         float4 sampleND = tex2D (_CameraDepthNormalsTexture, i.uv + offset);
35         float sampleD;
36         float3 sampleN;
37         DecodeDepthNormal (sampleND, sampleD, sampleN);
38         sampleD *= _ProjectionParams.z;
39         float zd = saturate(sD-sampleD); // zzy: 原来如此,使用saturate以后,在这边深度相差过大的就会被舍去
40         // zzy: 比较这个点的实际位置和其深度,如果比深度大,说明被挡住了,需要贡献AO值
41         if (zd > _Params.y) { // zzy: _Params.y ==> minZ
42             // This sample occludes, contribute to occlusion
43             occ += pow(1-zd,_Params.z); // sc2  // zzy: _Params.z ==> 1.0f / m_OcclusionAttenuation
44             //occ += 1.0-saturate(pow(1.0 - zd, 11.0) + zd); // nullsq
45             //occ += 1.0/(1.0+zd*zd*10); // iq
46         }        
47     }
48     occ /= sampleCount;
49     return 1-occ;
50 }
frag_ao

  顺带一提,直接SSAO噪点会比较大,Unity对其做了Blur来处理,通过frame debugger可以查看不Blur的效果:

 

   经过横纵向Blur以后:

 

 

 

 

   最后说一下性能,感觉Unity的性能应该比网上很多SSAO的实现要高的,不过也有文章说Asset Store中性能更高的SSAO Pro:https://indienova.com/u/dio19900/blogread/5099。之后有空继续学习一下。