在 OpenTK 中使用顶点数组

Using Vertex Arrays with OpenTK

我知道使用 VBO 可能更好,但我出于个人原因使用顶点数组(除了我的好奇心)

在 C++ 中,这里使用顶点数组:

// "vertices" is an array of Vertex Struct
const char* data = reinterpret_cast<const char*>(vertices);

glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0));
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 8));
glColorPointer(4, GL_FLOAT, sizeof(Vertex), data + 16));

glDrawArrays(GL_QUADS, 0, vertexCount);

指针在 C++ 中传递并运行良好,但是,我无法在 C# 中使用 OpenTK 使其运行。我遵循了 official documentation 并得到了以下代码:

Vertex[] vertices =  // .. Fill some vertex here

unsafe
{
    fixed (Vertex* data = vertices)
    {
        GL.VertexPointer(2, VertexPointerType.Float, Vertex.Stride, new IntPtr(data + 0));
        GL.TexCoordPointer(2, TexCoordPointerType.Float, Vertex.Stride, new IntPtr(data + 8));
        GL.ColorPointer(4, ColorPointerType.Float, Vertex.Stride, new IntPtr(data + 16));

        // Draw the Vertices
        GL.DrawArrays(PrimitiveType.Quads,
            0,
            vertices.length);

        GL.Finish();    // Force OpenGL to finish rendering while the arrays are still pinned.
    }
}

我得到的只是空白,没有任何显示。

我尝试在我的 VBO 实现中使用相同的顶点,也使用类似的顶点指针代码,它工作正常,所以我认为它不是设置代码错误,我确定 Vertex.Stride 返回 [=13 的有效步幅=]结构。

有什么想法吗?

最后,我能够通过取消交错 Vertex 结构使其工作(我不太确定它是否被称为 "de-interleave")。 但是,我认为这不是解决此问题的最佳方法。更好的解决方案将不胜感激。

这里是使用顶点数组去交错每个属性并在每一帧上传这些属性数据的代码:

    Vertex[] vertices = // Fill some vertices here..

    // This is workaround that i was talking about, de-interleave attributes of Vertex Struct
    // it might be better to de-interleaving those attributes when the change has been made to vertices rather than de-interleaving them each frames.
    List<Vector2> positions = new List<Vector2>();
    foreach (Vertex v in vertices)
        positions.Add(v.position);

    List<Vector2> texCoords = new List<Vector2>();
    foreach (Vertex v in vertices)
        texCoords.Add(v.texCoords);

    List<Vector4> colors = new List<Vector4>();
    foreach (Vertex v in vertices)
        colors.Add(v.color);

    // Use attribute stride instead of Vertex stride.
    GL.VertexPointer(2, VertexPointerType.Float, Vector2.SizeInBytes, positions.ToArray());
    GL.TexCoordPointer(2, TexCoordPointerType.Float, Vector2.SizeInBytes, texCoords.ToArray());
    GL.ColorPointer(4, ColorPointerType.Float, Vector4.SizeInBytes, colors.ToArray());

    // Draw the Vertices
    GL.DrawArrays(PrimitiveType.Quads,
        0,
        vertices.length);

unsafe 路由不起作用,因为 new IntPtr(data + 8) 将指向前方 8 个顶点而不是指向前方 8 个字节。您可以使用 IntPtr.Add 方法解决此问题:

fixed (Vertex* data = vertices)
{
  var stride = Marshal.SizeOf<Vertex>();
  GL.VertexPointer(2, VertexPointerType.Float, stride, 
      new IntPtr.Add(new IntPtr(data), 0));
  GL.TexCoordPointer(2, TexCoordPointerType.Float, stride, 
      new IntPtr.Add(new IntPtr(data), 8));
  GL.ColorPointer(4, ColorPointerType.Float, stride,
      new IntPtr.Add(new IntPtr(data), 16));

  GL.DrawArrays(PrimitiveType.Quads, 0, vertices.Length);

  GL.Finish();  
}

或者你可以通过直接抓取相关指针来降低出错率:

fixed (void* posPtr = &vertices[0].x, texPtr = &vertices[0].u, colorPtr = &vertices[0].r)
{
  var stride = Marshal.SizeOf<Vertex>();
  GL.VertexPointer(2, VertexPointerType.Float, stride, 
      new IntPtr(posPtr));
  GL.TexCoordPointer(2, TexCoordPointerType.Float, stride, 
      new IntPtr(texPtr))
  GL.ColorPointer(4, ColorPointerType.Float, stride,
      new IntPtr(colorPtr))

  GL.DrawArrays(PrimitiveType.Quads, 0, vertices.Length);

  GL.Finish();  
}

我假设 Vertex 定义为或类似于:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Vertex
{
   public float x, y;
   public float u, v;
   public float r, g, b, a;
};

编辑:

  1. StructLayout LayoutKind.Sequential 属性明确告诉编译器不要重新排列内存中变量的顺序。

    NB. 在这种情况下这不是绝对必要的,因为根据 MSDN LayoutKind.Sequentialstructs(值类型)的默认行为。

  2. Pack = 1 还告诉编译器将变量紧紧地打包在内存中,不留任何空隙。默认情况下,编译器会尝试在 4(或 8)字节边界上对齐内存。

    NB. 同样,对于给定的 Vertex 结构,这不是必需的,因为所有变量都在 4 字节边界上完美对齐,并且它们之间没有间隙。

在这种情况下我仍然保留 StructLayout 属性的原因是 crystal 清楚和明确 Vertex 结构,因为它在这种情况下很重要。


EDIT2:也适用于泛型

public struct Vertex
{        
    public Vector2<float> Pos;        
    public Vector2<float> Texcoord;        
    public Vector4<byte> Color;
}

public struct Vector2<T>
{
    public T x, y;
}

public struct Vector3<T>
{
    public T x, y, z;
}

public struct Vector4<T>
{
    public T x, y, z, w;
}

并得到指点:

fixed (void* posPtr = &vertices[0].Pos.x, 
    texPtr = &vertices[0].Texcoord.x, 
    colorPtr = &vertices[0].Color.x)