pages/projects/voxelengine.html

131 lines
10 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/styles.css">
<link rel="icon" type="image/png" href="/favicon.png">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Developing a Voxel Engine</title>
</head>
<body>
<header><center>
<a href="/index.html">Home</a>
|
<a href="/projects/index.html">Projects</a>
|
<a href="/notes/index.html">Notes</a>
|
<a href="https://git.emamaker.com/EmaMaker">Gitea</a>
|
<a href="https://github.com/EmaMaker">Github</a>
|
<a href="https://instagram.com/EmaMaker">Instagram</a>
</center>
</header>
<article>
<h1 id="developing-a-voxel-engine">Developing a Voxel Engine</h1>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-1.png" /> </p>
<p>Almost two years ago I decided to start working on a Voxel Engine &#8211;
knowing almost nothing about voxel engines.
This is not a guide to voxel engines nor a tutorial about making one,
it&#8217;s just the report of my experience from scratch to a fully working
project.</p>
<p>I decided to go with
<a href="http://jmonkeyengine.org">jMonkeyEngine</a>, as Java was the only language I knew two years ago and I didn&#8217;t want to start from raw <a href="http://lwjgl.org">LWJGL</a>, and this engine was easy enough to use for me.
Nothing excludes that the code can&#8217;t be ported to other languages, and it&#8217;s probably something I&#8217;ll do to learn new languages quickly. Maybe in the future I can port this engine to other languages or rewrite it using OpenGL with <a href="http://lwjgl.org">LWJGL</a>.</p>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-2.png" /> </p>
<p><a href="https://github.com/EmaMaker">The my code is available on my github:</a>
 </p>
