Procedural Terrain Rendering How-To

Oh no, this thread is being bumped again, already made me look at my code a few days ago. Anyway, don’t think I posted this video, it’s from the last PG spell.

2 Likes

:smile: run for the hills!

2 Likes

Passing the slot ID to the vertex shader is not the issue. I currently set a uniform int “_uniform int PatchGeneratedFinalDataBufferOffset;” from outside with the slot ID. Moving everything to the MaterialProperty would be my last step then as soon as I get the ComputeBuffer running.

My problem is to read from the right index of the buffer in the vertex shader.
While the change in the compute shader to write there seemed obvious…
int outBuffOffset = (id.x + id.y * constants.nVertsPerEdge) * constants.sharedPatchGeneratedFinalDataBufferOffset;

… I wonder how to change the following two lines in the vertex shader that read from the buffer, currently using

uniform int _PatchGeneratedFinalDataBufferOffset;

void vert(inout appdata_full_compute v, out Input o)
{
  UNITY_INITIALIZE_OUTPUT(Input, o);
    #ifdef SHADER_API_D3D11
       // Read Data from buffer
       float4 position = patchGeneratedFinalDataBuffer[v.id].position;
       float3 patchCenter = patchGeneratedFinalDataBuffer[v.id].patchCenter;
    #endif
}

v.id or SV_VertexID is AFAIK created under the hood, but somehow that must be changed to consider the slot ID value stored in _PatchGeneratedFinalDataBufferOffset too. Simply multiplying that value was too easy thinking, the asteroid looks pretty scrambled afterwards :wink:

Yes that my goal currently. The implementation of the slots is done, seems the vertex shader indexing is the only issue left, thus I dont want to put that aside now yet.

@innociv Nice one! Impressed that you were able to turn away from the project once you’ve gotten that far already :slight_smile:

I am sorry ;). The dynamic cloud movement is nice, definitely another topic we haven’t scratched here yet. Did you apply some certain rules to define the wind directions of each segment?

It’s very simple rules for the wind direction atm, I just sample a few points around the wind marker and add a direction offset based on the height at those sampled points. Suppose this could be extended to any data that is available at the sample point like temperature, pollution or precipitation.

About your problem, off the top of my head, just round the texture position to an int in your shader and use that for the computed buffer index, you might need an offset, depending on the orientation difference between texture and buffer though.

well not followed the discussion but just throwing an idea regarding double precision planet rendering, i have also restarted my work on this tech after getting bored with it in jan this year. I made my planet to the point where I struggled with double precision and collision issues as I was throwing a lot of work to gpu and kept cpu mostly free. But I have always struggled with this approach with planets as transfering data from gpu to cpu is a bottleneck and is a requirement for a lot of tasks, so my solution that I have restarted working on and I am sure some one may have discussed it in this thread above is to load everything related to terrain geometry generation on to cpu but divide the task on a separate thread, so one thread does the task of rendering whereas the other thread calculates/streams the data for the new / reused nodes (memory pooling/caching). This way you can scale the planets with double precision noise generation of terrain and/or also add custom height maps with circular blending on the planet surface on cpu and only generate the normal map on gpu. The 2nd thread also removes the hiccups in frame rate. It will also help with collision detection. You can also replicate this with planets oceans if you want to simulate floating physics. I have successfully made multi threaded approach work with 22 levels of detail without any frame rate hiccups on cpu, but my algo is still work in progress. so just throwing an idea for some one struggling with double precision planets.

Well at that level, it’s very basic.

I didn’t have normals calculated. I didn’t have different texturing areas. I didn’t have anything that looked like there are tectonic effects or erosion. I didn’t have LOD.

The learning I was doing was more about how to build a mesh procedurally, how to use noise, etc. There’s so much more if you want real working (gameplay wise) and beautiful planetary terrain.
I didn’t want to stretch spending 2 weeks on something into 2 years. :stuck_out_tongue:

Hmmm, I wasn’t sure how simple rounding-only would do it as I guess the texture coordinates given by texcoord would overall be between 0 and 1 so rounding would keep the resulting index between 0 or 1 everytime. But, simply using the texture coordinates was the right idea that helped me to get closer to get this problem done. Thank you so much @cybercritic!!

So for everyone else who faces a similar problem these lines of code in the compute shader

// Calculate vertex positions
void CSMain3 (uint2 id : SV_DispatchThreadID)
{
    // Get the constants
    GenerationConstantsStruct constants = generationConstantsBuffer[0];

    // Get outBuffOffset
    int outBuffOffset = id.x + id.y * constants.nVertsPerEdge;

    // Write to Temporary Buffer
    patchGeneratedFinalDataBuffer[outBuffOffset].position = float4(patchCoordCentered,1);
    patchGeneratedFinalDataBuffer[outBuffOffset].patchCenter = patchCenter;
}

