实例化着色器矩阵设置

6

我想绘制实例化的立方体。

我可以成功调用GL.DrawArraysInstanced(PrimitiveType.Triangles, 0, 36, 2);

我的问题是所有的立方体都在同一位置和同一旋转角度上绘制。如何为每个立方体单独更改它们的位置和旋转角度?

为了创建不同的位置等,我需要为每个立方体创建一个矩阵,对吗?我已经创建了这个:

Matrix4[] Matrices = new Matrix4[]{
  Matrix4.Identity, //do nothing
  Matrix4.Identity * Matrix4.CreateTranslation(1,0,0) //move a little bit
};

GL.BindBuffer(BufferTarget.ArrayBuffer, matrixBuffer);
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(sizeof(float) * 16 * Matrices.Length), Matrices, BufferUsageHint.StaticDraw);

我需要创建一个缓冲区来存储矩阵。 matrixBuffer 是指向我的缓冲区的指针。我不确定大小是否正确,我使用了 float * 4(用于 Vector4)* 4(用于 4 个向量)* 数组大小。

绘制循环:

GL.BindBuffer(BufferTarget.ArrayBuffer, matrixBuffer);
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, 0, 0);
//GL.VertexAttribDivisor(3, ?);
GL.EnableVertexAttribArray(3);

GL.DrawArraysInstanced(PrimitiveType.Triangles, 0, 36, 2);

任何大于4的数字在VertexAttribPointer(..., 4, VertexattribPointerType.Float, ...);中都会导致崩溃。我以为我需要将该值设为16?
我不确定是否需要一个VertexAttribDivisor,可能我需要每36个顶点调用一次GL.VertexAttribDivisor(3, 36);?但是当我这样做时,我看不到任何立方体。
我的顶点着色器:
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec4 color;
layout(location = 2) in vec2 texCoord;
layout(location = 3) in mat4 instanceMatrix;

uniform mat4 projMatrix;

out vec4 vColor;
out vec2 texCoords[];

void main(){
  gl_Position = instanceMatrix * projMatrix * vec4(position, 1.0);
  //gl_Position = projMatrix * vec4(position, 1.0);
  texCoords[0] = texCoord;
  vColor = color;
}

所以我的问题是:

  • 我的代码有什么问题?
  • 为什么我只能将VertexAttribPointer的size参数设置为4?
  • VertexAttribDivisor的正确值是什么?

编辑:

根据Andon M. Coleman的回答,我做出了以下更改:

GL.BindBuffer(BufferTarget.UniformBuffer, matrixBuffer);
GL.BufferData(BufferTarget.UniformBuffer, (IntPtr)(sizeof(float) * 16), IntPtr.Zero, BufferUsageHint.DynamicDraw);
//Bind Buffer to Binding Point
GL.BindBufferBase(BufferRangeTarget.UniformBuffer, matrixUniform, matrixBuffer);

matrixUniform = GL.GetUniformBlockIndex(shaderProgram, "instanceMatrix");
//Bind Uniform Block to Binding Point
GL.UniformBlockBinding(shaderProgram, matrixUniform, 0);

GL.BufferSubData(BufferTarget.UniformBuffer, IntPtr.Zero, (IntPtr)(sizeof(float) * 16 * Matrices.Length), Matrices);

关于着色器:

#version 330 core
layout(location = 0) in vec4 position; //gets vec3, fills w with 1.0
layout(location = 1) in vec4 color;
layout(location = 2) in vec2 texCoord;

uniform mat4 projMatrix;
uniform UniformBlock
{ mat4 instanceMatrix[]; };

out vec4 vColor;
out vec2 texCoords[];

void main(){
  gl_Position = projMatrix * instanceMatrix[0] * position;
  texCoords[0] = texCoord;
  vColor = color;
}
1个回答

9

您已经通过困难的方式发现,顶点属性位置始终是四个组件。

如果您认为mat4vec4大4倍,则唯一使4x4矩阵成为每个顶点属性的方法是让步。

考虑您的mat4顶点属性的声明:

layout(location = 3) in mat4 instanceMatrix;

您可能自然而然地认为,位置3存储了16个浮点值,但是您会错的。在GLSL中,位置始终是4组件。因此,mat4 instanceMatrix实际上占用了4个不同的位置。

这就是instanceMatrix的实际工作原理:

layout(location = 3) in vec4 instanceMatrix_Column0;
layout(location = 4) in vec4 instanceMatrix_Column1;
layout(location = 5) in vec4 instanceMatrix_Column2;
layout(location = 6) in vec4 instanceMatrix_Column3;

幸运的是,你不必以那种方式编写你的着色器,使用mat4顶点属性是完全有效的。

然而,你确实需要编写你的C#代码来表现出这种方式:

GL.BindBuffer(BufferTarget.ArrayBuffer, matrixBuffer);
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, 64,  0); // c0
GL.VertexAttribPointer(4, 4, VertexAttribPointerType.Float, false, 64, 16); // c1
GL.VertexAttribPointer(5, 4, VertexAttribPointerType.Float, false, 64, 32); // c2
GL.VertexAttribPointer(6, 4, VertexAttribPointerType.Float, false, 64, 48); // c3

同样地,您必须为所有4个位置设置顶点属性分频器:
GL.VertexAttribDivisor (3, 1);
GL.VertexAttribDivisor (4, 1);
GL.VertexAttribDivisor (5, 1);
GL.VertexAttribDivisor (6, 1);

顺带一提,因为顶点属性始终是四个组件,你实际上可以声明:
layout(location = 0) in vec4 position;

请不要写出这样丑陋的代码:

gl_Position = instanceMatrix * projMatrix * vec4(position, 1.0);

这是因为OpenGL会自动扩展顶点属性中缺失的组件。
(0.0, 0.0, 0.0, 1.0)
如果你在GLSL着色器中将一个顶点属性声明为vec4,但只提供了XYZ的数据,则W会自动赋值为1.0。
实际上,不建议将矩阵存储在每个顶点中,这样会浪费多个顶点属性位置。您可以考虑使用uniform数组,或者更好地使用uniform缓冲区。您可以使用顶点着色器预定义变量gl_InstanceID对该数组进行索引。这确实是最明智的方法,因为您可能会发现每个实例使用的属性比您拥有的顶点属性位置要多 (在GL 3.3中最少16个,只有一些GPU支持超过16个)。
请记住,顶点着色器在单次调用中可以使用的vec4 uniform数量有限,而mat4的大小是vec4的4倍。使用uniform缓冲区将允许您绘制比普通uniform数组更多的实例。

我修复了我的代码,它可以工作了 :) 现在我尝试按照你的建议使用统一缓冲区来实现。你可以在我的问题中看到编辑后的代码。问题是,当我将instanceMatrix[0]更改为instanceMatrix[gl_InstanceID]时,我会得到以下错误:"OpenGL需要常量索引来访问未定大小的数组(instanceMatrix)"。你能帮我解决这个问题吗? - fedab
如果你选择使用统一数组,你需要为其使用一个恒定的大小,或者使用着色器存储缓冲区。然而,SSB是GL4的一个特性。 - Andon M. Coleman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接