I'm reading the book Game Programming in C++ from Madhav Sanjay. I did the exercises in C++ and try to migrate them to C# with the Silk.Net library, which is an OpenGL wrapper in .Net: https://github.com/lehmamic/GameProgrammingExercises
I'm trying to implement skeleton animation. Until now, I had a vertex buffer containing only float values for position, normal and uv. Now I need to mix it with single bytes values. To be able to do that, I made a struct containing those values:
public struct VertexPosNormTex
{
public const uint SizeInBytes = 8 * sizeof(float);
public const VertexArrayObjectLayout Layout = VertexArrayObjectLayout.PosNormTex;
public Vector3D<float> Position;
public Vector3D<float> Normal;
public Vector2D<float> TexCoords;
public VertexPosNormTex(Vector3D<float> position, Vector3D<float> normal, Vector2D<float> texCoords)
{
Position = position;
Normal = normal;
TexCoords = texCoords;
}
}
And I create my VAO with the following code and that works. This means the old code without skeleton animation works with the new approach and it gets rendered correctly:
public unsafe VertexArrayObject(GL gl, ReadOnlySpan<VertexPosNormTex> vertices, uint[] indices)
{
_gl = gl;
NumberOfVertices = vertices.Length;
NumberOfIndices = indices.Length;
// Create vertex array
_vertexArray = _gl.GenVertexArray();
_gl.BindVertexArray(_vertexArray);
// Create vertex buffer
_vertexBuffer = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vertexBuffer);
fixed (void* d = &MemoryMarshal.GetReference(vertices))
{
_gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint) (vertices.Length * VertexPosNormTex.SizeInBytes), d, BufferUsageARB.StaticDraw);
}
// Create index buffer
_indexBuffer = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _indexBuffer);
fixed (void* d = indices)
{
_gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint) (indices.Length * sizeof(uint)), d, BufferUsageARB.StaticDraw);
}
// Specify the vertex attributes
// Position is 3 floats with offset 0
_gl.EnableVertexAttribArray(0);
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, VertexPosNormTex.SizeInBytes, (void*) 0);
// Normal is 3 floats with offset 3
_gl.EnableVertexAttribArray(1);
_gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, VertexPosNormTex.SizeInBytes, (void*) (3 * sizeof(float)));
// Texture coordinates is 2 floats with offset 6
_gl.EnableVertexAttribArray(2);
_gl.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, VertexPosNormTex.SizeInBytes, (void*) (6 * sizeof(float)));
}
Afterwards, I prepared the VAO and Shader for the skeleton animations. First I adjusted the shaders in variables according to the book:
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in uvec4 inSkinBones;
layout(location = 3) in vec4 inSkinWeights;
layout(location = 4) in vec2 inTexCoord;
The book is written in C++ and uses 4D vectors of unsigned bytes for skins and bones in the buffer to save memory. The bones get transferred directly to the uvec4 variable and the weights get converted to a 4D vector of float.
I prepared a new Vertex struct for that in C#:
public struct VertexPosNormSkinTex
{
public const uint SizeInBytes = 8 * sizeof(float) + 8 * sizeof(byte);
public const VertexArrayObjectLayout Layout = VertexArrayObjectLayout.PosNormSkinTex;
public Vector3D<float> Position;
public Vector3D<float> Normal;
public Vector4D<byte> SkinningIndices;
public Vector4D<byte> SkinningWeights;
public Vector2D<float> TexCoords;
public VertexPosNormSkinTex(Vector3D<float> position, Vector3D<float> normal, Vector4D<byte> skinningIndices,
Vector4D<byte> skinningWeights, Vector2D<float> texCoords)
{
Position = position;
Normal = normal;
SkinningIndices = skinningIndices;
SkinningWeights = skinningWeights;
TexCoords = texCoords;
}
}
And adjusted the VOA creation code:
public unsafe VertexArrayObject(GL gl, ReadOnlySpan<VertexPosNormSkinTex> vertices, uint[] indices)
{
_gl = gl;
NumberOfVertices = vertices.Length;
NumberOfIndices = indices.Length;
// Create vertex array
_vertexArray = _gl.GenVertexArray();
_gl.BindVertexArray(_vertexArray);
// Create vertex buffer
_vertexBuffer = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vertexBuffer);
fixed (void* d = vertices)
{
_gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint) (vertices.Length * VertexPosNormSkinTex.SizeInBytes), d, BufferUsageARB.StaticDraw);
}
// Create index buffer
_indexBuffer = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _indexBuffer);
fixed (void* d = indices)
{
_gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint) (indices.Length * sizeof(uint)), d, BufferUsageARB.StaticDraw);
}
// Specify the vertex attributes
// Position is 3 floats with offset 0
_gl.EnableVertexAttribArray(0);
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, VertexPosNormSkinTex.SizeInBytes, (void*) 0);
// Normal is 3 floats with offset 3
_gl.EnableVertexAttribArray(1);
_gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, VertexPosNormSkinTex.SizeInBytes, (void*) (3 * sizeof(float)));
// Skinning indices (keep as ints)
_gl.EnableVertexAttribArray(2);
_gl.VertexAttribIPointer(2, 4, VertexAttribIType.UnsignedByte, VertexPosNormSkinTex.SizeInBytes, (void*) (6 * sizeof(float)));
// Skinning weights (convert to floats)
_gl.EnableVertexAttribArray(3);
_gl.VertexAttribPointer(3, 4, VertexAttribPointerType.UnsignedByte, true, VertexPosNormSkinTex.SizeInBytes, (void*) (6 * sizeof(float) + 4 * sizeof(byte)));
// Texture coordinates is 2 floats
_gl.EnableVertexAttribArray(4);
_gl.VertexAttribPointer(4, 2, VertexAttribPointerType.Float, false, VertexPosNormSkinTex.SizeInBytes, (void*) (6 * sizeof(float) + 8 * sizeof(byte)));
}
Notice, that I send the bone indices as ints to the shader and the bone weights as floats, event though they are byte in the vertex object. It should be according to the implementation in the book.
The model gets rendered, so the position and normal variables get transferred correctly. But the texture is rendered wrong. I think, it must be something about the vertex layout in memory or I configure the vertex attribute pointers not correctly.
Edit: I check the first vertex in RenderDoc. The UV Coords seem to be transferred correctly. The shader code is complete the same except the in variables - I don't get it.
