#rust #glsl #webgpu
#Ржавчина #glsl #webgpu
Вопрос:
Я создаю учебное пособие по вычислению касательных и битангентов в вычислительном шейдере WGPU (Vulkan GLSL). Я создаю буфер вершин на процессоре из файла .obj, который я создал в blender.
Вот код для вычислительного шейдера.
#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(amp;wgpu::util::BufferInitDescriptor {
label: Some(amp;format!("{:?} Vertex Buffer", m.name)),
contents: bytemuck::cast_slice(amp;vertices),
// UPDATED!
usage: wgpu::BufferUsage::STORAGE,
});
let dst_vertex_buffer = device.create_buffer_init(amp;wgpu::util::BufferInitDescriptor {
label: Some(amp;format!("{:?} Vertex Buffer", m.name)),
contents: bytemuck::cast_slice(amp;vertices),
// UPDATED!
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::STORAGE,
});
let index_buffer = device.create_buffer_init(amp;wgpu::util::BufferInitDescriptor {
label: Some(amp;format!("{:?} Index Buffer", m.name)),
contents: bytemuck::cast_slice(amp;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(
amp;binding,
device,
Some("Mesh BindGroup")
);
let mut encoder = device.create_command_encoder(amp;wgpu::CommandEncoderDescriptor {
label: Some("Tangent and Bitangent Calc"),
});
{
let mut pass = encoder.begin_compute_pass();
pass.set_pipeline(amp;self.pipeline);
pass.set_bind_group(0, amp;calc_bind_group, amp;[]);
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;
}
Просмотр данных вершин в документе рендеринга показывает, что они размещают данные, которые становятся запутанными.
Также вот как выглядят кубы, если я установлю тангенс и битангенс vec3(0, 1, 0)
.
Я только предполагаю, что буферы хранилища имеют правило выравнивания байтов, о котором я не знаю. Я знаю, что это относится к однородным буферам, но я использую буферы хранения для моего инстанцирующего кода, и, похоже, у этого нет никаких проблем.
Комментарии:
1. В репозитории слишком много кода, который мне нужно переварить, и у меня нет установленных зависимостей для его запуска, но, скорее всего, у вас ошибка в другом коде шейдера, которая случайно обрабатывает касательную или битангент как преобразование, или вы каким-то образом испортили индексацию / привязку. Там много явных индексов и, вероятно, кода копирования вставки, и ошибка только в одном месте может легко вызвать такое поведение. Скорее всего, это не сам компьютерный шейдер из опубликованного вами кода.
2. Случайное рассмотрение его как чего-то, что вы добавляете в позицию в каком-либо коде шейдера, кажется наиболее вероятным, потому что, если оно равно нулю, это не влияет. Попробуйте установить для них постоянные малые значения, и я бы поспорил, что они будут предсказуемо искажены.
3. Я отредактировал вопрос
4. @LinearZoetrope зависимости для кода находятся в файле Cargo.toml в репозитории, который я связал.
Ответ №1:
Оказывается, GLSL в стиле Vulkan выравнивается по самому большому полю в структуре при использовании std430
.
https://github.com/KhronosGroup/glslang/issues/264
В моем случае это vec3
. vec2 tex_coord
Он отключается, заставляя шейдер извлекать данные из неправильных частей буфера вершин.
Исправление состояло в том, чтобы изменить struct
in model_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
не позволяет мне использовать его по независящим от меня причинам. Честно говоря, я думаю, что это довольно раздражает и громоздко, но это работает.
В результате все еще есть недостаток. На краевых гранях куба есть некоторые полосы. Я предполагаю, что одна вершина разделяет несколько треугольников, но это еще одна проблема, которую мне придется рассмотреть позже.