Procedural Terrain Rendering How-To

No problem. I totally know what you mean with something less time consuming.Its just for educational and learning purposes. I want to understand the technical limits and how programers get around it.
My goal is a prototype that combines near real-scale space, oculus rift support, and procedural created elements like planets or volumetric particle nebulas. Continue here and there, already added floating origin, referency frame velocity, multiple different scaled scenes at once (for local 1:1 and far away 1:1.000.000 objects seamless at the same time) like KSP did to get over the precision issues. Now want to get a least a bit into the procedural planet stuff. I find it fascinating to fly over a planet, go into space, get to multiple lightspeeds, all seamless. The procedural planet i try to create is already bound into this setting. I can fly onto this planet and back lightyears away thanks to the above techniques in realistic scales.

I dont plan to finish this and not at a professional level (I will never create a beautiful scene as on those two screenshots), as my knowledge lacks completely at GPU programing. It should look ok, but hopefully I get the basics to work (surface, water, atmosphere, textures, and more than 1 fps :smile:)

I’ll add a video shortly to show where I am at at the moment.

I’ve created a description how you can work with different spaces to realize large space scenes and show close and near objects at once, if anyone is interested or wants to do something similar.
Guess KSP uses a similar strategy. I realized this in Unity3D, works like a charm, and functions perfectly together with floating origin.

PS: Still appreciate any hint on the above questions for good noise usage and basic strategy to do water.

1 Like

Joerg,

Thank you for sharing! I have been traveling the past several days to visit family but have been reading your posts despite not having an opportunity to reply until now.

First, a question regarding the scaled space technique - do you only ever see one layer at a time, or can multiple layers be viewed on-screen at once? Do create different layers for different distances from the camera, or per-planet?

Regarding noise usage - I highly recommend checking out the textbook “Texturing and Modeling, A Procedural Approach”. Unfortunately all of the code samples are in a specific shading language - RenderMan - so you can’t plug them straight in to Unity or GLSL/HLSL. But the book teaches you the theory, techniques, and even artistry behind how to achieve a specific effect using noise.

One class of noise techniques I recommend you look into are called “Multi-Fractal”. Multi-Fractals use noise functions to essentially modify the input parameters (lacunarity, amplitude, frequency, etc) of another fractal noise function. As a result, they can achieve a very heterogeneous terrain using a single function - i.e. flat, plains, rolling hills, and peaked mountains.

Extending this concept a little further, you could use a fairly low-frequency / low-detail planet-wide noise function to define “regions”, and then use the region to select from different noise functions to achieve different terrain types. Blend between noise functions near the borders. The advantage of having a discrete ‘regionation’ function is that you can make it fairly complex, yet only need to evaluate it rarely (or even evaluate it once and use a lookup function if you select a low enough resolution). You could control this function with inputs such as latitude/longitude, planet-level variables (i.e. volcanic, iceworld, etc), and more.

Regarding oceans - I believe the general method is to render a separate ‘water sphere’, likely with the same LOD division scheme as the terrain. But I can’t speak to this subject from any personal experience.

For realistic-looking atmospheres, you’re going to have to learn about pixel shaders (HLSL-DirectX) or fragment shaders (GLSL-OpenGL) (same thing, different name). There are several techniques, but I believe the general principle is to render a sphere for the atmosphere and color it using the fragment shader, which calculates the required color for each pixel based upon a raycast into the planet. The amount of distance each ray spends in the atmosphere determines the resulting color. I’m not sure whether or not angle of incidence also has an effect.

Hi NavyFish

First, a question regarding the scaled space technique - do you only
ever see one layer at a time, or can multiple layers be viewed on-screen
at once? Do create different layers for different distances from the
camera, or per-planet?

Yes, there can be multiple layers viewed on screen. You assign a “level value” to each layer in Unity3D, this defines what is rendered first and what last. In my case this is
StellarSpace (-2) - Very far away objects (AU to lightyears), 1:1.000.000 scaled down (rendered first)
ScaledSpace (-1) - Far away objects, 1:10.000 scaled down (rendered afterwards)
LocalSpace (0) - Local objects, 1:1 scale (rendered afterwards)
InternalSpace (+1) The players cockpit (rendered at last)

