102.性能优化-GPU-着色器优化-ComputeShader-获取GPU侧数据
102.1 知识点
哪些数据可以被获取
想要在 CPU 侧(C# 中)获取通过 Compute Shader 在 GPU 中计算完成的数据,必须满足以下条件:
数据在 GPU 中必须是可读写的资源
- 常见的可读写资源包括:
RWStructuredBuffer/RWByteAddressBuffer(对应ComputeBuffer)RWTexture2D/RWTexture3D(对应RenderTexture)
- 只有 GPU 写入了这些资源,CPU 才有可获取的结果
- 常见的可读写资源包括:
数据在 CPU 和 GPU 之间必须通过
ComputeBuffer或Texture形式传递- 对于标量类型、向量类型、矩阵类型如果想要读取,需要使用可读写缓冲区进行包裹,利用
ComputeBuffer进行传递 cbuffer(常量缓冲区)是单向的,只能 CPU 到 GPU,不可反向读取
- 对于标量类型、向量类型、矩阵类型如果想要读取,需要使用可读写缓冲区进行包裹,利用
获取 GPU 侧计算好的数据 —— ComputeBuffer
先声明好测试的 Compute Shader
// 声明计算着色器的内核(Kernel),每个内核对应一个要编译执行的函数
#pragma kernel CSMain
#pragma kernel CSMain2
// 可读写的结构化缓冲区(RWStructuredBuffer),用于存储整数类型数据
RWStructuredBuffer<int> buffer;
// 自定义结构体,用于存储物体的基础属性
struct Test
{
float3 pos; // 位置坐标(x, y, z)
float3 v; // 速度向量(x, y, z)
float lifeTime; // 生命周期
};
// 可读写的结构化缓冲区,用于存储 Test 类型的结构体数据
RWStructuredBuffer<Test> buffer2;
// 定义线程组大小:每个线程组包含 32 个线程,沿 x 轴排列(y、z 轴维度为 1)
[numthreads(32,1,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
// id:当前线程的全局 ID(SV_DispatchThreadID 语义标识)
// 将当前线程的 x 维度 ID 直接赋值给 buffer 中对应的位置
buffer[id.x] = id.x;
}
// 第二个内核的线程组配置,与第一个保持一致
[numthreads(32,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID)
{
// 从 buffer2 中读取当前线程对应的 Test 结构体
Test test = buffer2[id.x];
// 修改结构体的属性:位置 x 分量设为线程 ID
test.pos.x = id.x;
// 速度 y 分量设为线程 ID 的 10 倍
test.v.y = id.x * 10;
// 生命周期固定设为 666
test.lifeTime = 666;
// 将修改后的结构体写回 buffer2 中对应的位置
buffer2[id.x] = test;
}
同步获取
该获取方式主要利用 ComputeBuffer 中的 GetData 方法,但该方法会阻塞 CPU,需等待 GPU 计算完成并获取数据后,才会继续执行后续逻辑。
// 创建 ComputeBuffer:参数 1 为元素数量,参数 2 为单个元素的字节大小(int 占 4 字节)
ComputeBuffer computeBuffer = new ComputeBuffer(1000000, 4);
// ComputeShader 适用于大规模数据并行计算;若数据量过小,直接用 CPU 循环处理效率更高
int[] array = new int[1000000];
// 将 CPU 端的数组数据上传到 GPU 的 computeBuffer 中
computeBuffer.SetData(array);
// 创建第二个 ComputeBuffer:Test 结构体大小为 28 字节(float3×2 + float = 12+12+4)
ComputeBuffer computeBuffer2 = new ComputeBuffer(2000000, 28);
Test[] array2 = new Test[2000000];
// 将 CPU 端的 Test 数组数据上传到 GPU 的 computeBuffer2 中
computeBuffer2.SetData(array2);
// 查找计算着色器中指定内核(Kernel)的索引,用于后续绑定参数和执行
int index = computeShader.FindKernel("CSMain");
int index2 = computeShader.FindKernel("CSMain2");
// 为指定内核绑定缓冲区参数
computeShader.SetBuffer(index, "buffer", computeBuffer);
computeShader.SetBuffer(index2, "buffer2", computeBuffer2);
// 计算线程组数量:总数据量 / 每组线程数(32),向上取整确保覆盖所有数据
int nums = Mathf.CeilToInt(1000000f / 32f);
computeShader.Dispatch(index, nums, 1, 1);
int nums2 = Mathf.CeilToInt(2000000f / 32f);
computeShader.Dispatch(index2, nums2, 1, 1);
// 同步获取 GPU 计算完成的数据,装入对应的 CPU 数组
// 注意:会阻塞主线程,需等待 GPU 计算完成
computeBuffer.GetData(array);
print(array[0]); // 0
print(array[50]); // 50
print(array[500]); // 500
computeBuffer2.GetData(array2);
print(array2[10].pos.x); // 10
print(array2[10].v.y); // 100
print(array2[10].lifeTime); // 666
print(array2[20].pos.x); // 20
print(array2[20].v.y); // 200
print(array2[20].lifeTime); // 666
异步获取(推荐使用)
若不想阻塞 CPU,待 GPU 计算完成后自动回调,则可以使用 AsyncGPUReadback 来异步读取数据。异步获取时最快也得下一帧返回信息。
注意: 回调函数中参数的 GetData 方法传入的泛型是结构体,代表单个元素的类型。
// 异步请求读取 computeBuffer(int 类型)数据,不阻塞主线程,读取完成后自动执行回调
AsyncGPUReadback.Request(computeBuffer, (asyncGPUReadbackRequest) =>
{
// 检查 GPU 数据读取是否成功,无错误时再处理数据
if (!asyncGPUReadbackRequest.hasError)
{
// 从异步请求结果中获取 NativeArray<int> 类型的数据(NativeArray 是 Unity 高效的原生数组)
NativeArray<int> array = asyncGPUReadbackRequest.GetData<int>();
print(array[0]); // 0
print(array[50]); // 50
print(array[500]); // 500
}
});
// 异步请求读取 computeBuffer2(Test 类型)数据,不阻塞主线程,读取完成后自动执行回调
AsyncGPUReadback.Request(computeBuffer2, (asyncGPUReadbackRequest) =>
{
// 检查 GPU 数据读取是否成功,无错误时再处理数据
if (!asyncGPUReadbackRequest.hasError)
{
// 从异步请求结果中获取 NativeArray<Test> 类型的结构体数据
NativeArray<Test> array2 = asyncGPUReadbackRequest.GetData<Test>();
print(array2[10].pos.x); // 10
print(array2[10].v.y); // 100
print(array2[10].lifeTime); // 666
print(array2[20].pos.x); // 20
print(array2[20].v.y); // 200
print(array2[20].lifeTime); // 666
}
});
获取 GPU 侧计算好的数据 —— Texture
先声明好测试的 ComputeShader2
// 声明计算着色器的内核(Kernel),指定要编译执行的函数为 CSMain
#pragma kernel CSMain
// 可读写的 2D 纹理(RWTexture2D),用于存储计算输出的颜色结果
// 需在 C# 脚本中绑定启用了 enableRandomWrite 标志的 RenderTexture
RWTexture2D<float4> Result;
// 定义线程组大小:每个线程组包含 8×8×1 = 64 个线程,沿 x、y 轴二维排列
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
// id:当前线程的全局坐标 ID(SV_DispatchThreadID 语义标识,对应线程在所有线程组中的全局位置)
// 根据线程的全局 x、y 坐标生成渐变颜色:
// 红色通道 = x/512.0(从左到右渐变),绿色通道 = y/512.0(从上到下渐变)
// 蓝色通道 = 0,Alpha 通道 = 1(完全不透明)
Result[id.xy] = float4(id.x / 512.0, id.y / 512.0, 0, 1);
}
同步获取(不推荐)
利用传入 RenderTexture 数据进行写入,写入完成后利用 Texture 装载结果数据。
// 创建 RenderTexture:参数 1 为宽度,参数 2 为高度,参数 3 为深度缓冲位数(0 表示不需要深度缓冲)
RenderTexture renderTexture = new RenderTexture(512, 512, 0);
// 启用随机写入:这是计算着色器能够向 RenderTexture 写入数据的必要设置
renderTexture.enableRandomWrite = true;
// 命令 GPU 侧根据当前数据分配显存
renderTexture.Create();
// 查找计算着色器中名为 "CSMain" 的内核索引
int index3 = computeShader2.FindKernel("CSMain");
// 如果纹理要在 GPU 侧写入,一定是传入 RenderTexture 对象
// 将 RenderTexture 绑定到计算着色器中名为 "Result" 的纹理变量上
computeShader2.SetTexture(index3, "Result", renderTexture);
// 命令 GPU 进行计算,一定保证线程容量要覆盖整个像素数量
// 调度计算:x、y 方向线程组数量 = 纹理尺寸 / 线程组大小(8x8),向上取整确保覆盖所有像素
computeShader2.Dispatch(index3, Mathf.CeilToInt(512f / 8), Mathf.CeilToInt(512f / 8), 1);
// 同步获取 GPU 侧计算完毕的内容
// 如果要同步获取,那么一定是通过 Texture 转存 RenderTexture 当中的内容
// 创建 Texture2D:参数 1-2 为宽高,参数 3 为纹理格式(RGBA32 表示 32 位 RGBA),参数 4 为是否生成 mipmap
Texture2D texture2D = new Texture2D(512, 512, TextureFormat.RGBA32, false);
// 从 RenderTexture 中拷贝像素到 Texture 中
// RenderTexture.active 代表当前 GPU 输出缓冲的指针
// 将 RenderTexture 设置为当前激活的 GPU 输出缓冲,以便后续读取像素
RenderTexture.active = renderTexture;
// 从激活的 RenderTexture 中读取指定矩形区域(0,0 到 512,512)的像素,写入 Texture2D 的起始位置(0,0)
texture2D.ReadPixels(new Rect(0, 0, 512, 512), 0, 0);
// 应用像素数据:将 CPU 端读取的像素数据上传到 GPU,使 Texture2D 真正可用
texture2D.Apply();
// 将 Texture2D 赋值给 UI 的 RawImage 组件,以在界面上显示计算结果
rawImage.texture = texture2D;
// 取消当前激活的对象,防止影响后续渲染过程
RenderTexture.active = null;
// 释放 RenderTexture 占用的显存资源,避免内存泄漏
renderTexture.Release();
运行结果

如果改了计算规则为 Result[id.xy] = float4(id.x / 512.0, 0, 0, 1); 只往红色渐变的话,也能生效。以后其实就是在这改计算规则。

异步获取(推荐使用)
若不想阻塞 CPU,待 GPU 计算完成后自动回调,则可以使用 AsyncGPUReadback 来异步读取数据。异步获取时最快也得下一帧返回信息。
注意: 在纹理回调中可以直接获取颜色数据赋值给纹理。
// 异步请求读取 RenderTexture 的第 0 层 mipmap 数据,不阻塞主线程,读取完成后自动执行回调
AsyncGPUReadback.Request(renderTexture, 0, (asyncGPUReadbackRequest) =>
{
// 检查 GPU 数据读取是否成功,无错误时再进行后续处理
if (!asyncGPUReadbackRequest.hasError)
{
// 创建 Texture2D:宽 512、高 512,格式为 RGBA32,不生成 mipmap
Texture2D texture2D = new Texture2D(512, 512, TextureFormat.RGBA32, false);
// 从异步请求结果中获取 NativeArray<Color> 类型的像素数据(NativeArray 是 Unity 高效的原生数组)
NativeArray<Color> data = asyncGPUReadbackRequest.GetData<Color>();
// 将 NativeArray 中的像素数据直接设置到 Texture2D 中(比 ReadPixels 更高效)
texture2D.SetPixelData(data, 0);
// 应用像素数据:将 CPU 端的像素数据上传到 GPU,使 Texture2D 真正可用
texture2D.Apply();
// 将 Texture2D 赋值给 UI 的 RawImage 组件,以在界面上显示计算结果
rawImage.texture = texture2D;
// 释放 RenderTexture 占用的显存资源,避免内存泄漏
renderTexture.Release();
}
});
102.2 知识点代码
Lesson102_性能优化_GPU_着色器优化_ComputeShader_获取GPU数据.cs
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
public struct Test
{
public Vector3 pos; // 12 字节
public Vector3 v; // 12 字节
public float lifeTime; // 4 字节 = 28
}
public class Lesson102_性能优化_GPU_着色器优化_ComputeShader_获取GPU数据 : MonoBehaviour
{
public ComputeShader computeShader;
public ComputeShader computeShader2;
public RawImage rawImage;
void Start()
{
#region 知识点一 哪些数据可以被获取
// 想要在 CPU 侧(C# 中)获取通过 Compute Shader 在 GPU 中计算完成的数据
// 必须要满足以下的条件:
// 1. 数据在 GPU 中必须是可读写的资源
// 常见的可读写资源包括:
// RWStructuredBuffer / RWByteAddressBuffer(对应 ComputeBuffer)
// RWTexture2D / RWTexture3D(对应 RenderTexture)
// 只有 GPU 写入了这些资源,CPU 才有可获取的结果
// 2. 数据在 CPU 和 GPU 之间必须通过 ComputeBuffer 或 Texture 形式传递
// 注意:
// 1. 对于标量类型、向量类型、矩阵类型如果想要读取
// 需要使用可读写缓冲区进行包裹利用 ComputeBuffer 进行传递
// 2. cbuffer(常量缓冲区)是单向的,只能 CPU 到 GPU,不可反向读取
#endregion
#region 知识点二 获取 GPU 侧计算好的数据 —— ComputeBuffer
// 1. 同步获取
// 该获取方式主要利用 ComputeBuffer 中的 GetData 方法
// 但该方法会阻塞 CPU,需等待 GPU 计算完成并获取数据后,才会继续执行后续逻辑
// 创建 ComputeBuffer:参数 1 为元素数量,参数 2 为单个元素的字节大小(int 占 4 字节)
ComputeBuffer computeBuffer = new ComputeBuffer(1000000, 4);
// ComputeShader 适用于大规模数据并行计算;若数据量过小,直接用 CPU 循环处理效率更高
int[] array = new int[1000000];
// 将 CPU 端的数组数据上传到 GPU 的 computeBuffer 中
computeBuffer.SetData(array);
// 创建第二个 ComputeBuffer:Test 结构体大小为 28 字节(float3×2 + float = 12+12+4)
ComputeBuffer computeBuffer2 = new ComputeBuffer(2000000, 28);
Test[] array2 = new Test[2000000];
// 将 CPU 端的 Test 数组数据上传到 GPU 的 computeBuffer2 中
computeBuffer2.SetData(array2);
// 查找计算着色器中指定内核(Kernel)的索引,用于后续绑定参数和执行
int index = computeShader.FindKernel("CSMain");
int index2 = computeShader.FindKernel("CSMain2");
// 为指定内核绑定缓冲区参数:参数 1 为内核索引,参数 2 为 Shader 中声明的缓冲区名,参数 3 为对应的 ComputeBuffer
computeShader.SetBuffer(index, "buffer", computeBuffer);
computeShader.SetBuffer(index2, "buffer2", computeBuffer2);
// 计算线程组数量:总数据量 / 每组线程数(32),向上取整确保覆盖所有数据
int nums = Mathf.CeilToInt(1000000f / 32f);
// 执行指定内核:参数 1 为内核索引,参数 2-4 为 x、y、z 维度的线程组数量
computeShader.Dispatch(index, nums, 1, 1);
int nums2 = Mathf.CeilToInt(2000000f / 32f);
computeShader.Dispatch(index2, nums2, 1, 1);
// 同步获取 GPU 计算完成的数据,装入对应的 CPU 数组
// 注意:会阻塞主线程,需等待 GPU 计算完成
// computeBuffer.GetData(array);
// print(array[0]); // 0
// print(array[50]); // 50
// print(array[500]); // 500
//
// computeBuffer2.GetData(array2);
// print(array2[10].pos.x); // 10
// print(array2[10].v.y); // 100
// print(array2[10].lifeTime); // 666
//
// print(array2[20].pos.x); // 20
// print(array2[20].v.y); // 200
// print(array2[20].lifeTime); // 666
// 2. 异步获取(推荐使用)
// 若不想阻塞 CPU,待 GPU 计算完成后自动回调
// 则可以使用 AsyncGPUReadback 来异步读取数据
// 异步获取时最快也得下一帧返回信息
// 注意:
// 回调函数中参数的 GetData 方法传入的泛型是结构体
// 代表单个元素的类型
// // 异步请求读取 computeBuffer(int 类型)数据,不阻塞主线程,读取完成后自动执行回调
// AsyncGPUReadback.Request(computeBuffer, (asyncGPUReadbackRequest) =>
// {
// // 检查 GPU 数据读取是否成功,无错误时再处理数据
// if (!asyncGPUReadbackRequest.hasError)
// {
// // 从异步请求结果中获取 NativeArray<int> 类型的数据(NativeArray 是 Unity 高效的原生数组)
// NativeArray<int> array = asyncGPUReadbackRequest.GetData<int>();
// print(array[0]); // 0
// print(array[50]); // 50
// print(array[500]); // 500
// }
// });
//
// // 异步请求读取 computeBuffer2(Test 类型)数据,不阻塞主线程,读取完成后自动执行回调
// AsyncGPUReadback.Request(computeBuffer2, (asyncGPUReadbackRequest) =>
// {
// // 检查 GPU 数据读取是否成功,无错误时再处理数据
// if (!asyncGPUReadbackRequest.hasError)
// {
// // 从异步请求结果中获取 NativeArray<Test> 类型的结构体数据
// NativeArray<Test> array2 = asyncGPUReadbackRequest.GetData<Test>();
// print(array2[10].pos.x); // 10
// print(array2[10].v.y); // 100
// print(array2[10].lifeTime); // 666
// print(array2[20].pos.x); // 20
// print(array2[20].v.y); // 200
// print(array2[20].lifeTime); // 666
// }
// });
#endregion
#region 知识点三 获取 GPU 侧计算好的数据 —— Texture
// 1. 同步获取(不推荐)
// 利用传入 RenderTexture 数据进行写入
// 写入完成后利用 Texture 装载结果数据
// 创建 RenderTexture:参数 1 为宽度,参数 2 为高度,参数 3 为深度缓冲位数(0 表示不需要深度缓冲)
RenderTexture renderTexture = new RenderTexture(512, 512, 0);
// 启用随机写入:这是计算着色器能够向 RenderTexture 写入数据的必要设置
renderTexture.enableRandomWrite = true;
// 命令 GPU 侧根据当前数据分配显存
renderTexture.Create();
// 查找计算着色器中名为 "CSMain" 的内核索引
int index3 = computeShader2.FindKernel("CSMain");
// 如果纹理要在 GPU 侧写入,一定是传入 RenderTexture 对象
// 将 RenderTexture 绑定到计算着色器中名为 "Result" 的纹理变量上
computeShader2.SetTexture(index3, "Result", renderTexture);
// 命令 GPU 进行计算,一定保证线程容量要覆盖整个像素数量
// 调度计算:x、y 方向线程组数量 = 纹理尺寸 / 线程组大小(8x8),向上取整确保覆盖所有像素
computeShader2.Dispatch(index3, Mathf.CeilToInt(512f / 8), Mathf.CeilToInt(512f / 8), 1);
// // 同步获取 GPU 侧计算完毕的内容
// // 如果要同步获取,那么一定是通过 Texture 转存 RenderTexture 当中的内容
// // 想要把算好的图显示在 UI 上
// // 创建 Texture2D:参数 1-2 为宽高,参数 3 为纹理格式(RGBA32 表示 32 位 RGBA),参数 4 为是否生成 mipmap
// Texture2D texture2D = new Texture2D(512, 512, TextureFormat.RGBA32, false);
//
// // 从 RenderTexture 中拷贝像素到 Texture 中
// // RenderTexture.active 代表当前 GPU 输出缓冲的指针
// // 将 RenderTexture 设置为当前激活的 GPU 输出缓冲,以便后续读取像素
// RenderTexture.active = renderTexture;
//
// // 从激活的 RenderTexture 中读取指定矩形区域(0,0 到 512,512)的像素,写入 Texture2D 的起始位置(0,0)
// texture2D.ReadPixels(new Rect(0, 0, 512, 512), 0, 0);
//
// // 应用像素数据:将 CPU 端读取的像素数据上传到 GPU,使 Texture2D 真正可用
// texture2D.Apply();
//
// // 将 Texture2D 赋值给 UI 的 RawImage 组件,以在界面上显示计算结果
// rawImage.texture = texture2D;
//
// // 取消当前激活的对象,防止影响后续渲染过程
// RenderTexture.active = null;
//
// // 释放 RenderTexture 占用的显存资源,避免内存泄漏
// renderTexture.Release();
// 2. 异步获取(推荐使用)
// 若不想阻塞 CPU,待 GPU 计算完成后自动回调
// 则可以使用 AsyncGPUReadback 来异步读取数据
// 异步获取时最快也得下一帧返回信息
// 注意:
// 在纹理回调中可以直接获取颜色数据赋值给纹理
// 异步请求读取 RenderTexture 的第 0 层 mipmap 数据,不阻塞主线程,读取完成后自动执行回调
AsyncGPUReadback.Request(renderTexture, 0, (asyncGPUReadbackRequest) =>
{
// 如果成功没有错误再处理
// 检查 GPU 数据读取是否成功,无错误时再进行后续处理
if (!asyncGPUReadbackRequest.hasError)
{
// 创建 Texture2D:宽 512、高 512,格式为 RGBA32,不生成 mipmap
Texture2D texture2D = new Texture2D(512, 512, TextureFormat.RGBA32, false);
// 从异步请求结果中获取 NativeArray<Color> 类型的像素数据(NativeArray 是 Unity 高效的原生数组)
NativeArray<Color> data = asyncGPUReadbackRequest.GetData<Color>();
// 将 NativeArray 中的像素数据直接设置到 Texture2D 中(比 ReadPixels 更高效)
texture2D.SetPixelData(data, 0);
// 应用像素数据:将 CPU 端的像素数据上传到 GPU,使 Texture2D 真正可用
texture2D.Apply();
// UI 上显示
// 将 Texture2D 赋值给 UI 的 RawImage 组件,以在界面上显示计算结果
rawImage.texture = texture2D;
// 释放之前使用的 RenderTexture
// 释放 RenderTexture 占用的显存资源,避免内存泄漏
renderTexture.Release();
}
});
#endregion
}
}
Lesson102_ComputeShader.compute
// 声明计算着色器的内核(Kernel),每个内核对应一个要编译执行的函数
#pragma kernel CSMain
#pragma kernel CSMain2
// 可读写的结构化缓冲区(RWStructuredBuffer),用于存储整数类型数据
// 需在 C# 脚本中通过 cs.SetBuffer 方法绑定对应的 ComputeBuffer
RWStructuredBuffer<int> buffer;
// 自定义结构体,用于存储物体的基础属性
struct Test
{
float3 pos; // 位置坐标(x, y, z)
float3 v; // 速度向量(x, y, z)
float lifeTime; // 生命周期
};
// 可读写的结构化缓冲区,用于存储 Test 类型的结构体数据
RWStructuredBuffer<Test> buffer2;
// 定义线程组大小:每个线程组包含 32 个线程,沿 x 轴排列(y、z 轴维度为 1)
[numthreads(32,1,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
// id:当前线程的全局 ID(SV_DispatchThreadID 语义标识)
// 将当前线程的 x 维度 ID 直接赋值给 buffer 中对应的位置
buffer[id.x] = id.x;
}
// 第二个内核的线程组配置,与第一个保持一致
[numthreads(32,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID)
{
// 从 buffer2 中读取当前线程对应的 Test 结构体
Test test = buffer2[id.x];
// 修改结构体的属性:位置 x 分量设为线程 ID
test.pos.x = id.x;
// 速度 y 分量设为线程 ID 的 10 倍
test.v.y = id.x * 10;
// 生命周期固定设为 666
test.lifeTime = 666;
// 将修改后的结构体写回 buffer2 中对应的位置
buffer2[id.x] = test;
}
Lesson102_ComputeShader2.compute
// 声明计算着色器的内核(Kernel),指定要编译执行的函数为 CSMain
#pragma kernel CSMain
// 可读写的 2D 纹理(RWTexture2D),用于存储计算输出的颜色结果
// 需在 C# 脚本中绑定启用了 enableRandomWrite 标志的 RenderTexture
RWTexture2D<float4> Result;
// 定义线程组大小:每个线程组包含 8×8×1 = 64 个线程,沿 x、y 轴二维排列
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
// id:当前线程的全局坐标 ID(SV_DispatchThreadID 语义标识,对应线程在所有线程组中的全局位置)
// 根据线程的全局 x、y 坐标生成渐变颜色:
// 红色通道 = x/512.0(从左到右渐变),绿色通道 = y/512.0(从上到下渐变)
// 蓝色通道 = 0,Alpha 通道 = 1(完全不透明)
Result[id.xy] = float4(id.x / 512.0, id.y / 512.0, 0, 1);
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com