6.3. Basic OpenGL rendering

TGLRenderer class does the basic OpenGL rendering of VRML nodes.

Basic rendering means that this class is not supposed to choose the order of rendering of VRML nodes. This implicates that TGLRenderer is not responsible for doing optimizations that pick only some subset of VRML nodes for rendering (for example, only the nodes visible within the camera frustum). This also implicates that it's not responsible for arranging the rendering order for OpenGL blending, see Section 6.4.1, “Material transparency using OpenGL alpha blending”. In fact, it doesn't set any OpenGL blending parameters (aside from setting colors alpha values as appropriate).

This limitation is done by design. A higher-level routines will internally use an instance of this class to perform rendering. These higher-level routines should choose in what shapes to render, and in which order. In the next Section 6.4, “VRML scene class for OpenGL” we will get familiar with such higher-level class.

The way to use TGLRenderer looks like this:

  1. First you must call Prepare method for all the State instances that you want to later use for rendering. You can obtain such State instances for example by a traverse callback discussed earlier in Section 3.6, “Traversing VRML graph”. The order of calling Prepare methods doesn't matter — it's only important for you to prepare all states before you will render them.

    For example Prepare calls may load textures into OpenGL, and triangulate outline fonts (used by VRML Text and AsciiText nodes).

    You are free to mix Prepare calls with any other rendering calls to OpenGL. This doesn't matter, as Prepare only prepares some resources, without changing OpenGL state. You cannot delete yourself any resources (texture names, display lists, buffer objects etc.) reserved inside Prepare calls. A properly written OpenGL program should always allocate free resource names using calls like glGenTextures anyway.

  2. Call RenderBegin to start actual rendering. This will set up some OpenGL state that will be assumed by further rendering calls.

    If Attributes.PreserveOpenGLState, this also does a push of OpenGL attributes stack, so that everything can be restored later by RenderEnd. Unfortunately, this is quite costly operation, and it's often not needed (when you don't do any custom OpenGL rendering), so Attributes.PreserveOpenGLState is false by default.

  3. Then you should call RenderShape for each VRML/X3D shape that you want to render. As mentioned earlier, all the shapes have to be previously prepared by a Prepare call.

  4. Finally after you rendered all your shapes, you should call RenderEnd.

    Between RenderBegin and RenderEnd you are not allowed to change OpenGL state in any way except for calling other TGLRenderer methods. Well, actually there are some exceptions, things that you can legitimately do — these include e.g. setting enabled state of OpenGL blending. But generally you should limit yourself to calling other TGLRenderer methods between RenderBegin and RenderEnd.

Of course the scenario above may be repeated as many times as you want. The key is that you will not have to repeat Prepare calls each time — once a state is prepared, you can use it in RenderShape calls as many times as you want. If you will not need some state anymore then you can release some resources allocated by it's Prepare call by using UnPrepare or UnPrepareAll methods.

Note that TGLRenderer doesn't try to control whole OpenGL state. It controls only the state that it needs to, to accurately render VRML nodes. Some OpenGL settings that are not controlled include:

  • global ambient light value (glLightModel with GL_LIGHT_MODEL_AMBIENT parameter),

  • polygon mode (filled or wireframe?),

  • blending settings.

So you can adjust some rendering properties simply by using normal OpenGL commands. Also you can transform rendered VRML models simply by setting appropriate modelview matrix before calling RenderBegin. So rendering done by TGLRenderer tries to cooperate with OpenGL nicely, acting just like a complex OpenGL operation, that plays nicely when mixed with other OpenGL operations.

However, for various implementation reasons, many other VRML rendering properties cannot be controlled by just setting OpenGL state before using RenderBegin. Instead you can adjust them by setting Attributes property of TGLRenderer.

6.3.1. OpenGL resource cache

Often when you render various VRML models, you will use various TGLRenderer instances. But still you want those TGLRenderer instances to share some common resources. For example, each texture has to be loaded into OpenGL context only once. It would be ridiculous to load the same texture as many times as there are VRML models using it. That's why we have TGLRendererContextCache. It can be used by various renderers to store common resources, like an OpenGL texture name associated with given texture filename.

Things that are cached include:

  • Fonts display lists.

  • Texture names. This way you can make your whole OpenGL context to share common texture pool — and all you have to do is to pass the same TGLRendererContextCache instance around.

  • Shape information: arrays and VBOs mentioned in previous chapter.

By default, each TGLRenderer creates and uses his own cache, but you can create TGLRendererContextCache instance explicitly and just pass it down to every OpenGL renderer that you will create. All higher-level objects that use TGLRenderer renderer allow you to pass your desired TGLRendererContextCache. And you should use it, if you want to seriously conserve memory usage of your program.

Also note that when animating, all animation frames of given animation object (TCastlePrecalculatedAnimation instance, that will be described in details in Chapter 7, Animation) always use the same renderer. So they also always use the same cache instance, which already gives you some memory savings thanks to cache automatically.

6.3.2. Specialized OpenGL rendering routines vs Triangulate approach

Historically, we used to have many rendering routines for various nodes. This turned out to be extremely cumbersome to maintain. The new "geometry arrays" approach unifies this, translating every shape to only a couple of primitives that map nicely to OpenGL (triangles, quads, quad strips etc.). The "geometry arrays" are also be used to implement TShape.LocalTriangulate and TShape.Triangulate. Thus, rendering and triangulating is nicely unified.

We also have an alternative, debugging renderer that will be used if you define USE_VRML_TRIANGULATION symbol for compilation of GLRenderer unit. Each node will be triangulated using TShape.LocalTriangulate method (mentioned earlier in Section 3.7.2, “Triangulating”) and each triangle will be passed to OpenGL. This is a very limited rendering method, only to show that TShape.LocalTriangulate works correctly:

  1. It's slower than normal rendering through arrays and VBOs.

  2. Things that are not expressed as triangles (IndexedLineSet, PointSet) will not be rendered at all.

  3. It lacks some features, because the triangulating routines do not return enough information. For example, only the first texture unit gets correct texture coordinates, so multi-texturing doesn't work (correctly).