Game Development BASICS (13)

Source: Internet
Author: User

 

Chapter 2
The terrain mesh is actually a series of triangle grids, but each vertex in the square is assigned a height value (that is, height or altitude ), in this way, you can use the smooth transition of the corresponding height of the square to simulate the changes from the three pulses to the three valleys in the natural terrain. Of course, you also need to apply some meticulous textures to the terrain mesh to express the beach, lush hills and snow-capped mountains

The height map (heightmap) is used to describe the hills and valleys in the terrain. The height chart is an array. Each element of the array specifies the height value of a specific vertex in the terrain square, (Another method may use each element in the array to specify the height of each triangle grid.) generally, a height chart is treated as a matrix, in this way, the elements in the height map correspond to the vertices in the terrain grid one by one.

A height chart is stored on a disk. Generally, each element is allocated with only one byte of storage space. In this way, the height can only be within the range [0,255, this range is sufficient to reflect the height changes in the terrain. However, in practical applications, in order to match the scale of the 3D world, proportional transformation of the height value may be required, which is likely to exceed the preceding range. For example, if the measurement unit in the 3D world is feet, the height between 0 and is not enough to express the height of any target point of interest, for the above reason, when the height data is loaded into the application, re-allocate an integer or floating-point array to store these height values, so that you do not have to stick to the range of 0-, so that you can match any scale well.

A height chart has multiple possible graphical representations, one of which is a grayscale map. The higher the elevation of a certain point in the terrain, the greater the brightness of the point in the grayscale map.

A height chart can be generated by a self-developed program or created by image editing software (such as Adobe Photoshop). Using image editing software may be the simplest way, this type of software often provides a visual interaction environment where users can create their desired terrain as they wish. In addition, some special features provided by the image editing software such as filters can be used) and so on.

Once a height chart is created, it must be saved as an 8-bit raw file. The raw file only continuously stores the gray value of each pixel in bytes of the image, this makes it very easy to read such files. The image editing software you use may ask if you want to add a file header to the raw file and select "no ".

You can select any format as needed.

For example, load raw files.

STD: vector <int> _ heightmap;

