3

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

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

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

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

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

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

一般来说,着色器是新手,我觉得我错过了一些关于计算着色器及其工作原理的非常基本的东西。目标是(我相信您已经猜到了)使用 Compute Shaders(或任何最适合此类工作的着色器)使用行进立方体在 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.     
}
4

2 回答 2

1

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

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

如果我们有一个 3D 数组索引[z][y][x](参见脚注 #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我在这里不使用统一的坐标系,因为通过交换 y 和 z 更容易解释。在这种情况下, [z][y][x]索引可以防止整个内存的跳转;看到这篇文章。Unity 将交换[z][y][x][y][z][x]主要在以相同方式布置的切片上操作。)

脚注 #2与和uint3 id : SV_DispatchThreadID相比,这一原则正是如此。请参阅文档:uint3 threadID : SV_GroupThreadIDuint3 groupID : SV_GroupID

SV_DispatchThreadID 是 SV_GroupID * numthreads 和 GroupThreadID 之和。

...因此,鉴于您的程序结构,请尽可能使用它。

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

于 2018-02-02T08:42:45.607 回答
0

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

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

而我应该做的就是让这成为一个二维问题。一些 seudo 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”。

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

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

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

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

于 2014-02-06T08:58:04.850 回答