网上的这方面教程实在太少,可能因为大佬们都是直接看源代码然后理解的。萌新这么做要踩很多坑,比如我 ,但为了Save Some Time所以我决定发在这里,以供大佬们参考。
看源代码和资料多了顺带着英语也变得好像会一点点,神奇!
效果
无图无证据,先结果图:
上图,在自定义的Shader中,拿到的SSAO信息。
上图,没有使用SSAO的结果。
上图,在URP管线中,对SSAO的设定参数
SSAO计算流程简介
首先要搞懂SSAO是如何计算的,这样才知道如何编写代码。
1. 首先写入深度图
位于帧调试器的:
如何写入深度图?
这个需要在自己的Shader里面添加一个Pass,专门用于写入。为什么?因为如果是后面的渲染顺序的话,那么这个步骤深度还没写入,那么后面的片元过程就要拿不到数据。
【文件:com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl】
#ifndef UNIVERSAL_DEPTH_ONLY_PASS_INCLUDED
#define UNIVERSAL_DEPTH_ONLY_PASS_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#if defined(LOD_FADE_CROSSFADE)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/LODCrossFade.hlsl"
#endif
struct Attributes
{
float4 position : POSITION;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
#if defined(_ALPHATEST_ON)
float2 uv : TEXCOORD0;
#endif
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
Varyings DepthOnlyVertex(Attributes input)
{
Varyings output = (Varyings)0;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
#if defined(_ALPHATEST_ON)
output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);
#endif
output.positionCS = TransformObjectToHClip(input.position.xyz);
return output;
}
half DepthOnlyFragment(Varyings input) : SV_TARGET
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
#if defined(_ALPHATEST_ON)
Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff);
#endif
#if defined(LOD_FADE_CROSSFADE)
LODFadeCrossFade(input.positionCS);
#endif
return input.positionCS.z;
}
#endif
使用方法很很简单,参考Unlit的那个shader,我们直接使用这段代码(引用):
【自己的代码】
// 写入深度图 来自Unlit 打开FrameDebugger来查看这些算法细节
Pass
{
Name "DepthOnly"
Tags
{
"LightMode" = "DepthOnly"
}
// -------------------------------------
// Render State Commands
ZWrite On
ColorMask R
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex DepthOnlyVertex
#pragma fragment DepthOnlyFragment
// -------------------------------------
// Material Keywords
#pragma shader_feature_local _ALPHATEST_ON
// -------------------------------------
// Unity defined keywords
#pragma multi_compile_fragment _ LOD_FADE_CROSSFADE
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl"
// -------------------------------------
// Includes
#include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
ENDHLSL
}
*** 如果没有这段代码,那么你的自定义的Shader是不会写入深度图的!
2. SSAO
这一步,通过深度图的信息,开始计算SSAO,这部分算法请参阅别的资料。总体来说,判断一个点周围,有多少点处于一个被遮蔽的状态,但是这个算法也有缺陷,因为它可能会错误计算一些信息。
也就是,能在URP管线中设置的这一项。
3. 在自己Shader中拿到这些信息
最后一步,你需要在片元着色器(Fragment)中拿到这些信息,然后进行计算。
#pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION // 需要这个宏定义
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" // 需要这个头文件
注意添加一个#pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
然后需要一个屏幕坐标,也就是ScreenUV,得到这一个像素的点,是在屏幕上的哪一个位置。
【Vert】
o.positionCS = ComputeScreenPos(o.positionHS);
【Fragment】
float2 screenUV = float2(input.positionCS.x, input.positionCS.y) / input.positionCS.w;
最后,就可以很愉快的拿到这些信息啦!
AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(screenUV);
half ind = aoFactor.indirectAmbientOcclusion;
half d = aoFactor.directAmbientOcclusion;
AmbientOcclusionFactor
里包含indirectAmbientOcclusion
和directAmbientOcclusion
,就是它名字的字面意思。
最后,可以根据这些自定义的信息,做进一步的处理。