Merge branch 'feature/documentation' into develop

This commit is contained in:
unknown 2012-09-12 11:55:59 +02:00
commit 9d6124d390
7 changed files with 54 additions and 20 deletions

View File

@ -1 +1,3 @@
Placeholder.
=================
Modifying Terrain
=================

View File

@ -4,13 +4,16 @@ Texture Mapping
The PolyVox library is only concerned with operations on volume data (such as extracting a mesh from from a volume) and deliberatly avoids the issue of rendering any resulting polygon meshes. This means PolyVox is not tied to any particular graphics API or rendering engine, and makes it much easier to integrate PolyVox with existing technology, because in general a PolyVox mesh can be treated the same as any other mesh. However, the texturing of a PolyVox mesh is usually handled a little differently, and so the purpose of this document is to provide some ideas about where to start with this process.
This document is aimed at readers in one of two positions:
1) You are trying to texture 'Minecraft-style' terrain with cubic blocks and a number of different materials.
2) You are trying to texture smooth terrain produced by the Marching Cubes (or similar) algoritm.
1. You are trying to texture 'Minecraft-style' terrain with cubic blocks and a number of different materials.
2. You are trying to texture smooth terrain produced by the Marching Cubes (or similar) algoritm.
These are certainly not the limit of PolyVox, and you can choose much more advanced texturing approaches if you wish. For example, in the past we have texture mapped a voxel Earth from a cube map and used an animated *procedural* texture (based on Perlin noise) for the magma at the center of the Earth. However, if you are aiming for such advanced techniques then we assume you understand the basics in this document and have enough knowledge to expand the ideas yourself. But do feel free to drop by and ask questions on our forum.
Traditionally meshes are textured by providing a pair of UV texture coordinates for each vertex, and these UV coordinates determine which parts of a texture maps to each vertex. The process of texturing PolyVox meshes is more complex for a couple of reasons:
1) PolyVox does not provide UV coordinates for each vertex.
2) Voxel terrain (particulaly Minecraft-style) often involves many more textures than the GPU can read at a time.
1. PolyVox does not provide UV coordinates for each vertex.
2. Voxel terrain (particulaly Minecraft-style) often involves many more textures than the GPU can read at a time.
By reading this document you should learn how to work around the above problems, though you will almost certainly need to follow provided links and do some further reading as we have only summarised the key ideas here.
@ -30,7 +33,8 @@ Triplanar Texturing
-------------------
The most common approach to texture mapping smooth voxel terrain is to use *triplanar texturing*. The basic idea is to project a texture along all three main axes and blend between the three texture samples according to the surface normal. As an example, suppose that we wish to write a fragment shader to apply a single texture to our terrain, and assume that we have access to both the world space position of the fragment and also its normalised surface normal. Also, note that your textures should be set to wrap because the world space position will quickly go outside the bounds of 0.0-1.0. The world space position will need to have been passed through from earlier in the pipeline while the normal can be computed using one of the approaches in the lighting (link) document. The shader code would then look something like this [footnote: code is untested as is simplified compared to real world code. hopefully it compiles, but if not it should still give you an idea of how it works]:
.. code-block:: c++
.. code-block:: glsl
// Take the three texture samples
vec4 sampleX = texture2d(inputTexture, worldSpacePos.yz); // Project along x axis
vec4 sampleY = texture2d(inputTexture, worldSpacePos.xz); // Project along y axis
@ -43,7 +47,8 @@ Note that this approach will lead to the texture repeating once every world unit
This idea of triplanar texturing can be applied to the cubic meshes as well, and in some ways it can be considered to be even simpler. With cubic meshes the normal always points exactly along one of the main axes, and so it is not necessary to sample the texture three times nor to blend the results. Instead you can use conditional branching in the fragment shader to determine which pair of values out of {x,y,z} should be used as the texture coordintes. Something like:
.. code-block:: c++
.. code-block:: glsl
vec4 sample = vec4(0, 0, 0, 0); // We'll fill this in below
// Assume the normal is normalised.
if(normal.x > 0.9) // x must be one while y and z are zero
@ -66,7 +71,8 @@ Both the CubicSurfaceExtractor and the MarchingCubesSurfacExtractor understand t
The following code snippet assumes that you have passed the material identifier to your shaders and that you can access it in the fragment shader. It then chooses which colour to draw the polygon based on this identifier:
.. code-block:: c++
.. code-block:: glsl
vec4 fragmentColour = vec4(1, 1, 1, 1); // Default value
if(materialId < 0.5) //Avoid '==' when working with floats.
{
@ -98,7 +104,7 @@ ADD FIGURE HERE
There are a couple approaches we can adopt to combat this problem. However, the solutions do have some performance and/or memory cost so we should first realise that this issue only applies to a small number of the tringles in the mesh. Typically most triangles will be using the same material for all three vertices, and so it is almost certainly worth splitting the mesh into two pieces. One peice will contain all the triangles which have the same material and can be rendered as normal. The other peice will contain all the triangles which have a mix of materials, and for these we use the more complex rendering techniques below. Note that splitting the mesh like this will also increase the batch count so it may not be desirable in all circumstances.
NOTE - THESE SOLUTIONS ARE WORK IN PROGRESS. CORRECTLY BLENDING MATERIAL IS AN AREA WHICH WE ARE STILL RESEARCHING, AND IN THE MEAN TIME YOU MIGHT ALSO BE INTERESTED IN OUR ARTICLE IN GAME ENGINE GEMS.
NOTE - THESE SOLUTIONS ARE WORK IN PROGRESS. CORRECTLY BLENDING MATERIAL IS AN AREA WHICH WE ARE STILL RESEARCHING, AND IN THE MEAN TIME YOU MIGHT ALSO BE INTERESTED IN OUR ARTICLE IN GAME ENGINE GEMS. See: http://books.google.com/books?id=WNfD2u8nIlIC&lpg=PR1&dq=game%20engine%20gems&pg=PA39#v=onepage&q&f=false
One approach is to attach an alpha value to each vertex so that corners of a triangle can optionally be faded out. If a triangle has the same material value at each vertex then we also give it full alpha at each vertex and the triangle draws normally, but if it has a differnt material for each vertex then we duplicate the triangle three times (once for each material). Each new triangle should then use the same material at each vertex, this material being one of those from the original triangle. The alpha values of the vertices of the new triangles are set such that when the three triangles are drawn on top of each other with additive alpha blending, the desired smoothly shaded triangle results.
@ -128,21 +134,19 @@ If your required number of textures do indeed exceed the available number of tex
A more practical approach would be to break the mesh into a smaller number of pieces such that each mesh uses several textures but less than the maximum number of texture units. For example, our mesh with one hundred materials could be split into ten meshes, the first of which contains those triangles using materials 0-9, the seconds contains those triangles using materials 10-19, and so forth. There is a trade off here between the number of batches and the number of textures units used per batch.
Furthermore, you could realise that although a your terrain may use hundreds of different textures, any given region is likely to use only a small fraction of those. We have yet to experiment with this, but it seems if you region uses only (for example) materials 12, 47, and 231, then you could conceptually map these materials to the first three textures slots. This means that for each region you draw the mapping between material IDs and texture untis would be different. This may require some complex logic in the application but could allow you to do much more with only a few texture units. We will investigate this furthr in the future.
Furthermore, you could realise that although your terrain may use hundreds of different textures, any given region is likely to use only a small fraction of those. We have yet to experiment with this, but it seems if you region uses only (for example) materials 12, 47, and 231, then you could conceptually map these materials to the first three textures slots. This means that for each region you draw the mapping between material IDs and texture untis would be different. This may require some complex logic in the application but could allow you to do much more with only a few texture units. We will investigate this furthr in the future.
Texture atlases
---------------
Probably the most widely used method is to pack a number of textures together into a single large texture, and to our knowledge this is the approach used by Minecraft. For example, if each of your textures are 256x256 texels, and if the maximum texture size supported by your target hardware is 4096x4096 texels, then you can pack 16 x 16 = 256 small textures into the larger one. If this isn't enough (or if your input textures are larger than 256x256) then you can also combine this approach with multiple texture units or with the mesh splitting described previously.
//Diagram of texture atlases
However, there are a number of problems with packing textures like this. Most obviously, it limits the size of your textures as they now have to be significantly smaller then the maximum texture size. Whether this is a problem will really depend on your application.
Next, it means you have to adjust your UV coordinates to correctly address a given texture inside the atlas. UV coordinates for a single texture would normally vary between 0.0 and 1.0 in both dimensions, but when packed into a texture atlas each texture uses only a small part of this range. You will need to apply offsets and scaling factors to your UV coordinates to address your texture correctly.
However, the biggest problem with texture atlases is that they causes problems with texture filtering and with mipmaps. The filtering problem occurs because graphics hardware usually samples the surrounding texels and performs linear interpolation to compute the colour of a given sample point, but when multiple textures are packed together these surrounding texels can actually come from a neighbouring packed texture rather than wrapping round to sample on the other side of the same packed texture. The mipmap problem occurs because for the highest mipmap levels (such as 1x1 or 2x2) multiple textures are being are being averaged together.
It is possible to combat these problems but the solution are non-trivial. You will want to limit the number of miplevels which you use, and probably provide custom shader code to handle the wrapping of texture coordinates, the sampling of MIP maps, and the calculation of interpolated values. You can also try adding a border around all your packed textures, perhaps by duplicating each texture and offsetting by half its size. Even so, it's not clear to us at this point whether the the various artefacts can be completely removed. Minecraft handles it by completely disabling texture filtering and using the resulting pixelated look as part of its asthetic.
It is possible to combat these problems but the solutions are non-trivial. You will want to limit the number of miplevels which you use, and probably provide custom shader code to handle the wrapping of texture coordinates, the sampling of MIP maps, and the calculation of interpolated values. You can also try adding a border around all your packed textures, perhaps by duplicating each texture and offsetting by half its size. Even so, it's not clear to us at this point whether the the various artefacts can be completely removed. Minecraft handles it by completely disabling texture filtering and using the resulting pixelated look as part of its asthetic.
3D texture slices
-----------------

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -1,10 +1,10 @@
Welcome to PolyVox's documentation!
===================================
Contents:
User Guide:
.. toctree::
:maxdepth: 2
:maxdepth: 1
Prerequisites
install
@ -14,7 +14,20 @@ Contents:
ModifyingTerrain
LevelOfDetail
Threading
Examples:
.. toctree::
:maxdepth: 1
tutorial1
Other Information:
.. toctree::
:maxdepth: 1
changelog
FAQ

View File

@ -553,7 +553,7 @@ WARN_FORMAT = "$file:$line: $text"
# and error messages should be written. If left blank the output is written
# to stderr.
WARN_LOGFILE = doxygen.log
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
@ -691,7 +691,7 @@ EXAMPLE_RECURSIVE = NO
# directories that contain image that are included in the documentation (see
# the \image command).
IMAGE_PATH =
IMAGE_PATH = ../../documentation/images
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program

View File

@ -32,6 +32,16 @@ freely, subject to the following restrictions:
namespace PolyVox
{
/// The CubicSurfaceExtractor creates a mesh in which each voxel appears to be rendered as a cube
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Games such as Minecraft and Voxatron have a unique graphical style in which each voxel in the world appears to be rendered as a single cube. Actually rendering a cube for each voxel would be very expensive, but in practice the only faces which need to be drawn are those which lie on the boundary between solid and empty voxels. The CubicSurfaceExtractor can be used to create such a mesh from PolyVox volume data. As an example, images from Minecraft and Voxatron are shown below:
///
/// \image html MinecraftAndVoxatron.jpg
///
/// Before we get into the specifics of the CubicSurfaceExtractor, it is useful to understand the principles which apply to *all* PolyVox surface extractors and which are described in the Surface Extraction document (ADD LINK). From here on, it is assumed that you are familier with PolyVox regions and how they are used to limit surface extraction to a particular part of the volume.
///
/// The images shown above shoulld hopefully make it clear how the resulting mesh should look, but the process is more easily explained in 2D. Hopefully you will find that the extension to 3D is intuitive.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType, typename IsQuadNeeded = DefaultIsQuadNeeded<typename VolumeType::VoxelType> >
class CubicSurfaceExtractor
{

View File

@ -42,6 +42,8 @@ freely, subject to the following restrictions:
namespace PolyVox
{
template <typename VoxelType> class ConstVolumeProxy;
/// The LargeVolume class provides a memory efficient method of storing voxel data while also allowing fast access and modification.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// A LargeVolume is essentially a 3D array in which each element (or <i>voxel</i>) is identified by a three dimensional (x,y,z) coordinate.
@ -49,6 +51,7 @@ namespace PolyVox
/// extractors) which form the heart of PolyVox. The LargeVolume class is templatised so that different types of data can be stored within each voxel.
///
/// <b> Basic usage</b>
///
/// The following code snippet shows how to construct a volume and demonstrates basic usage:
///
/// \code
@ -73,6 +76,7 @@ namespace PolyVox
/// that the width is 64 when the range of valid x coordinates goes from 0 to 63.
///
/// <b>Data Representaion</b>
///
/// If stored carelessly, volume data can take up a huge amount of memory. For example, a volume of dimensions 1024x1024x1024 with
/// 1 byte per voxel will require 1GB of memory if stored in an uncompressed form. Natuarally our LargeVolume class is much more efficient
/// than this and it is worth understanding (at least at a high level) the approach which is used.
@ -88,6 +92,7 @@ namespace PolyVox
/// recompressed and moved out of the cache.
///
/// <b>Achieving high compression rates</b>
///
/// The compression rates which can be achieved can vary significantly depending the nature of the data you are storing, but you can
/// encourage high compression rates by making your data as homogenous as possible. If you are simply storing a material with each
/// voxel then this will probably happen naturally. Games such as Minecraft which use this approach will typically involve large areas
@ -99,6 +104,7 @@ namespace PolyVox
/// your density values to reduce this problem (this threasholding should only be applied to voxels who don't contribute to the surface).
///
/// <b>Paging large volumes</b>
///
/// The compression scheme described previously will typically allow you to load several billion voxels into a few hundred megabytes of memory,
/// though as explained the exact compression rate is highly dependant on your data. If you have more data than this then PolyVox provides a
/// mechanism by which parts of the volume can be paged out of memory by calling user supplied callback functions. This mechanism allows a
@ -129,6 +135,7 @@ namespace PolyVox
/// anymore. But you still need to be ready to then provide something to PolyVox (even if it's just default data) in the event that it is requested.
///
/// <b>Cache-aware traversal</b>
///
/// You might be suprised at just how many cache misses can occur when you traverse the volume in a naive manner. Consider a 1024x1024x1024 volume
/// with blocks of size 32x32x32. And imagine you iterate over this volume with a simple three-level for loop which iterates over x, the y, then z.
/// If you start at position (0,0,0) then ny the time you reach position (1023,0,0) you have touched 1024 voxels along one edge of the volume and
@ -142,13 +149,11 @@ namespace PolyVox
/// is your cache sise is only one. Of course the logic is more complex, but writing code in such a cache-aware manner may be beneficial in some situations.
///
/// <b>Threading</b>
///
/// The LargeVolume class does not make any guarentees about thread safety. You should ensure that all accesses are performed from the same thread.
/// This is true even if you are only reading data from the volume, as concurrently reading from different threads can invalidate the contents
/// of the block cache (amoung other problems).
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename VoxelType> class ConstVolumeProxy;
template <typename VoxelType>
class LargeVolume : public BaseVolume<VoxelType>
{