0
\$\begingroup\$

I've been learning OpenGL graphics programming and I've decided I'd quite like to make a simple game using OpenGL.

Drawing something in OpenGL, needless to say, is quite the endeavour. However it usually follows the following steps:

1) Use an array to outline where the vertices are of what I want to draw, as well as

2) Create a vertex array object VAO to store the vertex buffer objects and store how the data is formatted, etc.

3) Generating vertex buffer objects VBOs and element buffer objects EBOs (if applicable). Binding the relevant buffer data to them (ie, passing in the vertex data to the VBO and vertex drawing order to the EBO).

4) Instructing OpenGL on how the data is actually formatted within the array I defined at the start (the vertex coordinates, texture coordinates, etc) using glVertexAttribPointer.

5) Binding the textures if necessary, enabling the vertex and fragment shaders, binding the vertex array, and then finally drawing.

6) Unbinding the vertex array

Since I'm almost always following these 6 steps when I want to draw things I thought I'd be able to abstract it. I thought I could do this by passing in a batch of vertices to a 'render' class every render call which would do these steps every time for me so I can render things more easily without the great big spiel that takes a good 20 lines of code every time I want to draw a box.

This would be immensely helpful. For example, suppose I wanted to draw a heightmap. All I'd have to do would be to pass in the vertex locations/textures to bind/shaders to use, etc, and it'd draw it for me! Possibly even alongside some trees/rocks that I could pass in to that one render call.

However I've realised that there're a lot of technical limitations with this and I'm beginning to question whether this is even a fundamentally correct approach to OpenGL rendering.The reason why I think this is fundamentally wrong is because it still doesn't really avoid the big spiel of code that I have to go through every time I want to draw some vertices - I just create a 'middleman' class inbetween, which is a bit useless and unnecessary.

What are the best practises when it comes to creating a simplified and easy-to-use rendering 'pipeline' like what I'm trying to describe?

\$\endgroup\$
5
  • \$\begingroup\$ Just to be clear, you're not repeating steps 1-3 every frame, right? Only when you load a new asset, then you re-use those buffers each frame those assets need to be drawn? \$\endgroup\$ – DMGregory May 4 '20 at 16:49
  • \$\begingroup\$ See also: Should I unbind buffers? \$\endgroup\$ – DMGregory May 4 '20 at 16:57
  • \$\begingroup\$ @DMGregory: the linked answer is wrong on at least 2 accounts: "You only bind the VBO/IBO again when you need to update them" is wrong. They are bound to tell the shader pipeline where to stream its data from, updates can be but aren't necessarily involved. "Optionally unbind everything to avoid accidental modification" is nonsense. Binding doesn't modify. Remains to say that OpenGL 4.5/.6 offer direct state access with pretty elegant possibilities for everything. \$\endgroup\$ – a_donda Jan 30 at 13:45
  • \$\begingroup\$ @a_donda that sounds like a comment to leave on that answer. You can also post an answer of your own with corrections. \$\endgroup\$ – DMGregory Jan 30 at 13:54
  • \$\begingroup\$ If only I had a concrete case :-) I tend to to prate ... \$\endgroup\$ – a_donda Jan 30 at 14:52
0
\$\begingroup\$

For most rendering use cases, the vertices of a model don't change every frame. Much of what we want to draw in a game is either completely static geometry, like the terrain or buildings; re-used props that need the same vertex buffer every time, just different transforms/materials; or character meshes that might animate according to a skeleton, but still use the same underlying mesh every frame.

So it's wasteful to create and dispose of the vertex data every frame, when we could just leave that data in video memory and reference it when we need it.

Instead you'll usually create a data structure to represent your mesh, that references the appropriate buffers/attribute bindings to use when drawing. You create this data structure once when the mesh is loaded/created and its data uploaded to video memory, and any object that needs to be drawn with this mesh will reference this data structure.

When you draw a frame, you look up into this data structure to find and use the appropriate, already-populated buffers, rather than re-creating them from scratch.

So, there's still a middle-man, but the extra layer does most of its work when assets are loaded / unloaded, and provides just a minimal bit of boilerplate reduction in each draw call. Your main savings is moving the rest of the work outside your draw loop entirely.

\$\endgroup\$
0
\$\begingroup\$

Yes as mentioned I've found creating a 'Mesh' base class works quite well. A Mesh nicely ties together the large number of OpenGL objects needed to render even a simple object and ensures I don't repeat verbose GL object setup code.

When creating an instance of Mesh, I specify the vertex format, provide vertex and index data, supply an array of textures, and finally the shader program (vert and frag shader).

The vertex format is itself just a struct containing primitive fields (x,y,z etc) and a function that knows how to setup (multiple VertexAttribPointer calls) the OpenGL vertex attributes for this format. Keeping the fiddly OpenGL vertex attribute right inside the vertex format itself really makes changing formats simpler. Vertex data provided to the Mesh is just an array of this struct.

A Mesh has a base class method Render() that I call as required to actually draw it. Render() takes care of all the boilerplate of activating shaders, binding the vao, calling GL.DrawElements, unbinding vao and deactivating shaders etc.

Internally each mesh contains an instance of a VertexArrayObject, VertexBufferObject and IndexBufferObject class that take care of correctly binding and buffering data etc to OpenGL as required.

Each mesh can be static or dynamic. A static mesh sends the provided vertex/index data to the GPU once and and saves memory by not storing the client side data. A dynamic mesh expects that the vertex data will be changed and keeps a copy to allow manipulation and sends it to the GPU when requested.

The ShaderProgram and Shader class encapsulate loading (from file/string), attaching, linking/compiling etc.

In summary this level of abstraction is about as far as I would go. Any higher abstractions such as Character, Screen, Sprite etc might be quicker for you to use but will often be slower for the GPU unless you are very careful.

\$\endgroup\$
1
  • \$\begingroup\$ This is a valid but not very performant approach, but it was my first time try on a renderer as well. When there are many render-objects, draw calls, state and pipeline changes it'll make things slow and you start to slide in an abstraction layer for state as well so you can sort. When I had enough of abstraction I went back to the roots and just prepare things for a given scene, render it, and and properly clean up on scene change. \$\endgroup\$ – a_donda Jan 30 at 9:22
0
\$\begingroup\$

This may be opinionated, but imo there is no such thing as a "a fundamentally correct approach to OpenGL rendering".

For "a simplified and easy-to-use rendering 'pipeline'" I wouldn't abstract too much, espcially when you're sure you're not going to switch graphics APIs.

Assuming your objects have a simple lifecycle of setup() - render() - cleanup(), the middle being called per frame, the outer ones on startup and when leaving the renderer. Maybe frame-specific preparations like state changes or dynamic data could be included in prepare_frame() and end_frame() methods if you don't want to clutter render() too much, called before and after calling an object's render() method.

Besides this render-"framework", a class for wrapping shader compilation is ok, imo, which could also handle single uniform objects. Generic buffers objects and their allocations would also be candidates for a wrapper in such a simplified framework. But I wouldn't abstract specific uniform buffers or vertex arrays with their respective buffers. There are too many special cases, data formats, etc. In OpenGL they are built with just a few API calls that easily fit in a render object's methods mentioned above.

With that simplistic approach one can already do a lot, a skybox only needs s-r-c as it is static, a text renderer may additionally need the prepare() method for the dynamic per-frame-data, and so on. You also have a pretty easy control of state changes.

I would also recommend to get the OpenGL Programming Guide 9th edition and the OpenGL Shading Language Cookbook 3rd edition to learn the details of object handling. Most if not all internet tutorials are outdated.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.