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效果。第一篇Computer Shader 入门 。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Compueter Shader是什么?

1.基本概念

首先我们需要认识Compueter Shader 。顾名思义,就是计算着色器。我们先来看一下百度百科上的概念。
在这里插入图片描述

Compute Shader是一种技术,是微软DirectX 11 API新加入的特性,在Compute Shader的帮助下,程序员可直接将GPU作为并行处理器加以利用,GPU将不仅具有3D渲染能力,也具有其他的运算能力,也就是我们说的GPGPU的概念和物理加速运算。多线程处理技术使游戏更好地利用系统的多个核心。
我们这里可以得出几个结论。Compuetrshader是在GPU上运行的,并且将GPU作为并行处理器,采用多线程处理,得以快速运行。
我们来看一下Unity官方API对Compueter Shader的解释。
在这里插入图片描述
我们可以看到Computer Shader是运行在GPU上,并且脱离常规的渲染管线。它们可用于大规模并行GPGPU算法,加速部分游戏渲染。以下是他的适用平台。
刚才我们讲到了他脱离常规渲染管线,首先我们来看看他渲染管线中的位置。
以下是我们的渲染管线流程图。
在这里插入图片描述
这张图很经典。涵盖了整个渲染管线流程。我们先来看一下左下角的备注。蓝色方块表示来自管线输入输出的各种Buffers。
绿色方块表示管线的固定函数不可编辑阶段
黄色方块表示可编程阶段
T表示贴图绑定
B表示Buffer绑定。
管线从左上角开始顶点数据buffer开始,经过中间这一块流程完成渲染主流程,分别经过顶点拉取进入顶点着色器然后经过曲面中色器,几何着色器,图元装配,最后到片段着色器,帧缓冲。整个渲染主流程完毕。渲染的主流程详细可以访问我的其他博客,感兴趣可以上去看看。这里不展开了。其次我们看到右上角就是我们ComputerShader的模块了。我们可以看到他是独立我们我们的管线之外的,所以很重要的一点他不会触发draw call。我们知道触发一次draw call就是cpu向gpu通信,cpu需要设置状态,准备数据。整个流程是很消耗性能。所以这也是computer shader效率高的原因之一。我们可以看到Computer由Disapth模块触发。进入Computer Shader模块。ComputerShader相比较常规Shader 而言,有一点比较特殊,就是其没有任何固定的输入或输出。但是我们可以通过Texture,Buffer作为输入输出。就是我们的蓝色方块。包含Shader storge buffers,Image Load Store,Uniform Block。
对以上做一个总结。
Computer Shader与渲染管线是分开的,他是一个在GPU上运行的渲染命令,这个命令没有用户定义的输入输出。但是可以通过Texuter和buffer作为传入数据载体,以及传出数据载体,不会触发draw Call。
我们继续来看API,触发Computer Shader
在这里插入图片描述
根据API解释,我们可以通过 ComputeShader.Dispatch()直接触发,这对应这一步操作。
在这里插入图片描述
在这里插入图片描述
我们继续来看API接口。
触发 ComputeShader.Dispatch()需要四个参数,分别是内核入口索引,threadGroupsX,threadGroupsY,threadGroupsZ。
此函数“运行”该计算着色器,从而启动 X、Y 和 Z 尺寸中指示数量的 计算着色器线程组。在每个工作组中均进行了一定数量的着色器调用(“线程”)。该工作组 大小是在计算着色器本身中指定的。因此计算着色器 调用的总数是组数乘以线程组大小。 可使用 GetKernelThreadGroupSizes 函数查询工作组的大小。我们前面有讲到Compuetrshader是在GPU上运行的,并且将GPU作为并行处理器,采用多线程处理。这里需要引入新的概念—线程分配。

2.线程分配

