This project is read-only.

Koeky 3D Framework Basics

Koeky 3D is designed to make it easy to use OpenGL. To do this it wraps OpenGL in .Net classes. I will use this tutorial to present some basic classes of this framework. The classes I will explain here will frequently be used in upcoming tutorials.

Simple Vertex Buffers

A vertex buffer is a buffer used to hold vertex data. For example: it can contain the position of every vertex of a 3d model. It is also possible to store vertex position, texture coordinate and normals in one vertex buffer.

Creating a vertex buffer to store the vertex position is done like this:

// Define the vertex buffer data
Vector3[] vertices = new Vector3[3]
{
        new Vector3(-1.0f, 0.0f, 0.0f),
        new Vector3(1.0f, 0.0f, 0.0f),
        new Vector3(0.0f, 1.0f, 0.0f)
};
// Create the vertex buffer
VertexBuffer vertexBuffer = new VertexBuffer(BufferUsageHint.StaticDraw, (int)BufferAttribute.Vertex, vertices);

The first attribute of the VertexBuffer constructor defines that the data in the buffer will never be changed.
The second attribute defines the attribute index. This is used in GLSL shaders to define where vertex data will go.
The last attribute is the data we previously defined. After this piece of code we got a working vertex buffer!

Custom Vertex Buffers

It is entirely possible that you want to control your own layout of the vertex buffer data.
Say, for example, we have to following struct:
struct VertexData
{
	public Vector3 position,
	public Vector3 normal1,
	public Vector3 normal2

	// constructor omitted for sake of simplicity
}

We want the entire structure to be placed in the vertex buffer. To do this we first have to define the buffer layout:
// Retrieve the size of the VertexData struct
int structSize = Marshal.SizeOf(typeof(VertexData));

// The VertexData structs has got three variables
BufferElement[] elements = new BufferElement[3]
{
    new BufferElement(1, VertexAttribPointerType.Float, 3, structSize, 0),
    new BufferElement(2, VertexAttribPointerType.Float, 3, structSize, sizeof(float) * 3),
    new BufferElement(3, VertexAttribPointerType.Float, 3, structSize, sizeof(float) * 6),
};

For every buffer attribute we define one BufferElement entry.
The first parameter of the BufferElement constructor is the attribute index
The second parameter is the type of data uploaded. Vector3 use floats internally, so we choose float.
The fourth parameter is the amount of elements. A Vector3 has 3 floats, so we choose 3.
The fifth parameter is the stride of the element, and the last parameter is the offset of the element.

Next when we create the vertex buffer the following code is used.
// Create a (empty) array which is our data we want in a vertex buffer
VertexData[] data = new VertexData[10];

// Create the vertex buffer. It is static, we will not change it. Also pass the BufferElement array we defined previously
VertexBuffer buffer = new VertexBuffer(BufferUsageHint.StaticDraw, elements);

// Set the data using the generic method SetData<T>
buffer.SetData<VertexData>(data);

That's it! We now got a vertex buffer ready for use.

Index buffers

Index buffers can be used to decrease the amount of data every vertex buffer needs. A index buffer basically defines which vertices (specified with an index) of the vertex buffer will form triangles.
You can create a index buffer using the following code:
short[] indices = new short[3]
{
    0, 1, 2
};
IndexBuffer indexBuffer = new IndexBuffer(BufferUsageHint.StaticDraw, indices);

We now got a working index buffer

Vertex Array Objects

Vertex array objects are used to easily manage state changes when switching between vertex buffers. Ideally it makes this process faster.
To use a vertex array object we first have to define some vertex buffers:
// These arrays are empty, in a real scenario you would ofcource fill them :)
Vector3[] vertices = new Vector3[100];
Vector2[] texCoords = new Vector2[100];
Vector3[] normals = new Vector3[100];

// Create the vertex buffer for the vertex position data
VertexBuffer verticesBuffer = new VertexBuffer(BufferUsageHint.StaticDraw, (int)BufferAttribute.Vertex, vertices);

// Create the vertex buffer for the texture coordinate data
VertexBuffer texCoordBuffer = new VertexBuffer(BufferUsageHint.StaticDraw, (int)BufferAttribute.TexCoord, texCoords);

// Create the vertex buffer for the normal data
VertexBuffer normalBuffer = new VertexBuffer(BufferUsageHint.StaticDraw, (int)BufferAttribute.Normal, normals);

Next we can create a vertex array object:
VertexArray vertexArray = new VertexArray(verticesBuffer, texCoordBuffer, normalBuffer);

