Processing vertices-create a Terrain Based on a vertex buffer and an index Buffer

Source: Internet
Author: User
Problem

Based on a 2D height chart, you want to create a terrain and draw it in an efficient way.

Solution

First, you need a height chart that contains all the height data used to define the terrain. This height chart has a fixed number of two-dimensional data points. We call it the width and height of the terrain.

Obviously, if you want to create a Terrain Based on the data, the terrain will be drawn from these width * Height vertices, as shown in the upper right corner of 5-14 (Note that the number starts from 0 ).

Figure 5-14 terrain grid

To use a triangle to completely overwrite the grid, you need to draw two triangles in the four points of each grid, as shown in Figure 5-14. A row requires two triangles (width-1). The whole terrain requires two triangles (height-1) * (width-1.

If you want to determine whether an index is required, see the Rules 5-3 in the tutorial. In this tutorial, the number of triangles divided by the number of vertices is less than 1, so the index should be used. All vertices that are not on the boundary are shared by no less than six triangles.

Besides, because all triangles share at least one side, you should use trianglestrip instead of trianglelist.

Working principle definition Vertex

First, define the vertex. The following method first accesses the heightdata variable, which is a two-dimensional array containing the heights of all vertices in the terrain. If you do not have such an array, the loadheightdata method at the end of this tutorial will create one based on a 2D image.

Private vertexpositionnormaltexture [] createterrainvertices () {int width = heightdata. getlength (0); int Height = heightdata. getlength (1); vertexpositionnormaltexture [] terrainvertices = new vertexpositionnormaltexture [width * Height]; int I = 0; For (INT z = 0; Z  

First, the height and width of the terrain are obtained based on the size of the heightdata array. Create an array to save all vertices. As mentioned above, the terrain requires width * Height vertices.

Then create all vertices in two cycles in China. Create a vertex on a row in a loop. After a row is complete, the first for loop switches to the next row until the vertex of all rows is defined.

You use the X and zcoordinates as the counter of the loop. The Z value is negative, so the terrain is built in the forward (-z) direction. The height information is taken from the heightdata array.

Now you give all vertices a default normal direction, which will be immediately replaced with the correct direction using the method in the previous tutorial. Because you may need to add textures to the terrain, You need to specify the correct texture coordinates. Depending on the texture, you want to control its terrain size. In this example, divide by 30, indicating that the texture repeats every 30 vertices. If you want to increase the texture to match a larger terrain, you can divide it by a larger value.

With this data, you are ready to create these new vertices and store them in the array.

Define Indexes

After the vertex is defined, you are ready to create a triangle by defining the Index Array (see tutorial 5-3 ). You will define a triangle using trianglestrip, and represent a triangle based on one index and the first two indexes.

Figure 5-16 shows how to use trianglestrip to draw a triangle. The first index in the array points to vertex 0 and W. Then add the vertex corresponding to the next row to each vertex in the row until it reaches the end of the row .. At this time, you need to define 2 * width indexes, corresponding to (2 * width-2) triangles, enough to cover the entire row.

However, you only define the first line. You cannot use this method to draw the second line, because you add each index based on the triangle defined in the first three indexes. Based on this, the last index you define points to the vertex (2 * W-1 ). If you start from the second row again, it will start from adding an index to the vertex W, as shown in the left figure 5-15. However, this time defines a triangle Based on vertex W, (2 * W-1) and (W-1! This triangle spans the entire length of the first line, which is not the result you want.

Figure 5-15 incorrect method of defining a triangle using trianglestrip

You can solve this problem by defining the second line on the right. However, it is not a good idea to simply start with the last index, because the long edges of the two triangles have different directions, as explained in the 5-9 tutorial, you want the triangle to have the same orientation.

Figure 5-16 shows how to solve this problem. After an index pointing to a vertex (2 * W-1), you will immediately add an index pointing to the same vertex! This adds a triangle Based on the vertex (W-1) and two vertices (2 * W-1), forming only one line between the vertex (W-1) AND (2 * W-1, therefore, this triangle is invisible, called the ghost triangle. Next, add an index pointing to the vertex (3 * W-1) because the triangle is actually a line based on two indexes pointing to the same vertex (2 * W-1. If you define the second line from the right, you will normally start with two vertices and remember that you have actually drawn two invisible triangles.

Figure 5-16 use trianglestrip to define the correct method of a triangle

Note:You might think that you don't need to add a second index pointing to (2 * W-1), you can immediately add an index to (3 * W-1. However, an additional index pointing to a vertex (2 * W-1) is required for two reasons. First, if you do not add this index, only one triangle will be added, and you will be disturbed by the reversal of the detour direction required by the trianglestrip method. Second, this adds a triangle based on (3 * W-1), (2 * W-1), and (W-1), which is displayed if the height of the three vertices is different.

The following describes how to generate an index:

Private int [] createterrainindices () {int width = heightdata. getlength (0); int Height = heightdata. getlength (1); int [] terrainindices = new int [(width) * 2 * (height-1)]; int I = 0; int z = 0; while (z  

Create an array to store all the indexes required by the terrain. As shown in tutorial 5-16, each line needs to define the width * Two triangles. In this example, you have three rows of vertices, but only two rows of triangles are drawn, so the width * 2 * (height-1) index is required.

FrontCodeThe Z value in indicates the current row. Create the first line from left to right, and add Z to switch to the next line. The second row is created from the right to the left. As shown in 5-16, the Z value is still increased. ThisProgramPlace it in the while loop until all the even rows are created from left to right, and the odd rows are created from right to left.

When Z changes to height-1, the while loop ends and an array of results is returned.

Normal, vertex buffer, and index Buffer

You need to create the normal data, send the data to the video card by creating a vertex buffer and an index buffer, and draw a triangle.

Add the following code to the loadcontents method:

 
Myvertexdeclaration = new vertexdeclaration (device, vertexpositionnormaltexture. vertexelements); keywords [] terrainvertices = random (); int [] terrainindices = random (); terrainvertices = random (terrainvertices, terrainindices); createbuffers (terrainvertices, terrainindices );

The first line of code is used to tell the video card that each vertex contains the position, normal, and texture coordinate data. I have discussed the following two methods: they generate all vertices and indexes. The generatenormalsfortrianglestrip method is in tutorial 5-7, which adds the normal data to the vertex so that the terrain illumination is correct. The final method sends data to the video card:

Private void createbuffers (vertexpositionnormaltexture [] vertices, int [] indices) {terrainvertexbuffer = new vertexbuffer (device, vertexpositionnormaltexture. sizeinbytes * vertices. length, bufferusage. writeonly); terrainvertexbuffer. setdata (vertices); terrainindexbuffer = new indexbuffer (device, typeof (INT), indices. length, bufferusage. writeonly); terrainindexbuffer. setdata (indices );}

You can find all methods and explanations of using parameters in tutorial 5-3.

After the data is sent to the video card, you can now draw the terrain. In the first part of the code, set the variable basiceffect (including lighting, see tutorial 6-1). Therefore, add the following code to the draw method:

Int width = heightdata. getlength (0); int Height = heightdata. getlength (1); basiceffect. world = matrix. identity; basiceffect. view = fpscam. viewmatrix; basiceffect. projection = fpscam. projectionmatrix; basiceffect. texture = grasstexture; basiceffect. textureenabled = true; basiceffect. enabledefalightlighting (); basiceffect. directionallight0.direction = new vector3 (1,-1, 1); basiceffect. directionallight0.enabled = true; basiceffect. ambientlightcolor = new vector3 (0.3f, 0.3f, 0.3f); basiceffect. directionallight1.enabled = false; basiceffect. directionallight2.enabled = false; basiceffect. specularcolor = new vector3 (0, 0, 0 );

To draw a 3D scene to a 2D screen, you need to set the world, view, and projection matrices (see tutorial 2-1 and 4-2 ). Specify the texture. The second code block sets a targeted light (as shown in tutorial 6-1 ). Turn off the mirror highlight (see tutorial 6-4), because there is no shining texture on the lawn terrain.

After effect is set, you can draw a triangle. This Code draws a triangle from an index trianglestrip. For more information, see tutorial 5-3:

Basiceffect. begin (); foreach (effectpass pass in basiceffect. currenttechnique. passes) {pass. begin (); device. vertices [0]. setsource (terrainvertexbuffer, 0, vertexpositionnormaltexture. sizeinbytes); device. indices = terrainindexbuffer; device. vertexdeclaration = myvertexdeclaration; device. drawindexedprimitives (primitivetype. trianglestrip, 0, 0, width * height, 0, width * 2 * (height-1)-2); pass. end ();} basiceffect. end ();

Use vertexbuffer and indexbuffer as the current buffer of the video card. Vertexdeclaration indicates the type of data required by the GPU and where necessary information is obtained from the data stream. Drawindexedprimitives: Draw trianglestrip, which processes all width * Height vertices and draws a total width * 2*(height-1)-2 triangles. To obtain the last value, you need to query the total number of indexes in the index array. Because you draw from a trianglestrip, the total number of vertices is reduced by 2.

Code

The last four lines of code in the loadcontent method generate all indexes and corresponding vertices. The normal data is added to the vertex, and the final data is stored in the vertex buffer and index buffer. Note that the loadheightmap method will be discussed later:

Protected override void loadcontent () {Device = graphics. graphicsdevice; basiceffect = new basiceffect (device, null); ccross = new coordcross (device); texture2d heightmap = content. load <texture2d> ("heightmap"); heightdata = loadheightdata (heightmap); grasstexture = content. load <texture2d> ("Grass"); myvertexdeclaration = new vertexdeclaration (device, vertexpositionnormaltexture. vertexelements); keywords [] terrainvertices = random (); int [] terrainindices = random (); terrainvertices = random (terrainvertices, terrainindices); createbuffers (terrainvertices, random );}

Create a vertex in the following method:

Private vertexpositionnormaltexture [] createterrainvertices () {int width = heightdata. getlength (0); int Height = heightdata. getlength (1); vertexpositionnormaltexture [] terrainvertices = new vertexpositionnormaltexture [width * Height]; int I = 0; For (INT z = 0; Z  

Create an index using the following methods:

Private int [] createterrainindices () {int width = heightdata. getlength (0); int Height = heightdata. getlength (1); int [] terrainindices = new int [(width) * 2 * (height-1)]; int I = 0; int z = 0; while (z  

The generatenormalsfortrianglestrip method adds the normal data to the vertex, while the createbuffers method saves the data to the video card:

Private void createbuffers (vertexpositionnormaltexture [] vertices, int [] indices) {terrainvertexbuffer = new vertexbuffer (device, vertexpositionnormaltexture. sizeinbytes * vertices. length, bufferusage. writeonly); terrainvertexbuffer. setdata (vertices); terrainindexbuffer = new indexbuffer (device, typeof (INT), indices. length, bufferusage. writeonly); terrainindexbuffer. setdata (indices );}

Finally, in the draw method, the terrain is drawn in trianglestrip mode:

Protected override void draw (gametime) {Device. clear (clearoptions. target | clearoptions. depthbuffer, color. cornflowerblue, 1, 0); ccross. draw (fpscam. viewmatrix, fpscam. projectionmatrix); // draw terrain int width = heightdata. getlength (0); int Height = heightdata. getlength (1); basiceffect. world = matrix. identity; basiceffect. view = fpscam. viewmatrix; basiceffect. projection = fpscam. projectionmatrix; basiceffect. texture = grasstexture; basiceffect. textureenabled = true; basiceffect. enabledefalightlighting (); basiceffect. directionallight0.direction = new vector3 (1,-1, 1); basiceffect. directionallight0.enabled = true; basiceffect. ambientlightcolor = new vector3 (0.3f, 0.3f, 0.3f); basiceffect. directionallight1.enabled = false; basiceffect. directionallight2.enabled = false; basiceffect. specularcolor = new vector3 (0, 0, 0); basiceffect. begin (); foreach (effectpass pass in basiceffect. currenttechnique. passes) {pass. begin (); device. vertices [0]. setsource (terrainvertexbuffer, 0, vertexpositionnormaltexture. sizeinbytes); device. indices = terrainindexbuffer; device. vertexdeclaration = myvertexdeclaration; device. drawindexedprimitives (primitivetype. trianglestrip, 0, 0, width * height, 0, width * 2 * (height-1)-2); pass. end ();} basiceffect. end (); base. draw (gametime );}
Read the heightdata array from an image

In most cases, you do not want to manually specify the heightdata array, but load it from an image. This method loads an image and maps the color of each pixel to a height value:

 private void loadheightdata (texture2d heightmap) {float minimumheight = 255; float maximumheight = 0; int width = heightmap. width; int Height = heightmap. height; color [] heightmapcolors = new color [width * Height]; heightmap. getdata 
  
    (heightmapcolors); heightdata = new float [width, height]; for (INT x = 0; x 
   
     maximumheight) maximumheight = heightdata [x, y] ;}for (INT x = 0; x 
    
   
  

The first part stores the intensity of each pixel's red channel in the heightdata array. The following code re-scales each value so that the values in the array are between 0 and 30.

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.