【问题标题】:Compute Shaders Input 3d array of floats计算着色器输入 3d 浮点数组
【发布时间】:2014-02-06 07:14:27
【问题描述】:

编写要在 Unity 4 中使用的计算着色器。我正在尝试获得 3d 噪声。

目标是从我的 C# 代码中将一个多维 float3 数组放入我的计算着色器中。这是否可以通过直接的方式(使用某种声明)实现,还是只能使用 Texture3D 对象来实现?

我目前有一个在单个 float3 点上工作的单纯形噪声实现,输出单个浮点 -1 到 1。我为计算着色器移植了找到的代码 here

我想通过对数组中的每个 float3 点应用噪声操作来扩展它以处理 float3 的 3D 数组(我想 C# 中最接近的比较是 Vector3[,,])。

我尝试了其他一些方法,但它们感觉很奇怪,并且完全忽略了使用并行方法的意义。以上是我想象的应该是什么样子。

我还设法让Scrawk's 实现作为顶点着色器工作。 Scrawk 使用 Texture3D 将 3D float4 数组放入着色器。但我无法从纹理中提取浮动。计算着色器也是这样工作的吗?依赖纹理?我可能忽略了一些关于从纹理中获取值的问题。这似乎是该用户在this post 中获取数据的方式。与我的类似问题,但不是我想要的。

一般来说,着色器是新手,我觉得我缺少一些关于计算着色器及其工作原理的基本知识。目标是(我相信您已经猜到了)使用 Compute Shader(或最适合此类工作的任何着色器)使用行进立方体在 GPU 上生成噪声和网格计算。

约束是 Unity 4 的免费试用版。

这是我正在使用的 C# 代码的骨架:

    int volumeSize = 16; 
    compute.SetInt ("simplexSeed", 10); 

    // This will be a float[,,] array with our density values. 
    ComputeBuffer output = new ComputeBuffer (/*s ize goes here, no idea */, 16);
    compute.SetBuffer (compute.FindKernel ("CSMain"), "Output", output);  

    // Buffer filled with float3[,,] equivalent, what ever that is in C#. Also what is 'Stride'? 
    // Haven't found anything exactly clear. I think it's the size of basic datatype we're using in the buffer?
    ComputeBuffer voxelPositions = new ComputeBuffer (/* size goes here, no idea */, 16); 
    compute.SetBuffer (compute.FindKernel ("CSMain"), "VoxelPos", voxelPositions);    


    compute.Dispatch(0,16,16,16);
    float[,,] res = new float[volumeSize, volumeSize, volumeSize];

    output.GetData(res); // <=== populated with float density values

    MarchingCubes.DoStuff(res); // <=== The goal (Obviously not implemented yet)

这是计算着色器

#pragma kernel CSMain

uniform int simplexSeed;
RWStructuredBuffer<float3[,,]> VoxelPos;  // I know these won't work, but it's what I'm trying
RWStructuredBuffer<float[,,]> Output;     // to get in there. 

float simplexNoise(float3 input)
{
    /* ... A bunch of awesome stuff the pastebin guy did ...*/

    return noise;
}

/** A bunch of other awesome stuff to support the simplexNoise function **/
/* .... */

/* Here's the entry point, with my (supposedly) supplied input kicking things off */
[numthreads(16,16,16)] // <== Not sure if this thread count is correct? 
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Output[id.xyz] = simplexNoise(VoxelPos.xyz); // Where the action starts.     
}

