Как мне сделать собственный формат вершины с OpenGL - PullRequest
5 голосов
/ 23 июля 2011

Я пишу свой собственный движок с использованием 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 Ответ

9 голосов
/ 23 июля 2011

Создание собственного формата вершин совсем немного. Все это делается в 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)), но вы не должны на это рассчитывать.

...