Hey! Forgot to answer this bit. Vobj, in the example I gave, is anything defined in patch space. For example, the light or camera position.
Lights and cameras are typically stored in world or planet space, however, so you have to pre-transform these positions into patch-space for each patch prior to rendering that patch. The transformed positions are passed to the Shader as uniforms.
To go from world or planet space into patch space is also fairly trivial but wholly dependent upon how you define your patches. I define all of my patches as centered at the origin, laying along the xy plane, with z (Negative z in Unity, since it uses a left-hand coordinate system) as the radial outward direction This is what I call ‘patchSpace’. But patches need to rotated from this orientation, and translated to their ultimate location on the planet. To do this. I then calculate the one ‘yaw’ rotation and one ‘pitch’ rotation required to rotate the patch to its correct orientation in planet space (without that transformation, all patches would be centered on the zNeg face). Yaw implies rotation around the vertical (or ‘Polar’/‘North’) axis, and pitch around the horizontal (‘Equatorial’/‘East’) axis: these axes are defined differently per face (well, the xPos, xNeg, zPos, zNeg faces all share a common ‘north’ axis: yPos), but are common to each patch upon that face.
Knowing these two rotations, their respective axes, and the patch’s ultimate center coordinate once transformed and normalized to a sphere (patchCenterPlanet), I can create a single transformation matrix to convert the patch vertices defined in patchSpace into their proper location in planetSpace.
Ok, lots of words. Let’s math this shit:
Definitions:
World space: right = x, up = y, forward = z, origin = 0,0,0)
Planet space: right, up, and z, account for any tilt in planet's axis, as well as its current
rotation about that axis. Origin = the planet's origin in world space
Patch Space: All of a patch's vertices are defined in this space, centered on its origin.
The space is different for each patch, but based upon two rotations and one translation. Equation for that is below.
For now we’re going to assume that PlanetSpace == WorldSpace, in other words, the planet’s axis has zero inclination, the planet is not rotating, and it is centered at the world space origin. I’m not going to detail how to build that transformation: If you understand the more difficult world-patch transformation, it will be easy to figure out how to do a world-planet transformation. And since transformations concatenate, world-patch is really: TworldToPatch = Tplanet * Tpatch (assuming column-major notation).
Note: I describe each face by its normal (radial outward) vector. So the ‘X Positive’ face lies within the Y-Z axis, and its radial outwards direction is +X.
So, for each face, define an “up” and a “right” vector. I’ll start with the simplest case, the Z Negative face (again, Unity, Left-Handed Coordinates. This would be Z Positive in a Right-Handed system).
//Z-Neg Face:
Vector3 faceUp = new Vector3(0,1,0); //y-positive
Vector3 faceRight = new Vector3(1,0,0); //x-positive
Next, we need to figure out where the patch’s center will be, once normalized to the planet-sized sphere. I call that position patchCenterPlanet . You need to know the patch’s center position in the quadtree, prior to ‘spherification’: i ca;; this the patchCenterCube The exact details will depend on your quadtree layout, but for me, the quadtree for each face is defined in planetSpace, the width of the LOD-0 node for that face is the planet’s diameter, and the face is displaced from the planet origin so that the center of the LOD-0 node just touches the surface of the planet-sized sphere at the very center of the node. In other words, the faces (and thus root nodes of each quadtree) form a box that tightly encloses the sphere. So:
(a == planet diameter)
//Z-Neg Face:
Vector3 patchCenterNormalized = patchCenterCube.normalize();
Vector3 patchCenterPlanet = patchCenterNormalized * planetRadius;
That was easy. Ok, so ‘yaw’ will be a rotation around the ‘up’ axis, and ‘pitch’ will be around the ‘right’ axis. Calculate the patchCenterSphere's yaw and pitch values:
//Z-Neg Face:
float yaw = Asin(patchCenterNormalized.dot(faceRight)/planetRadius); //Asin is arcsin
float pitch = Acos(patchCenter.Normalized.dot(faceUp)/planetRadius); //Acos is arccos
Now we have all we need to build our transformation matrix. Recall:
![]()
(These are in row major notation, so be careful when converting them to OpenGL’s column-major format input)
These matrices concatenate easily. I apply my yaw rotation before my pitch rotation, so yaw rotation matrix must be post-multiplied with the pitch rotation matrix (again, row-major). In other words: T = P * Y, since row-major notation matricies are multiplied with vectors on their right. Also: alpha == pitch, beta == yaw
thanks wolfram alpha!
Finally, let’s add in the translational component to make it a 4x4 Homogenous transformation matrix:
Where s, t, and u are the x,y,z components of patchCenterSphere
Alright, so what does this thing do? It transforms your patchSpace vertices into planet space (which is also world space, in this example). It also defines the basis for your particular patch space… in other words, the 1st row defines the patch’s “right” direction, the 2nd row is “up”, the 3rd “forward”, and the 4th is its planet-space origin.
But we wanted to go from world space to patch space, not the other way around. This is also easy… we just need the inverse of this matrix. The inverse is a bit annoying to compute, but there’s one shortcut: if each basis vector is orthogonal and of unit length, the inverse is equal to the transpose. That’s much easier. Just normalize each row of the above resulting matrix. (Note: you only have to calculate this matrix once, when the patch is first defined, so in my mind computational expense is less important than clarity. It may actually be cheaper to compute the inverse than to do what I’m suggesting).
After normalizing each row, take the transpose. The transpose is simply Mij = Mji, in other words, flip each value along the diagonal axis (top let to bottom right). values on the axis (i.e. M11 M22 etc) remain unchanged.
You now have the inverse transformation matrix: in other words, the transform that will take you from planet(world) space into patch space!! Once you transform your lights & camera into patch space, you can now transform them into the TBN (tangent) space, and in doing so, perform advanced eye-position dependent lighting, such as specular and reflection. Congratulations, mission accomplished.
I could go on, and probably should go into more detail on some of these points, but I’m tired and this is already a huge post.
That would be very much appreciated 

just confirming… That algorithm I developed is for distributing quadsphere vertices more evenly. I suppose the vertices could remain undistributed, with the texture coordinates distributed to make for a more even coverage… But haven’t tested that myself. If you end up getting it to work, please share!