Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 30 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
30
Dung lượng
367,55 KB
Nội dung
T EXTURE COLORING It is possible to color your image textures at run time. This technique might be handy for a number of instances—maybe you need your texture to be darker and you can’t wait for the artist to fix it, so you decide to shade it in your code. Maybe you want to create a stone pattern; you could use the same image to draw all stones but alternate the shade to create more contrast on the surface. The Texture.fx shader is already able to apply colors, which are stored in the verti- ces, to any textured item. If a non-white color is stored in the vertices, the image in the texture will be shaded by this color. To demonstrate how this works, it helps to examine the vertex shader and pixel shader. The vertex shader input receives the color stored in the vertices. The user-de- fined struct that stores the vertex shader output stores this color information. The vertex shader output, by design, serves as the input for the pixel shader. This vertex shader code receives the color from the vertices that are set in your C# code and passes it to the pixel shader: void VertexShader(in VSinput IN, out VStoPS OUT){ OUT.position = mul(IN.position, wvpMatrix); // transform object // orient it in viewer OUT.color = IN.color; // send color to p.s. OUT.uv = IN.uv; // send uv's to p.s. } The pixel shader can only return colored pixels as output. On the first line of the shader, the texture is applied to each vertex using the tex2D() function, which uses the textureSampler filter and UV coordinates as input parameters. The pixel shader uses linear interpolation to shade and texture the area between the vertices. On the second line, this optional instruction is added, which multiplies the colored pixel by the color that is stored in the vertices. This modification, in effect, applies a color to the image texture: void PixelShader(in vsOutput IN, out psOutput OUT) { // apply texture to vertices using textureSampler filter OUT.color = tex2D(textureSampler, IN.uv); // apply color from v.s. – p.s. interpolates between verts OUT.color *= IN.color; } MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 128 129 Texture Example This example begins with either the MGHWinBaseCode project or the MGH360BaseCode project, which can be found at the BaseCode folder on this book’s website. This project already has textured ground and uses the Texture.fx file described earlier in this chapter. Aside from the shader already being present, this demonstration shows how to add in new textured objects from scratch. This demon- strates the texturing process from start to finish. Each surface will be transformed into place, and the accompanying textures will be applied to each of them. Part A of this example demonstrates how to add in the ground that is used in the base code. By the time Part B of this example is complete, a textured side wall and a textured back will also be visible. In Part C of this example, a tree texture with transparency will be added. This tree will use a “billboarding” technique, which allows it to always face the viewer—re- gardless of the camera’s angle. Figure 9-3 shows the billboard tree from different camera angles. CHAPTER 9 Texturing Your Game World FIGURE 9-3 Billboarding applied to tree so it always faces viewer Texture Example, Part A: Adding the Grass Texture To demonstrate how to load an opaque texture into any 3D game project, and to draw it, Part A of this example will include adding the grass texture that is used in the base code. If you are following the “Building the Base Code from Scratch” example from Chapter 17, you will need to follow the steps in this section. If you are working through this chapter and have already started with the MGHWinBaseCode project or the MGH360BaseCode project, you can skip this section and go to Part B. How- ever, if you are reading Chapter 9 for the first time, you should probably still read this section so that you can understand how the texture is set up from the very beginning. When you are applying textures in a 3D game, you require a shader that can han- dle the texture-mapping data. The Texture.fx code presented earlier in this chapter can do the job. This shader must be referenced from the Content node of your game project. You can find the Texture.fx file in the Shaders folder on this book’s website. To keep your different project files organized, create a Shaders folder under the Con- tent node and add your Texture.fx shader file there. In Figure 9-4, you can see your Texture.fx file referenced from the Solution Explorer. At the top of the game class, you require an Effect object to load and access your shader. EffectParameter objects are also required to allow you to set values in your shader from your C# game code. In this case, you will need two EffectParameter objects. The first EffectParameter, textureEffectWVP, allows you to define how your world will be seen by your MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 130 FIGURE 9-4 Shader reference in the Solution Explorer 131 camera. The second EffectParameter, textureEffectImage, allows you to set the texture that is applied against surfaces that are drawn from the shader: Effect textureEffect; // shader object EffectParameter textureEffectWVP; // cumulative matrix w*v*p EffectParameter textureEffectImage; // texture parameter The Microsoft XNA game project template automatically generates a folder named “Content” for loading your shaders and media. Also, the project template au- tomatically adds a line of code to set the Content.RootDirectory property to this directory. To load your texture shader when the program begins, and to set your game project references to the shader’s camera settings and texture variables, add these instructions to the InitializeBaseCode() method: textureEffect = Content.Load<Effect>("Shaders\\Texture"); textureEffectWVP = textureEffect.Parameters["wvpMatrix"]; textureEffectImage = textureEffect.Parameters["textureImage"]; When drawing objects that have textures, the GraphicsDevice needs to re- trieve data from the vertex variable in the proper format. A new VertexDeclaration object is declared in the module declarations section so that the graphics device can later retrieve the correct position, color, and UV data. private VertexDeclaration positionColorTexture; Later, in the InitializeBaseCode() method, you need more code to set the VertexDeclaration object to store the VertexPositionColorTexture type: positionColorTexture = new VertexDeclaration(graphics.GraphicsDevice, VertexPositionColorTexture.VertexElements); As you have seen when you run the base code, the ground is created using a square surface that is covered with a grass texture. The sides of the square are set to have the length equivalent to the constant BOUNDARY. If you are building the base code, a dec- laration for BOUNDARY is required at the top of the game class: private const float BOUNDARY = 16.0f; A Texture2D object is required to load and access the image file at run time, so a declaration for the texture object is also needed at the top of the game project: private Texture2D grassTexture; CHAPTER 9 Texturing Your Game World MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 132 The grass.jpg image will be used to texture the ground. This file can be found in the Images folder in the download for this book. It is loaded in your project using the Load() method when the program begins. The code here works under the assump- tion that you have created an Images folder under the Content node in the Solution Explorer and you have copied the grass.jpg file to the Images folder, so it is referenced from there. To create an Images folder, right-click the Content node in the Solution Explorer and then click Add New Folder. To reference the image file from there, right-click the Images folder, select Add, and then navigate to the grass.jpg file and select it. Add this instruction to load the grass texture inside the LoadContent() method: grassTexture = Content.Load<Texture2D>("Images\\grass"); You will need an array of vertices to store the position, color, and UV tex- ture-mapping data. The declaration is made at the top of the game class so it can be initialized and then used for drawing the ground. VertexPositionColorTexture[] groundVertices = new VertexPositionColorTexture[4]; Next, add InitializeGround() to the game class to store the vertices with po- sition, color, and UV coordinates. For the ground vertices, the UV coordinates are set to tile the image ten times along the horizontal and vertical. The image has been de- signed specifically to create a repeating pattern that is not easily detected by the na- ked eye. Tiling the image allows it to be drawn with a higher resolution of detail. private void InitializeGround(){ const float BORDER = BOUNDARY; Vector2 uv = new Vector2(0.0f, 0.0f); Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f); Color color = Color.White; // top left uv.X= 0.0f; uv.Y= 0.0f; pos.X=-BORDER; pos.Y=0.0f; pos.Z=-BORDER; groundVertices[0] = new VertexPositionColorTexture(pos, color, uv); // bottom left uv.X= 0.0f; uv.Y=10.0f; pos.X=-BORDER; pos.Y=0.0f; pos.Z=BORDER; groundVertices[1] = new VertexPositionColorTexture(pos, color, uv); // top right uv.X=10.0f; uv.Y= 0.0f; pos.X= BORDER; pos.Y=0.0f; pos.Z=-BORDER; groundVertices[2] = new VertexPositionColorTexture(pos, color, uv); // bottom right 133 CHAPTER 9 Texturing Your Game World uv.X=10.0f; uv.Y=10.0f; pos.X= BORDER; pos.Y=0.0f; pos.Z=BORDER; groundVertices[3] = new VertexPositionColorTexture(pos, color, uv); } To set up the ground vertices when the program begins, add the call to InitializeGround() inside Initialize(): InitializeGround(); When rendering the new textured surfaces, you need to select the correct shader, but it is possible to use more than one shader for drawing. Just be certain of two things: When an effect is selected, all code for rendering should be triggered between the texture shader effect’s Begin() and End() methods. The shader effect’s End() method must be executed before another shader effect begins. When you are setting parameters in the shader, the more performance-friendly and common approach is to set these parameters before Begin() is called for the Effect object. Effect.Begin() sets all of the render states. However, if you need to set an effect parameter after Begin(), you would need to finalize this state by calling Effect.CommitChanges() or it will not be set. While the shader is active, you can then specify the vertex type and call DrawUserPrimitives() to draw your textured surface. DrawUserPrimitives() in this case must specify a textured vertex type. Then the primitive type, vertices, vertex offset, and number of primitive objects are supplied as parameters to this method. Add TextureShader() to your game class to trigger use of the Texture.fx shader and to draw your textured objects with it. If you are us- ing a prebuilt version of the base code, this project already contains this method: private void TextureShader(PrimitiveType primitiveType, VertexPositionColorTexture[] vertexData, int numPrimitives){ textureEffect.Begin(); // begin using Texture.fx textureEffect.Techniques[0].Passes[0].Begin(); // set drawing format and vertex data then draw surface graphics.GraphicsDevice.VertexDeclaration = positionColorTexture; graphics.GraphicsDevice.DrawUserPrimitives <VertexPositionColorTexture>( primitiveType, vertexData, 0, numPrimitives); textureEffect.Techniques[0].Passes[0].End(); textureEffect.End(); // stop using Textured.fx } MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 134 The code needed to draw textured surfaces, like your grass-covered ground, fol- lows the same steps that are taken when drawing surfaces with only color and posi- tion coordinates. The main differences are in step 4 and step 5. In step 4, an EffectParameter object is used to assign a texture in the shader. In step 5, the TextureShader() routine is called to select the appropriate shader and to draw with it. Add DrawGround() to your game class to implement this routine: private void DrawGround(){ // 1: declare matrices Matrix world, translation; // 2: initialize matrices translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f); // 3: build cumulative world matrix using I.S.R.O.T. sequence // identity, scale, rotate, orbit(translate & rotate), translate world = translation; // 4: set shader parameters textureEffectWVP.SetValue(world*cam.viewMatrix*cam.projectionMatrix); textureEffectImage.SetValue(grassTexture); // 5: draw object - primitive type, vertex data, # primitives TextureShader(PrimitiveType.TriangleStrip, groundVertices, 2); } To trigger the drawing, DrawGround() must be called from inside Draw(): DrawGround(); If you are working through the Chapter 17 exercise, “Building the Base Code from Scratch,” and have completed these steps, you are now done adding all of the code re- quired for texturing your surfaces. You can view your textured ground when you run the project now. Texture Example, Part B: Adding Two More Opaque Textures In this next portion of the example, two wall textures will be applied to the surfaces to create a side wall and back wall. To store the new wall images, you will require Texture2D objects, so declarations are needed for the wall textures in the game class declarations area: private Texture2D backWallTexture, sideWallTexture; 135 CHAPTER 9 Texturing Your Game World The backwall.jpg and sidewall.jpg images will be used to texture both walls. They can be found in the Images folder on this book’s website. They are loaded in your project using the Load() method when the program begins. Add these two image files to the Content\Images directory of your project and add them to your project us- ing the Solution Explorer. If the Images folder does not exist, right-click the Content node in your game project and select Add | New Folder to launch the dialog that al- lows you to add an Images folder. Then, once you have an Images folder under the Content node, right-click it, select Add, and then navigate and select each of the im- age files. Add these instructions to load each texture inside the LoadContent() method: backWallTexture = Content.Load<Texture2D>("Images\\backwall"); sideWallTexture = Content.Load<Texture2D>("Images\\sidewall"); The vertices used to draw the walls store position, color, and UV coordinates to map the images onto this surface. An array of VertexPositionColorTexture coordinates is needed: private VertexPositionColorTexture[] surfaceVertex = new VertexPositionColorTexture[4]; The method InitializeSurface() initializes the vertices with the position, color, and UV coordinates that you will use to create a rectangular surface for each wall. The UV coordinates will be mapped with U along the X axis and with V along the Z axis. Add InitializeSurface() to the game class to create these vertices with po- sition, color, and UV coordinates: private void InitializeSurface(){ Vector2 uv = new Vector2(0.0f, 0.0f); Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f); Color color = Color.White; // A. top left, B. bottom left, C. top right, D. bottom right uv.X=0.0f; uv.Y=0.0f; pos.X=-BOUNDARY; pos.Y=0.0f; pos.Z=-BOUNDARY; surfaceVertex[0] = new VertexPositionColorTexture(pos, color, uv);//A uv.X=0.0f; uv.Y=1.0f; pos.X=-BOUNDARY; pos.Y=0.0f; pos.Z=BOUNDARY; surfaceVertex[1] = new VertexPositionColorTexture(pos, color, uv);//B uv.X=1.0f; uv.Y=0.0f; pos.X=BOUNDARY; pos.Y=0.0f; pos.Z=-BOUNDARY; surfaceVertex[2] = new VertexPositionColorTexture(pos, color, uv);//C uv.X=1.0f; uv.Y=1.0f; pos.X=BOUNDARY; pos.Y=0.0f; pos.Z=BOUNDARY; surfaceVertex[3] = new VertexPositionColorTexture(pos, color, uv);//D } MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 136 The data for the surface is assigned at the beginning of the program. To do this, call InitializeSurface() from the Initialize() method: InitializeSurface(); You will reuse the DrawSurfaces() method to draw the side wall, back wall, and tree. An identifier for the surface to be drawn is passed as an argument to DrawSurfaces(). These surface-identifier declarations have to be added to the top of the game class so they can be recognized in the methods that use them: const int SIDE = 0; const int BACK = 1; Next, you must add DrawSurfaces() to the game class to transform each wall into position, to apply a texture, and to render each textured surface using the same set of vertices. Each time a surface is drawn, a switch selects the specific transformations and texture for the surface. When the texture is selected, it is set in the shader using the EffectParameter object’s SetValue() method. Once the cumulative transfor- mation has been set, the WorldViewProjection matrix value is set in the texture shader using another EffectParameter object, textureEffectWVP. The world-view projection-matrix is used in the shader to position each surface so that it can be seen properly by the camera. private void DrawSurfaces(int surfaceNum) { // shrink walls and tree then position them relative to world size const float SCALAR = 0.08f; float edge = SCALAR * BOUNDARY; float Z = -0.5f * BOUNDARY; // 1: declare matrices Matrix world, scale, translation, rotationY, rotationX; // 2: initialize matrices scale = Matrix.CreateScale(SCALAR, SCALAR, SCALAR); rotationX = Matrix.CreateRotationX(MathHelper.Pi/2.0f); rotationY = Matrix.CreateRotationY(0.0f); translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f); switch (surfaceNum){ // set transformations and texture for each surface instance case BACK: 137 CHAPTER 9 Texturing Your Game World translation = Matrix.CreateTranslation(0.0f, edge, Z); textureEffectImage.SetValue(backWallTexture); break; case SIDE: rotationY = Matrix.CreateRotationY(MathHelper.Pi/2.0f); translation = Matrix.CreateTranslation(-edge, edge, Z + edge); textureEffectImage.SetValue(sideWallTexture); break; } // 3: build cumulative world matrix using I.S.R.O.T. sequence // identity, scale, rotate, orbit(translate & rotate), translate world = scale * rotationX * rotationY * translation; // 4: set shader parameters textureEffectWVP.SetValue(world*cam.viewMatrix*cam.projectionMatrix); // 5: draw object - primitive type, vertices, # primitives TextureShader(PrimitiveType.TriangleStrip, surfaceVertex, 2); } With your DrawSurfaces() method in the game class, you can now call it twice to draw each textured wall. These call statements, of course, belong in the Draw() method: DrawSurfaces(SIDE); DrawSurfaces(BACK); When you compile and run the program, the output will show the two textured walls and textured ground. Texture Example, Part C: Transparent Textures This example shows how to draw a tree without the background pixels. The exam- ple continues with the code created for Part B, but some extra setup is required to load the tree texture. You will need a Texture2D object declaration at the top of the game class to store the tree image so that it can be referenced throughout the class: private Texture2D treeTexture; The tree.png file used to create the tree texture must be loaded when the program begins. Once your tree.png file has been added to the Content\Images directory of [...]...138 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE your project and is referenced in the Solution Explorer, this file can be loaded in your XNA code using the LoadContent() method: treeTexture = Content.Load("Images\\tree"); An identifier definition is added (at the top of the game class) so it can be used in the DrawSurfaces() method to... at the origin Also, the object must be centered in the image (see Figure 9-5) FIGURE 9-5 Objects within billboarded images must be centered on the X axis 139 Texturing Your Game World C H A P T E R 140 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE Billboarding Example This example begins with the solution from the transparency code in Part C of the previous example In Chapter 8, logic is used to rotate... Planetside website for licensing and update details FIGURE 10-3 Moving ceiling and draped walls with a stationary ground of skybox 145 Adding Skies and Horizons to Your Levels C H A P T E R 146 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE Using Terragen to Create a Skybox This demonstration explains the steps needed to create the six images used to make a skybox When you open Terragen, the application launches... Height Above Surface Target Position 4200.m Fixed Height Above Surface Yes Camera Position and Target Position Settings 0.0m 147 Adding Skies and Horizons to Your Levels C H A P T E R 148 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE Select the Land and Sky options in Terragen’s main window to generate ground and cloudscapes for your scenery Checking the Land and Sky Options Creating Each Image: Assigning... the texture using grassTexture with an instruction to use the groundTexture image: textureEffectImage.SetValue(groundTexture); 149 Adding Skies and Horizons to Your Levels C H A P T E R 150 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE If you try the program now, you will see the same 3D world, but this time the ground will be covered with the texture you created in Terragen Once the ground is properly rendered... camera = Matrix.CreateTranslation(cam.position.X, 0.0f, cam.position.Z); // 2: set transformations and also texture for each wall 151 Adding Skies and Horizons to Your Levels C H A P T E R 152 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE for (int i = 0; i < 5; i++){ switch (i){ case 0: // BACK translation = Matrix.CreateTranslation(0.0f, DROP, EDGE); textureEffectImage.SetValue(backTexture); break; case 1:... 10-4 Terragen main window 1 0 A higher pixel count enables better-quality images, but higher pixel counts also reduce the memory available This may lower the frame rate in your game because it takes more processing power for the game engine to render the sky The camera’s zoom magnification must be set to 1 to ensure that the images are scaled properly when creating each snapshot for the sides of the skybox... done, you will see all of your skybox images referenced under the Images folder in the Solution Explorer Now that your images are referenced in your game project; the next step is to declare variables for storing them These must be declared at the top of your game class Skybox Code Example Texture2D frontTexture, backTexture, groundTexture, leftTexture, rightTexture, skyTexture; To assign these images... direction settings as Head, Pitch, and Bank attributes These attributes set direction on the X, Y, and Z planes Later in this chapter, a code example shows you how to load and display Terragen images in your game project For the code example to work properly, you must generate the named image files and their corresponding Head, Pitch, and Bank properties with the settings summarized in Table 10-2 Setting Up... left.bmp 90 0 0 back.bmp 180 0 0 right.bmp –90 0 0 sky.bmp –90 90 0 ground2.bmp –90 –90 0 Camera Direction Settings for Each Image of the Skybox 1 0 and ground2.bmp images You can now load these into your game project and create the skybox This example takes your new images and renders them to create a seamless sky with an endless horizon For this example, you can use either the MGHWinBaseCode project or . texture object is also needed at the top of the game project: private Texture2D grassTexture; CHAPTER 9 Texturing Your Game World MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 132 The grass.jpg image will. = Blend.InverseSourceAlpha; DrawSurfaces(TREE); graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false; MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 138 139 With the right adjustments to your game application, you will now be able to look through. Figure 9-5). CHAPTER 9 Texturing Your Game World FIGURE 9-5 Objects within billboarded images must be centered on the X axis. MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 140 Billboarding Example This