SSAO By Computer Shader(三)

SSAO By Computer Shader(三)

开启一个专题,SSAO By Computer Shader。使用Computer Shader实现SSAO效果。第一篇Computer Shader 入门 。第二篇SSAO理论知识。第三篇SSAO By Computer Shader,使用Computer Shader实现SSAO效果。



前言

开启一个专题,SSAO By Computer Shader。使用Computer Shader实现SSAO效果。第三篇SSAO By Computer Shader 。我们已经大致了解Computer Shader 的实际用法以及SSAO效果的实现原理,我们将两者结合一下。


一、准备工作---------代码创建


创建一个控制Computer Shader的脚本,以及两个Computer文件分别用于生成AO图以及模糊处理,最后一个Shader文件用于实际渲染屏幕。

二、实现流程

1.Cs编写----------CommandBuffer设置渲染顺序

首先我们需要修改Computer Shader 执行的顺序,我们希望他在所有对象渲染后再执行,但是我们不希望他受到屏幕后处理的影响,这里我们用到CommandBuffer 类。一个功能及其强大的模块类。命令缓冲列表,可以执行的图形命令。
在这里插入图片描述
我们将Compuetr Shader的控制权交给CommandBuffer。首先注册CommandBuffer的渲染顺序。我们在ImageEffect之前激活CommandBuffer,
ImageEffect 就是我们的屏幕效果流程。

代码如下(示例):

  void UnregisterCommandBuffers()
    {
    
    
        _camera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, _renderCommand);
    }
    void RegisterCommandBuffers()
    {
    
    
        _camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _renderCommand);
    }

2.Cs编写---------参数准备

在LateUpdate()函数中我们触发PushDownsampleCommands(_renderCommand),并将我们的CommandBuffer作为参数传入。
在PushDownsampleCommands函数中,我们主要做这几件事情。
在这里插入图片描述
首先第一步参数准备
我们需要生成随机点。我们调用GenSampleKernal()函数获取随机点。
在这里插入图片描述
我们将循环n个随机点,随机点的生成范围x,y轴(-1,1),z轴(0-1)
其次我们要对比深度信息,我们需要Computer Shader 中采样到深度图以及法线信息。怎么传进去是一个问题。
为此我们需要申请一张RT存放深度,法线信息,再传入Computer Shader。我们将设置渲染目标为我们申请的RT,_depthCopy。调用DrawProcedural()函数绘制几何体。并用我们bitMat的Pass1来执行渲染。
bitMat中Pass1很简单,单纯渲染法线深度图。

  Pass
        {
    
    
            CGPROGRAM

            #pragma vertex vert_procedural
            #pragma fragment frag

            sampler2D_float _CameraDepthNormalsTexture;

            float4 frag(v2f_img i) : SV_Target
            {
    
    
                return tex2D(_CameraDepthNormalsTexture, i.uv);
            }

            ENDCG
        }

