New year’s resolutions! This year I will try to write often in this blog ( this is my first technical post in this blog ).
This post is a summary about my process making the walls of Attractio, I did this some months ago but I think is a good topic for my first technical post in this blog. English is not my first language so I hope you don’t find so many typos :).
Attractio rooms are made by a lot of boxes of different colors and materials, each box has the same shape, a box of 1x1x1 (Attractio size) with rounded edges
I thought two options for the rendering of the room:
- Only one draw call ( all the room in the same buffer ). The main problem with this is that you can’t do cullings (frustum culling, visibility culling, some portal cullings, etc) because you can’t avoid many draw calls if you only have one draw call. That means you have to process all the vertices even if they are not inside of the frustum.
- Split the room (with a DP) in walls (parallelepipeds), each wall with an arbitrary number of boxes inside. This increase the number of drawcalls but allows you to do some culling.
I chose the second option, some of the reasons:
- A better performance and accuracy in the collision system, because all the room geometry is made of boxes.
- An easy customization of the walls (color and material of each box).
- Optimizable shadow map pass.
- Dynamic rooms, like moving platforms.
Creating the buffer
My first approach for creating the buffer was very simple:
I took a rounded box mesh and I created the buffer repeating the mesh in a grid. I got good visual results but poor performance, in big rooms with walls of ~50×50 boxes, the buffers had ~250,000 vertices each buffer.
My second approach (current one) was harder to code, this time each wall is a box (36 vertices, 12 triangles) regardless of the number of boxes inside and size of the wall. In order to get similar visual results as the first approach I added POM and some G-Buffers tricks to the walls.
Filling the G-Buffers.
I used POM (parallax occlusion mapping) only to round the edges of the boxes, normal mapping is easy way to round the edges but I didn’t liked the visual quality, in game you can look very close the walls and notice the rounded edges of the boxes. POM could be an expensive method if you use it everywhere, in our case the edges only cover ~5%-10% of the walls. To optimize POM I used some properties of our walls: axis oriented boxes and a depth map with only depressions. With this properties you only need to do the small “ray casts” in one direction and the ray intersection becomes only one float comparison. Steps:
- Transform the normalized incoming vector (eye direction) from world space to tangent space and separate in UV component and normal (depth) component, this is very easy with gram-schmidth. I use the UV component to move over the texture and the normal component to compare the depth.
- Normalize the UV and normal components to the desired step size.
- Iterate over the accumulated UV and stop when the depth texture in the current position is greater than the accumulated normal component. The 90% of the pixels finishes in the first iteration.
POM Function (I don’t have the highlight plugin yet):
The render pipeline of GC Engine is deferred rendering, the fourth g-buffer of GC Engine is the scene depth, this allow us to fake the depth map and add the rounded edges in the depth map even if the whole wall has only 12 triangles. With this, any postprocess in screenspace will “see” the walls filled with boxes with rounded edges, this means that the rounded edges will be affected by the ambient occlusion, global illumination, depth of field, etc.
The walls needs to be customizables, it should be possible to change the color and material of any box of any wall. All the wall material are predefined and indexed, so it’s possible to access to any material inside of the wall fragment shader. The color and material of each wall is stored in a texture, RGB for the color and Alpha for the material index. With this texture each pixel inside of the wall fragment shader computes which box belongs and then sample the color and material from the material texture. If the size of the wall is NxM boxes then the size of the material texture is NxM pixels.
Unfortunately this generated a tile effect because the texture for each material is always the same, but some random rotations and reflections fixed the visual problem. It was a little tricky because I also needed to transform the tangent space and other things.
It’s possible to use the same methods in the shadow map pass, with the same trick in the shadow map gbuffers, the rounded edges could cast shadows. We have gbuffers for the shadow map pass because the GI algorithm implemented in GC Engine needs the color and normals of the shadow map.
The wall render is very fast, render a 10×10 wall takes almost the same time than a 1000×1000 wall and loading maps takes between 1 and 3 seconds.
Making maps also become a fast process and coloring maps is like coloring a grid in paint.
The main and sad problem is that we wanted to use a method that we presented in SIGGRAPH ’11 about shadow mapping for omnidirectional shadow lights, but the method doesn’t works good with big triangles (in screen space) and our walls now have very big triangles. http://www.cimat.mx/~alberto/Paper.pdf
Maybe soon I will post about the Attractio Liquid or particle systems.