| ▲ | jesse__ 4 hours ago | |
I'm nearing the end of a somewhat long and arduous journey of writing a GPU-accelerated voxel terrain generator and editor. The terrain and editor pipeline is a three stage process. The first stage is user-configurable; users write an arbitrary 'shaping' shader in GLSL. The second stage is a fused stage; it does a convolution on the output of the first stage to compute a normal, runs a second user-configurable 'decoration' function, and finally applies edits. Edits are collections of SDFs that are 'compiled' from a graph structure on the CPU into a bytecode, and interpreted in the shader. The last stage runs another convolution to compute the final normal, then packs an occupancy bit, color, and normal down into a 32-bit representation, suitable for transfer back to the CPU. I expect I will expand this to include additional user-level bits for encoding biome type and GPU-driven spawning. The entire shader pipeline is one fused 'ubershader' .. everything is bound at startup and the shader is dispatched three times, incrementing a 'Stage' uniform on each dispatch. This strategy conceptually makes the driver overhead nearly zero, and the profile agrees. The final step in the process is giving the CPU a chance to spawn additional entities and structures after generation. This is not completely fleshed out, but the bones are there. The idea is that the results of the GPU work are passed back to the CPU, which can run arbitrary callbacks to spawn additional edits and entities. Once spawning is complete, the result is meshed using a fast binary meshing algorithm and uploaded to the GPU. I keep 16 bits of data per voxel from the GPU work, 1 bit of occupancy, and 15 bits of normal for collision response, which works out to half a megabyte per chunk. At the moment I only keep chunks which contain a piece of the world surface, and discard the rest. Surface chunks are dense, and may be compressed in the future, but keeping this amount of data scales to world sizes greater than 1B voxels cubed, which seems good enough for now. In terms of raw performance, GPUs are ridiculous. My 3080ti mobile gets somewhere on the order of 500B-1T perlin samples per second with a naive perlin implementation. Compared to FastNoise2, that's about 1000x more, which sounds wrong since my GPU only has 35x more capacity, but, that's what my initial profiling said. That number is the marketing number; the real throughput is significantly less due to multi-stage nature of the system, but, there's a lot of headroom for improvement. For example, dropping the backing textures from 32bit to 16bit roughly quadruples performance. Since we only really care about the surface where values are near zero, I believe using 8 or 16 bit is a viable option, though this claim will require a fair amount of study to validate. All in all, I'm pleased with how this is all working out. My original goal was to write a highly flexible system giving users both easy-to-use tools and granular control over the final output. I think I've done a fairly good job at that, and the fact it's also super fast is the cherry on top. | ||