Zhao Gang Introduction In a virtual world built on 3D games, virtual scenarios require high fidelity. Among them, 3D terrain fidelity is one of the keys. However, the generation and drawing of 3D terrain requires a huge amount of computing, and the generation of real-world terrain also requires the support of the terrain database, generating Realistic 3D terrain in real time on PCs with limited computing power has always been a challenge in the industry. After years of exploration, the 3D terrain generation method has formed a series of excellent algorithms. The algorithm described in this article is an entry-level algorithm, learning this algorithm can lay the foundation for the system to learn the 3D terrain generation algorithm. The algorithm is low in complexity and fast in operation. The generated terrain can meet the needs of small-scale terrain visualization. I. Algorithm Implementation Process In direct3d instant mode, 3D drawing uses the drawprimitive method. The drawprimitive method breaks down the 3D model into three types: basic points, lines, and triangle surfaces, in this 3D terrain generation method, all the terrain is divided into triangles, and a series of triangles are drawn using the drawprimitive method to draw the entire terrain. There are three ways to draw a triangle using drawprimitive. The first method is to draw a discrete triangle, with each triangle specifying three vertices respectively. This method is suitable for creating scattered triangles, which is less efficient for generating triangles. The second is to draw a sequence of triangles. The first triangle specifies three vertices. The remaining triangles only need to specify one vertex. The other two take the last two vertices of the first triangle, the triangles drawn in this way are all connected together, which is suitable for creating triangles in slices. The third method is to draw a slice that consists of a triangle. The first vertex is used as the common vertex of all the triangles to form a slice. This method is only applicable to the face of the slice class. Therefore, the second method is used to generate a local shape. However, it is difficult to arrange the vertex coordinates of all triangles in the terrain by means of first and end connections, fortunately, direct3d instant mode provides the drawing method of vertex indexes. You only need to pass the number of the vertex to be drawn to drawprimitive's sister function drawindexedprimitive to complete the same functions as drawprimitive. Vertices are stored in the memory using arrays. Therefore, vertex numbers are sequential numbers. Therefore, you only need to provide an index sequence that allows the triangle vertices to start and end with each other to complete efficient plotting. The process of generating a terrain using the 3D terrain generation method is as follows: Step 1: initialize a square network (1). The number of grids is 64 × 64 (the size is too small, the generated terrain is not realistic, the size is too large, and the PC is difficult to handle. Therefore, after multiple experiments, it is most appropriate to use a 64 × 64 lattice). Each square mesh is represented by two triangles. The edges of the grid are determined by the size of the ground in the visible area. For example, the edges of the grid are 50 meters long and the visible ground is 50 × 64 = 3200 meters. (The edges of the grid are small, the ground generated is small, and the edges of the grid grow. The ground generated is large, but the fidelity of the ground is reduced. The experiment shows that it is suitable to use 50 meters ). Figure 1 square grid (4x3) Vertex information includes: 1. Coordinates X, Y, and Z (X indicates the left and right directions, y indicates the vertical directions, and Z indicates the depth direction) 2. Normal Line coordinates NX, NY, and NZ (indicating steep ground around the point) 3. Color diffuse (indicating the reflection property of the point to the Light) 4. texture coordinates Tu and TV (Tu represent the texture abscissa, and TV represents the texture ordinate) Define a structure to store the information: Struct Vertex { D3dvalue x, y, z; D3dvalue NX, NY, NZ; D3dcolor diffuse; D3dvalue tu, TV; }; In a 64 × 64 mesh, the number of vertices is (64 + 1) × (64 + 1) = 4225. Therefore, you can declare an array with a length of 4225 to store vertex information: vertex m_vertex [4225]; The vertex numbering rules are sequential numbers from left to right, from top to bottom, so that the first column of the first row of the vertex is 0, the second column of the first row of the vertex is 1, the vertex in the first column of the Second row is No. 65, and so on ......, Therefore, the vertex of the 64*64 square net can be initialized as follows: For (j = 0; j <65; j ++) { For (I = 0; I <65; I ++) { M_vertex [J * 65 + I]. x =-m_length * 0.5f + m_block * I; M_vertex [J * 65 + I]. Y = 0.0f; M_vertex [J * 65 + I]. z = m_length * 0.5f-m_block * J; M_vertex [J * 65 + I]. Nx = 0.0f; M_vertex [J * 65 + I]. ny = 1.0f; M_vertex [J * 65 + I]. nz = 0.0f; M_vertex [J * 65 + I]. Diffuse = 0 xffffffff; } } M_length indicates the visible ground length (3200 meters) m_block indicates the grid length (50 meters) The square net initialized in this way is a plane (coordinate Y is 0). It indicates that the ground is flat, without elevation data. The addition of elevation data will be introduced later. In order to draw a triangle using drawprimitive's second method, a sequence of vertex indexes must be created to sort the vertices in the order of first and last matching of the triangle. The sequence of the vertex index can be assigned as follows: Word m_index [64*64*6]; For (j = 0; j <64; j ++) { For (I = 0; I <64; I ++) { M_index [J * 64*6 + I * 6 + 0] = (Word) (J * 65 + I ); M_index [J * 64*6 + I * 6 + 1] = (Word) (J * 65 + I + 65 ); M_index [J * 64*6 + I * 6 + 2] = (Word) (J * 65 + I + 65 + 1 ); M_index [J * 64*6 + I * 6 + 3] = (Word) (J * 65 + I ); M_index [J * 64*6 + I * 6 + 4] = (Word) (J * 65 + I + 1 ); M_index [J * 64*6 + I * 6 + 5] = (Word) (J * 65 + I + 65 + 1 ); } } The Terrain elevation data is stored in a 512x512 pixel 256-color BMP file. The graphic file is mapped based on the actual Terrain elevation, and the red component in the image indicates the ground elevation, the red weight ranges from 0 ~ 255 has a total of 256 levels, so that the ground height is only 256 levels, such accuracy is sufficient for 3D terrain simulation. In this method, in order to further improve the terrain authenticity, the effects of the water surface are also incorporated into the terrain. As long as the red component of the elevation map is specified as 0, the water surface instead of the ground is generated, it must be specified as land, and the red weight ranges from 1 ~ 255. The green component in the elevation chart is used to specify whether there is any forest. The greater the green component, the more dense the forest is. At the same time, the land is displayed as a lawn effect, but the green component cannot be abused, because it takes a lot of time to draw trees, too many trees will reduce the real-time nature of the program. Generally, the number of visible trees is limited to 500. Typical elevation 2. Figure 2 Typical elevation chart For the sake of intuition, the area with zero red is drawn in blue, and the water area is clearly a river and two lakes. The resolution of an elevation chart is 512 pixels × 512 pixels. Each vertex of the terrain mesh corresponds to one pixel, and the vertex interval is 50 meters, in this way, the terrain range shown in this elevation map is a square area (512 square kilometers) with a side length of 25600 × 50 = 25.6 (655.36 kilometers), which is sufficient for general 3D scenarios, if it is not enough, you can increase the resolution of the elevation chart or splice multiple elevation maps, but it consumes more memory. To make the ground realistic, a texture map should be attached to the ground. A texture map can be a photo of a typical ground or made using image processing software. In order to correctly paste a texture map, we should specify the texture coordinates for the vertex. Because the Display memory is very limited, the texture map cannot be very large. Compared with the huge ground, the texture map is very insufficient, therefore, the texture map should be used repeatedly, as if it were pasted on the ground. The problem is that the texture on the ground is cyclical, as if it was actually a tile, in addition, the texture image is spliced with an ugly crack. To reduce this phenomenon, the texture image cannot be too small (1024x1024 pixel bitmap is used in this method, the edge of a texture map must be specially processed so that no cracks will occur during texture stitching (such images are called tiled images and are widely used in the shading of webpages) in this method, no special processing is required on the edge of the texture map, and no cracks occur, because this method makes the two textures at the stitching image relationship when pasting the texture, therefore, the edges are consistent and will not be misplaced, but the disadvantage is that many symmetrical patterns can be seen. The method of splicing a surface with a small texture needs to be improved. The typical ground texture figure 3. Figure 3 soil texture map In order to ensure the smoothness and low periodicity of the texture, this method sets the texture size of each grid to 64x64 pixels, in this way, a 1024x1024 pixel texture map can cover 16x16 mesh (that is, 800 m x 800 m area) texture coordinates. The algorithm is as follows: Long p, q, A, B; Float X, Y; X = m_centerpos [0] * m_ReciBlock-32; Y =-m_centerpos [2] * m_ReciBlock-32; For (j = 0; j <65; j ++) { For (I = 0; I <65; I ++) { P = (DWORD) x + 150 + m_texwidth/2 + I) * 64) % m_texwidth; Q = (DWORD) Y + 150 + m_texheight/2 + J) * 64) % m_texheight; A = (DWORD) x + 150 + m_texwidth/2 + I) * 64)/m_texwidth; B = (DWORD) Y + 150 + m_texheight/2 + J) * 64)/m_texheight; If (a % 2) m_vertex [J * 65 + I]. Tu = (float) P/m_texwidth; Else m_vertex [J * 65 + I]. Tu = (float) (m_TexWidth-p)/m_texwidth; If (B % 2) m_vertex [J * 65 + I]. TV = (float) q/m_texheight; Else m_vertex [J * 65 + I]. TV = (float) (m_TexHeight-q)/m_texheight; } } M_centerpos [0] And m_centerpos [2] Are the horizontal coordinates of the visible ground center. M_texwidth, m_texheight is the width and height of the texture M_reciblock is the reciprocal of the grid edge length (1/50) The variable m_centerpos [0] And m_centerpos [2] are used to determine the central coordinates of the drawn ground. The ground within the specified distance around the coordinate will be drawn. When the observation points in the 3D scene move, the coordinates of the ground center must also be moved accordingly. Otherwise, after the observation point moves to a certain position, the ground boundary will be seen, or even the ground will be invisible. In general, the visible area in a 3D scenario is a cone. The observation point is located at the cone top, and the Cone Bottom is perpendicular to the observation direction and extends to the observation direction. For such a visible area, it is not advisable to make the central coordinates of the ground consistent with the horizontal coordinates of the observation point, because a ground of the same size as the observation point is drawn after the observation point, however, it is a waste of time. Therefore, the central coordinates of the ground should be placed at a certain distance before the observation point. For the ground with a side length of 3200, it is appropriate to take the distance of 1200. Therefore, the program is responsible for calculating the coordinates of the Point 1200 metres from the observation point, and assigning values to m_centerpos [0], m_centerpos [2], and 4 using the coordinates. In this way, about half of the ground is located outside the visible area, and other methods are used to avoid the painting of the ground in the non-visible area. Figure 4 selection of the ground center in the visible area Till now, the ground is still a plane, and the elevation data has not been written to the vertex coordinates. The following describes how to write elevation data into vertex coordinates. The elevation data is stored in an image file of 512x512 pixels in sequence. We think that the center of the image is the coordinate origin point, and the top and right are the direction of the coordinate growth. This can be calculated as the pixel at the top left of the image (that is, the first pixel) the second pixel stores the elevation data of the ground (-256x50,256x50), that is, (-,), and the second pixel stores the elevation data of the ground (-,), and so on, therefore, you can address the elevation data in the elevation image based on the horizontal coordinates of the plane. The elevation data is added to the grid, as shown in Figure 5. Figure 5 Adding elevation data to a terrain grid
To increase the rendering speed, this method determines the visibility of each grid in the terrain grid. If the grid is located in the visible area, the grid is drawn. Otherwise, no painting is performed. There are a total of 64x64 = 4096 grids in the local shape, and 4096 judgments are required. However, the function itself that determines the visibility runs slowly. Excessive judgments are counterproductive. Therefore, in this method, the mesh visibility is determined by a group of four. A total of 1024 judgments are made. If one of the four statements is located in the visible area, the four statements are considered visible. Otherwise, the four statements are invisible. In this way, although the rendering efficiency is reduced, the visibility Judgment time is greatly reduced. Experiments show that the overall operation speed of the four groups is the highest. This method declares another vertex index array, word m_indextemp [4225]. For vertices with successful visibility judgment, the vertex indexes related to them will be stored in m_indextemp; otherwise, they will not be saved. In this way, m_indextemp only contains vertex indexes in the visible area, so that drawprimitive can draw the smallest triangle to maximize the speed. For areas with zero red weight in an elevation chart, describe the surface as the water surface using the following method: 1. The surface color is light blue and translucent. 2. The texture coordinates of the earth surface are periodically moved to make the water surface flow. The process is as follows: For (j = 0; j <65; j ++) { For (I = 0; I <65; I ++) {...... If (altitude = 0) // water zone { M_vertex [J * 65 + I]. Diffuse = 0x600030ff; // light blue, translucent If (count/16% 2) // process the flow of water { P = (DWORD) x + 150 + m_texwidth/2 + I) * 64 + Count % 16) % m_texwidth; Q = (DWORD) Y + 150 + m_texheight/2 + J) * 64 + Count % 16) % m_texheight; A = (DWORD) x + 150 + m_texwidth/2 + I) * 64 + Count % 16)/m_texwidth; B = (DWORD) Y + 150 + m_texheight/2 + J) * 64 + Count % 16)/m_texheight; } Else { P = (DWORD) x + 150 + m_texwidth/2 + I) * 64 + 16-count % 16) % m_texwidth; Q = (DWORD) Y + 150 + m_texheight/2 + J) * 64 + 16-count % 16) % m_texheight; A = (DWORD) x + 150 + m_texwidth/2 + I) * 64 + 16-count % 16)/m_texwidth; B = (DWORD) Y + 150 + m_texheight/2 + J) * 64 + 16-count % 16)/m_texheight; } } If (a % 2) m_vertex [J * 65 + I]. Tu = (float) P/m_texwidth; Else m_vertex [J * 65 + I]. Tu = (float) (m_TexWidth-p)/m_texwidth; I f (B % 2) m_vertex [J * 65 + I]. TV = (float) q/m_texheight; Else m_vertex [J * 65 + I]. TV = (float) (m_TexHeight-q)/m_texheight; } } Here, count is the counter, and the value increases by 1 every 0.1 seconds. If the green area in an elevation chart is a forest, use the following method to achieve 1. The color of the ground is dark green, indicating the lawn 2. Generate tree coordinates at random, and use the public board technology to display the tree coordinates. The implementation is as follows: If (m_showtree) // create a tree Parameter { Long r, s; If (green> 0 & m_treenum { R = ABS (long) (m_vertex [J * m_row + I]. Tu + M_vertex [J * m_row + I]. TV * 10) * 10000) % 1000 ); For (S = 0; s { Vect. x = m_vertex [J * m_row + I]. x + m_randpos [S + R] [0]; Vect. z = m_vertex [J * m_row + I]. Z + m_randpos [S + R] [2]; Vect. Y = m_vertex [J * m_row + I]. Y; D3dmath_vectormatrixmultiply (vect, vect, m_matrix ); Vect. Y = getheight (vect. x * 0.01f, vect. z * 0.01f, false) * 100366f; M_treepos [m_treenum] [0] = vect. X; M_treepos [m_treenum] [1] = vect. Y; M_treepos [m_treenum] [2] = vect. Z; M_treepos [m_treenum] [3] = (float) (long) (Green + S + r) % 5 ); M_treenum ++; } } } Here, m_showtree is the logical volume, which is used to draw trees only when it is true (you can do not draw trees to increase the speed) M_treemax is used to control the number of trees. When m_treenum is equal to m_treemax, no trees are added. Gethight () is a function used to calculate the ground height. Call drawindexedprimitive to draw the terrain as follows: M_pd3ddevice-> settexture (0, m_ptexture ); M_pd3ddevice-> settransform (d3dtransformstate_world, & m_matrix ); M_pd3ddevice-> setrenderstate (d3drenderstate_cullmode, d3dcull_none ); M_pd3ddevice-> drawindexedprimitive (d3dpt_trianglelist, d3dfvf_vertex, m_vertex, m_vertexnum, m_indextemp, m_indexnum, null ); Figure 6 final result example Ii. Conclusion The quick 3D terrain generation algorithm described in this article is a typical even grid terrain generation algorithm based on height graphs. It provides an easy-to-use guide for learning 3D terrain generation algorithms. Experiments show that the resolution of the matrix g400 display card is 667x1024 in the piII 768 MHz and MB memory, and the speed is over 35 frames/second. The program code of this algorithm has been used in a large project. |