得到这样效果。
在这里插入图片描述
这样我们的深度,法线信息就存入到_depthCopy 的RT中。
进入第二步参数传入
在这里插入图片描述
我们获取Computer Shader入口索引,CommandBuffer同样有提供Commputer Shader交互的接口。我们将深度法线图_depthCopy 传入,将_linearDepth 作为我们Computer Shader的输入输出,我们将对其写入数据。以及其他常规参数包括随机点集合,半圆半径的等参数。
最后我们激活 Computer Shader ,threadGroupsX,threadGroupsY,threadGroupsZ。我们线程组分配分别是屏幕分辨率/ 8 以及threadGroupsZ 1,简化维度。
以下是CS的完整代码

    void PushDownsampleCommands(CommandBuffer cmd)
    {
    
    
        _sourse.PushAllocationCommand(cmd);
      
        cmd.Blit(BuiltinRenderTextureType.CurrentActive, _sourse.id);
        GenSampleKernal();
        _depthCopy.PushAllocationCommand(cmd);
        cmd.SetRenderTarget(_depthCopy.id);
        cmd.DrawProcedural(Matrix4x4.identity, _blitMaterial, 0, MeshTopology.Triangles, 3);
        
        _linearDepth.PushAllocationCommand(cmd);
        var cs = _upsampleCompute;
        var kernel = cs.FindKernel("CSMain");

        cmd.SetComputeTextureParam(cs, kernel, "LinearZ", _linearDepth.id);
        cmd.SetComputeTextureParam(cs, kernel, "Depth", _depthCopy.id);
    
        cmd.SetComputeVectorArrayParam(cs, "SamplePoint", samplePoint.ToArray());
        cmd.SetComputeFloatParam(cs, "Bias", bias);
        cmd.SetComputeFloatParam(cs, "Stength", strength);
        cmd.SetComputeFloatParam(cs, "CameraWidth", _camera.pixelWidth);
        cmd.SetComputeFloatParam(cs, "CameraHeight", _camera.pixelHeight);
        cmd.SetComputeFloatParam(cs, "SamplePointCount", samplePointCount);
        cmd.SetComputeFloatParam(cs, "SampleKernelRadius", sampleKernelRadius);

        cmd.SetComputeMatrixParam(cs, "InverseProjectionMatrix", _camera.projectionMatrix.inverse);
      
        cmd.DispatchCompute(cs, kernel, 166, 96, 1);
        _ao_result.PushAllocationCommand(cmd);
        var blur_cs = _blurCompute;
        var blur_kernel = blur_cs.FindKernel("CSMain");
        cmd.SetComputeTextureParam(blur_cs, blur_kernel, "AOTEX", _linearDepth.id);
        cmd.SetComputeTextureParam(blur_cs, blur_kernel, "AO_Result", _ao_result.id);
        cmd.SetComputeTextureParam(blur_cs, blur_kernel, "Depth", _depthCopy.id);
        cmd.SetComputeFloatParam(blur_cs, "BilaterFilterFactor", 1f - bilaterFilterStrength);
        cmd.SetComputeVectorParam(blur_cs, "BlurRadius", new Vector4(blurRadius, 0, 0, 0));
        cmd.DispatchCompute(blur_cs, blur_kernel, 166, 96, 1);
        cmd.SetGlobalTexture("_AOTexture", _ao_result.id);
        cmd.SetGlobalTexture("_MainTex", _sourse.id);
     
        cmd.Blit(BuiltinRenderTextureType.CurrentActive, BuiltinRenderTextureType.CameraTarget, _blitMaterial, 1);
    }

3.Computer Shader编写----------AO计算

我们来看看这个Computer Shader实现了什么逻辑

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

#include "UnityCG.cginc"
#include "noiseSimplex.cginc"
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> LinearZ;
#define MAX_SAMPLE_POINT_COUNT 64
CBUFFER_START(CBO)
	float Bias;
	float Stength;
	float CameraWidth;
	float CameraHeight;
	float SamplePointCount;
	float SampleKernelRadius;
	
	float4x4 InverseProjectionMatrix;
CBUFFER_END
float4 SamplePoint[MAX_SAMPLE_POINT_COUNT];
Texture2D<float4> Depth;

同常规Shader一样,我们需要定义参数,分别是我们在cs那边传入的参数。LineZ 对应cs的_linearDepth,我们将其标记为可读可写,他将作为我们的返回值存储我们的AO值。
来看看我们的核心计算逻辑。
在这里插入图片描述
我们先看看一下上部分逻辑。差不多分为三块。
第一线程分配
首先我们要分配线程数numthreads(8,8,1)我们填的是8,8,1基本就是默认值。SV_DispatchThreadID (Int3)当前线程在所有线程组中所有线程里的ID。
第二部获取像素坐标对应视空间上的点。
首先这里的像素坐标就等于我们的id了,我们需要得到当前像素坐标的深度以及法线信息,我们可以直接采样传入_depthCopy得到。我们需要将当前像素坐标转化为视空间下的点,将其转到裁剪空间下再经过透视矩阵的逆矩阵变化到视空间下。乘上深度值得到像素坐标对应视空间上的点。
第三步,创建半圆空间转换矩阵TBN。
我们创建随机向量,减去 随机向量与法线点积乘上法线向量的结果,得到切线向量。通过叉积得到第三个向量组成我们的TBN矩阵。
我们来看下部分逻辑
在这里插入图片描述
同样分为三个部分,
第一部分生成采样点。
我们遍历随机点转换到半圆空间中,加上半圆空间原点生成半圆空间向量。再通过裁剪矩阵,归一化转换到UV空间生成我们的深度采样点。
第二步,深度比较
将采样点采样深度图得到采样深度,与原像素点深度做比较,大于返回1,小于返回0。再乘上一个范围权重,距离越近ao效果越明显。经过1-取反后强化深度效果写入我们的RT,LinearZ。
来看看frame debug的效果
在这里插入图片描述
我们我们的渲染流程发生在ImageEffect之前,右边分别是我们的Kernael名字,线程组分配,法线深度图,ao图以及其他参数。以上就是Computer Shader的逻辑以及实现效果了。
具体代码如下

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

