0
votes

I'm trying generate some matrices to place trees on a planet on the GPU. The position of each tree is predetermined - based on a biome map and various heightmap data - but this data is GPU resident so I can't do this on the CPU. At the moment I'm instancing using the geometry shader - this will change to traditional instancing if performance is bad, and I'd then compute the model matrices for each tree on a compute shader.

I've got as far as trying to use a modified version of lookAt() but I can't get it working and even if I did, the trees would be perpendicular to the planet instead of standing up. I know I can define a using 3 axis, so the normal of the sphere, a tangent and a bitangent, but given I don't care what direction these tangents and bitangents are in at the moment, what would be a quick way to calculate this matrix in GLSL? Thanks!

void drawInstance(vec3 offset)
{
  //Grab the model's position from the model matrix
  vec3 modelPos = vec3(modelMatrix[3][0],modelMatrix[3][1],modelMatrix[3][2]);
  //Add the offset
  modelPos +=offset;

  //Eye = where the new pos is, look in x direction for now, planet is at origin so up is just the modelPos normalized
  mat4 m = lookAt(modelPos, modelPos + vec3(1,0,0), normalize(modelPos));

  //Lookat is intended as a camera matrix, fix this
  m = inverse(m);

  vec3 pos = gl_in[0].gl_Position.xyz;
  gl_Position = vp * m *vec4(pos, 1.0);
  EmitVertex();

  pos = gl_in[1].gl_Position.xyz ;
  gl_Position = vp * m *vec4(pos, 1.0);
  EmitVertex();

  pos = gl_in[2].gl_Position.xyz;
  gl_Position = vp * m * vec4(pos, 1.0);
  EmitVertex();

  EndPrimitive();
}

void main()
{   
  vp = proj * view;
  mvp = proj * view * modelMatrix;

  drawInstance(vec3(0,20,0));
  // drawInstance(vec3(0,20,0));
  // drawInstance(vec3(0,20,-40));
  // drawInstance(vec3(40,40,0));
  // drawInstance(vec3(-40,0,0));

}
2

2 Answers

1
votes

I would recommend taking a different approach completely.

First, don't use geometry shaders for replicating geometry. That's what the glDrawArraysInstanced is for.

Second, it's hard to define such a matrix procedurally. This is related to the Hairy Ball Theorem.

Instead I would generate a bunch of random rotations on the CPU. Use this method to create a uniformly distributed quaternion. Pass that quaternion to the vertex shader as a single vec4 instanced attribute. In the vertex shader:

  1. Offset the tree vertex by (0, 0, radiusOfThePlanet) so that it's located at the north pole (assuming Z-axis is up).
  2. Apply the quaternion rotation (it will rotate around planet center so the tree stays on the surface).
  3. Apply the planet model-view and camera projection matrices as usual.

This will yield an unbiased uniformly distributed random set of trees.

0
votes

Found a solution to the problem which allows me to place objects on the surface of a sphere facing in the correct directions. Here is the code:

  mat4 m = mat4(1);

  vec3 worldPos = getWorldPoint(sphericalCoords);


  //Add a random number to the world pos, then normalize it so that it is a point on a unit sphere slightly different to the world pos. The vector between them is a tangent. Change this value to rotate the object once placed on the sphere
  vec3 xAxis = normalize(normalize(worldPos + vec3(0.0,0.2,0.0)) - normalize(worldPos));

  //Planet is at 0,0,0 so world pos can be used as the normal, and therefore the y axis
  vec3 yAxis = normalize(worldPos);

  //We can cross the y and x axis to generate a bitangent to use as the z axis
  vec3 zAxis = normalize(cross(yAxis, xAxis));

  //This is our rotation matrix!
  mat3 baseMat = mat3(xAxis, yAxis, zAxis);

  //Fill this into our 4x4 matrix
  m = mat4(baseMat);

  //Transform m by the Radius in the y axis to put it on the surface
  mat4 m2 = transformMatrix(mat4(1), vec3(0,radius,0));
  m = m  * m2;

  //Multiply by the MVP to project correctly
  m = mvp* m;

  //Draw an instance of your object
  drawInstance(m);