Bool terrain: readrawfile (STD: String filename)
{
// Restriction: raw file dimensions must be> = to
// Dimensions of the terrain that is a 128*128 raw file
// Can only be used with a terrain constructed with at most
// 128*128 vertices
 
// A height for each vertex
STD: vector <byte> in (_ numvertices );
STD: ifstream infile (filename. c_str (), STD: ios_base: Binary );
 
If (infile = 0)
Return false;
Infile. Read (
(Char *) & in [0], // Buffer
In. Size (); // Number of butes to read into Buffer

Infile. Close ();
 
// Copy byte vector to int Vector
_ Heightmap. Resize (_ numvertices );
 
For (INT I = 0; I <in. Size (); I ++)
_ Heightmap [I] = in [I];

Return true;
}

Note: copy the byte vector to an integer Vector so that you can perform proportional transformation on the height value to break through the limitations of 0 -.
The only limit of this method is that the number of bytes in the raw file to be read must be at least the same as the number of vertices in the terrain, if you want to read data from a 256*256 raw file, you can create only one terrain with 256*256 vertices.

Example: access and modify a height chart
Int getheightmapentry (INT row, int col)
{
Return _ heightmap [row * _ numvertsperrow + Col];
}

Int setheightmapentry (INT row, int Col, int value)
{
_ Heightmap [row * _ numvertsperrow + Col] = value;
}

These methods allow you to reference specified items in the height graph through row and column indexes, which hide how to access the matrix represented by a linear array

Class terrain
{
Public:
Terrain (
Idirect3ddevice9 * device,
STD: String heightmapfilename,
Int numvertsperrow,
Int numvertspercol,
Int cellspacing, // space between cells
Float heightscale );

... Methods snipped
PRIVATE:
.. Device/vertex buffer etc. Snipped
Int _ numvertsperrow;
Int _ numvertspercol;
Int _ cellspacing;
 
Int _ numcellsperrow;
Int _ numcellspercol;
Int _ width;
Int _ depth;
Int _ numvertices;
Int _ numtriangles;

Float _ heightscale;
};

Other terrain variables can be calculated by input parameters of the constructor.
_ Numcellsperrow = _ numvertsperrow-1;
_ Numcellspercol = _ numvertspercol-1;
_ Width = _ numcellsperrow * _ cellspacing;
_ Depth = _ numcellspercol * _ cellspacing;
_ Numvertices = _ numvertsperrow * _ numvertspercol;
_ Numtriangles = _ numcellsperrow * _ numcellspercol * 2;

Vertex structure definition of the terrain;
Struct terrainvertex
{
Terrainvertex (){}
Terrainvertex (float X, float y, float Z, float U float V)
{
_ X = x; _ y = y; _ z = z; _ u = u; _ v = V;
}
Float _ x, _ y, _ z;
Float _ u, _ V;
Static const DWORD fvf;
};
Const DWORD terrain: terrainvertex: fvf = d3dfvf_xyz | d3dfvf_tex1;
Terrainvertex is a nested class. The reason for this definition is that terrainvertex is only used inside the terrain class.

Vertex computing
To calculate the vertices of a triangle grid, you only need to generate each vertex from the vertex start line by line, and keep the row and column spacing of adjacent vertices as cell spacing until the vertex end is reached, in this way, the definition of X and Z coordinates is given. Y coordinates can be easily known only when the corresponding items in the height data structure loaded are queried.

Due to hardware restrictions, 3D devices preset the largest graph element and the largest vertex index value, you can check the maxprimitivecount and maxvertexindex member variables of the d3dcaps9 structure to find out the upper limit specified by the above value for your graphics device.

Calculate texture coordinates:
U = J * ucoordincrementsize
V = I * vcoordincrementsize

Where
Ucoordincrementsize = 1/numcellcols
Vcoordincrementsize = 1/numcellrows
 

Below is the code for generating Vertex
Bool terrain: computevertices ()
{
Hresult hR = 0;
HR = _ device-> createvertexbuffer (_ numvertices * sizeof (terrainvertex ),
D3dusage_writeonly,
Terrainvertex: fvf,
D3dpool_managed,
& _ VB,
0 );

If (failed (HR ))
Return false;

// Coordinates to start generating vertices
Int endx = _ width/2;
Int endz = _ depth/2;

// Compute the increment sizeof the texture coordinates
// From one vertex to the next
Float ucoordincreamentsize = 1.0f/(float) _ numcellsperrow;
Float vcoordincrementsize = 1.0f/(float) _ numcellspercol;

Terrainvertex * V = 0;
_ Vb-> lock (0, 0, (void **) & V, 0 );
 
Int I = 0;
For (INT z = startz; z> = endz; Z-= _ cellspacing)
{
Int J = 0;
For (INT x = startx; x <= endx; x + = _ cellspacing)
{
// Compute the correct index into the vertex buffer and heightmap
// Based on where we are in the nested loop
Int Index = I * _ numvertsperrow + J;
V [Index] = terrainvertex (
(Float) X,
(Float) _ heightmap [Index],
(Float) Z,
(Float) J * ucoordincrementsize,
(Float) I * vcoordincrementsize );
J ++; // next column
}
I ++; // next row
}

_ Vb-> unlock ();

Return true;
}

To calculate the index of each vertex in the triangle grid, you only need to traverse each square in sequence and calculate the vertex index of each triangle surface in each square.

The key to calculation is to derive a general formula for finding the vertex index of the two patches that constitute the square of the column J of the row I.
 
The following code calculates the index:
Bool terrain: computeindices ()
{
Hresult hR = 0;
HR = _ device-> createindexbuffer (
_ Numtriangles * 3 * sizeof (Word), // 3 indices per triangle
D3dusage_writeonly,
D3dfmt_index16,
D3dpool_managed,
& _ IB,
0 );
If (failed (HR ))
Return false;

Word * indices = 0;
_ IB-> lock (0, 0, (void **) & indices, 0 );
 
// Index to start of a group of 6 indices that describe
// Two triangles that make up a guad
Int baseindex = 0;
 
// Loop through and compute the triangles of each guad
For (INT I = 0; I <_ numcellspercol; I ++)
{
For (Int J = 0; j <_ numcellsperrow; j ++)
{
Indices [baseindex] = I * _ numvertsperrow + J;
Indices [baseindex + 1] = I * _ numvertsperrow + J + 1;
Indices [baseindex + 2] = (I + 1) * _ numvertsperrow + J;

Indices [baseindex + 3] = (I + 1) * _ numvertsperrow + J;
Indices [baseindex + 4] = I * _ numvertsperrow + J + 1;
Indices [baseindex + 5] = (I + 1) * _ numvertsperrow + J + 1;

// Next guad
Baseindex + = 6;

}
}
 
_ IB-> unlock ();

Return true;
}

For example, texture ing
Bool terrain: loadtexture (STD: String filename)
{
Hresult hR = 0;
 
HR = d3dxcreatetexturefromfile (
_ Device,
Filename. c_str (),
& _ Tex );
If (failed (HR ))
Return false;

Return true;
}

Another method for texture ing is to calculate the texture content one by one in order, that is, first create an "empty" texture, then, the color of the texture element is calculated based on some predefined parameters. In the following example, this parameter indicates the Terrain Height.

This method first creates an empty texture with d3dxcreatetexture, and then locks the top-level texture (a texture object may have multi-level progressive texture, so there are many layers of texture, at this point, we start to traverse each texture element and color it. The color is based on the approximate height of the coordinate Square. The idea is that the lower part of the terrain is colored as the beach color, the middle altitude is colored with green hills, and the high altitude is colored with snow mountains.
Use the height value of the vertex in the upper left corner of the coordinate square to represent the overall height of the square.
// Use the d3dxfiltertexture function to calculate the texture element in the lower-layer multi-level progressive texture.
Bool terrain: gentexture (d3dxvector3 * directiontolight)
{
// Method fills the top surface of a texture procedurally then
// Lights the top surface. Finally, it fills the other mipmap
// Surfaces Based on the top surface data using d3dxfiltertexure
 
Hresult hR = 0;
 
// Texel for each quad Cell
Int texwidth = _ numcellsperrow;
Int texheight = _ numcellspercol;
 
// Create an empty texture
HR = d3dxcreatetexture (
_ Device,
Texwidth, texheight,
0, // create a complete mipmap chain
0, // usage
D3dfmt_x8r8g8b8, // 32bit xrgb format
D3dpool_managed, & _ Tex );
If (failed (HR ))
Return false;

D3dsurface_desc texturedesc;
_ Tex-> getleveldesc (0, & texturedesc );

// Make sure we got the requested format because our code
// That fills the texture is hard coded to a 32 bit pixel depth
If (texturedesc. Format! = D3dfmt_x8r8g8b8)
Return false;

D3dlocked_rect lockedrect;
_ Tex-> lockrect (0/* Lock top surface */, & lockedrect, 0/* Lock entire Rex */, 0/* flags */);

DWORD * imagedata = (DWORD *) lockedrect. pbits;
For (INT I = 0; I <texheight; I ++)
{
For (Int J = 0; j <texwidth; j ++)
{
D3dxcolor C;

// Get height of upper left vertex of quad
Float Height = (float) getheightmapentry (I, j); // _ heightscale

If (height) <42.5f) C = d3d: beach_sand;
Else if (height) <85.5f) C = d3d: light_yellow_green;
Else if (height) <127.5f) C = d3d: puregreen;
Else if (height) <170.0f) C = d3d: dark_yellow_green;
Else if (height) <212.5f) C = d3d: darkbrown;
Else c = d3d: white;