【问题讨论】:

    标签: c# unity3d shader hlsl compute-shader


    【解决方案1】:

    使用 1D 缓冲区,通过特殊索引在 CPU 和 GPU 上像 3D 一样对其进行索引。

    在 HLSL 中只有一维缓冲区。 使用函数/公式将 N 维(例如 3D 或 2D)索引向量转换为 1D 向量,用于索引到 1D 数组中。

    如果我们有一个索引为 [z][y][x] 的 3D 数组(请参阅脚注 #1 了解原因),并创建了一个 array[Z_MAX][Y_MAX][X_MAX],我们可以将 [z][y][x] 转换为线性索引 [i]

    这是如何完成的......

    想象一个你从上到下切成片的块(所以它像一堆硬币一样堆积起来),其中xy 是每一层/片,沿着垂直轴z 向上运行。现在对于z(向上)中的每一个增量,我们知道我们已经考虑了x (width) * y (height) 元素。现在到这个总数,我们需要添加我们在当前 2D 切片中走了多少:对于 y 中的每一步(计算从左到右的行中的元素),我们知道我们已经考虑了 x (width) 元素,所以把它加到总数中。然后我们得到了当前行中的步数,即x,将其添加到总数中。您现在有一个一维索引。

    i = z * (Y_MAX * X_MAX) + y * (X_MAX) + x; //you may need to use "*_MAX - 1" instead

    脚注 #1 我在这里不使用 unity 的坐标系,因为通过交换 y 和 z 更容易解释。 在这种情况下, [z][y][x] 索引可以防止内存中的跳转;见this article。 Unity 会将[z][y][x] 替换为[y][z][x](主要在以相同方式布局的切片上进行操作。)

    脚注#2uint3 threadID : SV_GroupThreadIDuint3 groupID : SV_GroupID 相比,这个原则正是uint3 id : SV_DispatchThreadID 所做的。请参阅文档:

    SV_DispatchThreadID 是 SV_GroupID * numthreads 和 GroupThreadID 之和。

    ...因此,考虑到您的程序结构,请尽可能使用它。

    脚注 #3 这与在 C 中实现 N 维索引的方式相同。

    【讨论】:

      【解决方案2】:

      通常,您会使用噪声来生成高度图之类的东西……这是您的意图吗? 在我看来,您正在为数组中的每个点生成一个值。

      我的脑海中有一幅图像,你从体素引擎(16 x 16 x 16 体素)中取出一块并为所有点生成噪声值。

      而你应该做的就是把它变成一个二维问题。 一些伪 CPU 代码可能看起来像这样......

      for(x)
        for(z)
          fill all voxels below ( GenerateY(x,z) )
      

      基于我的假设是正确的,我会说你的着色器可能是错误的......

      这将尝试运行 16 x 16 x 16 线程,这远高于组的 1024 线程限制,您可以拥有无​​限组,但每个组不能超过 1024 个线程。

      [numthreads(16,16,16)] // <== Not sure if this thread count is correct? 
      

      我认为你需要的是更类似于 [numthreads(16,1,16)] 的东西,在 16 x 16 的点网格上运行噪声函数,并将每个点提高噪声 x maxHeight 量以给你点你想要的。

      您的调度呼叫看起来像这样......

      compute.Dispatch(0,1,0,0);
      

      ... 这将导致单个线程组生成 16 x 16 点的高度图值。 一旦达到这一点,您就可以扩大规模。

      所有这些结合你提到的行进立方体表明你正在做与我完全相同的事情,在 GPU 上构建体素引擎,其中原始体素数据在 GPU ram 中生成,然后从中生成网格。

      我已经破解了这部分过程,困难的部分是下一个阶段,从生成的体素数组生成网格/场景对象。 根据您的方法,接下来您可能希望真正熟悉光线行进或 AppendBuffers。

      祝你好运!

      平面缓冲区使用:

      假设我想要 128*128*128 体素的数组,而一个块是 32*32*32 体素,然后我这样做...

      //cpu code 
      var size = 128*128*128;
      var stride = sizeof(float);
      ComputeBuffer output = new ComputeBuffer (size, stride);
      computeShader.SetBuffer (0, "voxels", output);
      computeshader.Dispatch(0, 4,4,4);
      
      //gpu code
      #pragma kernel compute
      RWStructuredBuffer<float> voxels;
      
      [numthreads(32,1,32)] // group is your chunk index, thread is you voxel within the chunk
      void compute (uint3 threadId : SV_GroupThreadID, uint3 groupId : SV_GroupID)
      {
          uint3 threadIndex =  groupId * uint3(32, 1, 32) + threadId;
         //TODO: implement any marching cubes / dual contouring functions in
         //      here somewhere
         uint3 endIndex = uint(32, 0, 32) + threadIndex;
      
         float height = Noise();
         int voxelPos = voxPos.x+ voxPos.y*size+voxPos.z*size*size;
      
         // chunks are 32 * 32 blocks of columns the whole height of the volume
         for(int y = threadIndex.y; y < endIndex.y; y++)
         {
            if(y < height)
            {
               voxels[voxelPos] = 1; // fill this voxel
            }
                else
                {
                       voxels[voxelPos] = 0; // dont fill this voxel
                }
         }
      

      这应该会在 GPU 上的缓冲区中生成一个 128*128*128 的体素数组(虽然这全部来自我脑海中的 ram,所以它可能不会出现),其中包含“类似地形”的东西。

      我想你可以从那里拿它来做你需要的事情,如果你的噪声函数从 threadIndex(体素位置)传递了 xyz 值,你可能会在计算着色器中删除“if”。

      如果你找到破解这个的巧妙方法,请告诉我,这是我自己仍在努力的事情。

      我的代码(几乎)像这样工作......

      组件开始... 调用计算来生成体素缓冲区。 从体素缓冲区调用计算生成顶点缓冲区。

      绘制(每一帧)... 用材质渲染顶点缓冲区

      【讨论】:

      • 是的,我肯定在尝试解决 num 线程的问题。是的,目标是体素地形 :) 似乎这是大多数 proc 地形开发人员面临的考验。我要一个 3D 数组,因为我想要一个点云,而不是一个高度图。这允许产生悬垂和洞穴等。我有一个在 CPU 上运行的实现,但它在生成块时大约 20 fps。那么,将多维浮点数据输入计算着色器的唯一方法是使用 Texture2D 还是 Texture3D?或者是否有另一种类型语法可以用于 REStructuredBuffer
      • 这可能对我来说有点自以为是,但我认为你的做法是错误的......生成这样的数组是不可行的,这是一个 2 遍过程,首先生成“高度堆栈”到地面并填充“污垢”或其他东西,然后在同一阵列上运行一个过程(计算内核)以“挖掘隧道”。
      • 当然总有 GPU gems 方法...http.developer.nvidia.com/GPUGems3/gpugems3_ch01.html ...如果您使用它,那么我想您可能会将密度函数与噪声函数混淆,它们相似但仍然不同功能。
      • 对,但是对于这两个通道,我仍然需要噪音。我首先从 3d 噪声开始。问题是如何通过 Unity 4 将 vector3 的 :3d 数组放入 hlsl 计算着色器?也许我不应该提到地形,因为现在我们离题了。忘记 proc gen 的东西。我刚刚将初始化数据放入我的着色器中。
      • 当然,您只需在 cpu 上声明一个新的计算缓冲区并将其分配给您的计算着色器,然后在您的 gpu 代码中填充它,如果是这种情况,那么实际上您所做的已经是正确的,但是你不能有一个“3d数组缓冲区”你必须声明一个平面... RWStructuredBuffer buffer;
      猜你喜欢
      • 1970-01-01
      • 2022-06-10
      • 2012-07-15
      • 2013-11-23
      • 1970-01-01
      • 1970-01-01
      • 2013-02-22
      • 2022-11-18
      • 2015-11-10
      相关资源
      最近更新 更多