#include "UnityCG.cginc"
#include "noiseSimplex.cginc"
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> LinearZ;
#define MAX_SAMPLE_POINT_COUNT 64
CBUFFER_START(CBO)
	float Bias;
	float Stength;
	float CameraWidth;
	float CameraHeight;
	float SamplePointCount;
	float SampleKernelRadius;
	
	float4x4 InverseProjectionMatrix;
CBUFFER_END
float4 SamplePoint[MAX_SAMPLE_POINT_COUNT];
Texture2D<float4> Depth;


[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    
    
	float3 col;
    // TODO: insert actual code here!
    //深度值
	float pPointDepth;
	//法线值
	float3 viewNormal;
	float4 depth = Depth[id.xy] ;
	DecodeDepthNormal(depth, pPointDepth, viewNormal);
	float2 uv_pos = float2(id.x / CameraWidth, id.y / CameraHeight);
	float4 clips = float4(uv_pos * 2.0  - 1.0, 1, 1);
	float4 viewRay = mul(InverseProjectionMatrix, clips);
	viewRay = viewRay / viewRay.w;
	float3 viewPos = pPointDepth * viewRay;
	float noise = perlin(uv_pos.x, uv_pos.y);
	float noise2 = perlin(uv_pos.x + 0.1, uv_pos.y + 0.1);
	float noise3 = perlin(uv_pos.x - 0.1, uv_pos.y - 0.1);
	float3 randvec = float3(noise, noise2, noise3);
	float3 tangent = normalize(randvec - viewNormal * dot(randvec, viewNormal));
	float3 bitangent = cross(viewNormal, tangent);
	float3x3 TBN = float3x3(tangent, viewNormal, bitangent);
	int sampleCount = SamplePointCount;
	float oc = 0.0;
	for (int i = 0; i < sampleCount; ++i)
	{
    
    
		float3 randomVector = mul(TBN, SamplePoint[i].xyz);
		float3 randomPos = viewPos + randomVector * SampleKernelRadius;
		float3 rclipPos = mul((float3x3)unity_CameraProjection, randomPos);
		float2 rscreenPos = (rclipPos.xy / rclipPos.z) * 0.5 + 0.5;

		float samplePointDepth;
		float3 randomNormal;
		rscreenPos = float2(rscreenPos.x * CameraWidth, rscreenPos.y * CameraHeight);
		float4 rcdn = Depth[rscreenPos.xy] ;
		DecodeDepthNormal(rcdn, samplePointDepth, randomNormal);

		float rangeCheck = smoothstep(0.0, 1.0, SampleKernelRadius / abs(viewPos.z - samplePointDepth));
		oc += (pPointDepth >= samplePointDepth + Bias ? 1.0 : 0.0) * rangeCheck;
	}
	oc = 1.0 - oc / sampleCount;
	col = pow(oc, Stength);
    LinearZ[id.xy] = float4(col, 1);
}

4.Computer Shader编写----------AO模糊