Planets (or suns or whatever) are all assigned to a specific space. a space is not per planet, a space can have multiple objects.
Think of it like a whole world, but scaled at a certain level. Depending on the distance, you (the transitionmanager) apply an object from one space to another.

Since you see all layers at the same time, you:

  • can travel to everything you see on screen, no tricks necessary
  • have a seamless transition from each space to the next when using a transitionmanager like in my example, so objects move away farer and farer fluently.

I think this should be similar or the same like Kerbal Space Program did it.

Regarding noise usage - I highly recommend checking out the textbook
“Texturing and Modeling, A Procedural Approach”. Unfortunately all of
the code samples are in a specific shading language - RenderMan - so you
can’t plug them straight in to Unity or GLSL/HLSL. But the book teaches
you the theory, techniques, and even artistry behind how to achieve a
specific effect using noise.

Thanks nice hint. There is a bithday coming in two weeks, so I’ve put it on my wishlist finders-crossed :smile:

I think experimenting noise is the most fun stuff at a certain moment when the rest is done. Right now I use fbm, which i think is somewhat like Multi-Fractals. lacunarity, amplitude, frequency etc.
These regions will make the whole thing really interesting, applying different additional noise types (additional simplex noise or something more cellular). Nice topic, would love to know which noise strategies I-Novae use (probably something more complex).

Thanks for the hints with regards to water body and atmosphere. When I’ve fixed the VisibleSphere issue which causes my planet not to split when starting very nearby on surface (a good fellow from another forum gave me a nice hint how I can workaround this issue doing additional visibility checks) I continue with creating the waterbody, and then I need to go into the shader topic :frowning: I am still a bit afraid of the shaders (despite that I have less knowledge there). I tried to apply the atmosphere to my planet using the GPU GEMS atmosphere shaders, but they seem to suffer early when you use these on a sphere of a certain larger scale (they seem to work only at some scales around 1-100 units, which is waaaaay to small). I am afraid to get stuck when I need to apply a shader to my 1:1 scale planet at all. Of course I could scale everything farer down, but I’d love to stick to the Unity3D 1 “unit = 1 meter” convention to use the physics.
We’ll see. I’ll give an update on the fixed VisibleSphere check and the water body when there is some progress.

I got around my issue of the Visible Sphere check reporting “false” Horizon results when starting near the planet surface but at an initalliy low level quad / plane. The impact is that the surface does not start to split because you check only against the raw four edges of the plane which are behind the horizon, thus the Visible Sphere culling says the plane is not visible (but you are directly in front of it). My solution approach, by friendly help of joeydee from zfx.info, is to perform a volume check of the plane beforehand, to check if the plane MUST be visible (and not perform the visible sphere check then). This is approach can be a little extended to also calculate the nearest distance from camera to the spherical plane, and only use this one coordinate for the visible sphere check. But to solve the above issue this is not required (it adds more precision but likely lowers performance).

Not near a computer so excuse the brief response. Glad you found a solution, although I can’t speak to whether or not it’s more efficient than doing the box-sphere check I mentioned awhile ago.

My solution has always been to treat each terrain patch as a volume, whose ‘depth’ is determined by the minum and maximum possoble terrain heights, and then conduct a box-sphere against the patch’s bounding box and the visibility sphere. By transforming this check into a patch-oriented coordinate frame, the patch’s bounding box fits the patch fairly tightly - as depicted by one of the images I posted several posts ago.

Once I get to a computer I can elaborate on this concept a bit more if you’d like, although unfortunately I won’t have the code available to me until I return home in a couple of weeks (I really need to set up or use a code hosting/versioning server!). Thanks for the updates!

Alright, back at my PC and figured I would post a quick depiction of what I’m describing.

This first image shows a profile view of your patches, and the associated bounding boxes IF the bounding box remains calculated in “world coordinates”:


This next image shows the bounding boxes after you transform them to a “patch-aligned” coordinate system:

As you can see, the transformed bounding boxes fit your patches much better.


The transformation process is very simple:

Note: AABB == Axis-Aligned Bounding-Box

Note: The Normalized min/max corners are the only coordinates which need to be normalized for this operation. Smooth normalized arcs are presented for illustration purpose, but are not actually calculated. A 3D patch (unnormalized) will have 4 corners (i.e. a, b, c, d), resulting in 8 total points after normalization - (a,b,c,d)normMax, and (a,b,c,d)NormMin


You must also apply the same transformation (rotate by theta average) to your camera position, and then center the visibility sphere on the transformed camera’s position. Now the visibility sphere and patch’s bounding box are in the same coordinate system (one which treats the patch bounding box as being axis-aligned), and so you can perform a very cheap Sphere-AABB intersection test to determine if the patch is visible (or more appropriately - what level of detail it should be, by using ‘LOD visibility spheres’, discussed in a previous post).

Also, not depicted, but the sphere radius i’m using here when normalizing the patches is the Max Terrain Height, Rmax. If your terrain algorithm both increases AND decreases the radius of vertices (i.e. doesn’t just pull them up but also pushes them down)
Correction: The first two images only depict a single sphere - imagine that at that level of zoom, you can’t see the difference between the min and max terrain height spheres. The third picture can be thought of as having the difference between Rmin and Rmax exaggerated for illustration purposes

You actually need to perform a two normalizations of the patch corners: one for a Max Terrain height sphere, and one for a Min Terrain Height sphere. Then transform both of these into the AABB space, and calculate the patch bounding box based upon the min/max values of both spheres. This will give you a bounding volume that’s guaranteed to tightly cover the entire patch.

EDIT: I’ve changed the third picture to reflect that a min and max sphere must be used to calculate the AABB

Hope this makes sense!

1 Like

First of all NavyFish, thanks for providing this inside view of the techniques you’ve elaborated, and putting all the effort in documenting and explaining it at such quality and detail! I think this thread and your hints make it one of the pearls of resources when you search the web for the procedural planet generation and working with quads.

Got it (I understood your LOD sphere approach but was unsure about the bounding boxes)! Indeed very nice approach. The upside is that it can be easily implemented in parallel at the begining, calculating the bounding box and transform process in the background and check its results and performance, and then switch from another approach to that one - and the bounding boxes might come in handy for other topics too. Going to start with it into my current Unity3d approach now.

Glad it’s been helpful, Joerg! Thank you for your comments and contributions. I have considered consolidating much of this information into a blog so it’s easier to find… there seem to be plenty of high-level tutorials, but most of them don’t get into the nitty gritty implementation details.

As you mentioned, the bounding boxes and the transformation matrix can be calculated once when the patch is first generated, so that the only calculations which need to be performed each frame are to transform the camera into the patches coordinate space, and perform the very inexpensive AABB-sphere collision test (or multiple tests if you were using LOD spheres).

Another enhancement would be to determine the actual minimum and maximum Heights of the terrain patch when you first generate it, and use these values when computing your AABB, instead of the global Rmin/max values. This is trivial if you compute the patch vertices on the CPU, & a little more complex but definitely doable if you’re computing them on the GPU. now your level of detail calculations are truly 3d, where without this they should be considered more cylindrical.

A good example of where this is beneficial: imagine you are several hundred feet above a flat valley - without using three-dimensional level of detail checks, the patch directly beneath the camera would be at the highest level of detail, even though you’re several hundred feet away from it. Whereas come out with true 3d level of detail checks, these patches could be rendered at a lower level of detail with no visual degradation. Many LOD implementations do not actually consider height above terrain and simply hard-code values - ie if you are 10 kilometers above the average surface of the planet, render no patches above a certain level of detail, etc.

Dictating this from my phone, so apologies if there are any typos. excited to see your progress implementing this scheme! Talk to you later

