I think I finally finished with the base of the terrain texturing!
I refactored the terrain shaders, using a very modular approach with tons of function calls and a clean design. I sure hope that the shader compiler is really good. If not, a final version of the shader might have to be written someday that flattens out the implementation and uses all manner of optimizations.
I also started using branching heavily. I am using both good and bad kind of branching. The good one relies on constants passed to the pixel shader body and I am pretty sure the compiler does compile time evaluation of the constants and removes unnecessary branches from the code. The bad kind of branching is the use of run-time "if"s in shaders. GPU really don't love branching. Pixels are evaluated in a clustered fashion and branches can cause the entire cluster to wait for a sync. This can be mitigated if there is a high probability that all the parallel shaders executed for the cluster will take the same path. I done some testing and the results are inconclusive, maybe tending to go a little bit toward having lower performance if I use branching, even thought the body of the branch that is skipped is more expensive.
Using these methods I created two shader implementations, one very basic for the low quality and one that handles higher quality rendering. These are further parameterized with compile time flags to create all variants that I need. I also managed to greatly optimize the implementation, giving a 10-15 FPS increase on weak hardware. On strong hardware I can't tell, because currently I am bus capped.
I also implemented adaptive detail mapping, allowing you to specify a radius for detail mapping. Medium quality setting use this, not for the performance, but because it reduces repeating patterns in terrain somewhat. On high I am not using it because the small performance gain is not worth it when compared to the quality loss. It is a high quality setting for a reason.
The final step was to do something about the view distance. I determined that the landscape looks the best when I use a very distant and aggressive fog. The farther the fog start is, the larger the terrain seems. The fog is exponential and does a good job (but not a perfect one) of hiding polygons entering though the far plane. This small pop-in is so minor that you won't notice it unless you are really looking for it.
One thing that I need to dos till is make the view distance adjustable at run-time.
Using all the above I finished my hardest task: out of the dozens of permutations, choose only 5 quality settings. This was ridiculously hard because all were tough compromises. Just now I changed the spherical harmonics computation just like that and I'm not sure which one I like better. Anyway, there are 3 quality settings: low, medium and high. You can also choose to have enhance the harmonics for better quality, but this does not work for low quality, thus giving 5 quality levels instead of 6. I am fairly happy with these setting. I also made sure that they have comparable color warmness and intensity, but some minor differences are present.
Here is a video showing a 64 square kilometer map with small view distance at maximum terrain quality, large item density using 8xMSAA and SMAA while the character is running at very high speed traversing the map not quite diagonally (I wanted to go from corner to corner but I messed up :) ):
Now that the terrain shaders are finished (I hope) I need to add day and night cycles to it and see about those lights.
For the rest of the post let me entertain you with some very interesting shader variants I managed to produce:
These are not photoshoped or using any other textures than the one from the video. Just a shader variant that produces strange colors a more wet look:
If I ever need an alien looking landscape I know where to start. I did not manage to produce workable shaders out of this method because the output is too noisy and weird in lot of places. It also has pretty bad temporal aliasing.
This is sort of off-topic, but mind if I can take a look at your terrain ray intersection code? I have it so that can find the chunks that the ray intersects, and know how to apply triangle intersection code for the meshes, but would like to know how to get the EXACT location in the triangle it intersects so as to transpose this into precise X and Y float values on the map.
ReplyDeleteIn other words, I don't want the brush to move just along increments such as (X = 20, Y = 31) I want it to be like (X = 20.43, Y = 31.17) for example. This would greatly help me in making my terrain editor.
Hi Chris!
ReplyDeleteMy terrain has support for obtaining precise intersection and I use it for dynamic height/texture editing. It enables this though and API, but it is physics based (BEPUphysics). I use the same ray-casting that I use for other physics based tasks, like trying to figure out the optimal orientation and height to drop an objects so that it smoothly and quickly settles without any jumping movement, sliding or twitching. This is surprisingly difficult.
I do want to release the core of the engine as open-source ASAP and I am getting closer to this goal, but it will take some time.
The interesting question is what to do with the floating point values. I tried my hand at some interpolation and adjusting all 4 vertices of the quad that the ray intersects, but figuring out a good algorithm that maybe edits a large number of vertices is probably needed for a functional terrain editor. And support for different brush size/styles.
Ah I see, you're using BEPU to do the dirty work. Fortunately, that's what I am going to use as well. So you are probably using its TerrainShape class to do the ray casting and using the RayHit object to get its exact location, I'm guessing?
DeleteFor terrain brush editing, it's best do let the GPU handle these operations. Scape is an open-source terrain editor where I found some great ideas on this (and also how I cut down my memory usage with representing the mesh with little data). Send the heightmap texture (or a local section) to the shader, and do your operations in the pixel shader. Then in a ping pong fashion send the updated texture back to the CPU after this pass so then it's just a matter of updating the vertices with the new height values. In a second pass render the map with the new vertices.
Sorry for the late reply, I was out of town. You are correct in your guess and I implemented it as you mentioned. RayHit on BEPU space is a real bread & butter operation I use for a lot of editing and placement operations. But I can see on your blog that you managed just fine.
DeleteI am pretty low tech on terrain editing and never thought about using bitmaps to represent and edit the terrain. Actually, I did consider it but thought that there might be too many complications on patch borders, where the bitmaps must be identical on neighboring patches, so they must probably be padded. Currently I have a patched non-bitmap heightmap representing the entire map in RAM and vertex information for enough patches to represent the current maximal view distance.
But Scape is really great. I tried it and read the articles. I wasn't planning on enhancing the terrain just now, but I might re-implement some Scape stuff. And see about creating a better looking horizon and view distance for my terrain.