Draw a sphere on a billboard with world normal from a pointlist

807 views Asked by At

I am drawing spheres on billboards by passing center world positions via a StructuredBuffer and use the geometry shader to build a billboard facing the camera. The sphere is drawn correctly and I can figure out the UVs. Now I would like to light it but I cannot figure how to calculate the normals in world coordinates...

The code look like this:

StructuredBuffer<float3> bufferPositions;

VS_OUTPUT VS_Main(uint vertexId : SV_VertexId)
{
    VS_OUTPUT res = (VS_OUTPUT)0;

    res.position = float4(bufferPositions[vertexId], 1);
    res.vertexId = vertexId;

    return res;
}

[maxvertexcount(4)]
void GS_Main(point VS_OUTPUT input[1], inout TriangleStream<VS_OUTPUT> OutputStream)
{
    float halfWidth = SIZE / 2.0f;

    float3 objToCam = normalize(input[0].position.xyz - fCameraPos);
    float3 upVector = float3(0.0f, 1.0f, 0.0f);
    float3 rightVector = normalize(cross(objToCam, upVector));

    rightVector = rightVector * halfWidth;
    upVector = normalize(cross(rightVector, objToCam)) * halfWidth;

    float3 vert[4];

    vert[0] = input[0].position.xyz - rightVector - upVector; // Get bottom left vertex
    vert[1] = input[0].position.xyz + rightVector - upVector; // Get bottom right vertex
    vert[2] = input[0].position.xyz - rightVector + upVector; // Get top left vertex
    vert[3] = input[0].position.xyz + rightVector + upVector; // Get top right vertex

    float2 texCoord[4];
    texCoord[0] = float2(-1, -1);
    texCoord[1] = float2(1, -1);
    texCoord[2] = float2(-1, 1);
    texCoord[3] = float2(1, 1);

    VS_OUTPUT outputVert;
    for(int i = 0; i < 4; i++)
    {
        outputVert.position = mul(float4(vert[i], 1.0f), fViewProj);
        outputVert.wpos = vert[i];
        outputVert.normal = objToCam;
        outputVert.texcoord0 = texCoord[i];
        outputVert.vertexId = input[0].vertexId;

        OutputStream.Append(outputVert);
    }   
}

PS_OUTPUT PS_Main(VS_OUTPUT input)
{
    PS_OUTPUT res = (PS_OUTPUT)0;

    float3 n = float3(input.texcoord0.x, input.texcoord0.y, 0);
    float r2 = dot(n.xy, n.xy);

    // if the texel is not inside the sphere
    if(r2 > 1.0f)
      discard;

    n.z = sqrt(1 - r2);

    // calculate UV mapping
    float u = 0.5 + atan2(n.z, n.x) / (2.0 * PI);
    float v = 0.5 - asin(n.y) / PI;   

    // how to calculate normal in world space ?

    return res;
}

I should have way more information than required to do the calculations but I cannot wrap my head about the calculation to make

UPDATE: I tried to create an Axis-Normal from the plane vector and create a rotation matrix that I would apply to the local normal n without success

1

There are 1 answers

0
Gnietschow On BEST ANSWER

The following describes a way, which should work for your situation.

  1. Compute normal in billboard space

For example, you could use your texture coordinates here

float3 normal = float3(Tex.x*2-1, 0, Tex.y*2-1);
normal.y = sqrt(normal.x*normal.x+normal.y*normal.y)
  1. Create an orthonormal transformation matrix (World -> Billboard)

This matrix consists of the three normalized base vectors (Left, Normal, Up) of your billboard, you will have to pass these to the pixel shader.

  1. Inverse the matrix (Billboard -> World)

As the matrix is orthonormal the inverse is simply the transpose and the result transformes billdboardcoordinates to worldcoordinates.

  1. Apply the matrix to the normal

An here you go, your worldspace normal is finished.