// Fill locked data, note we divide the pitch by four because
// Pitch is given in bytes and there are 4 butes per DWORD
Imagedata [I * lockedrect. Pitch/4 + J] = (d3dcolor) C;
}
}
 
_ Tex-> unlockrect (0 );

If (! Lightterrain (directiontolight ))
{
: MessageBox (0, "lightterrain ()-failed", 0, 0 );
Return false;
}
 
HR = d3dxfiltertexture (
_ Tex,
0, // default palette
0, // use top level as source level
D3dx_default); // default filter
 
If (failed (HR ))
{
: MessageBox (0, "d3dxfiltertexture ()-failed", 0, 0 );
Return false;
}

Return true;
}

Calculate the shade factor for how each part of the terrain is adjusted according to the given light source)

You do not need to use direct3d to add light, but perform manual computing based on the following considerations:
1 manual computation saves a lot of memory because you do not need to store the vertex normal vector.
2 because the terrain is static, and the light source is generally not moved, you can calculate the illumination in advance, which saves the computing time of the real-time illumination of the terrain in direct3d.
3. You can learn and consolidate basic knowledge.

The attention technology used to calculate the brightness and shade of a terrain is very basic and commonly used, that is, diffusing lighting, which gives a parallel light source, use "direction to the light source" to describe the parallel light source.
For example, if a group of parallel light emits downward light from the air in the direction of lightraysdirection = (0,-), the direction to reach the light source should be in the opposite direction of lightraysdirection, that is, directiontolight = (, 0 ). Note that the direction vector of light should be a unit vector)

