首页 > 解决方案 > 计算着色器破坏顶点缓冲区

问题描述

我正在制作一个在 WGPU (Vulkan GLSL) 计算着色器中计算切线和双切线的教程。我正在用我在搅拌机中制作的 .obj 在 CPU 上创建顶点缓冲区。

这是计算着色器的代码。

#version 450

#define VERTICES_PER_TRIANGLE 3
layout(local_size_x = VERTICES_PER_TRIANGLE) in;

// Should match the struct in model.rs
struct ModelVertex {
    vec3 position;
    vec2 tex_coords;
    vec3 normal;
    vec3 tangent;
    vec3 bitangent;
};

layout(std140, set=0, binding=0) buffer SrcVertexBuffer {
    ModelVertex srcVertices[];
};
layout(std140, set=0, binding=1) buffer DstVertexBuffer {
    ModelVertex dstVertices[];
};
layout(std140, set=0, binding=2) buffer IndexBuffer {
    uint Indices[];
};

void main() {
    uint index = gl_GlobalInvocationID.x;

    // Grab the indices for the triangle
    uint i0 = Indices[index];
    uint i1 = Indices[index + 1];
    uint i2 = Indices[index + 2];
    
    // Grab the vertices for the triangle
    ModelVertex v0 = srcVertices[i0];
    ModelVertex v1 = srcVertices[i1];
    ModelVertex v2 = srcVertices[i2];

    // Grab the position and uv components of the vertices
    vec3 pos0 = v0.position;
    vec3 pos1 = v1.position;
    vec3 pos2 = v2.position;
    vec2 uv0 = v0.tex_coords;
    vec2 uv1 = v1.tex_coords;
    vec2 uv2 = v2.tex_coords;

    // Calculate the edges of the triangle
    vec3 delta_pos1 = pos1 - pos0;
    vec3 delta_pos2 = pos2 - pos0;

    // This will give us a direction to calculate the
    // tangent and bitangent
    vec2 delta_uv1 = uv1 - uv0;
    vec2 delta_uv2 = uv2 - uv0;

    // Solving the following system of equations will
    // give us the tangent and bitangent.
    //     delta_pos1 = delta_uv1.x * T + delta_u.y * B
    //     delta_pos2 = delta_uv2.x * T + delta_uv2.y * B
    // Luckily, the place I found this equation provided
    // the solution!
    float r = 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
    vec3 tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
    vec3 bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * r;

    // We'll use the same tangent/bitangent for each vertex in the triangle
    dstVertices[i0].tangent = tangent;
    dstVertices[i1].tangent = tangent;
    dstVertices[i2].tangent = tangent;
    dstVertices[i0].bitangent = bitangent;
    dstVertices[i1].bitangent = bitangent;
    dstVertices[i2].bitangent = bitangent;
}

这导致如下图所示。

损坏的立方体

问题出现在最后六行。

dstVertices[i0].tangent = tangent;
dstVertices[i1].tangent = tangent;
dstVertices[i2].tangent = tangent;
dstVertices[i0].bitangent = bitangent;
dstVertices[i1].bitangent = bitangent;
dstVertices[i2].bitangent = bitangent;

如果我删除这些行,则输出很好(尽管由于切线和双切线是 0 向量,所以照明都错了)。

在此处输入图像描述

为什么修改切线和双切线会弄乱顶点的位置?

这是上下文的其余代码。https://github.com/sotrh/learn-wgpu/tree/compute/code/intermediate/tutorial14-compute

编辑:

这是我调用计算着色器的代码。

let src_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
    label: Some(&format!("{:?} Vertex Buffer", m.name)),
    contents: bytemuck::cast_slice(&vertices),
    // UPDATED!
    usage: wgpu::BufferUsage::STORAGE,
});
let dst_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
    label: Some(&format!("{:?} Vertex Buffer", m.name)),
    contents: bytemuck::cast_slice(&vertices),
    // UPDATED!
    usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::STORAGE,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
    label: Some(&format!("{:?} Index Buffer", m.name)),
    contents: bytemuck::cast_slice(&m.mesh.indices),
    // UPDATED!
    usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::STORAGE,
});

let binding = BitangentComputeBinding {
    dst_vertex_buffer,
    src_vertex_buffer,
    index_buffer,
    num_elements: m.mesh.indices.len() as u32,
};

// Calculate the tangents and bitangents
let calc_bind_group = self.binder.create_bind_group(
    &binding, 
    device, 
    Some("Mesh BindGroup")
);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
    label: Some("Tangent and Bitangent Calc"),
});
{
    let mut pass = encoder.begin_compute_pass();
    pass.set_pipeline(&self.pipeline);
    pass.set_bind_group(0, &calc_bind_group, &[]);
    pass.dispatch(binding.num_elements as u32 / 3, 1, 1);
}
queue.submit(std::iter::once(encoder.finish()));
device.poll(wgpu::Maintain::Wait);

着色器应该循环遍历网格中的所有三角形,并使用该三角形顶点的位置和 uv 坐标计算切线和双切线。我猜测与多个三角形共享的顶点同时被写入,导致这种内存损坏。

我不认为这是其他地方的着色器的问题,因为我对光使用相同的模型,而负责该模型的顶点着色器根本不使用切线和双切线。

#version 450

layout(location=0) in vec3 a_position;

layout(location=0) out vec3 v_color;

layout(set=0, binding=0)
uniform Uniforms {
    vec3 u_view_position;
    mat4 u_view_proj;
};

layout(set=1, binding=0)
uniform Light {
    vec3 u_position;
    vec3 u_color;
};

// Let's keep our light smaller than our other objects
float scale = 0.25;

void main() {
    vec3 v_position = a_position * scale + u_position;
    gl_Position = u_view_proj * vec4(v_position, 1);

    v_color = u_color;
}

查看 Render Doc 中的顶点数据表明它们的位置数据变得混乱。

渲染文档截图

如果我将切线和副切线设置为 ,这也是立方体的样子vec3(0, 1, 0)

在此处输入图像描述

我唯一的猜测是存储缓冲区有一个我不知道的字节对齐规则。我知道统一缓冲区就是这种情况,但是我将存储缓冲区用于我的实例化代码,这似乎没有任何问题。

标签: rustglslwebgpu

解决方案


原来 Vulkan 样式 GLSL 在使用std430.

https://github.com/KhronosGroup/glslang/issues/264

就我而言,它是vec3. vec2 tex_coord扔掉它会导致着色器从顶点缓冲区的错误部分提取数据。

解决方法是更改struct​​ inmodel_load.comp以指定单个组件。

struct ModelVertex {
    float x; float y; float z;
    float uv; float uw;
    float nx; float ny; float nz;
    float tx; float ty; float tz;
    float bx; float by; float bz;
};

现在基本对齐是a float(4字节),着色器正确读取顶点缓冲区数据。

我知道有一个packed布局,但shaderc由于我以外的原因不允许我使用它。老实说,我认为这很烦人,也很麻烦,但它确实有效。

结果还是有缺陷的。立方体的边缘面上有一些条带。我的猜测是它是共享多个三角形的单个顶点,但这是我稍后必须研究的另一个问题。

腐败消失了


推荐阅读