进入下一个环节模糊处理
在这里插入图片描述
跟前面一样我们同样获取模糊的computer Shader的入口索引,以及参数传入。最后设置全局贴图,将AO图传入shader
在这里插入图片描述
模糊中,我们直接偏移多次UV坐标,多次采样然后取均值返回。
最后就是我们的屏幕渲染了。

在这里插入图片描述
以下是Blur Computer Shader的具体代码

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

#include "UnityCG.cginc"

RWTexture2D<float4> AO_Result;
#define MAX_SAMPLE_POINT_COUNT 64
CBUFFER_START(CBO)
Texture2D<float4> AOTEX;
Texture2D<float4> Depth;
float BilaterFilterFactor;
float4 BlurRadius;
CBUFFER_END


	float3 GetNormal(float2 uv)
	{
    
    
		//获得法线贴图
		float4 cdn = Depth[uv] ;
		return DecodeViewNormalStereo(cdn);
	}

	half CompareNormal(float3 normal1, float3 normal2)
	{
    
    
		return smoothstep(BilaterFilterFactor, 1.0, dot(normal1, normal2));
	}

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    
    
		float2 delta = BlurRadius.xy;

		float2 uv = id.xy;
		float2 uv0a = id.xy - delta;
		float2 uv0b = id.xy + delta;
		float2 uv1a = id.xy - 2.0 * delta;
		float2 uv1b = id.xy + 2.0 * delta;
		float2 uv2a = id.xy - 3.0 * delta;
		float2 uv2b = id.xy + 3.0 * delta;

		float3 normal = GetNormal(uv);
		float3 normal0a = GetNormal(uv0a);
		float3 normal0b = GetNormal(uv0b);
		float3 normal1a = GetNormal(uv1a);
		float3 normal1b = GetNormal(uv1b);
		float3 normal2a = GetNormal(uv2a);
		float3 normal2b = GetNormal(uv2b);

		fixed4 col = AOTEX[uv];
		fixed4 col0a = AOTEX[uv0a];
		fixed4 col0b = AOTEX[uv0b];
		fixed4 col1a = AOTEX[uv1a];
		fixed4 col1b = AOTEX[uv1b];
		fixed4 col2a = AOTEX[uv2a];
		fixed4 col2b = AOTEX[uv2b];

		half w = 0.37004405286;
		half w0a = CompareNormal(normal, normal0a) * 0.31718061674;
		half w0b = CompareNormal(normal, normal0b) * 0.31718061674;
		half w1a = CompareNormal(normal, normal1a) * 0.19823788546;
		half w1b = CompareNormal(normal, normal1b) * 0.19823788546;
		half w2a = CompareNormal(normal, normal2a) * 0.11453744493;
		half w2b = CompareNormal(normal, normal2b) * 0.11453744493;

		half3 result;
		result = w * col.rgb;
		result += w0a * col0a.rgb;
		result += w0b * col0b.rgb;
		result += w1a * col1a.rgb;
		result += w1b * col1b.rgb;
		result += w2a * col2a.rgb;
		result += w2b * col2b.rgb;

		result /= w + w0a + w0b + w1a + w1b + w2a + w2b;
		AO_Result[id.xy] = float4(result, 1);
}

4.Shader编写----------融合

在我们的渲染Shader中,我们已经知道ao图以及原图数据了,直接采样融合即可

 Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert_img2
            #pragma fragment frag

            sampler2D _AOTexture;
            sampler2D _MainTex;

            float4 frag(v2f_img i) : SV_Target
            {
    
    
                fixed ao = tex2D(_AOTexture, i.uv).r;
                fixed4 final_col = tex2D(_MainTex, i.uv);
                return final_col * ao;
            }
        ENDCG
        } 

5.FrameDebugger分析

以下就是我们最终效果图
在这里插入图片描述
在帧事件中分别在这四个事件中完成
在这里插入图片描述
1.渲染深度图(shader完成)
2.计算ao值(computer shader完成)
3.模糊ao效果(computer shader完成)
4.融合(shader完成)


总结

以上就是SSAO By Computer 的全部内容,若有错误,欢迎纠正。

猜你喜欢

转载自blog.csdn.net/weixin_39289457/article/details/125582916