Now added the patch-aligned bounding coordinates (calculated once at creation) for each plane into each quadnode.
Looking good (added a few screens from debug-mode) based on rMin and rMax. I also added the AABB vs Sphere test into Unity3D from your linked article, works too as the tests from the article show the expected results in my implementation too. I right now suffer in calculating the transformed bounding box coordinates for the AABB - whyever the patch aligned coordinates are not correctly rotated into the same orientation (it looks chaotic when looking at it in debuging, I would expect something like every transformed bounding box facing up), need to investigate into this. Probably a small issue, as the calucated average angle of the planes seem to be right (4x90 degree, 1x0 degree, 1x180 degree initially) but I guess there is something wrong when doing the rotation afterwards. Anyway, something that can be fixed.

Very nice idea to get more detail by keeping track of the actual min and max of each plane for more LOD precision. Especially as these values can easily be determined (its just keeping track of the actual min and max noise results of each plane) and kept as value in the quadnode during creation.

By the way, I implemented the LODSphere Strategy (not doing a AABB yet as mentioned, but as a workaround checking the LODsphere radiuses against the distance of the nearest vektor coordinate of each plane). Works very nice, a way better strategy for a split or merge condition. Lesser splits but still enough detail in the player’s focus, and quicker quadtree parsing.

2 Likes

Joerg,

Fantastic! Thank you for the screenshots… I’m very excited to see you having the LOD-sphere approach working with the AABBs, and once everything is working I’d love to see a video if you’d be willing to share! No pressure though.

Perhaps a degrees to radians issue on the AABB transform? Can you post a screenshot of the bug? How are you calculating the angle? I took the non-normalized & non-transformed patch coordinates (i.e. quadtree node from the original cube), normalized the corners, then converted those into rho & phi (elevation and asimuth), and then took the simple average of the for to get both az. and el. [Can’t remember if I mentioned this before or not, but I actually pre-distort my quadtree nodes after splitting then in order to fix the ‘pinching’ near each cube corner. It’s a one-line equation, and it successfully removes the pinching, but I don’t think it’s cricitcal. Happy to share the code if you’re interested]

With these average angles, I simply concatenated two rotation matrices (one about the x-axis, and one about the z-axis - assuming, your “polar axis” is the y-axis, that is) to get the resultant patch-aligned BB rotation matrix.

When debug-visualizing these in camera view, apply the transformation to a cube whose “bottom” plane has a y-value of rmin, and whose “top” plane has a y-value of rmax (again, assuming the y-axis runs through your planet’s poles), and which is centered on the y-axis: i.e. the ‘left’ plane of the cube is -N units in the x direction, and the ‘right’ plane is +N units in the x direction, where N is the half-width of the patch ONCE NORMALIZED. That last point caught me off-guard at first- you need to calculate the width/length of the AABB after the patch has been normalized. There’s probably an equation that could predict this for you, but it was easier to just normalize the patch’s corners to the planet’s radii (use rMax to garuntee complete coverage), then take the distance between tem and save those as width/length.

(I’m sure there are many qays to accomplish what I’m desribing - this is simply my approach. It looks like you have the patches displaying just fine!)

You can of course perform this transformation in the opposite direction to put the camera’s position into the patch’s “aligned coordinate space”, which is neccessary for the LOD-sphere + patch-aligned BB method. Just make sure you’re using the camera’s position relative to the planet’s origin before applying this transformation, then simply apply the backwards transformation to the camera’s position (i.e. the same concatenated rotation matrices, just with negated elevation and azimuth values). It’s efficient to precalculate and store both the ‘forward’ and ‘backwards’ transforms.

Again, I’m very much enjoying watching your progress. You’re quickly getting to the point where I left my own project (actually, you’ve surpassed me in a few areas, where I’d written the algorithms and built prototypes but hadn’t yet incorporated them into the entire engine), so I’m excited to break new ground with you and try out different ideas if you decide to continue your progress. Great work!

Oh, I forgot to ask - what language are using with Unity? C#?

EDIT- Made a few clarifications, might be worth re-reading if you read this before the edit
.

Hi NaviFish,