result in the following lines of code to re-access the buffer at the right index using the texture coordinates.

void vert(inout appdata_full_compute v, out Input o)
{
    UNITY_INITIALIZE_OUTPUT(Input, o);
        #ifdef SHADER_API_D3D11
            // Read Data from buffer (old version 
            int idx = v.texcoord.x * (nVertsPerEdge - 1);
            int idy = v.texcoord.y * (nVertsPerEdge - 1);
            int bufferIndex = idy + idx * nVertsPerEdge;
            float4 position = patchGeneratedFinalDataBuffer[bufferIndex].position;
            float3 patchCenter = patchGeneratedFinalDataBuffer[bufferIndex].patchCenter;
        #endif
}

and renders right.

EDIT: And finally applied the slot index, everything is working now. Two RenderTextures and two ComputeBuffers are instantiated and shared across the planes (roughly 26). Nice.
At the weekend I’ll be moving everything to one MaterialBlockProperty and one mesh, hopefully Batching will then start to take effect fingers-crossed.

3 Likes

You multiply the 0…1 value with the size of your array, as you have done, sorry I didn’t make that clear, glad you figured it out and everything is working. :slight_smile:

1 Like

If I followed correctly, you’re trying to procedurally generate asteroids but also make use of the performance gains of instancing the geometry?

In that case, aren’t you just going to have a few asteroids duplicated many times to take advantage of the instancing, which kind of gets rid of the benefit (uniqueness) of procedural generation?

You could instance and make them unique with a vertex shader, or simply the pixel/fragment shader on the normals, or tessellation shader even, but then that’s happening every frame.

Or is it just that you want to procedurally generate like 20, which you then instance thousands of times, for different types of rocks around different planets, so each planet sort of has its unique rocks even if they aren’t very unique in the ring itself?

Not exactly. With the above approach of using a tiled RenderTexture and a tiled ComputeBuffer each asteroid (or planet or whatever) is different. Basically it follows the process that @NavyFish suggested and we both stick to for quite a while. But I am trying to optimize it currently so that instancing of RenderTextures and ComputeBuffers can be avoided so that draw calls are batched across multiple of the asteroid’s planes or even across multiple asteroids.
In this process the vertice positions and normalmaps etc. are computed in a ComputeShader, passed over within a RenderTexture and ComputeBufer to the Vertex-/SurfaceShader, and within the Vertex Shader the positions of a flat single mesh are replaced with the ones in the ComputeBuffer.
So each plane or object is different and depends on the results within the ComputeShader, which are one time computed on the GPU and then kept in it’s memory.

You are right that in one of the previous posts I was also suggesting to make use of the GPU Instancing for asteroids, and do something similar I think (not sure) Limit Theory does. So, compute a set of ~100 asteroids one time procedurally and place them many times during runtime. For some kind of optimization these groups of asteroids could be recomputed sometimes during runtime depending on the position of the player with different seeds, so that repetition can be somewhat minimized. But that would be a second another approach to the one of the tiled RenderTextures/ComputeBuffers mentioned before.

I am thinking to utilize both approaches finally. So to basically use the tild RenderTexture/ComputeBuffer approach for larger asteroids and planets, and the GPU Instancing for smaller asteroids widely spreaded. The need of using the GPU Instancing approach depends a bit if and how good batching will work for the first approach.

So you are using the vertex shader to change them, not changing the position of the vertices on the cpu before your draw calls?

I feel that’s not going to scale well. Older harder might choke a bit on calculating the new vertices every frame. AFAIK, there’s no way to have an instance and yet have a vertex shader just run one frame on it to adjust it and to keep it stored like that change while still being instanced, but I could be wrong.

That’s actually probably a really good idea to lower the chance of someone seeing many are the same.
The problem, ultimately, is that once someone sees two that look exactly the same, it breaks the illusion and all the effort you made.

The best way would probably be to procedurally generate just a few far away ones, then at closer to draw time to more uniquely procedurally change their higher detail, closer up meshes and textures with more changes on the tesselation/vertex/normals/albedo.

The vast majority of them, thousands, are going to be far enough away that you can’t see if they’re exactly the same. As you get closer, and for larger ones, that’s just a few dozen probably that you can actually make unique. So basically like what you ended saying with.