Although it seems more intuitive to specify the outbound direction of light, it is more suitable for the calculation of diffuse light.

Calculate the angle between the light vector L ^ and the surface method vector n ^ for each coordinate square in the terrain.

Once the angle between the light vector and the surface normal is more than 90 degrees, the square surface will not receive any light

By using the angle relationship between the light vector and the square surface normal vector, You can construct a shading scalar in the interval [0, 1 ), to indicate the amount of light received by the square surface. In this way, a factor value close to 0 can be used to indicate that the angle between the two vectors is very large, when a color is multiplied by this factor, the color value is close to 0, showing a darker visual effect. On the contrary, a factor value close to 1 indicates that the angle between the two vectors is very small, therefore, when a color is multiplied by this factor, the color basically maintains the original brightness.

In the light and shade calculation of the coordinate square, a unit vector L ^ is used to represent the direction of the light source. In order to calculate the angle between the vector L ^ and the square's normal vector n ^, first, we need to obtain n ^. By calculating the cross product of the vector, we can easily find n ^, but we must first find two non-zero non-parallel vectors in the square.

For example, calculate the brightness factor of a specific coordinate square.

Float terrain: computeshade (INT cellrow, int cellcol, d3dxvector3 * directiontolight)
{
// Get heights of three vertices on the quad
Float heighta = getheightmapentry (cellrow, cellcol );
Float heightb = getheightmapentry (cellrow, cellcol + 1 );
Float heightc = getheightmapentry (cellrow + 1, cellcol );

// Build two vectors on the quad
D3dxvector3 U (_ cellspacing, heightb-heighta, 0.0f );
D3dxvector3 V (0.0f, heightc-heighta,-_ cellspacing );

// Find the normal by taking the cross product of two
// Vectors on the quad
D3dxvector3 N;
D3dxvector3cross (& N, & U, & V );
D3dxvector3normalize (& N, & N );
 
Float cosine = d3dxvec3dot (& N, directiontolight );

If (Cosine <0.0f)
Cosine = 0.0f;
 
Return cosine;
}

// Obtain the height of the camera
Float terrain: getheight (float X, float Z)
{
// Translate on xz-plane by the transformation that takes
// The terrain start point to the origin
X = (float) _ width/2.0f) + X;
Z = (float) _ depth/2.0f)-Z;

// Scale down by the transformation that makes
// Cellspacing equal to one This is given
// 1/cellspacing since; cellspacing * 1/cellspacing = 1
 
X/= (float) _ cellspacing;
Z/= (float) _ cellspacing;

// First, perform a translation transformation. The vertex start is translated to the coordinate origin. Then, the unit interval of the coordinate square is normalized through the proportional transformation of the negative reciprocal of the unit interval through the scaling factor, it is converted to a new reference system, where the Z axis direction is down. Of course, the program code does not change the coordinate system framework itself. Of course, the program code does not change the coordinate system framework itself, just understand the positive direction of the Z axis as downward