在这里插入图片描述
这张图也是超级经典,简单明了的解释了Computer Shader的线程分配。线程组分配建立在一个三维空间
我们通过ComputeShader.Dispatch()知道该函数触发需要四个参数。后三个参数X,Y,Z分别对应三个维度的坐标。我们称之为工作组,每个工作组下又是一个三维空间,存放线程。我们看图说话。
ComputeShader.Dispatch()我们将5,3,2代入。我们将建立一个三维线程组空间。x坐标5个格子,y坐标3个格子,z坐标2个格子。每个格子代表一个线程组。我们可以看到532=30,30个线程组被定义。每个线程组我们定义了(10,8,3)的三维线程空间。((10,8,3)我们在ComputerShader中定义)。这个每个线程组有10 * 8 * 3 = 240个线程被定义。以上就是我们的线程空间了。可以看出这是一个三维坐标空间,每个坐标又嵌套一个三维坐标空间。一个挺复杂的线程空间。这时候我们就需要定位线程,我们需要知道每个像素对应哪个线程。
为了方便定位线程,ComputerShader提供了四个数据。
在这里插入图片描述
SV_GroudThreadID(Int3)当前线程在所在线程组内的ID
SV_GroupID:(Int3)当前线程组ID
SV_DispatchThreadID (Int3)当前线程在所有线程组中所有线程里的ID
SV_GroupIndex int 当前线程在所在线程组内的下标
举个例子。ComputeShader.Dispatch()我们将5,3,2代入。我们将建立一个三维线程组空间。x坐标5个格子,y坐标3个格子,z坐标2个格子。每个格子代表一个线程组。在线程组坐标(2,1,0)下,我们展开其线程空间。线程空间我们定义(10,8,3),x坐标10个格子,y坐标3个格子,z坐标3个格子。线程坐标(7,5,0)对应四个数据的值
SV_GroudThreadID = (7,5,0)
SV_GroupID =(2,1,0)
SV_DispatchThreadID = [2, 1, 0] * (10, 8, 3) + (7, 5, 0) = (27, 13, 0)
SV_GroupIndex = 0 * 10 * 8 + 5 * 10 * 7 = 57

在这里插入图片描述
再举个例子。线程组的概念如上图所示。一般情况下我们会将屏幕如上图分割线程组,每块线程组都是三维的,当然你定义成2维也没问题。每一块我们继续分割,就是我们的线程空间。以上就是我们的线程组分配。
我们继续来看API。变体和关键字。他说你可以使用Keyword在computer shader中去生成变体,用法跟常规shader一样。

2.keywords和关键字

我们看到Computer Shader是同样支持keywords和关键字的。
在这里插入图片描述
对以上做一个总结。ComputerShader可视为一种特殊的,单一阶段的管线。每个ComputerShader执行一个称为工作组的工作单位,每个工作组细分多个线程并行执行。无固定输入输出,通过Texture,Buffer交互数据。

二、使用步骤

1.demo需求

接下来我们开始实际操作以下。
举个例子,当前我们这样一个需求,在一个面片的显示区域内划分一个矩形并填充颜色,使之与面片颜色区分开。
在这里插入图片描述

2.操作步骤

我们选择增加一个shader文件,选中Computer Shader 类型。点击创建。
在这里插入图片描述
生成一个带cs标签的文件,每个一个Compuetr都需要的一个cs文件来控制,为此我们再创建一个cs脚本。再创建一个常规shader 文件用于面片的实际渲染。
我们开始编辑cs脚本

代码如下(示例):

    public ComputeShader shader;
    public int texResolution = 1024;

    Renderer rend;
    RenderTexture outputTexture;

    int kernelHandle;

    // Use this for initialization
    void Start()
    {
    
    
        outputTexture = new RenderTexture(texResolution, texResolution, 0);
        outputTexture.enableRandomWrite = true;
        outputTexture.Create();

        rend = GetComponent<Renderer>();
        rend.enabled = true;

        InitShader();
    }

我们定义公共变量包含 ComputerShader, 以及texResolution,就是我们贴图的大小一个1024的图。Rend 用于获取对象材质球修改shader属性。RenderTexture outputTexture(前面我们有讲到,computer没有专门的输入输出),所以我们定义 outputTexture将作为Computer Shader的输出。kernelHandle作为我们Computer Shader的入口索引。在Star()函数中我们初始化 outputTexture并将其设置维可以随机写入,因为我们需要在Compueter中写入数据。并调用了InitShader()函数初始化 shader。

代码如下(示例):

   private void InitShader()
    {
    
    
        kernelHandle = shader.FindKernel("Square");

        int halfRes = texResolution >> 1;
        int quarterRes = texResolution >> 2;

        Vector4 rect = new Vector4( quarterRes, quarterRes, halfRes, halfRes );

        shader.SetVector( "rect", rect );
        shader.SetTexture(kernelHandle, "Result", outputTexture);
       
        rend.material.SetTexture("_MainTex", outputTexture);

        DispatchShader(texResolution / 8, texResolution / 8);
    }

    private void DispatchShader(int x, int y)
    {
    
    
        shader.Dispatch(kernelHandle, x, y, 1);
    }

