- Home /
Mass billboarding systems BEFORE geometry shaders - GPU instancing under DirectX 9?
Hello forum. I'm a long time lurker, usually finding all of my answer by browsing trough old posts, for this I thank you.
I'm currently working on creating a custom planting algorithm for placing trees and the like onto custom terrain mesh (with overhangs, caves and stuff like that not supported by a unity terrain). I've managed to create a planting system that is efficient enough for handling detailed meshes of a few hundred trees continuously being planted as one traverse the landscape, My question relates to how the built in terrain system achieve rendering of it's massive number of plants for things like billboards and grass?
Seeing the unity built in terrain system in action there can be thousands of billboards displayed effectively giving the illusion of vast distant forest coverage or grassy meadows. Studying code I've grasped how a billboard shader is achieved by cleverly shifting the vertices of a quad using a simple vertex shader. From my understanding this will require a unique pivot for every billboard, naively implemented resulting in thousands of gameObjects, thousands of draw calls and single digit frame rates.
My own naive idea was that I could just write a vertexshader that load a mesh onto the GPU and instanced it onto every vertex of another mesh. Trying to browse on the subject I've found many examples of doing just this, but they're all using directX 11 and the new geometry shaders. Clearly this is not what unity nor scores of older games like oblivion etc does. So how is it actually achieved?
Reading trough the terrain shaders and the terrain.cginc files, they seem to suggest there is some kind of nesting of vertex shaders going on doing something similar to what I originally thought. But I fail to make any sense of how it is achieved, what mesh and how it is feed into these vertex shaders since the whole 'terrain script' connecting it all together is obscured from our view.
First Nvidia GPU gems (5th Printing September 2007) go into some details about this, but it somehow seem to fail to mention how the actual instancing take place: http://http.developer.nvidia.com/GPUGems/gpugems_ch07.html
Thanks for reading, any nudge in the right direction would be much appreciated.
Answer by MisterMike · Feb 25, 2014 at 12:26 AM
Seeing how I asked a new question I take the time to answer this one myself for anyone coming across this wondering.
The way it's done, or at least one way, is by assembling meshes of thousands of quad-planes with all their 4 vertices condensed to a single point located where you want your billboard to appear relative to the meshes pivot. The vertices of each of these quad-planes are mapped to sit at each the four corners of UV space. They're then displaced by a vertex-shader so that the U 1.0 vertices is raised by a up-vector, and the V 1.0 vertices is displaced sideways by a vector perpendicular to camera angle. This forces them to face towards the camera no matter what the view angle is.
So a finished mesh like this is loaded onto the GPU and no actual instancing takes place in the shader code, that's only possible on directX 11 using geometry shaders.
The idea using geometry shader is tasty! Howerver, you have to use Batch to reduce draw calls with d3d9 like this link text. Other techs are also avaiblable, as LOD etc. In the grass rendering demo you given, every GrassObject needs a Draw Call(or any other batch techs not mentioned?)
Answer by Lumen Digital · May 18, 2014 at 12:29 PM
Hi
I have been down the same path with some success but my billboards still have some perspective - they don't appear to be perfectly facing the camera.
// Face the vert towards the camera .
half3 up = UNITY_MATRIX_MV[1].xyz;
half3 eyeVector = ObjSpaceViewDir( v.vertex );
// Find the sideways vector perpendicular to camera angle
half3 right = normalize(cross(up, eyeVector));
// Offset the vert
half3 pos = v.vertex;
pos += v.offsets.x * right * _Size;
pos += v.offsets.y * up * _Size;
o.pos = mul( UNITY_MATRIX_MVP, half4(pos, 1) );
Could you possibly post your solution?