yes, I am using C# only. I dont like the scripting language and avoid it completely in my Unity3D projects, thus its all C# (nice language anyway I think).

Glad you are always willing to help. This transformation into the AABB is a nice strategy, way better than assuming the nearest point like I do currently. So I really want to have this AABB working completely until moving on to the next topics (I’ve been playing with RidgedMultiFractals today to get more natural looking terrain, works fine for the continents, but thats one for the next update :-)).

These screens show how its looking in debug mode. First screenshot shows nodes at level 1 (so one box), second one at level 3. You can see that a) they arent all rotated into the same direction and b) things get even worse at lover levels.
The angles seem right to me, as you can see them in the debug-consolde righthand-side.

My strategy is:

  • Use the centercoordinate of the plane (I store this during quadtree node creation) for angle checks
  • Get the angle between Vector3.Right (shorthand Vector3(1,0,0)) and the centercoordinate for the averageAngleX value; (I dont need this I think, but anyway…)
  • Get the angle between Vector3.Up (shorthand Vector3(0,1,0)) and the centercoordinate for the averageAngleY value;
  • Get the angle between Vector3.Forward (shorthand Vector3(0,0,-1)) and the centercoordinate for the averageAngleZ value;
  • Rotate all aligned BB Vectors around the Vector3.Forward (the one which faces into the screen) by the -averageY value;
  • Rotate all aligned BB Vectors around the Vector3.Right (the one which faces to the right) by the -averageZ value;

But seems I am taking things to easy here…

This is the important part of the code:
Class to determine the angles:

Code that gets the average angle and rotates:

EDIT:
For sure I will continue this. I dont want to stop until the procedural planet is fully integrated into the universe engine (well, it is already :slight_smile: ), texturing, and things like atmosphere, collision and water are there as well as more flexible biomes for planets is done). This will keep some work to be done for a while. :smile:
And besides the planet engine itself there is further stuff to be done, space particles and warpeffects when a player goes for light speeds, asteroids and nebula (did a first test iteration of asteroids and volumentric nebula already) and black hole effects.
Would be very glad if I could ask for advice here and there or furthermore and better, push ideas forward together. There is so much valuable information and effort in your posts and hints!
As soon as I get the AABB transformation above done I’ll record a video of the current state!

So from your debug console, it looks like you’re calculating angles for the quad-tree nodes BEFORE they’ve been normalized to the surface of the sphere (ie centervector=(1,0.5,0.5) sits on the face of the cube). You need to determine centervector for the patch after it’s been normalized.

After you do that, the method for calculating the angles should be fine.

As far as applying the rotation, there are a few things to note- First, you only need to create the rotation once (Quaternion.AngleAxis(…)), not once for each vertex. Secondly, you can concatenate the two rotations by multiplying them, i.e. totalRotation = Quaternion.AngleAxis( …-y, forward) * Quaternion.AngleAxis( …-z, right). Then you simply apply totalRotation to each corner.

I don’t see any other big gotchas, so hopefully taking the angles from normalized patches vice cube patches will solve things.

