计算着色器 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); } }