Thank you!
Teknologicus
Creator of
Recent community posts
P.S. I've now switched to 15-bit RGB voxel color because I noticed some time ago in procedural block generation where a noise function it uses to modulate brightness of colors was slightly shifting voxel colors. The noise function modulates the RGB channels equally but because green had more bits (6), red (5) and blue (5) didn't have an even distribution with respect to the green channel.
These procedurally generated blocks support Hilbert Curve grooves which are scaled. In the above screen shot I'm using a scale of three, which means there are two voxels between each voxel groove. To calculate the the ideal "order" for the Hilbert Curve function based on the bounds of the block (or world selection bounds) I use the following math/code:
int order = log2i(clampMin(pow2Ceil(divCeil(max(bounds.width(), bounds.height(), bounds.depth()), scale)), 2))
FYI, all of these nested function calls are of functions in my C++ (template) math library.
And the inverse Hilbert Curve function I wrote is based on code from here:
https://people.math.sc.edu/burkardt/cpp_src/hilbert_curve/hilbert_curve.html
Thank you! I'm doing my best to create my "dream" voxel engine (within the confines of my abilities as a software engineer).
I'm considering at some point when my voxel engine is fully functional and multi-platform (Windows, Linux and maybe other OSs), I may release the voxel engine as a SDK in closed source form (object code, C++ headers, SPIR-V shader code) under a licence where free games could use it for free and commercial games would pay royalty per copy of games sold.
Link to PDF explaining how to do Three-Dimensional Rotations By Three Shears
P.S. Technically, fragment shaders cannot read the z-buffer due to limitations imposed by GLSL, but fragment shaders can write to the z-buffer. Given that detail, the raymarched voxel rendering pass should be done as the first rendering pass so that triangles, lines, point, etc. z-buffer clip against voxels rendered.
At some point I'm going to have to implement multiple raymarched voxel rendering passes for rendering moving voxel objects (falling trees, rotating things), so I will have to introduce a programmer defined z-buffer into my code to allow for clipping of multiple voxel rendering passes against each other.
Hopefully you won't run into the problem I did with getting the raymarched voxel rendering to line up with raster rendering (triangles, lines, points). The raymarched voxel rendering also has to write to and/or read from the z-buffer. I eventually figured it all out.
In short: The raymarched voxel rendering shader has to receive both the same view projection matrix and inverse view projection matrix via a uniform buffer object as raster rendering does. The inverse view projection matrix is used in calculating raymarched voxel rendering ray origin and direction vectors and the view projection is used in calculating z-buffer writes and/or reads based on ray to voxel hit.
If you run into any problems in this area, let me know because I may be able to help you resolve this issue.
Tiny Glade is cool looking and definitely shows what Bevy is capable of!
Interesting, I've heard of Bevy but never looked at it before.
There's nothing wrong with building on top of an existing game engine. My first voxel game was written on top of Urho3D. I never finished it because I decided to learn Vulkan and write my own game engine on top of it.
I don't know Rust, but I've read good things about it.
I look forward to seeing how your game progresses!
Some of the responses on this Reddit post explain the differences and nuances better than I could.
I'm still figuring out the details on how to go about implementing it. Pyramid-tracing is probably a more accurate geometric term. For each location in a scene to be lit, six pyramids -- one for each 3D axis aligned cube face direction -- bounds will be traced into the scene accumulating light values based on light sources intersected by said pyramids taking into account light source distance from start of each pyramid. There's also occlusion at play in the traced pyramid paths.
Exactly!
Stating the obvious: The default graphics settings in game should be set so the LOD isn't noticeable, but one can set LOD to be very aggressive for better performance at the cost of visual quality.
To calculate the LOD level during raymarching, I use the equation logN(t/d) where N is the logarithm of base N (2, 4, 8, up to 128), t is the ray's distances from the camera and d is a divisor (1, 2, 4, up to 32768). d controls how close to the camera the first LOD level is active and N controls the (logarithmic) world distance between LOD levels.
This video shows how the graphics looks (both with and w/o color coding) while adjusting the LOD equation's d parameter (via hot keys). See yellow text status line labeled "lod:". (This video was recorded before I made LODs conform to voxel volume boundaries.)
This was all "over-engineered", but I had fun implementing it for the most part. The "fragment shading rates" graphics settings yields the best performance gains at the least cost of visual quality in my opinion, so the LOD feature is cool to have, but to a lesser extent for performance and more so for visual quality when set non-aggressively -- it reduces shimmering of distant voxel volumes because of the use of 3D mipmaps.
Wikipedia on Mipmap: https://en.wikipedia.org/wiki/Mipmap
I'm using 3D mipmaps of voxel volumes to improve performance and visual quality of volumes that are farther away as part of the level of detail system in my voxel engine.
This video shows them (color coded for testing) in action:
You're welcome. Keep up the hard work! I say "hard work" because it has been a struggle to get my voxel engine to where it is at now. I'm still struggling with the compute shader stuff in between working on others parts of the code.
I got mipmaps generating via a compute shader but mipmap regeneration for voxel volume updates (adding or removing voxels) still has a bug.
If you haven't done so already, check out Douglas Dwyer's videos about his voxel engine which is farther along than my voxel engine: https://www.youtube.com/@DouglasDwyer/videos
I tested it with larger block of voxels sizes and it is definitely slower. Once I get the more computationally expensive part of block placement (procedural generation, voxel adjacency bits and mipmap updates) moved to compute shaders (computationally parallel), it will be fast for any reasonable block size or number of blocks.
For placing a block made of 32x32x32 voxels, I'm seeing an update time of about 1/10 of a second. For removing a block made of 32x32x32 voxels, I'm seeing an update time of about 1/100 of a second.
Placing a procedurally generated block takes longer than removing a block because procedural generation is more computationally expensive depending on the block type.
I still haven't gotten the voxel volume mipmap and voxel adjacency bits updating working in compute shaders, so these numbers are measured with code compiled to do said updates on the CPU and push the updated voxel data to the GPU. (I would like to move procedural block generation to a compute shader too.)
For simple game play, these numbers are acceptable, but for procedural terrain and/or procedural structure (villages, castles, etc.) generation, world editing or anything that would update a lot of voxels at once, these numbers are way too slow in my opinion.
I'll have to give it some thought. If I do let you read over (some of) the code, I'm leaning towards very specific parts of the code which would be advantageous for developing your voxel graphics engine because it's a "monster" of a codebase to try and take-in in its entirety!
It's the most complicated piece of software I've ever written (thousands of moving parts so to speak and I'm not even close to done on all the features it will eventually support) and I've been programming in C++ since the mid 1990's.
I also program the C++ code using many of the modern C++20 syntax and constructs, so the syntax (mostly the templated code) is mind numbingly abstract if one is not familiar with all the wild syntax features that have been added to the language over the years. I've also taken some novel approaches to Vulkan buffer updates for performance reasons which is not so easy to explain in my opinion.
It's a crazy amount of code! Counting voxel engine, support and framework code (mainly C++, which is not all used because it's general purpose code), the project stands at over 29000 lines of code total (not counting comments and blank lines). Shader code is still under 500 over 1100 lines of code. Thin Vulkan helper/abstraction C++ code is nearly at 6000 lines. Voxel engine specific C++ code is over 7000 lines.
Probably too much information...