The art of rendering planets has always been a very fascinating aspect of graphics programming, mostly due to the fact that it offers an impressive sense of scale to the player, but also because it pushes the developers to think of new ways to workaround the hardware limitations that comes with objects that exceeds the limit of a floating point. From games such as Spore, Kerbal Space Program or the more recent No Man’s Sky and Star Citizen, we have learned that, although it won’t warrant a good game, planet rendering offers many technical and gameplay related advantages, some more obvious than others.
Advantages of Planet Rendering:
-
Natural Horizon Culling:
The shape of a planet makes it convenient to hide tiles that are below the horizon line, without having to resort to artificial distance based fog.
-
More Accurate Lighting:
This one is more related to the atmoshere than the planet itself, but having access to precomputed transmittance/irradiance/inscatter textures during the lighting stage is very useful to get an accurate lighting model, at least for the first bounce.
-
Large Procedural Scene:
Using procedural data as the base for a scene allows you to free a lot of storage space that could be used for more important data. I think it’s faire to say that using only procedural content can get old very fast, but it’s possible to combine procedural and lower resolution residual data in order to acheive a scene made by an artist (or generated with other external programs such as World Machine or L3DT) at a very low cost.
Framework:
I started this project mainly to build a framework along side with it, the reason being that planet rendering exposes a lot of edge cases that could not be encountered otherwise. The framework is made in C++ and uses both LuaJIT and luabind (deboostified) for scripting. The core idea is to create all the generic components needed in an engine on the C++ side and then expose them in Lua so that we can seperate all the game logic into a compressed archive, in the same vein as Love2D. It’s basicaly a single Lua context on steroid.
Scripting:
The first thing called by the framework is an entry script located at the root of the project, this script then creates ScriptObjects that can optionally be tagged as dynamic. A dynamic ScriptObject is updated by the framework and allows you to perform update/rendering calls. For this project, the only aspect implemented in the framework is the terrain class since it’s generic and can be used in other context, everything else is implemented in Lua.
Atmosphere/Planet/Ocean Rendering:
After searching around for a while I found that the most elegant open source approach is by far Proland. It offers many advantages such as having a Deformation class that can be applied to a specific terrain, this allows all the code related to planet rendering to be backward compatible with a regular planar terrain approach.
Deferred Rendering:
One of the big challenge to make this approach ready for modern games was to change the rendering type from forward to deferred, this requires a couple of small tricks in order to acheive acceptable results. The firt step is to render the depth, color and normals into a gbuffer, and process the lighting in a post process pass. In order to properly apply the atmosphere lighting I had to rebuild the word space position from the depth buffer. Since the actual camera position exceeds the floating point precision, I rebuild the position using a view matrix that is not translated and add the translation after. The main difficulty I encountered was that now that the position were rebuilt from the depth buffer, I had major precision artifacts in the far view. In order to fix this I split the view frustum into two parts, the near view uses deferred rendering, and the far view uses forward rendering, this way the objects inside the near view have the lighting applied to them without having to modify their original shaders. I expected a major seam artifact at the split location but suprisingly it’s not even visible, meaning that the information rebuilt in the deferred stage is accurate.
Cascaded Shadow Mapping:
At first I tried to implement VSM but unfortunately, the performance I got were actually worse than regular PCF. So in the end I went with Deferred Cascaded PCF Shadow Mapping, I also finally got the shadows to stop flickering when moving by rounding the shadow projection matrix in texel space, a very small but effective tweak.
local rounded_origin = math.round(origin) local round_offset = (rounded_origin - origin) * (2.0 / self.resolution_) round_offset.z = 0.0 round_offset.w = 0.0 sun_proj:set(3, sun_proj:get(3) + round_offset)
Ambient Occlusion:
I implemented HBAO, and combined with a bilateral blur you get a very decent ambient occlusion approximation.
Pseudo Lens Flare:
John Chapman framework code is another base I used, mainly for the camera code and the HDR post processing. This effect coupled with motion blur really gives you the feeling that you are seeing the world trough a camera lens, and it’s actually a good thing in a context where you are seeing the world trough a screen. This effect is generated and then applied at the very end of post processing.
Subpixel Morphological Antialiasing:
Not much to say here, you input a render texture and get a antialiased output.
Conclusion:
If you are further interested in planet rendering I would strongly recommend taking a look at Proland publications page, Outerra blog, and Making Worlds by Steven Wittens. I included the scripting side code, keep in mind that this code is in no way release ready nor properly commented, just some work in progress.
Hi, I looked at your videos about the artifacts you were getting, and I am having the same artifacts in your ocean implementation, it appears when the camera is slightly under the surface. Can you explain a little bit what was the cause, and how you solved the problem?
Thanks!
LikeLike
Hey, nice to see you got it working. I’m not really sure what the exact cause is, but to fix it, I enable glEnable(GL_CULL_FACE) and just flip the ocean grid when I know that the camera position is under the ocean level. It works pretty well, although I’m sure there’s a better way to handle this.
LikeLike