<h2 id="face-culling">Face Culling</h2>
<p>My first test worked using strange formula to have less cubes as position on the y axis increased and used full cubes with a yellow-purple texture to test the borders. This is the first big error when making a voxel engine.
Even if you see a world made out of cubes, in reality there are any. All the &#8220;cubes&#8221; are made of simple quads that appear or disappear based on what you need. For example, if you have two adjacent cubes with each cube touching a single face of the other, both cubes will have a face that won&#8217;t be seen by the user, so the simplest thing to do is just not rendering them and save workload on the GPU. This method is called Face Culling.
You can read more about rendering methods for voxel engines <a href="https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/">in this very interesting article by 0fps</a>.
I spent some months working on a better version of this face culling algorithm, fixing and tweaking the RAM usage due to some bad-practices I&#8217;ve used during the first test. This version was based on a <em>Cell</em> object, that kept note of the faces to show during rendering. <em>Cells</em> are grouped into <em>Chunks</em>, making the progressive world generation much easier.</p>
<p><em>Chunks</em> store a three-dimensional array of <em>Cells</em>, while the entire World is stored in a three-dimensional array of <em>Chunks</em>. I kept this structure for all the development.</p>
<p>But there were still some problems with this: the <em>Cell</em> generated six different objects that had do be rendered separately on screen: it used too much RAM and was still too much for the GPU. Despite this, it run at
100fps on my Dell XPS M1330, which now is a 12-years-old laptop.
But when it came to generate new parts of the world, the Java Garbage Collector started crying for all the mess it had to clean.</p>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-3.jpg" /> 
<img src="/resources/projects/voxel-engine-1/voxelengine1-4.jpg" /> </p>
<h2 id="greedy-meshing-to-overcome-perfomance-issues">Greedy Meshing to overcome perfomance issues</h2>
<p>Speaking with some folks on the <a href="https://hub.jmonkeyengine.org">jMonkeyEngine Forums</a>, I got the advice to abandon the six-objects method I had been using, in favour of GreedyMeshing. As you can read from <a href="https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/">in the 0fps article</a>, greedy meshing constructs a single mesh for the cubes faces with same texture that are next to each other.
I didn&#8217;t really understand the maths that 0fps did, as I&#8217;m still in high school and I haven&#8217;t studies most of the concepts he used yet, but I was successfull in writing my own. It&#8217;s quite dumb code, as it&#8217;s mostly made of if statements and while cycle with mostly no Maths in it, but it works well, and I&#8217;m happy with it.</p>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-5.jpg" /> </p>
<h4> This is a wirefram of a chunk mesh generated with greedy meshing </h4>
<h2 id="smooth-world-generation-using-simplex-noise">Smooth world generation using Simplex Noise</h2>
<p>As I mentioned before, the early tests used a strange formula to generate less cubes on each layer as the position on the y-axis increased. This was abandoned in less than a couple of weeks, and I tried to have a smooth terrain generating ellipses starting for a center point, with the radius decreasing as the position on the position on the y-axis increased.
But I started reading more and more about smooth pseudo-random number generation and I came across <a href="https://en.wikipedia.org/wiki/Perlin_noise">Perlin Noise</a>, which sounded really interesting.
I tried to implement Perlin Noise by myself, but after a couple of unsuccesfull tries, I found this <a href="https://github.com/SRombauts/SimplexNoise/blob/master/references/SimplexNoise.java">Simplex Noise java implentation by SRombauts</a>.
<a href="https://en.wikipedia.org/wiki/Simplex_noise">Simplex Noise</a> is computationally less expensive than Perlin Noise, which is can be really useful in this situation. I then adapted the Simplex Noise to my needs, added a couple of features for saving and loading the permutation table to and from file and the world generated was much more beatiful than before.</p>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-6.jpg" /> 
<img src="/resources/projects/voxel-engine-1/voxelengine1-7.jpg" /> </p>
<h4> The first working test with Simplex Noise (left) and the first test ever with Simplex Noise (right) </h4>
<p>At this point of the development, I decided to not use the <em>Cell</em> object anymore and replace them with simple integers inside the three-dimensional array. This saved up to 200MB of RAM when the player is still.</p>
<h2 id="saving-and-loading-from-file">Saving and Loading from file</h2>
<p>As I wanted to preserve the world when I closed the engine, I implemented a basic Chunk load&#47;save system to save RAM by not keeping loaded <em>Chunks</em> that are very far apart from the player.
This is also used to save the world when the game windows is closed and load it again when it&#8217;s reopened. The <em>Chunks</em> are saved in a plain text file, with each line corresponding to a single index of the 3d-array in the <em>Chunk,</em> with the x position, y position, z position and the id integer separated by spaces.</p>
<p>Along with the <em>Chunks,</em> also the <em>Simplex Noise Permutation Table</em> and the game settings are saved.</p>
<p>This system is absolutely bad, saving lots of <em>Chunks</em> requires even gigabytes of space on the disk, which is really insane.</p>
<h2 id="multithreading-block-picking-and-customization.">MultiThreading, Block Picking and Customization.</h2>
<p>Greedy Meshing and Loading <em>Chunks</em> from files require some time, which depends on the CPU and disk speed of the Computer. To remove this delay, which blocked the entire Main Thread of the game, I decided to load and generate <em>Chunks</em> in a separated thread, which doesn&#8217;t affect the main one.
This makes the engine really smooth to run and the <em>Chunks</em> generating in front of you is really nice.</p>
<p>Implementing <em>Block</em> <em>Picking</em> alongside with <em>MultiThreading</em> was an hard time. jMonkeyEngine allows you to modify the scene only in the main thread, but I used to generate new <em>Chunks</em> in a secondary one.
To solve this problem, I had to make the secondary thread only prepare the data for the <em>chunk</em> <em>mesh</em>, that the main thread uses to generate and show it.</p>
<p>Having the main features completed, it was time to implement some secondary ones. At first, during the making of the engine, I noticed I needed some way to make it more flexible to test different situations.
So, I implemented lots of functions to do this: toggling debug messages, wireframe, test functions, changing the method used to generate the world, changing the size of the <em>Chunks</em> and of the world, adding new <em>block types</em> with just a couple of functions. This makes the engine way more flexible, also for other people, to use.</p>
<p> </p>
<p><strong>Conclusion</strong></p>
<p>At this point, I can declare my Voxel Engine completely working, and I&#8217;m really happy with it. During those years of development (I have to specify that I abandoned the development even for months during this time), I learned lots of new things about games and programming in general which helped me becoming a better programmer.</p>
<p>You can see all the history of the development on the <a href="https://github.com/EmaMaker/voxel-engine-jme3.git">github
repo</a>.</p>
<p><img src="/resources/projects/voxel-engine-1/voxelengine1-1.png" /> </p>
<p>But my learning journey isn&#8217;t finished yet, I still have tons of new things to learn.</p>
</article>
<footer><p>
Author: EmaMaker
<a href="mailto:emamaker@tutanota.com">emamaker@tutanota.com</a>
<br>
This website is statically generated from markdown using <a href="https://git.alemauri.eu/alema/rivet">rivet</a>,
a static website generator written in POSIX compliant sh by my dear friend <a href="https://alemauri.eu">Alessandro Mauri. Check him out!</a>
<br>
This website is hosted on Codeberg Pages. <a href="https://codeberg.org/EmaMaker/pages>Feel free to check out the sources</a>
</p>
</footer>
</body>
</html>