To be absolutely precise, yes, the vertex shader is used to change them. The vertex shader is not used to compute the necessary position of each vertice (the necessary position is stored across many frames in the computebuffer on GPU), but yes I have to do a few operations to replace each vertice each frame:

// Get data from buffer
float4 position = patchGeneratedFinalDataBuffer[bufferIndex].position;
float3 patchCenter = patchGeneratedFinalDataBuffer[bufferIndex].patchCenter;

// Translate the patch to its 'planet-space' center:
position.xyz += patchCenter;

// Apply data
v.vertex = float4(position);

As these per-vertex operations are quite simple (basically its a buffer read, one add operation and one vertex-parameter change) I hope the loss of performance is lower than the benefit of doing all the noise and normal calculations on GPU. But I dont know exactly, as the missing batching had a huger performance impact when drawing lots of objects, so a reasonable profiling was not possible yet.

Only way to avoid these vertex operations IMHO would be to read back the computebuffer from GPU to to CPU once the computeshader has done his work, to create a mesh with the final position on the CPU. Or, so to say, to utilize the computeshader for a highly parallel computation on GPU between the creation of a new mesh to gather position data and normalmaps, but do all the final mesh creation stuff etc. on CPU.

As this read-back from GPU to CPU results in stalls I haven’t yet tried to go this route. That this stall happens is a pain, things like bounding and collider checks (or building a mesh) would be way simpler if you had some of the data back on CPU. Only some minimal information would be necessary (low resolution vertex positions from the buffer would be ideal, all the high res normalmap stuff could remain on GPU), but I guess the volume of data doesn’t matter with regards to the stall. I think if @NavyFish would get that async plugin done he’d be a rich man :slight_smile:

Yeah but your idea is better to utilize GPU Instancing preferable for farer away objects despite of the size and at closer distance start to detail them differently (eventually depending on the size).
I wanted to implement the GPU Instancing with precalculated objects once I’ve profiled the current technique to create asteroids described beforehand. I am then going to check how I can utilize the GPU Instancing like you suggested.

You should be able to call back the resulting heightmap created on the GPU to the CPU asynchronously without any stalls.

IE, you have the GPU compute the higher detail heightmap and blend the vertices over a few frames to smoothly create the higher detail as you get closer, and while it’s blending the CPU is already taking what the GPU first created and is using it to create the higher detail mesh. Then it replaces the mesh that’s getting vertex shaded with one that’s pre-computed on the CPU seamlessly since it’s the same mesh as the vertex shaded result.

But I am not sure myself whether the cost of the vertex shading is higher than a bit less instancing.
My gut feeling is that less instancing on the few dozen closer objects would be much, much better performing than vertex shading on many high detail objects for every vertex.
But vertex shading is generally really fast. Much faster than pixel shading. You’re basically talking about the moving water that’s done in many games, which is a subdivided plane with a vertex shader moving the points around. So, ultimately, it probably doesn’t matter which way you go unless you’re trying to inch out the best performance. But yeah, obviously thousands of non-instanced geometries is slow, we know that much.

I’m interesting in seeing the comparisons in performance, myself.

Hey folks, haven’t had much time to respond recently but just wanted to toss in my 2c regarding the vertex shader operations Joerg and I are using.

These ops are exactly what the GPU and specifically vertex shader are designed to do. In a ‘normal’ mesh rendering scenario (ie drawing a ship), the vertex shader performs a buffer lookup of each vertex’s model-space position (and potentially more lookups, i.e. normal vectors for vertex lighting calls etc). It then transforms these positions by a model-view-projection matrix to achieve appropriate screen-space (well, technically the normalized device coordinate space) locations.

In other words, we’re doing nothing in the vertex shader that does not already occur in ever other 3D rendering engine. The additional GPU work our approach requires is the one-time patch generation - ie running the procedural terrain function and applying various baked-in patch-to-planet space transformations (at least in my implementation). If not done on the GPU, the exact steps would need to occur on the CPU, which would end up being slower on anything newer than a ~ gefore 200-series . So aside from the asynchronous gpu-cpu data transfer issue (only an issue in unity, because it can be achieved easily by a custom-built opengl 3+ engine), this approach hands-down will yield more terrain throughput than standard CPU generation when all is said and done.

Taking the uv, doing a texture2D() look up, adding the appropriate length from that look up, normalized, to the current point is fast, for sure. But it’s not simply “gl_Position = vec4( position, 1.0 );”, and I’m wondering at which point the lack of instancing makes it better to rearrange the vertices on the CPU instead of doing it every frame on the GPU for those close asteroids.