We now got a working vertex array. You can bind it by calling vertexArray.Bind();

Textures

Currently Koeky 3D supports 1D, 2D and cubemap textures wrapped in a .Net class.

1D textures

You can create a 1D texture using the following code:
// Create an array to hold the pixel values
float[] pixelValues = new float[100];

// Create the texture
Texture1D texture = new Texture1D(PixelInternalFormat.R32f, PixelType.Float);

// Set the texture data
texture.SetData<float>(pixelValues, PixelFormat.Red);

Binding the texture to texture unit 0 is done like this:
texture.Bind(TextureUnit.Texture0);

2D textures

2D textures can be created in two ways:
  • You can create a Bitmap object and use it to create the texture2D
  • Create a 2d array of pixel values and use it to create the texture2D

Using the Bitmap class is done using the following code:
// Load a bitmap from disk
Bitmap bitmap = new Bitmap("someImage.bmp");

// Create the Texture2D and set generate mipmaps to true
Texture2D texture = new Texture2D(bitmap, true);


Using a 2d array is done using the following code:
// Create the pixel array
Color4[,] pixels = new Color4[100, 100];

// Create the Texture2D with the correct settings
Texture2D texture = new Texture2D(PixelInternalFormat.Rgba32f, PixelType.Float);

// Set the texture data
texture.SetData<Color4>(pixels, PixelFormat.Rgba);

Cubemap Textures

Cubemap textures are basically 6 2d textures which together form a cube. Creating a cubemap is done using the following code:
TextureCube cubeMap = new TextureCube(PixelInternalFormat.Rgba, PixelType.UnsignedByte);

Next to store data in each of the 6 faces use the following code:
// The 6 2d Color4 arrays contain the data for each face.
// In a realy case scenario they would, ofcourse, be filled.
Color4[,] topFace, bottomFace, leftFace, rightFace, frontFace, backFace;

// Bind the cubemap. This is required.
cubeMap.BindTexture();

// Set the left face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapNegativeX, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, leftFace);
// Set the right face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapPositiveX, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, rightFace);
// Set the bottom face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapNegativeY, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, bottomFace);
// Set the top face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapPositiveY, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, topFace);
// Set the back face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapNegativeZ, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, backFace);
// Set the front face
cubeMap.SetData<Vector3>(TextureTarget.TextureCubeMapPositiveZ, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, frontFace);

// Finally unbind the texture
cubeMap.UnbindTexture();

We have now filled the cubemap with data. You can also fill a face of a cubemap using an IntPtr (which can be retrieved from a Bitmap for example).

Framebuffers

Framebuffers are used for offscreen rendering. The pixels are not drawn on the default opengl framebuffer (which you will see when you call swapbuffers).
You could, for example, use a framebuffer to render the scene from a security camera's point of view and then render the resulting texture to a ingame computer screen. However, framebuffers got a lot more uses.

Creating a frame buffer is easy:
FrameBuffer frameBuffer = new FrameBuffer(200, 200, 1, true);

This creates a framebuffer with a width and height of 200 pixels. The third parameters defines there is one color buffer active. The last parameter means the framebuffer has a depth component (we need this if we want depth testing while drawing to this framebuffer).

Enabling a framebuffer for drawing is done like this:
frameBuffer.BindBuffer();

Call UnbindBuffer to disable it again.

Custom framebuffers

The above example of framebuffers uses the default initialisation. Meaning the color buffer is always a default texture with pixelformat rgba8. But what if you want to define your own pixel format? This can be achieved by using the following code:

// Define the color buffer textures
Texture2D[] textures = new Texture2D[2]
{
    new Texture2D(PixelInternalFormat.Rg16f, PixelType.Float),
    new Texture2D(PixelInternalFormat.Rgb32f, PixelType.Float)
};

// Define the depth texture (used for depth testing)
Texture2D depthTexture = new Texture2D(PixelInternalFormat.DepthComponent32, PixelType.Float);

// Create the frame buffer
FrameBuffer frameBuffer = new FrameBuffer(200, 200, textures, depthTexture);

This could, for example, be used to create a deferred renderer where you want the output textures to have a specific pixel format.

Final words

This tutorial introduced you to some basic concepts of this framework. If you encounter any problems while using the examples: please let me know.

Last edited Aug 17, 2012 at 11:25 AM by Mathyn, version 3

Comments

Mathyn Aug 13, 2012 at 4:48 PM 
Thanks :)

ChristianLindenau Aug 7, 2012 at 11:48 PM 
Keep up the good work, this is pretty awesome!