计算着色器 Compute Shader

Compute Shader 是一种高级功能,用于在 GPU 上执行并行计算任务。它非常适合处理大量数据,执行复杂的数学计算,或在高性能图形处理中使用。

常用于,需要密集的并行多线程计算,CPU不擅长并行计算,丢给GPU去算,然后把结果返回给CPU,或者直接渲染到屏幕上。

需要图形API,Vulkan 或 DX11以上 或 OpenglES3以上 才支持

Compute Shader是独立于渲染管线的,不属于管线流水线过程,属于额外的功能

 

注:但是在安卓下,Vulkan 不知道为啥不支持,OpenglES3才行

Unity设置默认是开启auto api的,安卓实测在手机上会自动选OpenglES3,

但若取消勾选,可以看到有2个api,默认Vulkan在上面,在手机上通过SystemInfo.graphicsDeviceType获取到的当前api是OpenGLES3,但是也加载不出图像,把OpenGLES3挪到上面变成默认的就正常了

 Unity可以通过 SystemInfo.graphicsDeviceType 获取设备的图形API版本

通过 SystemInfo.supportsComputeShaders 判断设备是否支持Compute Shader

 

示例:

MyComputeShader.compute

// 这行代码告诉编译器将 CSMain 函数编译为一个 Compute Shader 内核。你可以在一个着色器文件中定义多个内核,但每个内核都会有自己的 #pragma kernel 指令。例如,一个内核用于图像处理,另一个内核用于物理模拟。
// 内核(Kernel) 是指一个具体的计算任务单元。每个内核都可以看作一个独立的函数,它们在 GPU 上并行执行计算操作,
#pragma kernel CSMain

// 计算着色器的输入和输出缓冲区,一般是从C#里往这里传入一张图片,在这里计算完后修改这张图片
RWTexture2D<float4> Result;

// 定义一个线程组的大小,xyz分别表示三个维度上线程的数量,这里是一个组有8*8*1=64个线程
// 为什么要这样分成3个维度,是跟GPU的硬件设计相关,GPU 硬件在调度和访问内存时通常对某些线程组大小和形状有优化。例如,许多 GPU 硬件对 2D 线程组(如 8x8)进行优化,使得内存访问和线程调度更加高效。
// xyz分别能分配多少线程,要看具体硬件,下面的C#脚本就有通过UnityAPI获取的方式,例如手机上就是512x512x64,PC上是1024x1024x64
// x * y * z <= SystemInfo.maxComputeWorkGroupSize
[numthreads(8, 8, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
   // uint3 id : SV_DispatchThreadID 表示每个线程在执行时都会获得一个唯一的 3D 线程 ID (id),这个 ID 是由 Dispatch 函数确定的调度线程组的索引。xy实际上相当于图片的uv坐标
    // 在这里编写你的计算代码,这里是将上面传入的这张纹理的每一个像素的颜色修改为它的uv坐标,结果就是看到一张彩虹图片
    Result[id.xy] = float4(id.x, id.y, 0.0, 1.0);
}

ComputeShaderExample.cs

// 创建一张RT,并传给CS去计算,然后将结果直接画在相机上
using UnityEngine;

[RequireComponent (typeof(Camera))]
public class ComputeShaderExample : MonoBehaviour
{
    public ComputeShader computeShader;
    public RenderTexture outputTexture;

    void Start()
    {
     // 检查 Compute Shader 支持
        if (SystemInfo.supportsComputeShaders)
        {
            Debug.Log("Compute Shaders are supported.");
        }
        else
        {
            Debug.LogError("Compute Shaders are not supported on this device.");
            return;
        }

        // 获取硬件信息,可以获取一个组中线程的分配情况,但是Unity提供的API好像没有获取最多允许多少个组的
        Debug.Log("Device Name: " + SystemInfo.graphicsDeviceName);                         // 显卡名
        Debug.Log("Device Type: " + SystemInfo.graphicsDeviceType);                         // 图形API
        Debug.Log("Max Compute Buffer Size: " + SystemInfo.maxComputeBufferInputsVertex);   // 最大缓冲区
        Debug.Log("Max Compute Work Group Size: " + SystemInfo.maxComputeWorkGroupSize);    // 单个组中可以分发到计算着色器的最大调用总数,即X*Y*Z的最大值,实际限制取决于着色器复杂性,因此可能较低。
        Debug.Log("Max Compute Work Group Size X: " + SystemInfo.maxComputeWorkGroupSizeX); // 计算着色器一个组可以在 X 维度使用的最大线程数
        Debug.Log("Max Compute Work Group Size Y: " + SystemInfo.maxComputeWorkGroupSizeY);
        Debug.Log("Max Compute Work Group Size Z: " + SystemInfo.maxComputeWorkGroupSizeZ);

        // 初始化 RenderTexture
        int textureSize = 256;
        outputTexture = new RenderTexture(textureSize, textureSize, 0);
        outputTexture.enableRandomWrite = true;
        outputTexture.Create();

        // 设置要使用的内核,设置计算着色器的参数
        int kernelHandle = computeShader.FindKernel("CSMain");
        computeShader.SetTexture(kernelHandle, "Result", outputTexture);

     // 执行计算着色器
     // 图像分辨率是128x128,X维度分成16个组,每个组有8个线程,刚好128个线程可以处理128个像素,Y维也同理,因为是处理二维图像,所以Z维可以只为1
        // 分辨率X ÷ 一个组在X轴上的线程数量WorkGroupSizeX = 所需的X轴组数WorkGroupCountX
        computeShader.Dispatch(kernelHandle, textureSize / 8, textureSize / 8, 1);
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        // 将计算结果渲染到屏幕上
        Graphics.Blit(outputTexture, destination);
    }
}

 

posted @ 2024-06-28 19:14  JeasonBoy  阅读(167)  评论(0编辑  收藏  举报