The transformed coordinate system is consistent with the order of the matrix, that is, the upper left corner is the origin, and the column index and row index increase along the right direction and the bottom direction, because the current Unit spacing is 1, you can quickly find the row and column indexes of the current coordinate square.
Float Col =: flograding (X );
Float ROW =: flograding (z );
// That is, the column index is equal to the integer part of the X coordinate, and the row index is equal to the integer part of the zcoordinate. Here, the floor (t) function is used to obtain the maximum integer not less than T.
Now that you know the current coordinate square, you can find the height of the four vertices that constitute the square.
// Get the height of the quad we're re in
// A B
//*----*
// | \ |
//*----*
// C d
Float a = getheightmapentry (row, col );
Float B = getheightmapentry (row, Col + 1 );
Float c = getheightmapentry (row + 1, col );
Float d = getheightmapentry (row + 1, Col + 1 );

// At this time, the position of the current square and the height of the four vertices that constitute the square are known. Now, when the camera is located at any position (x, z, the height of the coordinate square unit. It seems a little tricky because the cell may be skewed along multiple directions.

 
}

To obtain the height, you must first determine the coordinate square in which the camera is currently located. Note that the coordinate square is drawn by a combination of two triangle surfaces. To find the current triangle of the camera, you need to transform the current coordinate square so that the vertices in the upper-left corner overlap with the coordinate origin.

Because Col and row describe the positions of vertices in the upper left corner of the current coordinate box, you must pan-Col units along the X axis and pan-row units along the Z axis.
Float dx = x-Col;
Float DZ = z-row;

If DZ <1.0-dx is currently in the top triangle, otherwise it will be in the bottom triangle.

In the above triangle surface, along the triangle AB, AC two sides, with the vector q = (QX, A, qz) as the starting end of the two vectors u = (cellspacing, B-A, 0), V = (0, C-A,-cellspacing), then along the U axis of DX points linear difference, and then along the V axis of Dy points linear interpolation, given the X and zcoordinates, the Y component of the vector (q + dxu + dzv) is the height of the point.

Note: because only the height interpolation is concerned, only the Y component can be interpolated. In this way, the height of a specified vertex can be obtained by formula A + dxu + dzv.

Float Height = 0.0f;
If (Dz <1.0f-dx) // upper Triangle ABC
{
Float Uy = B-A; // A-> B
Float Vy = C-A; // A-> C

Height = a + d3d: lerp (0.0f, Uy, dx) + d3d: lerp (0.0f, Vy, Dz );
 
}
Else // lower triangle DCB
{
Float Uy = C-D; // D-> C
Float Vy = B-D; // D-> B
Height = d + d3d: lerp (0.0f, Uy, 1.0f-dx) + d3d: lerp (0.0f, Vy, 1.0f-Dz );
}

The lerp function supports linear interpolation along the ID line.
Float d3d: lerp (float a, float B, float T)
{
Return a-(A * t) + (B * t );
}
 
The d3dxsplitmesh function splits the terrain mesh into several smaller subgrids.
Void d3dxsplitmesh (
Lpd3dxmesh pmeshin,
Const DWORD * padjacencyin,
Const DWORD maxsize,
Const DWORD options,
DWORD * pmeshesout,
Lpd3dxbuffer * ppmesharrayout,
Lpd3dxbuffer * ppadjacencyarrayout,
Lpd3dxbuffer * ppfaceremaparrayout,
Lpd3dxbuffer * ppvertremaparrayout );

# Pmeshin is used to receive source grids. Then, this function can split the source mesh into several smaller grids.
# Padjacencyin is a pointer to an array adjacent to the source mesh.
# Maxsize is used to specify the maximum number of vertices allowed by the split Mesh
# Option flag specifies the option for split grid Creation
# Pmeshesout returns the created grid array, and the created grid is saved in the array cache directed by the ppmesharrayout parameter.
The last three parameters are optional (null indicates ignore these parameters), and the adjacent array, patch re-painting information, and vertex re-painting information of each newly created grid are returned respectively.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.