kernelHandle = shader.FindKernel(“Square”);
我们在这里获取 Computer Shader入口索引。来看看API的解释

        //
        // 摘要:
        //     Find ComputeShader kernel index.
        //
        // 参数:
        //   name:
        //     Name of kernel function.
        //
        // 返回结果:
        //     The Kernel index, or logs a "FindKernel failed" error message if the kernel is
        //     not found.
        [NativeMethod(Name = "ComputeShaderScripting::FindKernel", HasExplicitThis = true, IsFreeFunction = true, ThrowsException = true)]
        [RequiredByNativeCode]
        public int FindKernel(string name);

函数的作用是查找Computer Shader 的入口索引。参数是 入口函数的名字,返回结果是入口索引,如果找不到的话就会有 FindKernel failed 的错误报警。
我们将texResolution 右移一位,右移两位作为我们的矩形范围传入shader,以及我们的 outputTexture,并将 outputTexture作为面片shader的MainTex属性。调用DispatchShader(),激活Computer Shader。参数就是我们线程组分配。这里填写 texResolution/8,texResolution/8,1,一般情况下我们都会这样填。8是我们在Computer Shader中定义线程个数。这样总线程数就是我们实际texResolution 定义的分辨率像素个数。
以上就是CS代码的内容。
我们来看一下ComputerShader的内容

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


// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
float4 rect;

float inSquare( float2 pt, float4 rect ){
    
    
    float horz = step( rect.x, pt.x ) - step( rect.x + rect.z, pt.x );
    float vert = step( rect.y, pt.y ) - step( rect.y + rect.w, pt.y );
    return horz * vert;
}

[numthreads(8,8,1)]
void Square (uint3 id : SV_DispatchThreadID)
{
    
    
    float res = inSquare( (float2)id.xy, rect );

    Result[id.xy] = float4(0.0, 0.0, res, 1.0);
}

这里定义我们入口名称,我们可以定义很多个入口。变量我们定义了 RWTexture2D Result;一个支持读写的Textrure。以及矩形范围rect,这些变量实际值都由cs代码传入。至此数据从CPU传递到GPU。我们来看入口函数,Square(),标签[numthreads(8,8,1)],这里定义我们单个线程组的线程个数。参数填(8,8,1)。这样分辨率1024的图的像素一共用 1024 * 1024 = 1,048,576个像素,而我们的线程总数就是
1024 * 1024 = 1,048,576
(1024/8 * 1024/8 * 1) *( 8 * 8 * 1) = 1,048,576
可以看出像素个数跟相乘总数是一致的。每个像素都有一个线程处理。
函数Square()我们定义了三维参数 id :来自于SV_DispatchThreadID。我们前面有讲到,SV_DispatchThreadID 就是当前线程在所有线程组中所有线程里的ID,在这里我们用为定位线程,可以看到我们第三参数一直都是填1,这里是为了简化维度,将三维空间转化为二维空间,在这里SV_DispatchThreadID可以简单理解为像素坐标。
接下来我们将参数id,以及从cs代码传进来的矩形范围参数rect 放入函数inSquare()计算检测,范围内范围1,范围外返回0,写入Result,就是我们在cs定义的RenderTexture。从函数inSquare()中的逻辑,我们可以看到Computer Shader一直在强调我是用于计算的。ComputerShader没有返回值,所有这里没有Return 语法。
最后我们来看渲染面片的Shader。

  fixed4 frag (v2f i) : SV_Target
            {
    
    
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }

这里就很简单了,直接采样贴图数据返回。得到以下效果
在这里插入图片描述
接下来我们来看一下 Frame Debug
在这里插入图片描述
我们可以看到在未调正渲染顺序时,Computer Shader总是第一个被执行,右边面板中显示了Computer Shader的名字,入口名字,线程组分配。以及被写入的Texture以及用到的矩形范围参数。
左边的面板中我们可以看到在Status的面板中, Batches是0,即他没有触发draw call。我们可以看到以上几种特性。
这样我们就能得出使用Compuetr Shader的一套组合拳。
CS代码分配,传输变量,并激活Computer Shader,
Computer Shader 完成计算逻辑并将计算结果写入 Texture或者 Buffer
渲染Shader 引用Computer Shader的计算结果完成相应显示效果。
在这里插入图片描述

以上就是一个及其简单的入门例子,计算量也很小。还有很多复杂计算的例子,比如生成草地,计算重力等等。
在这里插入图片描述

总结

以上就是今天要讲的内容,先对Computer Shader有个大概了解,下一节,我们开始讲SSAO理论知识。

猜你喜欢

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