Как мне сделать собственный формат вершины с OpenGL
Я пишу свой собственный движок с использованием OpenTK (в основном это просто привязки OpenGL для C#, gl* становится GL.*), И я собираюсь хранить множество буферов вершин с несколькими тысячами вершин в каждом. Поэтому мне нужен мой собственный, настраиваемый формат вершин, поскольку Vec3 с плавающей точкой просто занимал бы слишком много места. (Я говорю о миллионах вершин здесь)
Я хочу создать свой собственный формат вершин с этим макетом:
Byte 0: Position X
Byte 1: Position Y
Byte 2: Position Z
Byte 3: Texture Coordinate X
Byte 4: Color R
Byte 5: Color G
Byte 6: Color B
Byte 7: Texture Coordinate Y
Вот код на C# для вершины:
public struct SmallBlockVertex
{
public byte PositionX;
public byte PositionY;
public byte PositionZ;
public byte TextureX;
public byte ColorR;
public byte ColorG;
public byte ColorB;
public byte TextureY;
}
Байт в качестве позиции для каждой оси достаточно, так как мне нужно всего 32^3 уникальных позиции.
Я написал свой собственный вершинный шейдер, который принимает два входа vec4 для каждого набора байтов. Мой вершинный шейдер такой:
attribute vec4 pos_data;
attribute vec4 col_data;
uniform mat4 projection_mat;
uniform mat4 view_mat;
uniform mat4 world_mat;
void main()
{
vec4 position = pos_data * vec4(1.0, 1.0, 1.0, 0.0);
gl_Position = projection_mat * view_mat * world_mat * position;
}
Чтобы попытаться изолировать проблему, я сделал свой вершинный шейдер максимально простым. Код для компиляции шейдеров тестируется в режиме непосредственного рисования, и он работает, так что этого не может быть.
Вот моя функция, которая генерирует, устанавливает и заполняет буфер вершин данными и устанавливает указатель на атрибуты.
public void SetData<VertexType>(VertexType[] vertices, int vertexSize) where VertexType : struct
{
GL.GenVertexArrays(1, out ArrayID);
GL.BindVertexArray(ArrayID);
GL.GenBuffers(1, out ID);
GL.BindBuffer(BufferTarget.ArrayBuffer, ID);
GL.BufferData<VertexType>(BufferTarget.ArrayBuffer, (IntPtr)(vertices.Length * vertexSize), vertices, BufferUsageHint.StaticDraw);
GL.VertexAttribPointer(Shaders.PositionDataID, 4, VertexAttribPointerType.UnsignedByte, false, 4, 0);
GL.VertexAttribPointer(Shaders.ColorDataID, 4, VertexAttribPointerType.UnsignedByte, false, 4, 4);
}
Из того, что я понимаю, это правильная процедура: Сгенерировать объект Vertex Array и связать его Сгенерировать буфер вершин и связать его Заполнить буфер вершин данными Установить указатели атрибутов
Шейдеры.*DataID устанавливается с этим кодом после компиляции и использования шейдера.
PositionDataID = GL.GetAttribLocation(shaderProgram, "pos_data");
ColorDataID = GL.GetAttribLocation(shaderProgram, "col_data");
И это моя функция рендеринга:
void Render()
{
GL.UseProgram(Shaders.ChunkShaderProgram);
Matrix4 view = Constants.Engine_Physics.Player.ViewMatrix;
GL.UniformMatrix4(Shaders.ViewMatrixID, false, ref view);
//GL.Enable(EnableCap.DepthTest);
//GL.Enable(EnableCap.CullFace);
GL.EnableClientState(ArrayCap.VertexArray);
{
Matrix4 world = Matrix4.CreateTranslation(offset.Position);
GL.UniformMatrix4(Shaders.WorldMatrixID, false, ref world);
GL.BindVertexArray(ArrayID);
GL.BindBuffer(OpenTK.Graphics.OpenGL.BufferTarget.ArrayBuffer, ID);
GL.DrawArrays(OpenTK.Graphics.OpenGL.BeginMode.Quads, 0, Count / 4);
}
//GL.Disable(EnableCap.DepthTest);
//GL.Disable(EnableCap.CullFace);
GL.DisableClientState(ArrayCap.VertexArray);
GL.Flush();
}
Кто-нибудь может быть так добр, чтобы дать мне несколько советов (не каламбур)? Я делаю это в неправильном порядке или есть некоторые функции, которые мне нужно вызвать?
Я искал по всей сети, но не могу найти ни одного хорошего учебника или руководства, объясняющего, как реализовать пользовательские вершины. Если вам нужна дополнительная информация, скажите, пожалуйста.
1 ответ
Существует не так много, чтобы сделать свой собственный формат вершины. Это все сделано в glVertexAttribPointer
звонки. Прежде всего, вы используете 4 в качестве параметра шага, но ваша структура вершины имеет ширину 8 байтов, поэтому от начала одной вершины до следующей имеется 8 байтов, поэтому шаг должен быть 8 (конечно, в обоих вызовах). Смещения верны, но вы должны установить для нормализованного флага значение true для цветов, так как вы наверняка хотите, чтобы они находились в диапазоне [0,1] (я не знаю, должно ли это быть в случае с позициями вершин).).
Далее, при использовании пользовательских атрибутов вершин в шейдерах, вы не включаете устаревшие массивы фиксированных функций (gl...ClienState
вещи). Вместо этого вы должны использовать
GL.EnableVertexAttribArray(Shaders.PositionDataID);
GL.EnableVertexAttribArray(Shaders.ColorDataID);
и соответствующий glDisableVertexAttribArray
звонки.
И что count/4
значит в glDrawArrays
вызов. Имейте в виду, что последний параметр указывает количество вершин, а не примитивов (в вашем случае это четырехугольники). Но, возможно, так и было задумано.
Помимо этих реальных ошибок, вы не должны использовать такой скопированный формат вершин, что вам придется самостоятельно декодировать его в шейдере. Это то, что параметры шага и смещения glVertexAttribPointer
для. Например, немного переопределите ваши данные вершин:
public struct SmallBlockVertex
{
public byte PositionX;
public byte PositionY;
public byte PositionZ;
public byte ColorR;
public byte ColorG;
public byte ColorB;
public byte TextureX;
public byte TextureY;
}
и тогда вы можете просто использовать
GL.VertexAttribPointer(Shaders.PositionDataID, 3, VertexAttribPointerType.UnsignedByte, false, 8, 0);
GL.VertexAttribPointer(Shaders.ColorDataID, 3, VertexAttribPointerType.UnsignedByte, true, 8, 3);
GL.VertexAttribPointer(Shaders.TexCoordDataID, 2, VertexAttribPointerType.UnsignedByte, true, 8, 6);
И в шейдере у вас есть
attribute vec3 pos_data;
attribute vec3 col_data;
attribute vec2 tex_data;
и вам не нужно извлекать координату текстуры из позиции и цвета самостоятельно.
И вам следует подумать о том, действительно ли ваши требования к пространству требуют использования байтов для позиций вершин, так как это чрезвычайно ограничивает точность ваших данных о положении. Возможно, шорты или поплавки половинной точности будут хорошим компромиссом.
А также не должно быть необходимости звонить glBindBuffer
в методе рендеринга, так как это необходимо только для glVertexAttribPointer
и сохраняется в VAO, который активируется glBindVertexArray
, Вы также должны обычно не звонить glFlush
так как в любом случае это делается операционной системой при замене буферов (при условии, что вы используете двойную буферизацию).
И последнее, но не менее важное: убедитесь, что ваше оборудование поддерживает все функции, которые вы используете (например, VBO и VAO).
РЕДАКТИРОВАТЬ: На самом деле активированные флаги массивов также хранятся в VAO, так что вы можете вызвать
GL.EnableVertexAttribArray(Shaders.PositionDataID);
GL.EnableVertexAttribArray(Shaders.ColorDataID);
в SetData
метод (после создания и привязки VAO, конечно), а затем они включаются при привязке VAO с помощью glBindVertexArray
в функции рендера. О, я только что увидел еще одну ошибку. Когда вы связываете VAO в функции рендеринга, включенные флаги массивов атрибутов перезаписываются состоянием из VAO, и, поскольку вы не включили их после создания VAO, они все еще отключены. Так что вам придется сделать это, как сказано, включить массивы в SetData
метод. На самом деле в вашем случае вам может повезти, и VAO все еще привязан, когда вы включаете массивы в функции рендеринга (как вы не вызывали glBindVertexArray(0)
) но вы не должны на это рассчитывать.