Как создать собственный формат вершин с помощью 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);
    }

Насколько я понимаю, это правильная процедура для: создания объекта массива вершин и его привязки; создания буфера вершин и его привязки; заполнения буфера вершин данными; установки указателей атрибутов.

Шейдеры. * 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();
}

Может ли кто-нибудь быть так любезен, чтобы дать мне несколько указателей (без каламбура)? Я делаю это в неправильном порядке или мне нужно вызвать какие-то функции?

Я поискал по всей сети, но не нашел ни одного хорошего учебника или руководства, объясняющего, как реализовать собственные вершины. Если вам нужна дополнительная информация, сообщите об этом.


person Azzi777    schedule 23.07.2011    source источник
comment
Итак ... что происходит, когда вы его запускаете? В любом случае, что вам нужно сделать, это привести систему в состояние, в котором она работает. Если вы знаете, как заставить его работать с плавающими данными, используйте это. Просто заставь это работать. Как только у вас будет правильный рендеринг, вы можете постепенно оптимизировать данные. На каждом этапе проверяйте, что это работает. Затем, когда вы попадете в точку, где он сломается, вы поймете, что пошло не так.   -  person Nicol Bolas    schedule 23.07.2011
comment
Ничего не происходит, если я его запустил. То есть он запускается, но ничего не отображается. Я также попробую использовать только плавающие данные и развивать оттуда, но проблема в том, что я не знаю точно, что я должен делать, чтобы заставить работать свой собственный формат вершин (отсюда и название). Это один большой, решающий шаг (который, вероятно, нельзя разделить на более мелкие шаги), на котором многое может пойти не так. Так что это в основном метод проб и ошибок. Если вы знаете как, можете ли вы обнаружить ошибки в моем коде?   -  person Azzi777    schedule 23.07.2011


Ответы (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)), но вы не должны на это рассчитывать.

person Christian Rau    schedule 23.07.2011
comment
Спасибо за действительно подробный ответ. Это объясняет много! Я посмотрю на него завтра и доложу. Причина, по которой мне нужен только один байт на каждой оси позиций, заключается в том, что каждая вершина является частью блока размером 32 * 32 * 32, и мне нужно отображать только целые позиции. Затем я просто смещаю вершины с помощью матрицы перевода. - person Azzi777; 23.07.2011
comment
@ Azzi777 Обновил свой ответ, обнаружил еще одну возможную проблему. - person Christian Rau; 23.07.2011