Like I said, the perf of what you’re doing is similar to doing moving water that’s moving vertices around every frame to make the ocean 3d and moving. Re-updating to the same position each frame is really no different than if you were having them morph and move. Unless I’m mistaken and there’s some sort of caching that happens there, there probably is.

You are absolutely correct in that the performance will be similar to morphing waves, or perhaps more commonly a skinned character mesh with bone based animations.

But keep in mind that rearranging the vertices on the CPU would still require them to be submitted to the graphics card for rendering. Likely, the graphics driver would retain those uploaded positions in vram, but my intuition tells me that generating the positions on the GPU and leaving them there will always be faster than generating them on the GPU, transferring them to the CPU, then re uploading them to the gpu. But if I’ve misinterpreted your assertion please correct me.

What I was saying is that what I think would be optimal would be to compute the heightmaps on the gpu, and ease in the morphing of the heightmap so you don’t have popping on the vertex shader.
But, once they’re closer, and you could, I imagine, have a few that are hundreds of thousands of vertices and there isn’t greater LOD to morph in, it may be better to retrieve that heightmap from the GPU and update the mesh on the CPU.

At least, I believe that would give the best performance. But if you don’t end up with a few close up at their highest LOD which is a few hundred thousand vertices, it might not matter. I’m curious how they compare, though. :x

I don’t see what you mean.
“Updating the positions on the GPU” requires that update to be done every frame on the vertex shader, as far as I’m aware. The only way to do it once and have those vertices positions stored in vram, as far as I’m aware, is to have the positions set where you want them on the CPU.

Ah, I understand your wording now. You’re right, It would potentially speed things up, because technically you’re saving one buffer read and a couple of arithmetic ops per vertex. Personally, I doubt that those are the bottenecking factors, but regardless: this conversation is a moot point. Here’s why:

The “mesh morphing” you’re trying to optimize is a kludge/shim that we had to put in place in order to get Unity to work with our ComputeShaders. Period.

A lower-level GL implementation of this technique absolutely doesn’t need an intermediary “mesh” (which in Unity is a CPU-based data structure w/ associated GPU data buffer, nothing else) to function. Instead, as I do in my own OpenGL implementation, each patch is generated “wholly” on the GPU, and then simply rendered. No morphing required.

To do what you’re suggesting in Unity, you would need to create a new mesh. Unity provides no way to define a mesh using a vertex buffer already on the GPU, so you either define a new flat mesh and then “morph” it every frame - as we do - OR, you ‘download’ all of the vertex data from the GPU, serially copy them into a CPU-based data structure that defines the mesh, then submit it for drawing, which causes that data in host ram to be uploaded back to the GPU. You are having to take the long-way around through the CPU, which as a whole, will be much slower.

Anyway, thanks for the suggestion. I don’t believe it’s viable in Unity, but you’re 100% correct that that’s the way it should be done in an optimized, low-level GPU implementation.

Short update from my side: Now that I have the tiled large ComputeBuffer and RenderTexture, trying to see how that performs, I struggle at the last step to render the final fully asteroid. Using a single mesh, a single material, and a MeshRenderer applied, I struggle at submitting the different offset values (different per plane) to the shader, as I only have a single material instance and the offset gets overwritten inbetween before rendering. Only one plane is rendered (probably the given be the last offset value). At least I think this is what happens. It seems to be pretty quick and batching is invoked, but I havent yet found out how (or if) the problem of the offset getting overwritten can be solved here.

Using Graphics.DrawProcedural() instead works. When setting the offset of ComputeBuffer and RenderTexture in the material new after every seperate DrawProcedural() call, the full asteroid is rendered. However strangely there is some batching ongoing, but, no matter how many tiles for ComputeBuffer and RenderTexture I set, the batching amount remains the same. Even if ComputeBuffer and RenderTexture only have a single slot (and so act like a single instance). Thats really strange, and I haven’t found out what happens. And DrawProcedural tends to get slow very quickly when rendering a bit more on screen.

So… sorry I haven’t yet come with a result, I still need to check whats going on in the background, doing a bit more deeper debugging.

@NavyFish
I have a question concerning your AABB approach outlined in post #32
[Procedural Terrain Rendering How-To]

You mention:

Next, I transform the camera (and hence the visibility sphere) into the model’s coordinate system.

Could you share what transformation matrix you use to transform the camera into patch model space?

I also noticed that this was quite an early approach seemingly prior to the compute shader generation of the patches and i wonder if there is an AABB approach that is more tailored for the compute shader way.
In particular the inability to get back the height info from the shader to the CPU (without performance hits) seems to make that difficult.