Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 60 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
60
Dung lượng
1,33 MB
Nội dung
The type of texture we need is for a regular plane. Perlin has support for seamless texture generation and can even generate sphere textures with the correct stretching applied to the poles so the texture looks correct wrapped on a sphere. (We used this technique in Starflight—The Lost Colony [www .starflightgame.com] to generate random planets.) noise::utils::NoiseMapBuilderPlane heightMapBuilder; Next, we tell our heightmap object to use the Perlin object for it’s random algorithms, set the destination to the noise map object, and set the width and height of the data set. heightMapBuilder.SetSourceModule( perlin ); heightMapBuilder.SetDestNoiseMap( noiseMap ); heightMapBuilder.SetDestSize( width, length ); Finally, we have to tell the heightmap object where on the Cartesian coordinate system it should base its calculations for tiling purposes. Tiling is an advanced feature that I won’t get into here because we just don’t need it, but I encourage you to look into it if you want to generate a huge game world without consuming huge amounts of memory. After setting the bounds to the upper- right quadrant, we can then build the random data. heightMapBuilder.SetBounds( 0.0, 5.0, 0.0, 5.0 ); heightMapBuilder.Build(); At this point, we have the data needed to apply height data to a terrain vertex buffer. To get at the data, access the GetValue() function in NoiseMap. float value = noiseMap.GetValue(x,z); The value coming from Perlin will be fairly small, so it’s normally multiplied by the desired height value to bring the terrain up from the sub-1.0 range into a tangible height. Finally, if you would like to save the height data to a texture, libnoise can do that as well. Just to be clear, it’s normal to add the “noise” namespace to simplify variable declarations, so I’ll include it here for reference. using namespace noise; utils::RendererImage renderer; utils::Image image; renderer.SetSourceNoiseMap(noiseMap); 340 Chapter 12 n Environmental Concerns: Recycling Terrain Polygons Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com renderer.SetDestImage(image); renderer.Render(); utils::WriterBMP writer; writer.SetSourceImage (image); writer.SetDestFilename("texture.bmp"); writer.WriteDestFile(); Terrain Generation A terrain system is basically a vertex buffer filled with two-triangle quads that share four vertices. The vertex buffer has a defined flexible vertex format (FVF) with vertex position, texture coordinates, and lighting normal angles supported. TERRAIN_FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1; The texture coordinate property tells me that the terrain requires at least one texture to render properly. We could generate the ground textures needed here with Perlin very easily, but texture theory is a bit beyond the goals for this chapter so I have just selected three interesting textures that will make the terrain look vaguely like an alien world with a curious pattern in greens and browns. We will supply the terrain system with three textures to accommodate three height levels (water, grass, and hills). Our height data is nothing more complex than an array of floats initialized to zero: heightMap = new float[width * length]; memset(heightMap, 0, sizeof(float)*width*length); What you do with heightMap after this point will be based on the type of environment needed for the game’s genre! We’ve seen how to generate height data from Perlin, but the real question is this: how do you go from a bunch of floats to a rendered terrain mesh? First, to build the terrain system’s vertex buffer we will use patches that will simplify texture mapping and also divide the terrain system into a grid, which will also be helpful for gameplay code. Assuming width and length represent the dimensions of the terrain (oriented “flat” on the Z-axis, out and away from the origin), we can divide up the terrain system into a series of patches, each represented by a rectangle or quad. Creating Terrain 341 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com for (int y=0;y<numPatches;y++) { for (int x=0;x<numPatches;x++) { RECT r = { (int)(x * (width - 1) / (float)numPatches), (int)(y * (length - 1) / (float)numPatches), (int)((x+1) * (width - 1) / (float)numPatches), (int)((y+1) * (length - 1) / (float)numPatches) }; Patch *p = new Patch(); p->CreateMesh(*heightMap, r); patches.push_back(p); } } For each patch, a mesh is created with the D3DXCreateMeshFVF function, which generates a mesh based on a defined FVF format (in this case, we’ll be using TERRAIN_FVF, previously defined). The heightmap data is then used to build the vertex buffer. TerrainVertex* ver = 0; mesh->LockVertexBuffer(0,(void**)&ver); for(int z=source.top,z0=0; z<=source.bottom; z++,z0++) for(int x=source.left,x0=0; x<=source.right; x++,x0++) { D3DXVECTOR3 pos = D3DXVECTOR3( (float)x, hm.heightMap[x + z * hm.width], (float)-z); D3DXVECTOR2 uv = D3DXVECTOR2(x * 0.2f, z * 0.2f); ver[z0 * (width + 1) + x0] = TerrainVertex(pos, uv); } mesh->UnlockVertexBuffer(); Afterward, an index buffer and attributes are added to improve rendering performance. After the vertex buffer has been filled with height data, and the vertices positioned correctly into patch-based quads, then the whole thing is normalized with D3DXComputeNormals. Regarding the attributes, this is an important section of the code because it determines which texture is used on the terrain based on height. Take a look at the if statement, currently with hard-coded ranges. The water level is at a height of 0.0, while the grass level goes 342 Chapter 12 n Environmental Concerns: Recycling Terrain Polygons Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com up to 60% of the height, and the final 40% of the terrain is set to the final texture (normally granite or other rocky-looking texture). A good improvement to this code will be to make those ranges definable, and perhaps even add in support for more than just three. for(int z = source.top; z < source.bottom; z++) for(int x = source.left; x < source.right; x++) { //calculate vertices based on height int subset; if (hm.heightMap[x + z * hm.width] == 0.0f) subset = 0; else if (hm.heightMap[x+z*hm.width] <= hm.maxHeight*0.6f) subset = 1; else subset = 2; att[a++] = subset; att[a++] = subset; } Terrain Class The snippets of code presented thus far will be easier to re-use in the form of a class—namely, the Terrain class, which is now part of the Octane engine if you peruse the projects included with this chapter. This Terrain class makes use of two helper structs ( Heightmap and Patch) to help manage heightmap and mesh generation for the terrain. #include " \Engine\Engine.h" struct TerrainVertex { D3DXVECTOR3 position, normal; D3DXVECTOR2 uv; TerrainVertex(){} TerrainVertex(D3DXVECTOR3 pos, D3DXVECTOR2 texuv) { position = pos; uv = texuv; normal = D3DXVECTOR3(0.0f, 1.0f, 0.0f); } }; Creating Terrain 343 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com struct Heightmap { int width,length; float maxHeight; float *heightMap; Heightmap(int _width, int _length, float _depth); ~Heightmap(); void Release(); bool CreateRandom(int seed, float frequency, float persistence, int octaves, bool water=false); }; struct Patch { ID3DXMesh *mesh; Patch(); ~Patch(); void Release(); bool CreateMesh(Heightmap &hm, RECT source); void Render(int texture); }; class Terrain { private: int p_width,p_length; int p_numPatches; int p_maxHeight; Heightmap *p_heightMap; std::vector<Patch*> p_patches; std::vector<IDirect3DTexture9*> p_textures; public: Terrain(); virtual ~Terrain(); void Init(int width,int length,int depth,std::string tex1, std::string tex2,std::string tex3); void Flatten(float height); void CreateRandom(float freq=0.8f, float persist=0.5f, 344 Chapter 12 n Environmental Concerns: Recycling Terrain Polygons Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com int octaves=5, bool water=false); void Render( Octane::Effect *effect ); void BuildHeightmap(); int getWidth() { return p_width; } int getLength() { return p_length; } float getHeight(int x,int z); void Release(); }; The terrain system is actually comprised of three components, as you saw in the header listing: the Heightmap and Patch structs and the Terrain class. It is possible to combine all into just the Terrain class but the functionality of the terrain system is cleaner in these separate parts (using helper structs for each component of the terrain system). First, let’s see how the Heightmap struct works. const DWORD TERRAIN_FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1; Heightmap::Heightmap(int _width, int _length, float _depth) { try { width = _width; length = _length; maxHeight = _depth; heightMap = new float[width * length]; memset(heightMap, 0, sizeof(float)*width*length); } catch( ) { debug ( "Error creating Heightmap"; } } Heightmap::~Heightmap() { Release(); } void Heightmap::Release() { Creating Terrain 345 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (heightMap != NULL) delete [] heightMap; heightMap = NULL; } bool Heightmap::CreateRandom(int seed, float frequency, float persistence, int octaves, bool water) { //init perlin noise library module::Perlin perlin; perlin.SetSeed( seed ); perlin.SetFrequency( frequency ); perlin.SetOctaveCount( octaves ); perlin.SetPersistence( persistence ); //build the heightmap utils::NoiseMap noiseMap; utils::NoiseMapBuilderPlane heightMapBuilder; heightMapBuilder.SetSourceModule( perlin ); heightMapBuilder.SetDestNoiseMap( noiseMap ); heightMapBuilder.SetDestSize( width, length ); heightMapBuilder.SetBounds( 0.0, 5.0, 0.0, 5.0 ); heightMapBuilder.Build(); //copy Perlin generated height data to our heightmap for(int z=0; z<length; z++) { for(int x=0; x<width; x++) { //get height value from perlin float value = noiseMap.GetValue(x,z) * maxHeight; //cap negatives to 0 for water if (water) { if (value < 0.0f) value = 0.0f; } //copy height data to our terrain heightMap[x + z * width] = value; } } 346 Chapter 12 n Environmental Concerns: Recycling Terrain Polygons Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com return true; } Now we have the Patch struct with its primary purpos e of housing the mesh data for the terrain system. The entire terrain system is made up of these patches, and this is the object actually drawing when the terrain is rendered. Patch::Patch() { mesh = NULL; } Patch::~Patch() { Release(); } void Patch::Release() { if(mesh != NULL) mesh->Release(); mesh = NULL; } bool Patch::CreateMesh(Heightmap &hm, RECT source) { if(mesh != NULL) { mesh->Release(); mesh = NULL; } try { int width = source.right - source.left; int height = source.bottom - source.top; int nrVert = (width + 1) * (height + 1); int nrTri = width * height * 2; if(FAILED(D3DXCreateMeshFVF(nrTri, nrVert, D3DXMESH_MANAGED, TERRAIN_FVF, g_engine->getDevice(), &mesh))) { Creating Terrain 347 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com debug ( "Error creating patch mesh\n"; return false; } //create terrain vertices TerrainVertex* ver = 0; mesh->LockVertexBuffer(0,(void**)&ver); for(int z=source.top, z0 = 0;z<=source.bottom;z++, z0++) for(int x=source.left, x0 = 0;x<=source.right;x++, x0++) { D3DXVECTOR3 pos = D3DXVECTOR3( (float)x, hm.heightMap[x + z * hm.width], (float)-z); D3DXVECTOR2 uv = D3DXVECTOR2(x * 0.2f, z * 0.2f); ver[z0 * (width + 1) + x0] = TerrainVertex(pos, uv); } mesh->UnlockVertexBuffer(); //calculate terrain indices WORD* ind = 0; mesh->LockIndexBuffer(0,(void**)&ind); int index = 0; for(int z=source.top, z0 = 0;z<source.bottom;z++, z0++) for(int x=source.left, x0 = 0;x<source.right;x++, x0++) { //triangle 1 ind[index++] = z0 * (width + 1) + x0; ind[index++] = z0 * (width + 1) + x0 + 1; ind[index++] = (z0+1) * (width + 1) + x0; //triangle 2 ind[index++] = (z0+1) * (width + 1) + x0; ind[index++] = z0 * (width + 1) + x0 + 1; ind[index++] = (z0+1) * (width + 1) + x0 + 1; } mesh->UnlockIndexBuffer(); 348 Chapter 12 n Environmental Concerns: Recycling Terrain Polygons Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com //set attributes DWORD *att = 0, a = 0; mesh->LockAttributeBuffer(0,&att); for(int z=source.top;z<source.bottom;z++) for(int x=source.left;x<source.right;x++) { //calculate vertices based on height int subset; if (hm.heightMap[x + z * hm.width] == 0.0f) subset = 0; else if (hm.heightMap[x + z * hm.width] <= hm.maxHeight * 0.6f) subset = 1; else subset = 2; att[a++] = subset; att[a++] = subset; } mesh->UnlockAttributeBuffer(); //compute normal for the terrain D3DXComputeNormals(mesh, NULL); } catch( ) { debug ( "Error creating patch mesh\n"; return false; } return true; } void Patch::Render(int texture) { if (mesh != NULL) mesh->DrawSubset(texture); } Creating Terrain 349 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... Vector3 balls[100]; void game_ end() { if (fire) delete fire; if (ball) delete ball; if (terrain) delete terrain; if (camera) delete camera; if (effect) delete effect; if (font) delete font; } bool game_ preload() { g _engine- >setAppTitle("Terrain Following Demo"); g _engine- >setScreen(1024 ,76 8,32,false); g _engine- >setBackdropColor(D3DCOLOR_XRGB(30,30,0)); return true; } bool game_ init(HWND hwnd) { font... things in a single chapter, they are being explored as the engine continues to evolve beyond the version shared here Visit the forum at www.jharbour.com/forum to learn about new things happening References 1 “Graphics Engine: RealSpace”; Moby Games http://www.mobygames.com/ game- group/graphics -engine- realspace 2 Granberg, Carl Programming an RTS Game with Direct3D Charles River Media, 2006 Simpo PDF... PDF Merge and Split Unregistered Version - http://www.simpopdf.com Creating Terrain 355 if (font) delete font; } bool game_ preload() { g _engine- >setAppTitle("Terrain Demo"); g _engine- >setScreen(1024 ,76 8,32,false); g _engine- >setBackdropColor(D3DCOLOR_XRGB(30,0,30)); return true; } bool game_ init(HWND hwnd) { font = new Font("Arial",14); camera = new Camera(); camera->setPosition(0,10,10); camera->setTarget(0,10,0);... (for the so-called “ragdoll physics” often applied to game characters) The way animation is created is usually with live motion capture Motion capture (“mo-cap”) data is usually shared by many meshes in a game, and generic libraries of mo-cap animations are recycled Some game engines will dynamically use mo-cap data to animate characters Other game engines will “bake” the mo-cap animation directly into... the “asset pipeline.” In a professional game development environment, tools are built to automate the asset pipeline as much as possible, since it is otherwise a very manual, time-consuming process Exporters are written for the modeling software, and tools are written to convert the assets into a format used by the game engine The animation system of a game engine will usually “interpolate” from one... Polygons matWorld.Translate( -terrain->getWidth()/2,0,0 ); effect->setWorldMatrix( matWorld ); //render the terrain terrain->Render( effect ); } void game_ update(float deltaTime) { camera->Update(); } void game_ render2d() { ostringstream out; out ( "Core: " ( g _engine- >getCoreFrameRate() ( endl; out ( "Camera: " ( camera->getTarget().x ( "," ( camera->getTarget().y ( "," ( camera->getTarget().z ( endl; out... font->Print(0,0, out.str()); } void game_ event( IEvent* e ) { switch( e->getID() ) { case EVENT_KEYPRESS: { KeyPressEvent* evt = (KeyPressEvent*) e; switch(evt->keycode) { case DIK_ESCAPE: g _engine- >Shutdown(); break; case DIK_SPACE: terrain->CreateRandom(0.8f,0.5f,5,true); break; Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Walking on Terrain 3 57 case case case case case case... false); //load terrain textures IDirect3DTexture9* levels[3]; for (int n=0; ngetDevice(),tex1.c_str(),&levels[0]); D3DXCreateTextureFromFile(g _engine- >getDevice(),tex2.c_str(),&levels[1]); D3DXCreateTextureFromFile(g _engine- >getDevice(),tex3.c_str(),&levels[2]); for (int n=0; nUpdate(); //move the balls for (int n=0; ngetLength(); if (balls[n].z < -size || balls[n].z > 0) { ballVel *= -1; balls[n].z += ballVel * deltaTime; } int actualx = balls[n].x; int actualz = -balls[n].z; balls[n].y = terrain->getHeight(actualx,actualz) + 1.0f; } } void game_ render2d() . font; } bool game_ preload() { g _engine- >setAppTitle("Terrain Demo"); g _engine- >setScreen(1024 ,76 8,32,false); g _engine- >setBackdropColor(D3DCOLOR_XRGB(30,0,30)); return true; } bool game_ init(HWND. terrain terrain->Render( effect ); } void game_ update(float deltaTime) { camera->Update(); } void game_ render2d() { ostringstream out; out ( "Core: " ( g _engine- >getCoreFrameRate() ( endl; out. ( Heightmap and Patch) to help manage heightmap and mesh generation for the terrain. #include " Engine Engine.h" struct TerrainVertex { D3DXVECTOR3 position, normal; D3DXVECTOR2 uv; TerrainVertex(){} TerrainVertex(D3DXVECTOR3