1
votes

I need an instanced mesh to have all instances face the camera.

uniform vec2 size;
uniform vec3 center;

vec3 billboard(vec3 v, mat4 view) {
    vec3 up = vec3(view[0][1], view[1][1], view[2][1]);
    vec3 right = vec3(view[0][0], view[1][0], view[2][0]);
    vec3 pos = center + right * v.x * size.x + up * v.y * size.y;
    return pos;
}

void main() {
   vec3 worldPos = billboard(position, viewMatrix);
   gl_Position = projectionMatrix * modelViewMatrix * vec4(worldPos, 1.0);
}

This is my code currently to render one billboard. This doesn't display anything. Passing 'position' in the vec4 constructor on the last line renders the shape as expected, so I know there is something wrong with the calculation.

I have been trying to follow this tutorial: http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/billboards/ 1, specifically solution #2, however, I haven’t had much luck figuring this out.

The answer here also looks promising, but I can’t get anything working. THREE.JS GLSL sprite always front to camera 1

The solution for this question: THREE.js - Billboard Vertex Shader is on the right track, but not accurate for my use case.

My understanding is that the center position needs to be sent after any translations to the geometry or mesh to have the correct calculation in world space.

Three.js passes the camera position in the vertex shader. I was thinking some transformation could be applied to each vertex based on the subtraction between the camera position and center position of the billboard. Would that have a hit on performance?

Finally, being that this will be instanced, using a Sprite object or a mesh and setting LookAt to the camera position every frame isn't an option.

Thanks for any help!

1

1 Answers

1
votes

I just solved it!

The difference is I don't want all the billboards to be parallel to the camera forward vector. It looks fine in most cases, but in the case where the billboard is not close to the camera forward vector, it's looks wrong since the billboard is facing a point elsewhere besides the camera position.

Vertex shader:

uniform vec2 size;
uniform vec3 center;

vec3 look = normalize(cameraPosition - center);
vec3 cameraUp = vec3(view[0][1], view[1][1], view[2][1]);
vec3 billboardRight = cross(cameraUp, look);
vec3 billboardUp = cross(look, billboardRight);
vec3 pos = center + billboardRight * v.x * size.x + billboardUp * v.y * size.y;
return pos;

JS:

        const fullWidth = 0.75;
        const fullHeight = 0.25;

        containerShape.moveTo(0, 0);
        containerShape.lineTo(0, fullHeight);
        containerShape.lineTo(fullWidth, fullHeight);
        containerShape.lineTo(fullWidth, 0);
        containerShape.lineTo(0, 0);

        const containerGeo = new ShapeBufferGeometry(containerShape);
        containerGeo.translate(-fullWidth / 2, -fullHeight / 2, 0);

        const containerMaterial = new ShaderMaterial({
            uniforms: {
                size: {
                    value: new Vector2(fullWidth, fullHeight),
                },
                center: {
                    value: new Vector3(1, 0.5, 1),
                },
            },
        });

This should be everything. Despite missing images and broken links, this: http://nehe.gamedev.net/article/billboarding_how_to/18011/ got me past the finish line.

EDIT 4/12/19 I found a pretty serious artifact with the above solution: http://prntscr.com/nayjic. If you are willing to accept having the billboard fixed along one axis, the code below solves the problem. Without setting the billboard up vector to a fixed value, I couldn't fix the glitch.

If the camera is above the billboard (http://prntscr.com/naz4iy), then you can more easily tell the billboard isn't really "looking" at the camera. If anyone has a solution to this, please post it!

            vec3 billboard(vec3 v, mat4 view) {
                vec3 look = cameraPosition - instanceOffset;
                look.y = 0.0;
                look = normalize(look);
                // vec3 cameraUp = vec3(view[0][1], view[1][1], view[2][1]);
                vec3 billboardUp = vec3(0, 1, 0);
                vec3 billboardRight = cross(billboardUp, look);
                vec3 pos = instanceOffset + billboardRight * v.x * size.x + billboardUp * v.y * size.y;
                return pos;
            }