{"author_name":"maffydub","cat":"LD #29","comments":[],"epoch":1398712560,"likes":3,"metadata":{"p_key":"30438","p_author":"maffydub","p_authorkey":"36549","p_urlkey":"65912","p_title":"Voxel Map Algorithm","p_cat":"LD #29","p_event":"LD29","p_time":"1398712560","p_likes":"3","p_comments":"0","p_status":"UPD5","us_key":"36549","us_name":"maffydub","us_username":"maffydub","event_start":"1398384000","event_key":"22","event_name":"LD29"},"text":"<p>Last weekend (26-27 April 2014) I took part in <a href=\"http:\/\/www.ludumdare.com\/compo\/ludum-dare-29\/\" target=\"_blank\">Ludum Dare 29<\/a>. My game, <a href=\"http:\/\/www.ludumdare.com\/compo\/ludum-dare-29\/?action=preview&amp;uid=36549\" target=\"_blank\">Frog V Ducks<\/a> is quite fun (at least I think so) but not hugely original. It looks a bit like this:<\/p>\n <p><img class=\"alignnone\" src=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/compo2\/342546\/36549-shot1.png\" alt=\"\" width=\"600\" height=\"547\" \/><\/p>\n <p>While the game isn&#8217;t that innovative, it uses quite a cool voxel sprite technique, and this post describes how it works.<\/p>\n <p>This technique uses GLSL shaders &#8211; these are small programs that run on the GPU itself and can modify data as it streams through its rendering pipeline. In OpenGL ES and WebGL, there are two types.<\/p>\n <ul>\n <li>Vertex Shaders &#8211; these act on the vertices of your 3D mesh and implement its translation, rotation and projection<\/li>\n <li>Fragment Shaders &#8211; these act on each pixel of your screen and perform operations like texture mapping and per-pixel lighting<\/li>\n <\/ul>\n <p>The approach I used is entirely implemented in the fragment shader &#8211; the vertex shader just does\u00a0transformation of a plain cube. \u00a0Here&#8217;s a zoomed-in shot of a duck, compared with a similar image if I replace the fragment shader with code that just makes every pixel red.<\/p>\n <p><a href=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/duck1.png\"><img class=\"alignnone size-medium wp-image-355355\" src=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/duck1-300x264.png\" alt=\"duck\" width=\"300\" height=\"264\" srcset=\"http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/duck1-300x264.png 300w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/duck1-550x484.png 550w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/duck1.png 592w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><a href=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/cube2.png\"><img class=\"alignnone size-medium wp-image-355369\" src=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/cube2-300x264.png\" alt=\"cube\" width=\"300\" height=\"264\" srcset=\"http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/cube2-300x264.png 300w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/cube2-550x484.png 550w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/cube2.png 592w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n <p>As you can see, there is no topology for\u00a0any of the models in the mesh itself.<\/p>\n <p>Instead, the topology is encoded as voxels in a texture, which looks like this:<\/p>\n <p><a href=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/voxelmap.png\"><img class=\"alignnone size-large wp-image-355380\" src=\"http:\/\/www.ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/voxelmap-550x134.png\" alt=\"voxelmap\" width=\"550\" height=\"134\" srcset=\"http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/voxelmap-300x73.png 300w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/voxelmap-550x134.png 550w, http:\/\/ludumdare.com\/compo\/wp-content\/uploads\/2014\/04\/voxelmap.png 742w\" sizes=\"(max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n <p>The texture (a &#8220;voxel map&#8221;) consists of 8 layers of 8&#215;8 slices through the model. \u00a0Each slice has the X and Y axes oriented the same as the 3D model will be, and the layers are stuck together in the Z axis. \u00a0Colored pixels have the same color in the 3D model &#8211; transparent pixels are transparent and can be seen through. \u00a0(This conveniently means I can build my 3D models in <a href=\"http:\/\/www.gimp.org\/\" target=\"_blank\">Gimp<\/a> or any other paint package.)<\/p>\n <p>The algorithm used to render this is per-pixel raycasting. \u00a0For each pixel, the shader works out the path of the ray from the camera\u00a0to\u00a0the cube, and then steps along this ray\u00a0through the voxel map until it comes to an opaque voxel. \u00a0Once it hits an opaque voxel, it does a simple lighting calculation and then returns the color of the voxel as the color of the pixel. \u00a0If it doesn&#8217;t find any opaque voxels, the shader &#8220;discards&#8221; the pixel, causing it to be rendered transparently.<\/p>\n <p>Raycasting is a fairly standard algorithm, but there are two slightly unusual things here.<\/p>\n <ul>\n <li>Often raycasting\u00a0does two\u00a0&#8220;render-to-texture&#8221; renders to calculate the points at which the ray intersects with the front and back sides of the cube. \u00a0See <a href=\"http:\/\/graphicsrunner.blogspot.co.uk\/2009\/01\/volume-rendering-101.html\" target=\"_blank\">this article<\/a> for an example. \u00a0<a href=\"http:\/\/prideout.net\/blog\/?p=64\" target=\"_blank\">This article<\/a> gives an alternative, in which the fragment shader calculates the ray direction based on screen coordinate and knowledge of the position of the camera and then calculates the intersection with the front and back of the cube. \u00a0My approach is\u00a0a hybrid\u00a0&#8211; I use the same approach as the &#8220;render-to-texture&#8221; method to determine the intersection point with the front face and then use knowledge of the position of the camera to\u00a0calculate the ray direction.<\/li>\n <li>Often raycasting uses high-resolution datasets (e.g. MRI scans). \u00a0The algorithm then steps a regular step distance along the ray&#8217;s path. \u00a0In my implementation, the datasets are very low resolution and,\u00a0to avoid aliasing, it&#8217;s important that the shader examines every voxel along the path. \u00a0Rather than stepping a regular distance, it instead performs a series of cube intersection tests (each of which\u00a0is\u00a0actually surprisingly efficient). \u00a0Due to the limited resolution (8x8x8), there is also a tight bound on the number of voxels that a ray could hypothetically pass through.<\/li>\n <\/ul>\n <p>For the full code, see LD29.Sprite3DRenderer in\u00a0<a href=\"https:\/\/github.com\/matt-williams\/ld29\/blob\/master\/ld29.js\" target=\"_blank\">https:\/\/github.com\/matt-williams\/ld29\/blob\/master\/ld29.js<\/a>. \u00a0Note that a restriction in WebGL means that the depth of each pixel is not calculated &#8211; in full OpenGL shaders, it would be possible to fix this. \u00a0The only impact of this is aliasing when the cubes defining two objects intersect.<\/p>\n <p>I&#8217;d like to experiment more with this. \u00a0In particular, I&#8217;d like to see whether I could apply a texture map on top of the model, and also encode more data into the voxel map, e.g. to add half-full voxels defined by the plane that divided the full and empty halves (allowing the models to be less blocky).<\/p>","time":"April 28th, 2014 2:16 pm","title":"Voxel Map Algorithm"}