By the way, I’m not super familiar with Unity (I’ve written a mod for KSP using C# unity scripting, but never written a game using the engine), so I downloaded it last night and am starting to wrap my head around it. If you’re willing to share the full code, I’d gladly take a look at it. Your choice, I won’t be offended if you don’t wish to do so.

Regards!

OK so I think I get closer to the problem. You were right about the normalized vectors for the angle calculation (I wonder why I missed this), however this is not the root problem. I did a step back and started again trying only one rotation around one axis first.

See the following screen. Goal is to rotate all planes to the north pole (Vector3.up, or (0,1,0)).
In red the transformed Bounding Boxes BB (after the rotation). In grey I connected the rotated BB-edges to the plane’s center point (before the rotation).

Below is the code that I used:

You can recognize very good on the screen what has happened so far:

The four BoundingBoxes left, right, top, bottom were correctly rotated around the Z-axis (Vector.forward).
As I rotate around Z-axis I need to consider if I have to roate clock- or anticlockweise. As you see e.g. (1,0,0) and (-1,0,0) have the same angle to the Y-Axis. I can use X-angle for this, which tells me if the vector is left or righthand to Y. You can see this check in the code. Left, right, top and bottom are now all facing to top. So far so good.

Now the problem are the two remaining planes front and back. You can tell by the cross that they were not rotated (or better, not as needed), which is logical as the Z-axis crosses their center.

Now in theory I would need to rotate around X-axis for the front and back plane. But how should I do this without rotating the top and bottom plane again (they are already at their expected position after the first rotation)? And, after the first splits, the left/right/top/bottom bounding boxes get out of order, because the rotation around the Z-Axis is not adequate anymore.
I start to think that Quaternion.AngleAxis(angle, rotation-axis) is not the right way to go, at least not to try more AngleAxis-rotations around the standard axises (Vector3.up, forward, right).
Or if it is, then the secret should be in the determination of the right rotation axis per plane, and really doing only that one rotation for all cases.

Hope that makes sense?

PS: Sharing the Unity project for shared work or code review if you dont hand it forward shouldnt be an issue. I need some time for this as I then need to cleanup some code and assets not needed from the project, as its really large currently (few Gigs). But anyway I’ll get in contact with you again for that.

EDIT [2015-06-20]:
Cross-Products rule! :smile: I think I got it. The following very simple code

leads to the long desired result:

I stored the angles (as well as the aligned and transformed BB) in the quadtree node for later transform of the camera for the AABB test. The transformed BBs are still rotated around the Y-axis, but I guess that doesnt matter for the AABB.
Time for some nasty AABBvsSphere testing now and use that for the split and merge condition. Will give an update if it works soon. If it works I’ll additionally consider the actual minimum and maximum Heights of the terrain patch for the BB, and then this should be an perfect approach, performance- and detail-wise… Afterwards it should be time for some code cleanup and documentation, and moving on to the next topic :slight_smile:

Great job! You are correct in using the cross-product. The cross product of two vectors will return a third vector that is perpendicular to BOTH of them. Thus, if you rotate either of those vectors around the perpendicular vector, eventually they will align in the same direction. So as a result, this operation gives you the appropriate rotation axis per patch, and then you find the angle between the pre- and post- transformation center vectors around said rotation axis. Well done. I have taken a totally different approach, but it’s nice to see the same results from different methods.

I noticed something through your pictures which I hadn’t considered before, and am not sure if or how it will cause any issues. Your planet sphere is BIGGER than your cube (i.e. the corners of the cube lay on the surface of the sphere), where as my planet sphere is SMALLER than the cube (i.e. the centers of each cube face lay on the surface of the sphere). I don’t think one approach is more correct than the other, but I’ve likely made several assumptions based upon my approach that may be incorrect for yours. We may want to keep this in mind!

Anyhow, yes I’d love to browse your code and would not share it with anyone, aside from discussing snippets of it with you via this forum (although I expect we’d communicate back and forth via email over certain details).

My method of rotating all BBs north is very simple in a positive way yes, although it lacks one thing, which is the orientation of the bounding boxes not being X/Z-axis aligned.They slightly rotate around Y-axis as you see on the last screen.
My first though was “dont care”, but I start to think this could be a problem in the AABB test, because if my understanding is right how the AABBvsSphere test works, if Min and Max is rotated around Y-axis, the AABB will asume a different volume for collision testing and so the results will not always be correct. Is that right? So there is maybe still some work to be done here, or maybe a different approach. E.g. trying to rotate them in direction of the X axis, and then to the Y axis.

Yes you are right I see on the screen too that the volume is a little different. Thats because right now the edge coordinates of the aligned bounding box simply take the original plane edge coordinates and apply rMin and rMax. They don’t “know” of the spherical volume. So at lower resolutions they tend to be within the sphere, and after a few splits things get better and better because the sphere range and effect gets smaller. Right now this does not seem a bigger issue, as the sphere starts to split, although it would be more precise to consider the volume and really enclose the sphere (added on the todo-list).

[quote=“JoergZdarsky, post:58, topic:765, full:true”]
My method of rotating all BBs north is very simple in a positive way yes, although it lacks one thing, which is the orientation of the bounding boxes not being X/Z-axis aligned.They slightly rotate around Y-axis as you see on the last screen.[/quote]

To fix this, could you apply one final rotation around the Y axis, where the amount was the angle between the center vector and the forward axis?

Stepping back for a second, you should be to “un-rotate” a center vector back to the Y-axis (with proper “roll”) by doing just two transformations: Rotate the vector around the Y axis (FIRST), by the angle between the center vector and the forward vector, then rotating the vector around the X axis by the angle between the center vector and the Y axis.

Note that when concatenating rotations, the order (at least with matrices) matters. Not sure if this applies to quaternions. But I recommend first applying the rotations separately, and trying different ordering of the operations until you achieve what you want, and only then concatenating them.

If this doesn’t work, perhaps you may want to try my approach - although I don’t know how easy it will be to do in Unity. I’ll start putting together the math (it’s been awhile and I didn’t write it down, so I’ll have to do a bit of reverse-engineering).

The following assumes that your patch starts off at the origin, with “up” being positive Y.

transform.Rotate(Vector3.up, azimuth);
transform.Rotate(Vector3.right, elevation);
transform.Translate(Vector3.up, radius);

Depending on how Unity handles concatenating transformations, you may need to perform these in the opposite order, i.e.:

transform.Translate(Vector3.up, radius);
transform.Rotate(Vector3.right, elevation);
transform.Rotate(Vector3.up, azimuth);

Let me know how it goes!

Yeah that makes sense. Checking this picture.

The issue might be to get azimuth. Could be something like
float azimuthAngle = Mathf.Atan2 (quadtreeTerrain.WSCenterVector.z, quadtreeTerrain.WSCenterVector.x)*Mathf.Rad2Deg;
(gives OK results for the side BB as it seems but not for top and bottom where I would have hoped for 0 angle but it shows -155 and 117).

Before I tried to continue I had an idea and continued to work on my original implementation. I added the following lines of code after I did the rotation to the north Y-axis:

This leads to the following result:

It is based on a simple idea: I know that I want to achive a 45 degree angle between a certain transformed BB edge vector (I took RMax1). So, get the angle between RMax (-the center of the RMax vectors) and Vector3.forward, and rotate it by this angle (+45 degrees). All in all very simple operations. I was first then confused that it does not look 100% aligned. But then I remembered that in a quadsphere the planes are not 90 degrees due to its nature (correct me if I am wrong).

This leads to a weak point of my implementation, as I took a certain vector (RMax), the assumption that I should have 45 degrees to vector3.foward is not always right (as the plane BB is stretched). On the other hand I start to question myself:
1st question:
Since I know the BB is stretched, how much effort should I put into finding a different way of rotation? Would another strategy really provide a better result? Especially as the above implementation has very few operations and as the core problem lies within the stretched plane. And: As further the plane splits, the more and more it gets correctly correctly aligned to X and Z, as the distortion gets less apparent.
If I stick with this approach, I maybe should only review if I always throw in the right two coordinates into the AABB (right now I use always RMax1 and RMin4) - as I am unfamiliar with AABB.

2nd question:
Should I apply the second rotation around the Y-Axis also to the camera? I think I might do so, but would it really matter?

Hope I am not totally off track.

PS: I yesterday started to clean up and especially document the code to provide it. Ongoing, but I am working on it.

I will have to address your post in detail this afternoon when I get home, but wanted to mention the following first. It seems like most of your problems are because the patches are originally defined in world space. As such, you must find the “reverse transform” to put them in “Y-up” patch-space.

My approach is to instead build ALL patches in their own “Y-up” patch-space (this is traditionally called “Model Space”), where the center point of the patch is at 0,0,0 and the patch’s normal vector is 0,1,0. I then simply transform the patch into world space when I want to draw it. This seems simpler, but probably because I’m more familiar with this approach than the one you’ve taken. As I said, I’ll review your post in detail this afternoon and try to help out.