Overview
This web page documents the format of maps for the group project. These maps are not restricted to being square; instead they are composed of square cells arranged in a rectangular grid.
A map covers a \(w 2^m \times h 2^n\) unit rectangular area, which is represented by a \((w 2^m+1) \times (h 2^n+1)\) array of height samples. This array is divided into a grid of square cells, each of which covers \((2^k+1) \times (2^k+1)\) height samples.
For example, the image below shows a map that is \(3 \cdot 2^{11} \times 2^{12}\) units in area and is divided into \(3\times2\) square cells, each of which is \(2^{11}\) units wide.
The cells of the grid are indexed by \((\mathit{row},\mathit{column})\) pairs, with the north-west cell having index \((0,0)\).
A map is represented in the file system as a directory.
In the directory is a JSON file map.json
that documents
various properties of the map.
The fields of this JSON object are follows (note that some
of these are optional):
-
name
is the name of the map. -
h-scale
is the horizontal scaling factor in meters. -
v-scale
is the vertical scaling factor in meters. -
base-elev
is the base elevation (i.e., what an elevation value of zero maps to). -
min-elev
is the elevation of the lowest point on the map in meters. -
max-elev
is the elevation of the highest point on the map in meters. -
min-sky
is the lower bound of the sky box in meters. -
max-sky
is the upper bound of the sky box in meters. -
width
is the unscaled width of the map (i.e., the number of grid. squares in the east-west axis). This value must be a multiple of the cell size. -
height
is the unscaled height of the map (i.e., the number of grid squares in the north-south axis). This value must be a multiple of the cell size. -
cell-size
is the width (and height) of map cell. -
color-map
is true when the map has a color-map texture. -
normal-map
is true when the map has a normal-map texture. -
water-map
is true when the map has a water-map. -
sun-dir
an 3D vector that points toward the sun (this field is optional; default \(\langle 0,1,0 \rangle\)). -
sun-intensity
the color and intensity of the sun light (this field is optional; default \(\langle 0.9,0.9,0.9 \rangle\)). -
ambient
the color and intensity of the ambient light (this field is optional; default \(\langle 0.1,0.1,0.1 \rangle\)). -
fog-color
the color of fog (this field is optional). -
fog-density
the density of fog (this field is optional). -
grid
an array of the names of the cell subdirectories in row-major order. -
assets-dir
specifies the name of the subdirectory that contains any graphical assets referenced by the map cells (this field is optional).
For example, here is the map.json
file from a map for the Grand Canyon:
{
"name" : "Grand Canyon",
"h-scale" : 60,
"v-scale" : 10.004,
"base-elev" : 284,
"min-elev" : 414.052,
"max-elev" : 2154.75,
"min-sky" : 410,
"max-sky" : 10000,
"width" : 4096,
"height" : 2048,
"cell-size" : 2048,
"color-map" : true,
"normal-map" : true,
"water-map" : true,
"sun-dir" : [ -0.5, 1, -0.2 ],
"sun-intensity" : [ 0.8, 0.8, 0.8 ],
"ambient" : [ 0.2, 0.2, 0.2 ],
"fog-color" : [ 0.725, 0.722, 0.612 ],
"fog-density" : 0.00005,
"grid" : [ "00_00", "00_01" ]
}
Note that the width and height are one less than the number of vertices; i.e., in this example, the number of vertices is \(4097\times 2049\).
Each cell has its own subdirectory, which contains the data files for that cell. These include:
-
hf.png
— the cell’s raw heightfield data represented as a 16-bit grayscale PNG file. -
hf.cell
— the cell’s heightfield organized as a level-of-detail quadtree of chunks. -
color.tqt
— a texture quadtree for the cell’s color-map texture. -
norm.tqt
— a texture quadtree for the cell’s normal-map texture. -
water.png
— a 8-bit black and white PNG file that marks which of the cell’s vertices are water (black) and which are land (white). This image has the same dimensions as thehf.png
image. -
objects.json
— a list of object instances represented as a JSON array. The format of this JSON file is described below. Theobj
,mtl
, and texture files for the objects will be in the map’s optionalobjects
directory (to allow sharing across cells).
Of these files, the first two are guaranteed to be present and the others
are optional (the map.json
file specifies if they are present).
In addition to the cell subdirectories, if the map has objects on it, there will be an
additional subdirectory to hold the obj
, mtl
, and
texture files for the objects. The name of the directory is defined by the
assets-dir
field of the map’s JSON object. Objects may have instances
on multiple cells, which is why they are stored outside the cell subdirectories.
The sample code includes support for reading the map.json
file, as well as the
other data formats (.tqt
and .cell
files).
Because the map datasets can be quite large (some are in the 100’s of megabytes), we have only included some small examples in the GitHub repositories. The larger examples are available from the project webpage and will also be made available on the CSIL Macs.
Map Cell Files
The .cell
files provide the key geometric information about the terrain being
rendered.
Each cell file consists of a complete quadtree of tiles (see cell.cpp
for details on how a cell file is organized).
Each level of the quad tree defines a different level of detail and consists of \(2^{\textit{lod}} \times 2^{\textit{lod}}\) tiles arranged in a square grid. The tiles are indexed by their level, row, and column. A tile covers a square region of the cell and contains various bits of information, including a chunk, which is the triangle mesh for that part of the cell’s terrain at the tile’s level of detail.
Chunks are represented as a vertex array and an array of indices.
The triangle meshes have been organized so that they can be rendered as triangle strips
(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
).
Vertices in the mesh are represented as
struct Vertex {
int16_t _x; // x coordinate relative to Cell's NW
// corner (in hScale units)
int16_t _y; // y coordinate relative to Cell's base
// elevation (in vScale units)
int16_t _z; // z coordinate relative to Cell's NW
// corner (in hScale units)
int16_t _morphDelta; // y morph target relative to _y (in
// vScale units)
};
Note that the \(x\) and \(z\) coordinates of a vertex are relative the
the cell’s
coordinate system, not the world coordinate system.
For large terrains, using single-precision floating point values for world coordinates
can cause loss of precision when the viewer is far from the world origin.
One can avoid these problems by splitting out the translation from model (i.e., cell)
coordinates to camera-relative coordinates from the rest of the model-view-projection transform.
The transformation of the Vertex
structure to camera-relative coordinates in
the vertex shader is straightforward.
If we assume that \(\langle{} x, y, z, m \rangle{}\) is the 4-element
Vertex
,[1] we compute
where \(\mathbf{s} = \langle{}s_x, s_y, s_z, s_m\rangle{}\) is a scaling vector and \(\mathbf{o}_{\textit{cell}}\) is the camera-relative origin of the cell (\(\mathbf{s}\) and \(\mathbf{o}_{\textit{cell}}\) are both uniform variables in the shader). The fourth component of the vertex is used to compute the vertex’s morph target, which is the projected position of the vertex in the next-lower level of detail. As described in \secref{sec:vertex-morph} below, the morph delta (\(m\)) is used to offset the altitude of the vertex (the adjusted altitude is called the morph target). Notice that \(\mathbf{v}\) is in a space where the axes are aligned with the world-space axes, but the origin is at the camera.
The chunk data structure also contains the chunk’s geometric error in the Y direction, which is used to compute screen-space error, and the chunk’s minimum and maximum Y values, which can be used to determine the chunk’s AABB.
The mesh data in a chunk is rendered using triangle strips (see buffer-cache.hpp
)
and we use
Vulkan’s primitive restart
mechanism to enable the joining of disjoint meshes
into a single drawing call (the mesh is actually five separate meshes: one for the terrain
and four skirts).
Your rendering code should enable primitive restart and set the restart value to
0xffff
, which can be done the the following code.
Note that primitive restart should be disabled after drawing the cell meshes, since
it might break other drawing in your code.
Texture Quad Trees
For each map cell, there are also two texture quad trees (TQTs) for the cell’s color and normal maps. TQTs provide a parallel structure to the tile quad tree and use the same indexing scheme. Support for loading texture quads from the file system is included as part of the common code.
Objects
It is also possible to include objects on a map. If a map has objects, there will be
a subdirectory containing the assets (i.e., object meshes, materials, and textures)
needed to render the objects. The name of the asset directory is specifed by the optional
assets-dir
field in the map.json
file. In addition, each cell directory may
contain a file named objects.json
that specifies the instances of objects that
are in the cell.[2]
The objects.json
file in a cell directory consists of an array
of JSON objects that have the following fields:
-
The
file
field specifies a file name relative to the map’s asset directory. The named file is an OBJ file that holds a description of the object’s mesh. -
The
pos
field specifies the cell-relative coordinates at which the object should be placed (i.e., relative to the cell’s NW corner). -
The
frame
field is a JSON object with three fieldsx-axis
,y-axis
, andz-axis
, that represent the object-space’s basis in world coordinates. Taking the frame vectors as the columns of a \(3\times3\) matrix and combining it with the object’s position, gives an affine transformation for mapping points in object space to the cell’s coordinate system. Note that the transformation is not restricted to be isotropic. -
The
color
field specifies the color to use when rendering the object as a wireframe (the color is an RGB triple). -
The optional
transparent
field is true when the object’s textures are not fully opaque. Transparent objects need to be rendered after opaque objects and in decreasing distance-from-viewer order.
History
-
[2023-11-19] revised description of map JSON to match implementation.
-
[2023-11-10] original version.