January 2012 Archives
There are two significant changes that have been made to Functy recently. The first is improved per-pixel colouring and the second is improved per-pixel lighting. I’m going to leave the lighting to another time and just concentrate on the colouring for now.
Functy is basically a tool for rendering surfaces. Luckily the 3D graphics hardware in modern computers is ideally set up for rendering 3D surfaces; it’s what they were built for. However, those surfaces are almost universally defined by vertices – the positions of points on the surface – meaning that every surface is actually made up of a collection of flat triangles. The result is that graphics cards are great at rendering objects with edges, but less great at rendering curves. Modern cards are so phenomenally fast they can render enough triangles – along with a number of other clever tricks – to make this unnoticeable (nonetheless, it would be great if they actually had the ability to render curved surfaces as first-class objects).
Functy is no different in that it also renders surfaces using triangles. However, it has the advantage that in addition to the vertices, it actually knows the function that defines the surface coordinates and the colours of the points on the surface. Strictly speaking, this means it knows the surface and surface colours to an infinite level of detail.
With the recent changes made to Functy for using vertex and pixel shaders, Functy is now able to properly take advantage of this fact.
Previous versions coloured the surface by specifying the colour of each vertex that makes up a triangle on the rendered surface. The colours in between these points were interpolated, resulting in a smooth transition between different colours at adjacent vertices. This is standard practice and the graphics card can be told to do this – using for example Phong shading – automatically.
Using a fragment shader however, we can improve on this. A fragment shader is a small piece of code that’s executed for every pixel rendered on the screen. By compiling the colour function into a fragment shader, we can therefore calculate the colour accurately for ever pixel rendered, rather than having to approximate between vertices.
The following diagram illustrates this. Suppose we’re rendering a triangle which we want to colour with blue and mauve stripes. The correct result according to the function definitions is a triangle split into two colours. However, if we just colour the vertices and interpolate between them, the resulting output is like that shown on the top right. By contrast, calculating the colour correctly for every pixel gives us the result shown on the bottom right, which is much closer to the correct result we were after.
It sounds like this should be a lot of work, but in fact the result is that (in most cases) it also improves performance. The reason is that previously the colours were calculated for each vertex on the main CPU, taking this processing time away from other tasks that could otherwise be making use of it. Now the calculations are performed entirely on the graphics card’s GPU instead, through the shader processing streams. My (fairly old) graphics card has over 700 processing streams that can run in parallel, allowing the calculations to be performed easily, and taking away the need to do this on the CPU. Everyone’s a winner!
The benefits can be seen most on surfaces with lower accuracy (fewer triangles), but which still have intricate or discrete colouring detail. As you can see in the top right screenshot below, previously the detail would have become blurred or lost (especially if it appeared entirely within one triangle), but can now be rendered in full, with discrete edges as shown in the bottom right image. What’s more, using fragment shaders the colour detail is now entirely independent of the number of triangles rendered, so previous surfaces that needed high accuracy (top left) can now potentially be rendered using as few as two triangles (bottom left).
Although this is a surprisingly simple change to the program, in my view the improvements are impressive, and demonstrate the latent processing power available on graphics cards that often are left unused. Functy has also benefited from similar improvements to the method used for calculating the light reflections from surfaces, and I’ll be looking at this in a future post.
Development of Functy has dropped off for quite a while as the code was in a relatively stable state, and various other things have been taking priority. But while code commits have been rare, posts on this site have been even scarcer.
I’m therefore pleased to say that over the last few weeks I’ve actually found the time to do a bit more development, and hopefully I’ll be able to post some info about the expected changes as well.
There are two main developments, one a feature addition, the other a major optimisation:
- As well as defining colour functions, I’ve also been adding in the functionality to wrap surfaces with texture maps.
- All of the function rendering code has now been moved into shader programs.
Both of these capabilities work, but need a little more care and attention to get them to a ‘production’ state. For example, adding textures has thrown up a number of problems (such as how to reference the texture in the function file) which I still need to address.
The move to using shaders has resulted in really quite significant changes to the codebase and it’s something I’m particularly excited about. The whole intention with Functy has always been to move the function calculation into shaders; it seemed like a perfect fit. To be honest though, I didn’t think I’d actually ever manage it. However, with a bit more experience and confidence using shaders from other projects, an injection of time and effort, and the impetus that fixed pipeline rendering is becoming a legacy approach, it seemed like a nice challenge to try to tackle.
Although there’s still some more work to be done here too, I’m quite pleased with the result, since not only does it allow the latent capabilities of the graphics hardware to be better utilised, it also generates better results (faster, more accurate rendering).
There’ll be more posts about both of these features in due course. In the meantime, as we enter 2012 I’m hoping it’ll also be possible to find more time to work on Functy further.