102.ComputeShader获取GPU侧数据

102.性能优化-GPU-着色器优化-ComputeShader-获取GPU侧数据


102.1 知识点

哪些数据可以被获取

想要在 CPU 侧(C# 中)获取通过 Compute Shader 在 GPU 中计算完成的数据,必须满足以下条件:

  1. 数据在 GPU 中必须是可读写的资源

    • 常见的可读写资源包括:
      • RWStructuredBuffer / RWByteAddressBuffer(对应 ComputeBuffer
      • RWTexture2D / RWTexture3D(对应 RenderTexture
    • 只有 GPU 写入了这些资源,CPU 才有可获取的结果
  2. 数据在 CPU 和 GPU 之间必须通过 ComputeBufferTexture 形式传递

    • 对于标量类型、向量类型、矩阵类型如果想要读取,需要使用可读写缓冲区进行包裹,利用 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

×

喜欢就点赞,疼爱就打赏