學習教程來自:【技術美術百人計劃】圖形 4.2 SSAO演算法 螢屏空間環境光遮蔽
筆記
0. 前言
SSAO的使用一般在IPhone10及驍龍845之后的機型中使用
1. SSAO介紹
AO:環境光遮蔽 Ambient Occlusion
SSAO:螢屏空間環境光遮蔽 Screen Space Ambient Occlusion 通過深度緩沖、法線緩沖計算AO
2. SSAO原理
2.1 樣本緩沖
- 深度緩沖:每一個像素距離相機的深度值
- 法線緩沖:相機空間下的法線資訊
- Position
2.2 法線半球
其中:深度(深度值)+位置(相機空間下向量)—>世界空間下相機到像素的向量
相機空間下向量:
v2f vert_Ao(appdata v){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o);
o.vertex = UnityObjectToClipPos(v.vertex);//頂點位置:轉換到裁剪空間
o.uv = v.uv;
float4 screenPos = ComputeScreenPos(o.vertex);//頂點位置:轉換到螢屏空間
float4 ndcPos = (screenPos / screenPos.w) * 2 - 1;//歸一化
float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0)* _ProjectionParams.z;//像素方向:倒推回裁剪空間
o.viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz;//像素方向:倒推回相機空間
return o;
}
3. SSAO演算法實作
3.1 獲取深度和法線緩沖
private void Start()
{
cam = this.GetComponent<Camera>();
cam.depthTextureMode = cam.depthTextureMode | DepthTextureMode.DepthNormals;//與運算,增加深度和法線的渲染紋理
}
3.2 重建相機空間坐標
參考:Unity從深度緩沖重建世界空間位置
其中:深度(深度值)+位置(相機空間下向量)—>相機空間下相機到像素的向量
相機空間下向量:
//第一步:獲得相機空間下的像素方向
v2f vert_Ao(appdata v){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o);
o.vertex = UnityObjectToClipPos(v.vertex);//頂點位置:轉換到裁剪空間
o.uv = v.uv;
float4 screenPos = ComputeScreenPos(o.vertex);//頂點位置:轉換到螢屏空間
float4 ndcPos = (screenPos / screenPos.w) * 2 - 1;//歸一化
float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0)* _ProjectionParams.z;//像素方向:倒推回裁剪空間
o.viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz;//像素方向:倒推回相機空間
return o;
}
//第二步:獲得深度資訊,相乘得到向量
fixed4 frag_Ao(v2f i) : SV_Target{
fixed4 col tex2D(_MainTex, i.uv);//螢屏紋理
float3 viewNormal;//相機空間下法線方向
float linear01Depth;//深度值0-1
float4 depthnormal = tex2D(_CameraDepthNormalsTexture, i.uv);//獲得紋理資訊并解碼
DecodeDepthNormal(depthnormal, linear01Depthm viewNormal);
float3 viewPos = linear01Depth * i.viewVec;//相機空間下像素向量
}
3.3 構建法向量正交基(TBN)
tangent bitangent viewNormal
fixed4 frag_Ao(v2f i) : SV_Target{
//重建向量正交基
viewNormal = normalize(viewNormal) * float3(1, 1, -1);//N
float2 noiseScale = _ScreenParams.xy / 4.0;//噪聲紋理的縮放
float2 noiseUV = i.uv * noiseScale;
float3 randvec = tex2D(_NoiseTex, noiseUV).xyz;//采樣得到隨機向量
float3 tangent = normalize(randvec - viewNormal * dot(randvec, viewNormal));//T
float3 bitangent = cross(viewNormal, tangent);//B
float3x3 TBN = float3x3(tangent, bitangent, viewNormal);
}
3.4 AO采樣核心
//第一步:在C#部分生成采樣核心
private void GenerateAOSampleKernel()
{
if(sampleKernelCount == sampleKernelList.Count)
{
return;//list為滿則回傳
}
sampleKernelList.Clear();
for(int i = 0; i < sampleKernelCount; i++)
{
var vec = new Vector4(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), Random.Range(0, 1.0f), 1.0f);
vec.Normalize();//初始化隨機向量
var scale = (float)i / sampleKernelCount;
scale = Mathf.Lerp(0.01f, 1.0f, scale * scale);//i從0-63對應0-1的二次方程曲線
vec *= scale;
sampleKernelList.Add(vec);
}
}
//第二步:從每一個采樣位置的深度變化程度累計ao
fixed4 frag_Ao(v2f i) : SV_Target{
//采樣并累加
float ao = 0;//ao值
int sampleCount = _SampleKernelCount;
for(int i = 0; i < sampleCount; i++){
float3 randomVec = mul(_SampleKernelArray[i].xyz, TBN);//采樣方向
float weight = smoothstep(0, 0.2, length(randomVec.xy));//針對不同的采樣方向分配權重
//采樣位置
float3 randomPos = viewPos + randomVec * _SampleKernelRadius;//相機空間下的采樣位置
float3 rclipPos = mul((float3x3)unity_CameraProjection, randomPos);//相機空間到裁剪空間(投影空間)下
float2 rscreenPos = (rclipPos.xy / rclipPos.z) * 0.5 + 0.5;//裁剪空間到螢屏空間
float randomDepth;//采樣位置的深度
float3 randomNormal;//采樣位置的法線方向
//從紋理中讀取上述資訊
float4 rcdn = tex2D(_CameraDepthNormalsTexture, rscreenPos);
DecodeDepthNormal(rcdn, randomDepth, randomNormal);
//對比計算ao
float range = abs(randomDepth - linear01Depth) > _RangeStrength ? 0.0 : 1.0;//深度變化
float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0;//深度變化過大的歸零
ao += range * selfCheck * weight;
}
ao = ao / sampleCount;
ao = max(0.0, 1 - ao * _AOStrength);
return float4(ao, ao, ao, 1);
}
4. AO效果改進
從上面代碼中截取出來的
4.1 采樣Noise獲得隨機向量
float2 noiseScale = _ScreenParams.xy / 4.0;//噪聲紋理的縮放
float2 noiseUV = i.uv * noiseScale;
float3 randvec = tex2D(_NoiseTex, noiseUV).xyz;//采樣得到隨機向量
4.2 裁剪掉例外值
- 差距巨大的深度值
float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0;//深度變化過大的歸零
- 同一平面深度值由于精度問題造成的深度變化
float range = abs(randomDepth - linear01Depth) > _RangeStrength ? 0.0 : 1.0;//深度變化
- 根據距離Smooth權重
float weight = smoothstep(0, 0.2, length(randomVec.xy));//針對不同的采樣方向分配權重
- 雙邊濾波模糊(C#部分)
RenderTexture blurRT = RenderTexture.GetTemporary(rtW, rtH, 0);//獲取模糊渲染紋理
ssaoMaterial.SetFloat("_BilaterFilterFactor", 1.0f - bilaterFilterStrength);
ssaoMaterial.SetVector("_BlurRadius", new Vector4(BlurRadius, 0, 0, 0));//x方向
Graphics.Blit(aoRT, blurRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);
ssaoMaterial.SetVector("_BlurRadius", new Vector4(0, BlurRadius, 0, 0));//y方向
Graphics.Blit(blurRT, aoRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);
5. 對比模型烘焙AO
5.1 烘焙方式
- 建模軟體烘焙到紋理:可控性強(操作繁瑣,需要UV,資源占用大),自身細節性強(缺少場景細節),不受靜動態影響,
- 游戲引擎烘焙,如Unity3D Lighting:較簡單,整體細節好,動態物體無法烘培
- SSAO:復雜度基于像素多少、實時性強、靈活可控;性能消耗較前兩種最大,最終效果比1差(理論上)
6. SSAO性能消耗
消耗的點:
- 隨機采樣:IF FOR回圈打破了GPU的并行性,過高的采樣次數大大提高了復雜度
- 雙邊濾波的模糊處理:增加了螢屏采樣的次數
作業
1. 實作SSAO效果
跟著敲了一遍,內容見上述,
2. 使用其他AO演算法實作進行對比
比如HBAO
參考知乎:Ambient Occlusion環境遮罩1提到了以下AO演算法
SSAO-Screen space ambient occlusion
SSDO-Screen space directional occlusion
HDAO-High Definition Ambient Occlusion
HBAO±Horizon Based Ambient Occlusion+
AAO-Alchemy Ambient Occlusion
ABAO-Angle Based Ambient Occlusion
PBAOVXAO-Voxel Accelerated Ambient Occlusion
git上找了個GTAO-Ground Truth Ambient Occlusion的演算法
原理參考:UE4 Mobile GTAO 實作(HBAO續)
代碼來源:Unity3D Ground Truth Ambient Occlusion
整體有點偏黑:
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299761.html
標籤:其他
下一篇:C#中的反射和特性(一)
