[Solved] Billboarding


Hello Monkeys,

im currently working on adding grass to my voxelgame.
I decided to go with a geometry shader approach and to put a long story short, while meshing a chunk i also create a point mesh that contains one point per visible top-face of vegetation spawning blocks (currently only for grass blocks but i put the blocktype in a vertex attribute so in the shader i could spawn different vegetation based on blocktype)

in the shader i then spawn several triangle-strip grass-blades per point and offset them using a noise-texture so they spread over the 1x1 sized top-face of the block.

that all works fine as long as i’m facing negative z. As soon as i rotate the cam the grassblades get thinner until they disappear. so i obviously want to do some billboarding in that i rotate the grassblades, around the y axis only, to always be facing the cameras position, but beeing the shadernoob that i am, i cannot seem to get it working
here is the code for the rotation:

//msPosCenter is the origin of the grassblade, it already contains the offset caused by the noisetexture to spread over the whole face, in modelSpace (its the point i want to rotate around basically)
//msPosOffset is the grass-blade-vertexes offset relative to the msPosCenter, in modelSpace (so over the 7 calls nedded to form a full grassblade, msPosCenter is the same for all calls, only msPosOffset varies
vec3 faceCamera(vec3 msPosCenter, vec3 msPosOffset) {
    //return (g_WorldMatrix * vec4(msPosCenter+msPosOffset, 1.0)).xyz;
    //prepare some variables
    //ms modelspace
    vec4 msPos = vec4(msPosCenter, 1.0);
    vec4 msOffPos = vec4(msPosOffset, 1.0);
    vec3 msNorm = vec3(0.0, 0.0, 1.0);
    //ws worldspace
    vec3 wsPos = (g_WorldMatrix * msPos).xyz;
    vec3 wsOffPos = (g_WorldMatrix * msOffPos).xyz;
    vec3 wsNorm =(g_WorldMatrix * vec4(msNorm, 1.0)).xyz;

    //first calculate direction to camera (in worldspace)
    wsNorm.y = 0.0;
    wsNorm = normalize(wsNorm);
    vec3 deltaCam =g_CameraPosition-wsPos;
    deltaCam.y = 0.0;
    deltaCam = normalize(deltaCam);
    //calc angle between normal and view
    float dot = dot(wsNorm, deltaCam);
    float angle = acos(dot);
    //now rotate the offset
    mat2 rot = mat2(vec2(cos(angle), sin(angle)), vec2(-sin(angle), cos(angle)));
    vec2 wsOffPosFlat = wsOffPos.xz;
    vec2 wsOffPosRot = rot * wsOffPosFlat;

    //and return the offset added to the basePosition
    return wsPos.xyz + vec3(wsOffPosRot.x, wsOffPos.y, wsOffPosRot.y);


EDIT: the ‘vec3 msPosOffset’ parameter does only offset in x and y directions, thus the line ‘vec3 msNorm = vec3(0.0, 0.0, 1.0);’

im aware of the fact that i pass in positions in modelspace and return a position in worldspace, but im taking care of that and if i uncomment the first line, and comment out all other lines instead, their position is correct, only they are not rotated ofc
i could also use the same rotation matrix for all vertices of a grass-blade but i can do such optimization later, i want it to work first :smiley:

So thanks a lot in advance already, have a nice day and many greetings from the shire,


I did not try to understand the whole code/post, but you’re probably making your life too complicated:
Billboarding in a Shader simply means NOT applying the View Matrix (I think) in the Vertex Shader.

If you want more control maybe look into the BillboardControl Class (e.g. rotating around the x/z axis, that means panning up and down should not be allowed, as for some grass that is the case).

The other thing is probably that you use an angle which is prone to issues (you only have one angle, not all euler angles or a quaternion, etc). It would’ve been better if you use matrix magic somehow (inverting them and multiplying them).

The rough approach is: applying the inversion of the camera transform to the world space (e.g. by multiplying)


Ugh… yeah, don’t ever use angles like this. It’s never needed.

…get the cosine of the angle.

…get the angle…

…to turn it back into the cosine of the angle. lol.

You don’t need to do all that.

What you need:
grass blade world position: wsPos (I guess)
camera position g_CameraPosition

…then calculate the vectors in that billboarded grass space.

deltaCam just like you’r doing… though I wouldn’t completely get rid of y else your blades will look dumb when looking straight down at them. Let them lean back a little. But that’s fine, let’s use yours for now.

vec3 xAxis = cross(deltaCam, vec3(0.0, 1.0, 0.0);
vec3 yAxis = cross(deltaCam, xAxis);

Your corner will be at:
corner = (xAxis * xOffset) + (yAxis * yOffset);

Note: my IsoSurfaceDemo has an example of batched billboarded grass:

It’s doing a bit more because I let the grass blades lean with the terrain normal:


Sorry for the late reply and thanks both for your answers.
Since that is some “matrix magic” for me too, i thought i could reduce the problem down to a 2d rotation on the xz plane

hope you had a good laugh :smiley:

So i had a look at BillboardControl and Grass.vert. i tried to translate the idea of BillboardControl class rotation around y axis into glsl but i failed and Grass.vert looks confusing to shadernoob-me because its filled with lines over lines, but at least i got the general idea:
…do the delcaCam thing, then cross that vector with 0,1,0 to get the new xAxis, then cross this with the deltaCam to get the new y axis (although, shouldnt it always be 0,1,0 still?) and since these axis are of length 1 now i can scale them by multiplying the appropriate one with its offset, finally add it to the center position and be done.

my code now looks like:

vec3 faceCamera(vec3 msPosCenter, vec3 msPosOffset) {
    //get worldspace position of Center, Offset and Normal
    vec3 wsPos = (g_WorldMatrix * vec4(msPosCenter, 1.0)).xyz;
    vec3 wsPosOffset = (g_WorldMatrix * vec4(msPosOffset, 1.0)).xyz;
    vec3 wsNorm =(g_WorldMatrix * vec4(0.0, 0.0, 1.0, 1.0)).xyz;
    wsNorm.y = 0.0;
    wsNorm = normalize(wsNorm);

    //get worldspace direction from position to camera 
    vec3 deltaCam = g_CameraPosition - wsPos;
    deltaCam.y = 0.0;
    deltaCam = normalize(deltaCam);
    //calculate x and y axis based on that direction
    vec3 wsxAxis = cross(deltaCam, vec3(0.0, 1.0, 0.0));
    vec3 wsyAxis = cross(deltaCam, wsxAxis);

    //now offset center position by wsPosOffset.x on xAxis and wsPosOffset.y on y Axis, add it to center and return
    return wsPos + wsxAxis*wsPosOffset.x + wsyAxis*wsPosOffset.y;

@pspeed i know its basically exactly what you suggested (i hope), but i’m still not able to get it working
and i’m not sure about the line “…then calculate the vectors in that billboarded grass space.”, you mean like the last line
wsPos + wsxAxis*wsPosOffset.x + wsyAxis*wsPosOffset.y?
well, i cannot see any grassblade at all, not even flickering around or with wrong rotation, but they are completly gone like moved by couple of hundred units away or always behind the camera or such. i tried negating the deltaCam to see if they are maybe always facing back from the camera but that doesnt seem to be the case

here is a quick diagramm of what the situation actually is:

|      :6
|      :
|      4 5
|      :
|     2: 3
|      :
|    0 x 1
 -------------------- X
0,1,2... = positions of the 7 vertices of a grassblade 
           (msPosOffset in parameters, faceCamera() is called 7 times per grassblade once for each vertex)
           i spawn the blades only on x,y-plane thus the msPosOffsets z-component is always 0
       x = origin of the grassblade (msPosCenter in parameters)
           this one however is in 3d, that is z-component might vary 
       : = axis i want to rotate around

though both my first approach and the one you suggested seem to make perfect sense to me, i dont get why neither of them is working
although it starts to dawn on me that i just make a lot of mistakes and thats why :sweat_smile:

Thanks for your patience and for any help,


… yeah well, it works now :smiley:
last line in the method had to be

return (g_WorldMatrix * vec4( msPosCenter + wsxAxis*msPosOffset.x + wsyAxis*msPosOffset.y , 1.0)).xyz;

i had to change the line
vec3 wsxAxis = cross(deltaCam, vec3(0.0, 1.0, 0.0));
vec3 wsxAxis = cross(deltaCam, vec3(0.0, -1.0, 0.0));
though since otherwise the grass grow into the ground

so thats something to work with, maybe add the leaning you mentioned and such, i hope i can get that done by myself

big thanks again for your help!


Also could have just swapped the arguments. cross product is order sensitive.