198
1 IWKS 3400 LAB 8 1 JK Bennett This lab will build upon what we learned in Lab 7, and will present more advanced terrain techniques. We will create terrain that is very much like what you could use in a real game. You should complete Lab 7 before starting Lab 8. In this Lab we will cover: First-person mouse camera Multitexturing with high resolution textures close to the camera Realistic, moving water with reflections-refractions Bump mapping Specular reflections Perlin noise Shape-changing clouds HLSL cylindrical billboarding Region growing You will need to load several files to complete this lab. Download and unzip this file to obtain them. If this link does not work for some reason, here is the hard link: http://inworks.ucdenver.edu/jkb/IWKS3400/labs/Lab8_Mono_Files.zip. We will start with code that is almost identical to the final version of the code of Lab 7 (cleaned up a bit, and with a few bells and whistles removed, but that is all). This file is named Lab8_Mono_Starting_Game1.cs. It is worth reading this file to see what has changed (and why), and to learn, for example, how to change to a HiDef grapgics profile in a way that handles a current (Version 3.7) bug in MonoGame. Create a new Windows 4.0 game project, and replace the contents of Game1.cs with the contents of Lab8_Mono_Starting_Game1.cs (located in the directory that you just downloaded). Make sure this file is named Game1.cs in your project. If necessary, change the namespace declaration in Program.cs to Lab8_Mono. Add the following files to the MonoGame Content Pipeline from the Lab8_Mono_Files folder: effects.fx (after renaming from Lab8_Mono_Starting_effects.fx) heightmap2.bmp Make sure the result compiles, and produces a result similar to that of Lab 7 (albeit with a different terrain). You should be able to rotate the terrain (although the code that makes the rotation axis the center of the terrain has been removed so as not to cause problems later) with the PageUp and PageDown keys. Creating a Movable Camera 1 This lab is derived substantially from a series of excellent on-line XNA tutorials created by Riemer Grootjans. You can find his web site at http://www.riemers.net/. The code and content in Riemer’s tutorials have been modified for MonoGame (Verion 3.7) jkb, Oct. 2018.

IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

1

IWKS 3400 LAB 81

JK Bennett

This lab will build upon what we learned in Lab 7, and will present more advanced terrain techniques.

We will create terrain that is very much like what you could use in a real game. You should complete

Lab 7 before starting Lab 8. In this Lab we will cover:

First-person mouse camera

Multitexturing with high resolution textures close to the camera

Realistic, moving water with reflections-refractions

Bump mapping

Specular reflections

Perlin noise

Shape-changing clouds

HLSL cylindrical billboarding

Region growing

You will need to load several files to complete this lab. Download and unzip this file to obtain them. If

this link does not work for some reason, here is the hard link:

http://inworks.ucdenver.edu/jkb/IWKS3400/labs/Lab8_Mono_Files.zip.

We will start with code that is almost identical to the final version of the code of Lab 7 (cleaned up a bit,

and with a few bells and whistles removed, but that is all). This file is named

Lab8_Mono_Starting_Game1.cs. It is worth reading this file to see what has changed (and why), and to

learn, for example, how to change to a HiDef grapgics profile in a way that handles a current (Version

3.7) bug in MonoGame.

Create a new Windows 4.0 game project, and replace the contents of Game1.cs with the contents of

Lab8_Mono_Starting_Game1.cs (located in the directory that you just downloaded). Make sure this file

is named Game1.cs in your project. If necessary, change the namespace declaration in Program.cs to

Lab8_Mono.

Add the following files to the MonoGame Content Pipeline from the Lab8_Mono_Files folder:

effects.fx (after renaming from Lab8_Mono_Starting_effects.fx)

heightmap2.bmp

Make sure the result compiles, and produces a result similar to that of Lab 7 (albeit with a different

terrain). You should be able to rotate the terrain (although the code that makes the rotation axis the center

of the terrain has been removed so as not to cause problems later) with the PageUp and PageDown keys.

Creating a Movable Camera

1 This lab is derived substantially from a series of excellent on-line XNA tutorials created by Riemer Grootjans. You can find his

web site at http://www.riemers.net/. The code and content in Riemer’s tutorials have been modified for MonoGame (Verion 3.7)

– jkb, Oct. 2018.

Page 2: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

2

Let’s first add a moveable camera to our scene, so later on we can easily move it around our terrain.

Generally, we will want a first-person camera to rotate around the up vector (turning) and around the side

vector (looking up-down). Therefore, instead of storing a rotation matrix, we will simply store the rotation

values around the side and up vector, next to the position of the camera. To accomplish this, add the

following constants and instance variables to the relevant Game1 class regions. Regions are a mechanism

provided in C# to organize source code into sections that contain related code. These regions can then be

collapsed or expanded when desired. Regions are created by defining the beginning and end of each

section, as shown below.

#region Constants

// Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; #endregion #region Game1 Instance Vars //...existing variables Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; #endregion

The two constant values will indicate how fast we want our camera to respond to mouse and keyboard

input.

We will start by creating the UpdateViewMatrix method, which creates a view matrix based upon the

current camera position and rotation values. As you recall, to create the view matrix, we need the camera

position, a point where the camera is pointed, and the vector that’s considered to be ‘up’ by the camera.

These two last vectors depend on the rotation of the camera, so we first need to construct the camera

rotation matrix based on the current rotation values. Put this code in the Camera Methods region below

SetUpCamera():

private void UpdateViewMatrix()

{ Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

Page 3: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

3

viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); }

The first line creates the camera rotation matrix, by combining the rotation around the X axis (looking up-

down) with the rotation around the Y axis (looking left-right).

As the target point for our camera, we take the position of our camera, plus the (0,0,-1) ‘forward’ vector.

We need to transform this forward vector with the rotation of the camera, so it becomes the forward

vector of the camera. We find the ‘Up’ vector the same way: by transforming it with the camera rotation.

With these vectors known, it’s easy to construct the viewMatrix. Do this by replacing the code in

SetUpCamera() with the following code:

UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f);

Next, we will make our camera react to user input. We want our camera to go forward/backward or pan

left/right when we press the corresponding arrow buttons on the keyboard. Camera rotation will be

directed by mouse input. We will create a method for this that updates at the frame rate, so the camera

will turn at a speed not dependent upon the computer speed. Put this code in the Camera Methods region

as well:

private void ProcessInput(float amount)

{ Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); }

This method reads the keyboard, and sets a moveVector accordingly. This moveVector is multiplied by

the amount variable, which will indicate the amount of time passed since the last call. The result is passed

to the AddToCameraPosition method, which is defined below (place this code in the Camera Methods

region).

private void AddToCameraPosition(Vector3 vectorToAdd)

{ Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

Page 4: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

4

Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); }

This method again creates the rotation matrix of the camera. Once we have this matrix, we use it

transform the moveDirection. This is needed, because if we want the camera to move into the Forward

direction, you don’t want it to move in the (0,0,-1) direction, but rather in the direction that is actually

Forward for the camera at the current time. The last line transforms this vector by the rotation of our

camera, so ‘Forward’ will actually be ‘Forward’ relative to our camera.

We still need to call the ProcessInput method from within the Update method (place this code above the

worldMatrix definition):

float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

ProcessInput(timeDifference);

Run this code; you should be able to move the camera using the arrow buttons.

Now let’s use mouse input to control the camera. We can read out the mouse just like we did with the

keyboard: by reading the MouseState. The MouseState structure contains (among other things) the

absolute X and Y position of the mouse cursor, but not the amount of movement since the last call. So we

will need to reposition the mouse cursor to the middle of the screen at the end of each update. By

comparing the current position of the mouse to the middle position of the screen, we can check every

frame how much the mouse has moved. To do this, add the following Game1 class instance variable:

MouseState originalMouseState;

During the startup of our project, we will want to position the mouse in the middle and store this state; do

this by putting this code in our LoadContent method (before SetUpCamera()):

Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2);

originalMouseState = Mouse.GetState();

Finally, add this code to top of our ProcessInput method (in Camera Methods):

MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); }

Page 5: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

5

First, we retrieve the current MouseState. Next, we check whether this state differs from the original

mouse position, in the middle of the screen. If there is a difference, the corresponding rotation values are

updated according to the amount of mouse movement, multiplied by the time elapsed since the last frame.

NOTE: Since we are continuously setting the mouse cursor to the middle of our window, there will

be no way to click on the X to close the window. Use Alt+F4 to close the running program.

Run this code. You should be able to move over your terrain as in any other first person game.

Experiment with the various ways to move both the camera and the terrain.

Although we did not change a lot to the screenshot, we now have a moving camera that does what we tell

it to do.

Adding Texture to Terrain

We learned how to apply textures to triangles in Lab 6. In this section, we will apply a grass texture to

our terrain by copying it a few times over our terrain in mirrored mode. We will use the grass.dds texture

that we downloaded earlier with other content files. Import this texture into the content pipeline as we

have done before (if you wish, you can go ahead and import all of the other content for use later), and add

the following Game1 instance variable:

Texture2D grassTexture;

Let’s create a small separate method to load all our textures:

#region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); } #endregion

And call this method from the end of our LoadContent method:

LoadTextures();

Now, instead of using our user-defined VertexPositionColorNormal format, we will use the MonoGame-

provided VertexPositionNormalTexture format. Don’t delete the VertexPositionColorNormal struct

though, as we will use it in the next section.

Replace all instances of VertexPositionColorNormal in the code with VertexPositionNormalTexture

(you can use Ctrl+H for this), but do not rename the VertexPositionColorNormal struct, since this

would override MonoGame’s VertexPositionNormalTexture struct!

Page 6: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

6

We also need to change the way we specify our VertexBuffer in CopyToBuffers() (in the Terrain Methods

region), as follows:

myVertexBuffer = new VertexBuffer(device, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices);

Now, instead of specifying the color of each vertex, we’ll need to pass in the texture coordinate. So

replace the existing SetUpVertices method with the following code:

private void SetUpVertices() { vertices = new VertexPositionNormalTexture[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; } } }

We need to add a constant to indicate the maximum normalized height. This will reduce the range of

heights displayed, and will make things look a little better. #region Constants

// Terrain texture mapping constants public const float MAX_HT = 30.0f; #endregion

And also clean up LoadHeightData (in Terrain Methods) to use this constant, and to compute the

maximum and minimum heights found in our height bitmap:

private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++)

Page 7: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

7

{ heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; }

In the Draw method, we need to indicate that we will be using the Textured technique to draw our terrain,

instead of the Colored technique, and pass our grass texture to the shader code (leave the other

parameters):

effect.CurrentTechnique = effect.Techniques["Textured"]; effect.Parameters["xTexture"].SetValue(grassTexture);

Run your code, and you should see some terrain covered with grass, as shown below.

Page 8: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

8

Here is our code so far: using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexPositionNormalTexture[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix;

Page 9: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

9

VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexPositionNormalTexture[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; } } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++)

Page 10: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

10

{ int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices); } private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue;

Page 11: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

11

float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } #endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

Page 12: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

12

viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() {

Page 13: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

13

// JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in Monogame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, Monogame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to do 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion

Page 14: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

14

#region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["Textured"]; effect.Parameters["xTexture"].SetValue(grassTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix);

Page 15: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

15

foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); // JKB Note: Originally, this was: // device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3); // using the obsolete method: // DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount) // In Monogame, minVertexIndex and numVertices are unused and ignored, so we use: // DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int startIndex, int primitiveCount) // instead. } base.Draw(gameTime); } #endregion } }

Creating Multi-textured Terrain

Our textured terrain already looks a lot nicer than a simply color-coded terrain, but it could still be

improved by, for example, using different textures according to the height level of each vertex. In this

section, we will divide our terrain into sand, grass, rocky and snow-covered areas. If you have not already

done so, add the three textures sand.dds, rock.dds and snow.dds to your content project, and declare the

following Game1 instance variables:

Texture2D sandTexture;

Texture2D rockTexture; Texture2D snowTexture;

Now load them in the LoadTextures method (Texture Loads region):

sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow");

We could take the same approach as we did with the four colors, but we would have the same problem: at

the boundaries between textures, we would clearly see the edges between the two different textures. What

we want is a smooth transition between the four textures. We can accomplish this by giving each vertex a

combination of multiple textures, as follows. Each vertex will store four weights, one for each texture. For

example: the highest vertex of the terrain would have weight 1 for the snow texture, and weight 0 for the

other three textures, so that vertex would get its color entirely from the snow texture. A vertex in the

middle between the snowy and the rocky region will have weight 0.5 for both the snow and rock texture

and weight 0 for the other two textures, which would result in a color taken for 50% from the snow

Page 16: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

16

texture and 50% from the rock texture.

To do this, we first have to define a new custom vertex format, which will enable us to store the four

weights for each vertex, along with the position, normal and texture coordinates. We will call this format

VertexMultitextured, defined as follows:

#region Vertex Structs

public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0 ), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0 ), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0 ), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1 ) ); } #endregion

The last entry is new: for each vertex we’ll be storing an additional Vector4, which will contain the four

weights. Because there’s no standard part of a VertexElement called “textureweights” or something, we

will pass this information as an additional TextureCoordinate. Because we are already passing another

TextureCoordinate, we have to give this one an index of 1, instead of 0.

Replace all instances in your code of VertexPositionNormalTexture with VertexMultitextured (using

Ctrl+H).

Now that each vertex has an additional Vector4 to store the weights, let’s fill them. For this, we need a

scheme that maps the height of each vertex into texture weights. To make things easy, we usually want

weights to have a value between 0 and 1, where 0 means ‘nothing’ and 1 means ‘all’. This means in each

vertex, we need to find four weights between 0 and 1. Consider the left image below, which shows the

weight we ideally want to find. Note that the horizontal X axis indicates the weight values.

Page 17: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

17

In the LoadHeightData method, the heights of the terrain vertices are set to values between 0 and MAX_HT

(30 in this example). These heights are represented by the vertical arrow. You can see the vertices with

height 0 should have weight=1 for the sand texture, while the vertices with height 30 should have

weight=1 for the snow texture. A vertex with height=25 should get a weight between 0 and 1 for both the

snow and the rock texture. Thus sand will range from 0 to 8, grass will range from 6 to 18, rock will

range from 14 to 26, and snow will range from 24 to 30. To implement this approach, we have to

compute texture weights that will produce this effect.

Consider how to find the weight for the grass texture, TexWeights.Y. We see in the left image that a vertex

with height 12 should have weight 1. The weight should become 0 for heights 6 and 18, which are (12-6)

and (12+6), respectively. In other words, all heights that are within 6 meters from the 12 meter mark

should have some weight factor for the grass texture. This explains the ‘abs(height-12)/6’: it will be 0 for

height = 12, and become 1 as the height approaches 6 or 18. But we need the opposite: at height 12 we

need weight = 1, and at heights 6 and 18 we need weight = 0. So we subtract ‘abs(height-12)/6’ from 1 to

get ‘1- abs(height-12)/6’. This value become smaller than 0 for height lower than 6 and larger than 18, so

we clamp this value between 0 and 1. Finally, since imbedded constants are a pain, let’s make this work

for any MAX_HT.

First add some additional Terrain texture mapping constants:

public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24

Page 18: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

18

And now replace the inner for loop (DO NOT overwrite the outer for loop) of SetUpVertices (In Terrain

Methods) as follows:

for (int y = 0; y < terrainLength; y++)

{ vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); }

Now we can assign any MAX_HT we like, and the code will compute texture weights correctly. In order

for us to use the absolute value method (part of System.Math), we need to add the following using

statement (at the top of Game1.cs) to tell Monogame what we are up to:

using System;

Although our progress so far is a step in the right direction, it isn’t perfect yet. For example: since snow

and rock weights are 0.2, the pixels corresponding to height 25 will get 20% of their color from the snow

texture, and 20% from the rock texture. The remaining 60% will remain black, so these pixels will look

very dark. If you like, run the code now to see this (you will have to jump ahead and add the

MultiTextured effect to effects.fx, as described below). In some cases we might like this effect (in a

Twilight game, perhaps?) but we will usually want to ensure that for every vertex, the sum of all weights

is exactly 1. To do this, for each vertex we will make the sum of all weights, and divide all weights by

this sum. In case of the previous example, the sum would be 0.2 + 0.2 = 0.4. Next, 0.2 divided by 0.4

gives 0.5 for both the new snow and rock weights. And of course, 0.5 + 0.5 equals 1. This is what is

shown in the right part of the image above. You’ll notice that for each height, the summed weight value is

1.

This normalization is accomplished by the following code, which must be performed for each vertex, so it

also must be placed inside the inner for loop (below the code we just added):

float total = vertices[x + y * terrainWidth].TexWeights.X;

total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z;

Page 19: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

19

total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total;

Now that we have texture weights for each vertex of our terrain and a vertex structure that allows

MonoGame to pass these values to the HLSL effect, it’s time to actually code this effect. Open effects.fx

(if necessary, first add it to the Content folder “Add->Existing”, and double click on it), and modify the

code as follows:

First, add the new texture samplers (near the top of the effects file) that will process our four textures (you

should leave the xTexture sampler alone): Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR;

minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; };

Now add the actual technique code at the end of the file:

//------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection);

Page 20: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

20

float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; float lightingFactor = 1; if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); Output.Color = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; Output.Color += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; Output.Color += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; Output.Color += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } }

Note how the data enters our vertex shader: the usual texture coordinates are passed as TEXCOORD0,

while our newly added texture weights are using TEXCOORD1. Check our vertex definition structure to

remember why. This data is simply passed on to the output. The pixel shader only needs to calculate the

color of each pixel, so we use the default output structure.

The most interesting code of this technique is the pixel shader. We define the color of the pixel by

sampling all four textures, and multiplying these four colors with their corresponding weight. The four

results are all summed together, and we multiply the final result with the lighting factor.

Finally, fix the way we create the vertex buffer to deal with our custom vertices:

In the CopyToBuffers() method of Game1.cs, replace:

Page 21: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

21

myVertexBuffer = new VertexBuffer(device, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices);

with:

myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices);

Back in the Draw method of Game1.cs, we need to specify that we will be using this new technique to

draw our terrain, and we have to pass in the four textures. Replace:

effect.CurrentTechnique = effect.Techniques["Textured"]; effect.Parameters["xTexture"].SetValue(grassTexture);

with: effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture);

Run this code, you should see a multitextured terrain, similar to that shown below. Use the camera to

have a closer look at the transitions between our textures. How cool is that! Although this multitextured

terrain looks pretty good, it still can be improved. Try moving the camera very close to the sand, and

notice the pixels in the texture. In the next section, we will discover how we can have higher detail closer

to the camera.

Page 22: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

22

Optional - test your knowledge:

Try different MAX_HT values with different height maps.

Play around with the weight mapping values. For each texture, there are two parameters to

change: the central height value of the peak, as well as the width of the peak.

Open up the heightmap2.bmp file in Microsoft Paint (or other image editing program that can

handle .bmp files), and find the rivers. In the middle of a river, add a few completely white pixels.

Run the program, and try to explain what you see.

Here is our code so far:

Game1.cs

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal;

Page 23: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

23

public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity;

Page 24: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

24

Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total;

Page 25: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

25

} } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices);

Page 26: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

26

myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices); } private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } #endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); }

Page 27: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

27

private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion

Page 28: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

28

#region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers();

Page 29: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

29

LoadTextures(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary>

Page 30: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

30

/// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } base.Draw(gameTime); } #endregion } }

effects.fx

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0;

Page 31: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

31

float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color;

Page 32: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

32

return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0;

Page 33: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

33

float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; } PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld));; Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; }

Page 34: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

34

technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; float lightingFactor = 1; if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); Output.Color = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; Output.Color += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; Output.Color += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; Output.Color += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; Output.Color *= lightingFactor;

Page 35: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

35

return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } }

Adding More Detail Close to the Camera

When we look at the terrain from a distance, it looks pretty good. But when we move the camera very

close to the terrain, we see the individual pixels of each texture are spread out over a large area. This is

not good, because in a game, the camera will usually be placed close to the terrain. How might we fix

this? One approach would be to enlarge our texture coordinates, so that more of the texture would be put

on each terrain grid. This would give good results when the camera was close to the terrain, but as we

moved the camera further away, the textures will be so small that we would see the repeating pattern. So

the problem is now the other way around.

Instead of choosing one of these approaches, we will combine both. This will be done in the pixel shader:

when the pixel is far away from our camera, we will use the big textures. When the pixel is very close to

the camera, we will use the smaller, more detailed textures. For the region in between, we’ll blend

between these two. This whole operation can be done in the pixel shader, so the load on our CPU will

remain the same. How cool is that?

All of what we need to do involves editing the effects.fx file. First, we will need to know the distance to

our camera for each pixel. In the MultiTextured shader (in effects.fx), add the following variable to the

MTVertexToPixel struct to keep track of this:

float Depth : TEXCOORD4;

This distance is just the z coordinate of the position in camera space. Remember, because this is the result

of a 4x4 matrix multiplication, we first need to divide it by the w component before we can use it. Add

this code at the end of the MultiTexturedVS routine:

Output.Depth = Output.Position.z/Output.Position.w;

Now that we can access this value in the pixel shader, we know the distance between each pixel and the

camera. Now, in the pixel shader (MultiTexturedPS), define the values that will govern the blending: the

distance to the camera at which blending will begin, and the width of the blending border (place this code

below float lightingFactor = 1;):

float blendDistance = 0.99f; float blendWidth = 0.005f;

Page 36: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

36

float blendFactor = clamp((PSIn.Depth-blendDistance)/blendWidth, 0, 1);

The last line represents the blending function. All pixels will have a Depth value between 0 and 1, where

0 corresponds to the near clipping plane distance, and 1 to the far clipping plane distance (as set in our

projectionMatrix). Using this function, all pixels closer to the camera than 0.99 will get a blendFactor of

0; all pixels further than 0.99+0.005=0.995 will get a blendFactor of 1, and all pixels in between will get

a linearly interpolated blendFactor. This idea is visualized in the image below:

In this figure, solid blue connotes blendFactor 0, and solid red blendFactor 1. Pixels with blendFactor 0

(blue) will use the highly detailed textures, and pixels with blendFactor 1 (red) will use the standard

textures.

First, we will calculate both the high-detail color and the standard color for each pixel.

Replace:

Output.Color = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; Output.Color += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; Output.Color += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; Output.Color += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w;

with:

float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords*3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w;

Page 37: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

37

For the near color, we multiply our TextureCoords by 3, so the texture is three times smaller, and thus three

times more detailed.

Now we have both our near and far colors for the pixel, we need to mix them according to the

blendFactor.

Replace:

Output.Color *= lightingFactor;

with:

Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor;

This is simple linear interpolation (the “lerp” function linearly interpolates between two values), useful

when the blendFactor is between 0 and 1. When we multiply this with the lighting factor, and run the

resulting code, we should get the result shown below:

Here is the final version of effects.fx:

Page 38: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

38

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed --------

Page 39: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

39

VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; }

Page 40: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

40

technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; } PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld));; Output.LightingFactor = 1;

Page 41: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

41

if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z / Output.Position.w;

Page 42: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

42

return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; float lightingFactor = 1; float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } }

Building a Skydome

Before we start drawing water, let’s first draw something that can be reflected by the water. We already

have a nice terrain; let’s do something about the solid black sky. We will use a “skydome” for this

purpose. A dome is a section of a sphere. Load dome.x and cloudMap.jpg into your content project (if

you have not already done so. Below you can see a wireframe of the dome model.

Page 43: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

43

Next we will put a cloud texture on top of our skydome. Add two new Game1 instance variables: Texture2D cloudMap; Model skyDome;

Note that we are not using an array of textures, because we know the model only has one texture. Add

code to our LoadContent method to load the Model:

skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone();

We load the Model from file, and replace its effect with our own. Also load the cloudmap into the correct

variable by adding this code in the LoadTextures method: cloudMap = Content.Load<Texture2D>("cloudMap");

Now we have to draw our dome. We will create a new method to do this. Since our model ranges from -1

to +1, we need to scale it up by a factor of 500 so the near clipping plane does not create problems. We

will always reposition the skydome around our camera, and we will disable Z buffer writing when

rendering the skydome.

#region Skydome Methods private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes)

Page 44: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

44

{ foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix;

currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"];

currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } #endregion

Besides scaling up the dome by factor 500 and positioning it over the camera, we also move it slightly

downward, so its edges are below the camera. This makes sure the largest part of the camera’s view

frustum is covered by the dome. Next, we pass the cloudMap texture and render the dome using the

Textured technique. Finally, we need to call this method from within our Draw method. However, we

need to make this call BEFORE we draw the terrain. Put this code in the Draw method:

DrawSkyDome(viewMatrix);

Finally, we need to add a new technique to our effects.fx file. This is because the MonoGame shader

compiler is more picky than XNA about unspecified inputs. In our case, the dome.x model file only

contains position and texture coordinate information, but our “Textured” technique expects lighting

normal information for every vertex. The XNA shader compiler was happy to ignore this omission, but

Monogame is more eager to protect us from ourselves. So, add the following new technique to

effects.fx:

//------- Technique: SimpleTextured -------- VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; } PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient;

Page 45: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

45

return Output; } technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } }

Now run this code. You should see something like the following image:

Introduction to Water

Now that we have removed every single pixel of solid background color on our screen, we move will

move on to the most complex terrain technique: water. Is’t not that realistic water is incredibly

complicated or difficult to implement; it just takes several steps to get it right. However, you will be

pleased with the result - the addition of realistic water to a 3D scene will greatly enhance its quality.

We will review the entire process before we dive in to the details.

Page 46: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

46

For 3D games, you generally have two kinds of water: ocean water and lake water. The difference is the

height and length of the waves: for ocean water, we need a lot of vertices for which the height is adjusted

in the vertex shader. For lake water, as in our case, we can manage with a completely flat surface, upon

which we will create the illusion of ripples. Because of this, to draw the flat surface of our water we will

only need two triangles! The effect of water is completely created in the pixel shader, as we will see

below.

Recall that in the pixel shader we are primarily concerned with a single question: for each pixel of the

water, what will its color be? We could simply take a texture of some waves, and put it over the surface.

But in reality, the color of water depends completely on its surroundings: it is partly a mirror, so we see

the reflections of the surrounding scene in the water (this is called the reflective color). But water is also

partly transparent, so we also see a bit of the underlying sand shining through (this is called the refractive

color). So, the final color of the water is a clever combination of the reflective and the refractive color.

The flowchart below depicts all of the steps we will go through in the process of creating realistic water:

Page 47: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

47

We will first need to know the reflective and refractive color for each pixel, so we will begin by rendering

these maps into two textures. Imagine that for each pixel we would only use the reflection color. This

would give a perfect mirror. But real water never looks like a real mirror; it is always deformed by

ripples. So before our pixel shader will sample the color from both maps, we will add a small deformation

to the texture coordinates of each pixel, which will simulate these ripples.

Next, both colors are blended together in such a way that when we look straight into the water, all we see

is the refractive color (the underlying sand). When we look out over the water from a shallow angle, all

we will see is the reflective color. The blendFactor that handles this difference is called the “Fresnel

term,” named for the French physicist Augustin-Jean Fresnel who studied optics and is most famously

known for the lenses he creased for lighthouses, which turned out to be useful in many other applications

(including solar ovens!).

The water color at this point would be the color of perfectly pure and clean water. To make our water a bit

more realistic, at the end we will blend in a bit of grayish-green-blue. (As a small extension, we could

also sample the heightmap of the terrain in the pixel shader, so deeper patches of water could have a

deeper as their refractive color.)

After these basic computations, we will add some bump mapping, so we can add specular reflections

(where the sunlight is directly reflected in the water). Also, reflections of the mountain will be deformed

by the ripples. And as a last note: the water very close to the border is a bit more blue-ish, which is due to

the last step we will apply.

Rendering the Refractive Map

As a first step towards drawing our water, we will render the refractive map into a texture. We will need

this map for each pixel of our water, to determine the color of what’s underneath that pixel.

In fact, this almost comes down to rendering the scene, as we see it through the camera, into a texture.

However, there are cases where some hills could obstruct our view to the bottom of the river behind them.

This would mean that for the pixels of that river, we would sample the color of the hill, instead of the

bottom of the river.

As we’ll be rendering to a texture, we’ll need these Game1 constants and variables (by now you know

where to put them):

// Water Constants

const float WATER_HEIGHT = 5.0f; #region Game1 Instance Vars

… RenderTarget2D refractionRenderTarget; Texture2D refractionMap;

Page 48: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

48

The constant WATER_HEIGHT indicates how high we want our water to be positioned.

We need to initialize the refractionRenderTarget, so add this code to our LoadContent method:

PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat);

We only want to draw the things that are BELOW the water, and clip everything above the water away.

To do this, we use a mechanism called a “clip plane”. The name explains what it does: it is a plane, and

the part of the scene on one side of that plane is clipped away. In our case, we will define a horizontal

plane at the height of the water, so everything above that plane will not be drawn. We will also have to

modify our pixel shader to actually do the clipping for us; that step is discussed below.

Defining a plane generally in 3D space can be complicated. The easiest way to define a plane is to specify

the normal direction of the plane, and its shortest distance to the (0,0,0) point of the scene. For each

combination of normal and distance, there is only one plane. In our case, this is pretty easy because our

plane is completely horizontal: in that case, the normal is simply the (0,1,0) Up-vector, and the distance to

the (0,0,0) point is the height level of our water. Because the lowest points of our terrain have Y height

coordinate 0, we cannot define the water at Y coordinate 0, as the water would be lower than all vertices

in our terrain. Instead, we will say the water is at Y coordinate WATER_HEIGHT (5 in this case), which we

will use as a clip plane, so everything above this plane will not be drawn.

We will start by creating the plane; we need the height and the normal direction. Then we need to specify

whether we want to clip everything away below, or above, the plane. We will pass this information to the

method as the Boolean value clipSide. If we specify clipSide=false, we want to clip everything away

below the plane. If we want the clip everything away above the plane, we will specify clipSide=true,

which results in a sign change of the four coefficients.

We first normalize to ensure that our normal is of unity length. Next, we create the plane coefficients,

from which MonoGame can create the plane. As the clipping will occur in hardware after the vertices

have passed the vertex shader, the vertices that will be compared to the plane will already be in camera

space coordinates. This means we need to transform our plane coefficients with the inverse of the camera

matrix, before creating the plane from these coefficients. Recall that the camera matrix is the combination

of the world, view and projection matrices. We get the inverse of a matrix by inverting it and taking the

transpose of the inverted matrix. Note that the vertices of the terrain and water are already defined in

absolute World space, so we use Matrix.Identity as the World matrix, or just leave the World matrix out

of the computation. Now we can transform our plane coefficients into the correct space, and create our

plane, as follows:

#region Water Methods

private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height);

Page 49: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

49

if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; }

#endregion

With our plane ready, we are ready to define a method DrawRefractionMap, which, well, draws the

refraction map. Add this code to our new Water Methods region:

private void DrawRefractionMap()

{ Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false); effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); refractionMap = refractionRenderTarget; // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); }

Don’t worry that Visual Studio is flagging an error (the absence of a DrawTerrain() method); we will fix

this below).

The first line creates a horizontal plane, a bit above the value of the WATER_HEIGHT variable. The last

argument indicates that we only want the things under the plane to be rendered. The next lines

communicate with our HLSL shader code (which we will modify in a moment) to enable clipping, and to

tell the graphics card how to create the clipping plane.

Once the clip plane is enabled, we create a render target (a place for the graphics card to send the output

of the shader computations in lieu of the display) named refractionRenderTarget, and we clean it (set all

pixels to black). Then we render the terrain onto this render target (only the part below the clip plane will

Page 50: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

50

be rendered), after which we store the contents of the render target into the refractionMap texture and

disable clipping.

Now we have to modify the shader code associated with drawing the terrain. Open the effects.fx file for

editing and make the following changes to the Multitextured technique (additions are shown in yellow;

“//...” means other code that you leave alone.):

//... //------- Global Constants Passed Into the Effects File-------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; //...

struct MTVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; //... MTVertexToPixel MultiTexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0, float4 inTexWeights: TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = mul(normalize(inNormal), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z/Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0); return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn)

Page 51: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

51

{ MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); //...

So what have we just done? First, we created two global constants (Clipping and ClipPlane0) to receive

the information passed in from our Game1.cs code. Then we created an MTVertexToPixel struct member

(clipDistances) to hold the MTVertexToPixel output. The clipDistances are computed by taking the dot

product of the input positions and the clip plane. This passes all of the information that the pixel shader

needs to clip the correct pixels in 3D space. Finally, we use the HLSL intrinsic function clip (which will

discard any pixels whose value is 0 or less) to perform the actual clipping.

Finally, we need to encapsulate our code that draws the terrain into a callable function, so that we can call

it from our water code. Create a new method in the Terrain Methods region called DrawTerrain, and

populate that method with the existing code from the current Draw method that is highlighted below.

Here is what your new method should look like:

private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } }

Then, replace all of the code that you just copied from the Draw method with a call to DrawTerrain (after

the DrawSkyDome () call):

DrawTerrain(viewMatrix);

Page 52: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

52

Now we are ready to call DrawRefractionMap at the beginning of the Draw method (BEFORE the

device.Clear):

DrawRefractionMap();

When you run this code, you should get the same result as last section, but in the background, the

refraction map is stored into an image to be used later.

Rendering the Reflection Map

Now that we have created a clip plane, rendered the part of the scene that is under this clip plane, and

created the refraction map, we will use the same technique to render the reflection map into a texture.

This texture is needed, because for each pixel of our water, we need to know the reflective color.

We will need another texture to render into, so let’s define these variables:

RenderTarget2D reflectionRenderTarget;

Texture2D reflectionMap;

And initialize them in our LoadContent method:

reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth,

pp.BackBufferHeight, false, pp.BackBufferFormat,

pp.DepthStencilFormat);

Now let’s think about how we can render the reflections onto a texture. We will need to reposition our

camera, as we need to see the scene as seen by the water. We just need to know where exactly to position

that camera, and where it needs to point.

Consider the figure below. Camera A represents where the actual camera (our eyes) is located. To see the

mountain correctly reflected on the water, Camera B needs to be positioned at the same X and Z

coordinates as Camera A, but with the Y coordinate mirrored over the water plane.

Page 53: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

53

Thus, for each pixel of the water, the reflective color as seen by Camera A is the same color that Camera

B would see. So, in order to know the reflective colors of each pixel of the water, we have to render the

scene as seen by Camera B onto a texture, and then place that texture on the surface of the water.

However, there is one additional problem: part of the terrain that is under the water may obstruct Camera

B’s view below the water. This means that Camera B would only render the bottom of the terrain onto the

texture, which is not what we want. To handle this potential problem, we will clip away all parts of the

terrain that are below the water, as we did (in the reverse) for the refractive image

We begin our implementation by defining the matrix corresponding to Camera B. Add this Game1

instance variable:

Matrix reflectionViewMatrix;

The UpdateViewMatrix method (In Camera Methods) is where we will populate this matrix. Recall that

to define a camera matrix, we need a position, target and up vector for the camera.

We already know the position: the X and Z coordinates of Camera B are the same as those of Camera A,

and the Y coordinate of Camera B is the negative of the distance between the water and Camera A. That

distance is equal to the negative of Camera A’s height, plus two time the water height (work this out for

yourself). Add this code to UpdateViewMatrix:

Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2;

To obtain the target of Camera B, we can apply the same reasoning, so add this code as well:

Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2;

Now we still need to define the Up vector. For this we need a little math. Recall that the cross product of

two vectors is the vector that is perpendicular to both vectors. So, if we take the cross product of the

forward and side vector of camera B, we get the upvector of camera B. This is the code that does exactly

this:

Page 54: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

54

Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition);

Now we have the position, target and up vector for Camera B, we’re ready to define its View matrix:

reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector);

With the View matrix of Camera B defined, we can create a DrawReflectionMap method in the Water

Methods region. This will look a lot like the DrawRefractionMap code:

private void DrawReflectionMap()

{

Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D)); // Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget; }

The first line creates a plane as before, except that we need to flip the sign of the Y parameter, and we

need to pass in the view matrix of Camera B (instead of Camera A), and we need to indicate that we only

want to render things that are ABOVE the clipping plane. Next, we activate the clipping plane and define

the correct render target for our graphics card, and clean the render target. Then the Terrain and Skydome

are rendered. Note that in this method, we pass in the reflectionViewMatrix, as we need the terrain and

skybox as seen by camera B.

Finally, we need to call this method from the Draw method (after the call to DrawRefractionMap):

DrawReflectionMap();

At this point we have created textures for the refractive and reflective images, but we have not used them.

Let’s fix that now, beginning with reflection.

Perfect Mirror Reflection

Page 55: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

55

Now that we have rendered a reflective texture we will paste the texture over our mirror: the water. That

way our water will look like a perfect mirror. This is done through projective texturing. We will begin by

creating the water (our mirror). We use two large triangles to do this, and paste the reflective texture onto

the rectangle created by these triangles. Begin by defining the buffer that will hold the triangle vertices

used to define this rectangle in Game1 Instance Vars:

VertexBuffer waterVertexBuffer;

Now create a method (in our Water Methods region) to define the water triangle vertices (that will create

a rectangle that spans the whole terrain):

private void SetUpWaterVertices()

{ VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, - terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, - terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); }

Although strictly speaking we don’t need to pass texture coordinates with the vertices to do projective

texturing (as the texture coordinate will be calculated in the vertex shader), later on we will need to be

able to specify texture coordinates here. So we are using VertexPositionTexture vertices to prepare for

that eventuality.

We need to call SetUpWaterVertices at the end of the LoadContent method:

SetUpWaterVertices();

Now add the method that draws the two triangles, using the Water technique which we will create in a

minute:

private void DrawWater(float time)

{ effect.CurrentTechnique = effect.Techniques["Water"];

Page 56: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

56

Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); //effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } }

Notice that we pass in the reflection map, but we comment out the line that passes the refraction map.

This is because the Monogame HLSL compiler complains if pass in a texture and then do not use it. We

will uncomment this line after we update our shader code to use the refraction map.

We need to call the DrawWater method at the end of our main Draw method (but before Base.Draw), and

not from the DrawRefectionMap or DrawRefractionMap (why?):

DrawWater(time);

And we need to create the time variable for the DrawWater method to use (put this at the top of Draw):

float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f;

Let’s move over to the HLSL code (effects.fx), First, we need new global constant:

float4x4 xReflectionView;

and texture samplers for the refraction and reflection textures:

Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; };

Now define the actual Water technique (at the end of effects.fx):

//------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; }; struct WPixelToFrame

Page 57: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

57

{ float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; Output.Color = tex2D(ReflectionSampler, ProjectedTexCoords); return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } }

Since the two triangles of the water are completely flat, we do not need to pass any normal data; if we

need the normal, it’s always pointing upward. We only need to calculate the sampling coordinates for the

reflection map. Notice that we only calculate two output values in WVertexToPixel: the 2D screen

position of the current vertex and the corresponding 2D position as it would be seen by Camera B. We use

this second 2D position as the sampling coordinate for our reflective texture in the pixel shader.

Run this code, and you should see a perfect mirror of the sky at the water level. In the interest of space, I

won’t list the code here. If you have a problem, look below for the next code sample.

Page 58: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

58

Rippling Water – Basic Bump Mapping

Right now our water looks like a mirror; we need to add a rippling effect to make it look more realistic.

To do this, we use a technique called “bump mapping,” which works follows: for each pixel of the water,

instead of sampling the reflection map at the correct position, we will change the sampling position a little

bit differently for each pixel. This technique is sometimes called “texture coordinate perturbation”. All the

needed texture coordinate calculations are performed in the pixel shader, which makes this technique fast.

Of course, we still need to figure out how to go about adjusting the texture coordinates of each pixel.

Consider how real water looks: if there is absolutely no wind, the water is flat, and looks like a mirror. If

there is wind, there are waves, whose height varies with the wind speed. At the top and bottom of the

waves, the water is still mostly flat; thus the reflections at the top and bottom the reflections are still

mostly perfect. As a result, we need the smallest perturbations at the bottom and top of each wave.

However, the sides of the waves are not horizontal, so there we will have the largest perturbations.

What we need is a measurement of the ‘flatness’ of each pixel in the water. For this, we will use an image

of real waves. We are not interested in the wave image itself, but in the flatness of each pixel. This is

exactly what a gradient map is: if you have a 256x256 wave image A, its gradient map B is also a

256x256 image where the color of each pixel indicates the difference in color compared to the same pixel

in image A.

A gradient map is the mathematical term; in games this usually is called the bump map. Below you can

see such a bump map:

Each pixel of this image contains three values, one for each color. These three values correspond to the

three components of the normal vector in the map. For example, if the surface would be completely flat in

a certain pixel, the normal vector would be pointing straight upward. So the X and Z components would

be 0, while the Y component would be 1. Using colors, the Red and Green components would be 0, the

Blue would be 1, so the whole map would be completely blue (this is not exactly what we will find in a

Page 59: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

59

bump map, as we will see in a moment).

However, since the water isn’t flat, the normal is different for each pixel, so the Red and Green

components contain exactly how much the normal is pointing in the X and Z directions. So the larger the

red and green components, the less the normal vector will point upwards, and the less flat the water will

be. The blue color component is only provided so we can create the normal vector, for which we need

three components. However, in this case we are only interested in how flat/rippled the water is at a certain

pixel, so the red and green components are all we need.

Add the file waterbump.dds to your content project, as we have done before.

There is not a lot we need to do in Game1.cs. First, define some new water constants that we will use to

characterize our water waves:

public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT= 0.3f;

Now, declare the bumpmap variable:

Texture2D waterBumpMap;

Fill it in the LoadTextures method:

waterBumpMap = Content.Load<Texture2D>("waterbump");

And pass all three new parameters to the shader in the DrawWater method:

effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH); effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT);

That’s it for the Game1 code; let’s move on to the HLSL part. Declare two new global constants to accept

the wave length and height:

float xWaveLength;

float xWaveHeight;

And provide a sampler for the water bump map:

Texture xWaterBumpMap;

sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap> ; magfilter =

LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};

Now we need to update our shaders to sample the water bump map. Add a new member to the

WVertexToPixel struct: struct WVertexToPixel { float4 Position : POSITION;

Page 60: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

60

float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; };

And use this term in WaterVS: WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex: TEXCOORD) { //... Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.BumpMapSamplingPos = inTex/xWaveLength; //...

Note that larger values of xWaveLength will make the texture coordinates smaller, and thus the bump map

will be stretched over a larger area.

On to the pixel shader. There, we first sample the bump map. At this moment, the red and green color

values of the bumpColor indicate how much the sampling coordinates of the reflection map should be

perturbated. So we need these values to range between -1 and +1. However, colors can only contain value

between 0 and 1. To fix this, values in the range [-1,1] are mapped to the range [0,1] before saving them

as a color. This is done by dividing by 2 and adding 0.5. As an example, if the normal is unadjusted, the X

and Z component are 0, and the Y component would be 1. When this value is converted to a color, we get

(0.5, 0.5, 1), which is the primary color found in most bump maps.

When we use the bump map, we need to remap the bump map values in the range [0,1] back to values in

the range [-1,1]. This conversion is just the opposite of the first conversion: we subtract .5 and multiply

by 2. When we are done, we compute the final texture coordinates we use to sample our reflection map.

To accomplish all of this, make the changes shown below:

WPixelToFrame WaterPS(WVertexToPixel PSIn)

{ WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight*(bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; Output.Color = tex2D(ReflectionSampler, ProjectedTexCoords); Output.Color = tex2D(ReflectionSampler, perturbatedTexCoords); return Output; }

Page 61: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

61

Run this code. You should finally give us something that looks more like water. However, we are not

done. Here is the code so far:

Game1.cs

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 // Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration (

Page 62: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

62

new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; VertexBuffer waterVertexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50);

Page 63: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

63

float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D cloudMap; Model skyDome; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; Matrix reflectionViewMatrix; Texture2D waterBumpMap; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1);

Page 64: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

64

vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total; } } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal;

Page 65: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

65

vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices);

}

private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture);

Page 66: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

66

effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } } #endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); cloudMap = Content.Load<Texture2D>("cloudMap"); waterBumpMap = Content.Load<Texture2D>("waterbump"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

Page 67: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

67

viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion

Page 68: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

68

#region Skydome Methods private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } #endregion #region Water Methods private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } private void DrawRefractionMap() { Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false);

Page 69: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

69

effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); refractionMap = refractionRenderTarget;

}

private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D)); // Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget; } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1));

Page 70: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

70

waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); //effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH); effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } } #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges();

Page 71: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

71

graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); SetUpWaterVertices(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary>

Page 72: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

72

protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap();

Page 73: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

73

device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); base.Draw(gameTime); } #endregion }

}

effects.fx

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; float4x4 xReflectionView; float xWaveLength; float xWaveHeight;

Page 74: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

74

//------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0

Page 75: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

75

{ VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output;

Page 76: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

76

} PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } }

Page 77: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

77

//------- Technique: SimpleTextured -------- VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; } PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; }

technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION0; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection);

Page 78: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

78

Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z / Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0); return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); float lightingFactor = 1; float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel

Page 79: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

79

{ float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; }; struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.BumpMapSamplingPos = inTex / xWaveLength; return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; Output.Color = tex2D(ReflectionSampler, perturbatedTexCoords); return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } }

Page 80: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

80

Adding Refraction and the Fresnel Term

Now that we have added some ripples to our water, it’s time we use our refraction map to blend in the

color of the bottoms of the water channels. We will use the rippling effect of last section. Since we

already know the reflective and refractive color, the only remaining question is: for each pixel, how much

of each color should we use? The answer to this question can be deduced from the figure below. The

horizontal flat line represents our water. The vector pointing upward is the normal vector of the pixel. The

other vector, called the “eyevector”, is the vector going from the camera to the pixel. The length of green

on the normal vector indicates the amount of reflection the current pixel should have, and the length of

red represents the amount of refraction. If you think about it, this makes since. If we look straight down

in clear water, we will see mostly the bottom (if the water is sufficiently shallow). If we look out across

the water, we will see mostly reflection. Photographers use this effect to capture stunning photographs of

mountain lakes.

So how do we find the lengths of the green and red bars? We need to project the eyevector onto the

normal vector. Conveniently, this is exactly what a dot product does: when you dot product the eyevector

and the normal vector, you get the length of the red bar. The green bar then equals (1- length of red bar).

How cool is that?

We will achieve this effect almost entirely in shader code, so we will do that first. Since we will need the

camera position in the pixel shader, we need to add a global constant to hold this information when we

pass it into the shader code. We will also add two other global constants whose purpose will be explained

later: float3 xCamPos; float xDirtyWaterFactor; float4 xDullColor;

Then we will calculate the refractive color for our water. This is done in exactly the same as we did for

the reflective water: we compute the projective textures, add some ripple perturbation to them, and

sample the refraction map. To know the projective textures, we again need the 2D screen coordinates for

that pixel, as seen by the camera that created the refraction map. This camera was our normal camera, so

add variables to hold this information this in the vertex shader output struct: struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1;

Page 81: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

81

float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3; float4 Position3D : TEXCOORD4; };

And compute the refraction map sampling position in the output of the vertex shader:

Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld);

For each pixel, we will also need its 3D position to calculate the eyevector, thus the last line to our vertex

shader. The vertices of the water have already been defined in absolute World space, but to keep our

shader code generally useful we multiply the water vertex positions by the World matrix.

Next, in our pixel shader, we first save the reflective color we previously obtained: float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords);

Now we do exactly the same as we did in the last section, but this time for the refraction map: float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f; float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords);

Now we know both the reflective and the refractive color, it’s time to blend them together according to

the Fresnel term. To obtain the Fresnel term we first need to find the eyevector: float3 eyeVector = (float3) normalize(float4(xCamPos,0.0) - PSIn.Position3D);

Now use our bump map to compute the normal for each pixel of our water: float3 normalVector = (bumpColor.rbg-0.5f)*2.0f;

And compute the Fresnel term: float fresnelTerm = dot(eyeVector, normalVector);

And finally we can blend the refraction and reflection colors according to the Fresnel term, using linear

interpolation. Save this color at this point: float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm);

The last thing we want to do in the pixel shader is to blend in a little bit of dull water color, since real

water is rarely devoid of dissolved solids that give it a greenish-grayish-blue tinge. To do this, we will

use two values passed in from the Game1 code: a color and a linear interpolation factor that tell us how

much of this color to blend in.

Page 82: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

82

Now blend it in: Output.Color = tex2D(ReflectionSampler, perturbatedTexCoords); Output.Color = lerp(combinedColor, xDullColor, xDirtyWaterFactor);

That’s it for the HLSL code. In our Game1 code, we only need to define two new water constants, and

pass in the camera position and water constants in the DrawWater method:

//Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; public Vector4 DULL_COLOR = new Vector4(0.3f, 0.4f, 0.5f, 1.0f); public const float WATER_DIRTINESS = 0.2f; // Increase to make the water "dirtier"

And in DrawWater: effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xDirtyWaterFactor"].SetValue(WATER_DIRTINESS); effect.Parameters["xDullColor"].SetValue(DULL_COLOR);

Finally, uncomment this line (since we are now using the Refraction map).

effect.Parameters["xRefractionMap"].SetValue(refractionMap);

Run this code. You should get water that has both a reflective and refractive color, with realistic a dull

color blended in. Your water should now look something like the image below. Try some other dull

water colors and blending factors.

Page 83: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

83

Here is our code so far.

Game1.cs:

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 // Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; public Vector4 DULL_COLOR = new Vector4(0.3f, 0.4f, 0.5f, 1.0f); public const float WATER_DIRTINESS = 0.2f; // Increase to make the water "dirtier" #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration (

Page 84: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

84

new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; VertexBuffer waterVertexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50);

Page 85: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

85

float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D cloudMap; Model skyDome; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; Matrix reflectionViewMatrix; Texture2D waterBumpMap; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1);

Page 86: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

86

vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total; } } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal;

Page 87: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

87

vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); }

private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices); } private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture);

Page 88: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

88

effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } } #endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); cloudMap = Content.Load<Texture2D>("cloudMap"); waterBumpMap = Content.Load<Texture2D>("waterbump"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector);

Page 89: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

89

Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion #region Skydome Methods

Page 90: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

90

private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } #endregion #region Water Methods private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } private void DrawRefractionMap() { Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false); effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map

Page 91: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

91

effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); refractionMap = refractionRenderTarget; }

private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D)); // Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget; } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly);

Page 92: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

92

waterVertexBuffer.SetData(waterVertices); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH); effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xDirtyWaterFactor"].SetValue(WATER_DIRTINESS); effect.Parameters["xDullColor"].SetValue(DULL_COLOR); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } } #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II";

Page 93: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

93

base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); SetUpWaterVertices(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here

Page 94: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

94

} #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap(); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

Page 95: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

95

DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); base.Draw(gameTime); } #endregion } }

Effects.fx:

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; float4x4 xReflectionView; float xWaveLength; float xWaveHeight; float3 xCamPos; float xDirtyWaterFactor; float4 xDullColor; //------- Texture Samplers --------

Page 96: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

96

Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS();

Page 97: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

97

PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; }

Page 98: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

98

PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } } //------- Technique: SimpleTextured --------

Page 99: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

99

VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; } PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; }

technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION0; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords;

Page 100: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

100

Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z / Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0); return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); float lightingFactor = 1; float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION;

Page 101: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

101

float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3; float4 Position3D : TEXCOORD4; }; struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.BumpMapSamplingPos = inTex / xWaveLength; Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords); float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords); float3 eyeVector = (float3) normalize(float4(xCamPos,0.0) - PSIn.Position3D);

Page 102: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

102

float3 normalVector = (bumpColor.rbg - 0.5f)*2.0f; float fresnelTerm = dot(eyeVector, normalVector); float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm); Output.Color = lerp(combinedColor, xDullColor, xDirtyWaterFactor); return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } }

Moving Water

Our water is looking pretty good, but a critical element of realism is missing: movement. In this section,

we will learn how to create the illusion of moving water. To do that, we need to remember the origin of

the ripples in our water. The water bump map indicates for each pixel exactly how much to perturbate the

sampling coordinates of the reflection and refraction map. Therefore, we can make our water move in

one direction by simply translating the texture coordinates of the bump map. Recall that these texture

coordinates were attached to the six vertices of the two large triangles that make up our water. So one way

to change the vertices would be in the Game1 code, but a MUCH better place would be to change them in

the vertex shader. In general, it is always better to offload graphical computations to the GPU. In this

case, we will simply pass a time (xTime) variable to our shader, which the vertex shader will use to update

the bump map texture coordinates. We will also pass in variables that govern the wind direction and

force. In effects.fx add the following global constants:

float xTime;

float3 xWindDirection; float xWindForce;

The xWindForce variable will control how fast the ripples scroll through our water; the xWindDirection

variable will control the direction of our ripples. Add the following code to the water vertex shader:

Output.BumpMapSamplingPos = inTex/xWaveLength; Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); float2 moveVector = float2(0, xTime*xWindForce); Output.BumpMapSamplingPos = (inTex + moveVector)/xWaveLength;

We will scroll our bump map along the Y direction; since the waves in the image are horizontal, we need

to scroll them vertically. The scrolling speed depends on the xWindForce value.

Page 103: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

103

We are done with the shader code. Now we need to modify Game1.cs. First add two new water

constants:

public const float WIND_FORCE = 0.0005f; // It doesn't take much public Vector3 WIND_DIRECTION = new Vector3(0, 0, 1);

Vectors cannot be constants in C#, so we just make them public. Now edit our DrawWater method:

effect.Parameters["xTime"].SetValue(time); effect.Parameters["xWindForce"].SetValue(WIND_FORCE); effect.Parameters["xWindDirection"].SetValue(WIND_DIRECTION);

Run your code at this point. You should see realistic moving water. Woo hoo!

The discerning reader will note that we have not yet used the WIND_DIRECTION vector. We set it, and

pass it to the shader, but the shader does not use this information to control the direction of the waves.

Let’s fix that. However, there is one factor we still cannot control: the direction of the wind, and thus the

direction of the waves. To understand how to do this, consider the images below.

The squares indicate our original texture coordinates; think of them as representing our complete water

plane. The red arrows indicate the direction we want the water to move (defined by the wind direction).

The green arrows represent the direction perpendicular to the red arrows. Look more closely at the right

image (the one with off-axis wind direction), and in particular, the small rectangle outlined by a dotted

black line in the upper left. For each pixel in this rectangle, we need to find a new X and Y texture

coordinate. The constraints on these coordinates are as follows:

1. All pixels on the red arrow must have the same X texture coordinate.

2. All pixels on the green arrow must have the same Y texture coordinate.

As stated, these constraints apply only to the pixels that are located exactly on the arrows. However we

can generalize these constraints, as follows:

1. For any line parallel to the red arrow, all pixels on that line must have the same X texture

coordinate.

Page 104: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

104

2. For any line parallel to the green arrow, all pixels on that line must have the same Y texture

coordinate.

For each pixel in the small rectangle, these constraints apply to the X and Y texture coordinates. Now we

need to determine the texture coordinates for all pixels. Here is how: In the right image, take a look at the

upper-left pixel of the black square. The Y texture coordinate is found by taking the corresponding

portion of the red arrow, as displayed by the thicker red line. The length of this thick red line will be the Y

texture coordinate for this pixel. In fact, all pixels on the long dotted black line have the same thick red

line, and thus the same Y texture coordinate, thus fulfilling the second constraint. In the same manner, the

X texture coordinate is found by projecting the pixel on the green arrow, and finding the length of the

thicker green line. All pixels on the short dotted black line will have the same X texture coordinate, which

is represented by thick green line.

We find the length of the thick red and green lines by taking the dot product between the pixel vector (the

thin black line), and the red and green vectors, respectively.

In order to obtain the X and Y texture coordinates, we should first find the red and green arrows. The red

one is easy, as it is the normalized version of the xWindDirection vector. The green arrow is perpendicular

to the red arrow and the Up direction, so it can be found by taking their cross product. Then, we find the

X and Y texture coordinates by taking the dot product of the pixel vector and both arrows. We now have

the fixed texture coordinates, rotated so the waves are perpendicular to the xWindDirection, exactly as

they should be. All we need to do now is to add the resulting change in the Y texture coordinate to make

the texture scroll correctly, and pass the result to the pixel shader. These changes are reflected in the

following vertex shader code:

float2 moveVector = float2(0, xTime*xWindForce); Output.BumpMapSamplingPos = (inTex + moveVector)/xWaveLength; float3 windDir = normalize(xWindDirection); float3 perpDir = cross(xWindDirection, float3(0,1,0)); float ydot = dot(inTex, xWindDirection.xz); float xdot = dot(inTex, perpDir.xz); float2 moveVector = float2(xdot, ydot); moveVector.y += xTime*xWindForce; Output.BumpMapSamplingPos = moveVector/xWaveLength;

Run this code. You should now be able to control the direction of your waves. It just keeps getting

better! With our water moving, there is one last effect we need to add: realistic lighting. That is the

subject of the next section.

Specular Highlights

Specular highlights are small spots of high reflectivity on the water, near the region where the sun (or

another light source) is reflected in the water. This idea is represented in the image below:

Page 105: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

105

If the water surface were a mirror, then the camera would see the light source reflected directly if the

camera is located along the reflection vector created by the light source. So, to compute specular

highlights, we mirror the direction of the light (the left arrow in the image) over the normal in the water

pixel, and compare this vector to the eye vector (the right arrow in the image). If both are almost the

same, the pixel is situated in a specular highlighted area.

The actual code to implement this idea in the water pixel shader is simple, thanks to the use of the HLSL

reflect intrinsic function (reflect returns a reflection vector given an incident ray and a surface normal),

and because we have already determined the normalVector and calculated and eyeVector in our pixel

shader. First we calculate the direction of the light, reflected over the normal vector:

float3 reflectionVector = -reflect(xLightDirection, normalVector);

Next, we need to find out how nearly this reflected vector is the same as the eyeVector. This can be done

easily by taking their dot product: the dot product will be 1 of they are 100% the same, and will be 0 if

they are perpendicular:

float specular = abs(dot(normalize(reflectionVector), normalize(eyeVector)));

Since we are interested only in those vectors that are very close to the same, we only want to consider dot

values that are higher than 0.95. By taking this number to a very high power, only those numbers very

close to 1 will remain (this is because 1256 is still 1; but 0.9256 is a very small number).

specular = pow(specular, 256); Output.Color.rgb += specular;

The last line adds this amount as white to our final color of the pixel. Run this code. You should see

beautiful water, similar to the image below.

Page 106: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

106

That’s it for water. We will now move on to make the sky look more realistic, which will indirectly make

our water look even better. Note that if you watch the water move for a while, you might be able to

discern the bump map pattern. This can be solved by scrolling the same bumpmap multiple times over

itself, each time with a different scaling, scroll speed, and possibly with the X and Y texture coordinates

swapped. Try this if you feel brave.

Here is our code at this point:

Game1.cs

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants

Page 107: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

107

Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 // Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; public Vector4 DULL_COLOR = new Vector4(0.3f, 0.4f, 0.5f, 1.0f); public const float WATER_DIRTINESS = 0.2f; // Increase to make the water "dirtier" public const float WIND_FORCE = 0.0005f; // It doesn't take much public Vector3 WIND_DIRECTION = new Vector3(0, 0, 1); #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate;

Page 108: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

108

public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; VertexBuffer waterVertexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D cloudMap; Model skyDome; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget;

Page 109: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

109

Texture2D reflectionMap; Matrix reflectionViewMatrix; Texture2D waterBumpMap; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total; } } } private void SetUpIndices()

Page 110: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

110

{ indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices);

Page 111: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

111

}

private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } }

Page 112: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

112

#endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); cloudMap = Content.Load<Texture2D>("cloudMap"); waterBumpMap = Content.Load<Texture2D>("waterbump"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); }

Page 113: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

113

private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion #region Skydome Methods private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix;

Page 114: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

114

currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } #endregion #region Water Methods private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } private void DrawRefractionMap() { Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false); effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); refractionMap = refractionRenderTarget;

Page 115: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

115

}

private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D)); // Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget; } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xRefractionMap"].SetValue(refractionMap);

Page 116: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

116

effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH); effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xDirtyWaterFactor"].SetValue(WATER_DIRTINESS); effect.Parameters["xDullColor"].SetValue(DULL_COLOR); effect.Parameters["xTime"].SetValue(time); effect.Parameters["xWindForce"].SetValue(WIND_FORCE); effect.Parameters["xWindDirection"].SetValue(WIND_DIRECTION); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } } #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load

Page 117: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

117

/// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); SetUpWaterVertices(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary>

Page 118: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

118

/// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap(); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); base.Draw(gameTime); }

Page 119: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

119

#endregion }

}

effects.fx

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; float4x4 xReflectionView; float xWaveLength; float xWaveHeight; float3 xCamPos; float xDirtyWaterFactor; float4 xDullColor; float xTime; float3 xWindDirection; float xWindForce; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};

Page 120: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

120

Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } }

Page 121: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

121

//------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; } PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0;

Page 122: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

122

Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } } //------- Technique: SimpleTextured -------- VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) {

Page 123: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

123

VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; } PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; }

technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION0; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights;

Page 124: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

124

Output.Depth = Output.Position.z / Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0); return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); float lightingFactor = 1; float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3;

Page 125: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

125

float4 Position3D : TEXCOORD4; }; struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); float3 windDir = normalize(xWindDirection); float3 perpDir = cross(xWindDirection, float3(0, 1, 0)); float ydot = dot(inTex, xWindDirection.xz); float xdot = dot(inTex, perpDir.xz); float2 moveVector = float2(xdot, ydot); moveVector.y += xTime * xWindForce; Output.BumpMapSamplingPos = moveVector / xWaveLength; return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords); float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f;

Page 126: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

126

float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords); float3 eyeVector = (float3) normalize(float4(xCamPos,0.0) - PSIn.Position3D); float3 normalVector = (bumpColor.rbg - 0.5f)*2.0f; float fresnelTerm = dot(eyeVector, normalVector); float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm); Output.Color = lerp(combinedColor, xDullColor, xDirtyWaterFactor); // add specular highlights float3 reflectionVector = -reflect(xLightDirection, normalVector); float specular = abs(dot(normalize(reflectionVector), normalize(eyeVector))); specular = pow(specular, 256); Output.Color.rgb += specular; return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } }

Making Better Clouds - Perlin Noise

Noise is an important aspect of realism in game programming. Noise (sometimes called “coherent noise”)

is not the same as static. Static (or incoherent noise) is represented by a set of totally random set of data

points, where each point has nothing to do with its neighboring points. An example of a 2D static map is

shown on the left side of the image below.

In a noise map, the value of each point smoothly changes from point to point, there are no discontinuities..

So although the value in each point is different, the value of a point is highly likely to be close to the

values of its neighboring points. An example of a noise map is shown on the right side of the image

below.

Page 127: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

127

Noise is routinely used by visual effects artists to increase the appearance of realism in computer-

generated graphical images. The cloudmap and heightmap are examples of noise maps that we have

already used.

Perlin noise is named for its inventor, Ken Perlin, who developed the idea while working at Mathematical

Applications Group, Inc. The use of Perlin noise in the 1982 movie “Tron” led to an Academy Award for

Technical Achievement. The Perlin noise function has a pseudo-random appearance, but all of its visual

details are the same size. This property allows it to be readily controllable; multiple scaled copies of

Perlin noise can be inserted into mathematical expressions to create a variety of textures. Perlin noise is

widely used in computer graphics for visual effects like fire, smoke, and clouds. It is also frequently used

to generate textures when memory is limited. Synthetic texture using Perlin noise is often used to imitate

the apparently random textures of nature. For an understandable explanation of the details of Perlin noise

generation, see https://en.wikipedia.org/wiki/Perlin_noise.

In this section we will generate an modified version of Perlin Noise to learn how to generate a noise map.

We will then use this noise map to make our clouds more realistic. We will begin with multiple static

maps, which are easy to generate as they only contain random numbers. The resolution of the static maps

has to be the same, but the level of detail will increase by a factor of two from map to map, as shown in

these images:

If we sum these maps together, we will get an image that looks like this:

So far so good, but we can do better. First instead of using six different maps, we will use the same map

six times, with differently scaled texture coordinates. Second, we need to linearly interpolate between the

small number of values used to create the map. As it happens, the texture interpolator on our graphics

Page 128: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

128

card will do this job automatically for us. How cool is that?

Let’s begin by coding a small method that generates a low-resolution static map. Add the following

method to the skydome region:

private Texture2D CreateStaticMap(int resolution)

{ Random rand = new Random(); Color[] noisyColors = new Color[resolution * resolution]; for (int x = 0; x < resolution; x++) for (int y = 0; y < resolution; y++) noisyColors[x + y * resolution] = new Color(new Vector3((float)rand.Next(1000) / 1000.0f, 0, 0)); Texture2D noiseImage = new Texture2D(device, resolution, resolution, true, SurfaceFormat.Color); noiseImage.SetData(noisyColors); return noiseImage; }

Simply specify the resolution, and this method will return a square texture with completely random values

stored as the red color component in all of its pixels.

Since we are going to render the noise map as a HLSL effect, we will need two triangles covering the

whole screen. Two triangles need six vertices as a TriangleList, or four vertices as a TriangleStrip:

private void SetUpFullscreenVertices()

{ VertexPositionTexture[] vertices = new VertexPositionTexture[4]; vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 1)); vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 1)); vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 0)); vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 0)); fullScreenVertices = vertices; }

And we need to add some new instance variables to our Game1 code:

RenderTarget2D cloudsRenderTarget; Texture2D cloudStaticMap; VertexPositionTexture[] fullScreenVertices;

We will use the cloudStaticMap to hold our basic static map. Initialize the render target in our

LoadContent method (after reflectionRenderTarget is initialized:

Page 129: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

129

cloudsRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight,

false, pp.BackBufferFormat,

pp.DepthStencilFormat);

Generate the static map in the LoadTextures method:

cloudStaticMap = CreateStaticMap(CLOUD_SIZE);

Which will generate a CLOUD_SIZE x CLOUD_SIZE static map. We need to define this constant on our

constant region (Larger CLOUD_SIZE values make for smaller clouds): //cloud constants public const int CLOUD_SIZE = 32; //use 64 to make smaller clouds

Finally, we need to call SetUpFullscreenVertices at the end of the LoadContent method:

SetUpFullscreenVertices();

Now we need to create a new HLSL technique to do the majority of the work for us. The PerlinNoise

vertex shader simply to pass the texture coordinates to the pixel shader. In the pixel shader, we will take

the static image, and sample it six times at different resolutions. Since we want the Perlin value to remain

between 0 and 1, we need to scale the result to do this. The first sampling, which has the lowest

resolution, has the largest influence: 50% of the final result. The next one has 25%, then 12.5% and so on

to the highest resolution sampling. We need to make sure that when we sum everything up, we end with a

total of exactly 100%.

The last thing we need to do is to make our clouds move. We do this by moving the texture over the

skybox. However, real clouds do not just move, they also change shape. We can simulate this effect by

having the images of different resolution scroll over each other at different speeds.

Finally, since the Perlin value is between 0 and 1, we can sharpen up the image by taking it to a power

larger than 1. The smaller the xOvercast value, the smaller and sharper our Perlin clouds will be (since we

subtract this value from 1.0f, we get the inverse). Here is the resulting PerlinNoise shader, which you

should place at the end of effects.fx:

//------- Technique: PerlinNoise -------- struct PNVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; }; struct PNPixelToFrame { float4 Color : COLOR0; }; PNVertexToPixel PerlinVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD) {

Page 130: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

130

PNVertexToPixel Output = (PNVertexToPixel)0; Output.Position = inPos; Output.TextureCoords = inTexCoords; return Output; } PNPixelToFrame PerlinPS(PNVertexToPixel PSIn) { PNPixelToFrame Output = (PNPixelToFrame)0; float2 move = float2(0, 1); float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords) + xTime * move) / 2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 2 + xTime * move) / 4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 4 + xTime * move) / 8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 8 + xTime * move) / 16; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 16 + xTime * move) / 32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 32 + xTime * move) / 32; Output.Color.rgb = 1.0f - pow(abs(perlin.r), xOvercast)*2.0f; Output.Color.a = 1; return Output; } technique PerlinNoise { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PerlinVS(); PixelShader = compile ps_4_0_level_9_1 PerlinPS(); } }

The last thing we need to do in our shader code is to add the xOvercast global constant:

float xOvercast;

That’s it for our shader code. Now in Game1.cs, we need to add a simple method that runs the

PerlinNoise effect on our skydome region:

private void GeneratePerlinNoise(float time) { device.SetRenderTarget(cloudsRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["PerlinNoise"]; effect.Parameters["xTexture"].SetValue(cloudStaticMap); effect.Parameters["xOvercast"].SetValue(OVERCAST_FACTOR); effect.Parameters["xTime"].SetValue(time / TIME_DIV); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply();

Page 131: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

131

device.DrawUserPrimitives(PrimitiveType.TriangleStrip, fullScreenVertices, 0, 2); } device.SetRenderTarget(null); cloudMap = cloudsRenderTarget; }

And we need to add three new cloud constants to parameterize the effect (we will use Sky_Top_Color

soon):

public const float OVERCAST_FACTOR = 1.2f; // Increase to make more cloudy public const float TIME_DIV = 4000.0f; // Increase to make clouds move more slowly public Vector4 SKY_TOP_COLOR = new Vector4(0.3f, 0.3f, 0.8f, 1);

Finally, we need to call this method from the our Draw method (after DrawReflectionMap is a good

spot):

GeneratePerlinNoise(time);

If you save the cloudMap texture to file, you would get something like this:

If we ran our code at this point (feel free to do this) we would have a sky with great clouds and a black

background. Let’s fix this by creating a skydome technique worthy of displaying this map on our

skydome. This will result in a gradient skydome.

If we take a look outside, we notice that the top of the atmosphere has a deeper color than the horizon. We

can imitate that effect in HLSL. Let’s create a new Skydome shader technique to accomplish this.

The vertex shader of our new technique will need to pass the texture coordinates to the pixel shader, but

also the object position of each vertex. The object position of a vertex is the position of the vertex inside

the object, in this case, the skydome itself. This position remains the same all the time, no matter whether

the object is rotated, moved or scaled in the 3D world. This will allow us to define a fixed gradient in the

pixel shader.

Page 132: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

132

This is our technique so far (through the vertex shader):

//------- Technique: SkyDome --------

struct SDVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; float4 ObjectPosition : TEXCOORD1; }; struct SDPixelToFrame { float4 Color : COLOR0; }; SDVertexToPixel SkyDomeVS( float4 inPos : POSITION, float2 inTexCoords: TEXCOORD0) { SDVertexToPixel Output = (SDVertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; Output.ObjectPosition = inPos; return Output; }

You can see that the original position and texture coordinates are routed immediately to the

ObjectPosition and TextureCoords outputs, respectively. Because the skydome is a 3D model that needs

to be transformed to 2D screen space, we as usual need to transform the 3D position by the

WorldViewProjection matrix and pass the result to the mandatory Output.Position.

The pixel shader is straightforward. We define two colors: one blue-ish color for the top of the skydome

(passed in as float4 xSkyTopColor; in “Constants”, which you need to add at this point), and white for

the horizons. Add this to our global constants:

float4 xSkyTopColor;

Next, we interpolate between both colors, based on how high the current pixel is in the skydome. The

highest point in the skydome has height Y=0.5, so all pixels above 0.4 will get the xSkyTopColor color.

The pixels lower than 0.4 will get a gradient color between the xSkyTopColor color and white. Finally, we

look up the value in the cloud map, corresponding to the current pixel. We use this value to interpolate

between our sky color and white, which adds the clouds to our skydome. The rest of our SkyDome

techniques looks like this:

SDPixelToFrame SkyDomePS(SDVertexToPixel PSIn) { SDPixelToFrame Output = (SDPixelToFrame)0; float4 bottomColor = 1; float4 baseColor = lerp(bottomColor, xSkyTopColor, saturate((PSIn.ObjectPosition.y) / 0.4f));

Page 133: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

133

float4 cloudValue = tex2D(TextureSampler, PSIn.TextureCoords).r; Output.Color = lerp(baseColor, 1, cloudValue); return Output; } technique SkyDome { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SkyDomeVS(); PixelShader = compile ps_4_0_level_9_1 SkyDomePS(); } }

Now, back in Game1.cs, we need to change the DrawSkydome method, to use our new SkyDome

technique, as follows:

currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.CurrentTechnique = currentEffect.Techniques["SkyDome"]; currentEffect.Parameters["xSkyTopColor"].SetValue(SKY_TOP_COLOR);

Run this code. We should see a blue sky with moving, changing clouds. The only things we do not have

at this point are trees, the subject of the next section. Here is our code so far:

Game1.cs

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { #region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f;

Page 134: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

134

public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 // Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; public Vector4 DULL_COLOR = new Vector4(0.3f, 0.4f, 0.5f, 1.0f); public const float WATER_DIRTINESS = 0.2f; // Increase to make the water "dirtier" public const float WIND_FORCE = 0.0005f; // It doesn't take much public Vector3 WIND_DIRECTION = new Vector3(0, 0, 1); //cloud constants public const int CLOUD_SIZE = 32; //use 64 to make smaller clouds public const float OVERCAST_FACTOR = 1.2f; // Increase to make more cloudy public const float TIME_DIV = 4000.0f; // Increase to make clouds move more slowly public Vector4 SKY_TOP_COLOR = new Vector4(0.3f, 0.3f, 0.8f, 1); #endregion #region Vertex Structs public struct VertexPositionColorNormal { public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration (

Page 135: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

135

new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; VertexBuffer waterVertexBuffer; private int terrainWidth; private int terrainLength; private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D cloudMap; Model skyDome; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; Matrix reflectionViewMatrix; Texture2D waterBumpMap;

Page 136: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

136

RenderTarget2D cloudsRenderTarget; Texture2D cloudStaticMap; VertexPositionTexture[] fullScreenVertices; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total; } } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6];

Page 137: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

137

int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals() { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices); } private void LoadHeightData(Texture2D heightMap) {

Page 138: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

138

terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } } #endregion #region Texture Loads

Page 139: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

139

private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); cloudMap = Content.Load<Texture2D>("cloudMap"); waterBumpMap = Content.Load<Texture2D>("waterbump"); cloudStaticMap = CreateStaticMap(CLOUD_SIZE); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState)

Page 140: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

140

{ float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion #region Skydome Methods private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; //currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.CurrentTechnique = currentEffect.Techniques["SkyDome"];

Page 141: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

141

currentEffect.Parameters["xSkyTopColor"].SetValue(SKY_TOP_COLOR); currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } private Texture2D CreateStaticMap(int resolution) { Random rand = new Random(); Color[] noisyColors = new Color[resolution * resolution]; for (int x = 0; x < resolution; x++) for (int y = 0; y < resolution; y++) noisyColors[x + y * resolution] = new Color(new Vector3((float)rand.Next(1000) / 1000.0f, 0, 0)); Texture2D noiseImage = new Texture2D(device, resolution, resolution, true, SurfaceFormat.Color); noiseImage.SetData(noisyColors); return noiseImage; } private void SetUpFullscreenVertices() { VertexPositionTexture[] vertices = new VertexPositionTexture[4]; vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 1)); vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 1)); vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 0)); vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 0)); fullScreenVertices = vertices; } private void GeneratePerlinNoise(float time) { device.SetRenderTarget(cloudsRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["PerlinNoise"]; effect.Parameters["xTexture"].SetValue(cloudStaticMap); effect.Parameters["xOvercast"].SetValue(OVERCAST_FACTOR); effect.Parameters["xTime"].SetValue(time / TIME_DIV); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply();

Page 142: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

142

device.DrawUserPrimitives(PrimitiveType.TriangleStrip, fullScreenVertices, 0, 2); } device.SetRenderTarget(null); cloudMap = cloudsRenderTarget; } #endregion #region Water Methods private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } private void DrawRefractionMap() { Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false); effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); refractionMap = refractionRenderTarget; } private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D));

Page 143: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

143

// Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget; } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH); effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xDirtyWaterFactor"].SetValue(WATER_DIRTINESS); effect.Parameters["xDullColor"].SetValue(DULL_COLOR); effect.Parameters["xTime"].SetValue(time); effect.Parameters["xWindForce"].SetValue(WIND_FORCE);

Page 144: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

144

effect.Parameters["xWindDirection"].SetValue(WIND_DIRECTION); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } } #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a // current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects");

Page 145: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

145

skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); cloudsRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); SetUpWaterVertices(); SetUpFullscreenVertices(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)

Page 146: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

146

this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap(); GeneratePerlinNoise(time); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); base.Draw(gameTime); } #endregion } }

Page 147: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

147

effects.fx

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; float4x4 xReflectionView; float xWaveLength; float xWaveHeight; float3 xCamPos; float xDirtyWaterFactor; float4 xDullColor; float xTime; float3 xWindDirection; float xWindForce; float xOvercast; float4 xSkyTopColor; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0;

Page 148: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

148

sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored --------

Page 149: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

149

VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; } PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color;

Page 150: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

150

return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } } //------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } } //------- Technique: SimpleTextured -------- VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection);

Page 151: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

151

float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; }

PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION0; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z / Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0);

Page 152: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

152

return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); float lightingFactor = 1; float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3; float4 Position3D : TEXCOORD4; };

Page 153: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

153

struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); float3 windDir = normalize(xWindDirection); float3 perpDir = cross(xWindDirection, float3(0, 1, 0)); float ydot = dot(inTex, xWindDirection.xz); float xdot = dot(inTex, perpDir.xz); float2 moveVector = float2(xdot, ydot); moveVector.y += xTime * xWindForce; Output.BumpMapSamplingPos = moveVector / xWaveLength; return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords); float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords); float3 eyeVector = (float3) normalize(float4(xCamPos,0.0) - PSIn.Position3D);

Page 154: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

154

float3 normalVector = (bumpColor.rbg - 0.5f)*2.0f; float fresnelTerm = dot(eyeVector, normalVector); float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm); Output.Color = lerp(combinedColor, xDullColor, xDirtyWaterFactor); // add specular highlights float3 reflectionVector = -reflect(xLightDirection, normalVector); float specular = abs(dot(normalize(reflectionVector), normalize(eyeVector))); specular = pow(specular, 256); Output.Color.rgb += specular; return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } }

//------- Technique: PerlinNoise -------- struct PNVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; }; struct PNPixelToFrame { float4 Color : COLOR0; }; PNVertexToPixel PerlinVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD) { PNVertexToPixel Output = (PNVertexToPixel)0; Output.Position = inPos; Output.TextureCoords = inTexCoords; return Output; } PNPixelToFrame PerlinPS(PNVertexToPixel PSIn) { PNPixelToFrame Output = (PNPixelToFrame)0; float2 move = float2(0, 1); float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords) + xTime * move) / 2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 2 + xTime * move) / 4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 4 + xTime * move) / 8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 8 + xTime * move) / 16;

Page 155: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

155

perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 16 + xTime * move) / 32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 32 + xTime * move) / 32; Output.Color.rgb = 1.0f - pow(abs(perlin.r), xOvercast)*2.0f; Output.Color.a = 1; return Output; } technique PerlinNoise { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PerlinVS(); PixelShader = compile ps_4_0_level_9_1 PerlinPS(); } } //------- Technique: SkyDome -------- struct SDVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; float4 ObjectPosition : TEXCOORD1; }; struct SDPixelToFrame { float4 Color : COLOR0; }; SDVertexToPixel SkyDomeVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { SDVertexToPixel Output = (SDVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; Output.ObjectPosition = inPos; return Output; } SDPixelToFrame SkyDomePS(SDVertexToPixel PSIn) { SDPixelToFrame Output = (SDPixelToFrame)0; float4 bottomColor = 1; float4 baseColor = lerp(bottomColor, xSkyTopColor, saturate((PSIn.ObjectPosition.y) / 0.4f)); float4 cloudValue = tex2D(TextureSampler, PSIn.TextureCoords).r; Output.Color = lerp(baseColor, 1, cloudValue); return Output; }

Page 156: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

156

technique SkyDome { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SkyDomeVS(); PixelShader = compile ps_4_0_level_9_1 SkyDomePS(); } }

Adding Trees - Billboarding

With our multitextured terrain and water finished, it’s time to add some trees to our terrain.

If you have not already done so, import tree.dds into your content project.

One way to create trees would be to load a 3D Model of a tree, and rendering it several hundred times on

our terrain. Unfortunately, this would likely result in an unacceptably slow frame rate. The solution is a

technique called “billboarding”. The idea behind billboarding is that most of the trees in a landscape are

there for scenery, not to be an important part of game play. Billboarding therefore replaces distant 3D

objects with simple 2D images. So in the case of a tree, we would replace a 3D tree model by a simple 2D

tree image, positioned at the same position in our 3D world. To make the tree realistic, we always keep it

facing toward the camera. “Spherical” billboarding keeps the 2D image facing toward the camera

regardless of position. “Cylindrical” billboarding keeps the image upright, and rotates the image only

about the Y axis. If you think about this, it is easy to see that cylindrical billboarding is what we need for

trees (since trees generally grow up, not sideways). Billboarding is a widely used technique in game

programming. And, if we ever really need a detailed 3D tree, we can always draw one when the camera

gets close enough to tell the difference.

The figures below illustrates how billboarding works. The left image shows five 2D images that are

positioned in a 3D world. The right image shows exactly the same five 2D images, rotated so that they

face the camera. If the images were of a tree, the right image would give a nice result, provided we were

not too close.

Billboards consist of two triangles of three vertices each, but two vertices of each triangle are shared, so

there are only four unique vertices. As we did with terrain, we will use indices to make the drawing of

billboards faster. There are six indices per billboard. We will thus render each billboard using two

triangles, identified by six indices that reference four vertices. For each billboard, the four vertices need to

Page 157: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

157

contain the correct texture coordinates, but they will all contain the same position: the central bottom

position of the billboard, as shown in the image below. That’s easy to specify, as it’s the position where

the trunk will hit the terrain. The graphics process will take care of the math to correctly locate the four

vertices for each billboard, since it would have to do that anyway to locate the billboard in its 3D space.

In the figure below, each of the three vertices are given unique coordinates: (0,0), (0,1), (1,0) and (1,1).

Notice that vertices (0,0) and (1,1) are shared by the two triangles, and that the blue dot denotes the initial

position of the four vertices in object space.

In a moment, we will create a list of positions where we want a billboard in our 3D world. First let’s see

how we define vertices and indices once we have the list. The method below uses a list (treeList in this

case), and for each position in the list it will generate four vertices, and the corresponding six indices. It

then places this information in a vertex buffer and an index buffer, respectively. Create a new region

(Tree Methods), and add the following code:

#region Tree Methods

private void CreateBBVertsAndIntsFromList() { VertexPositionTexture[] billboardVertices = new VertexPositionTexture[treeList.Count * 4]; int [] billboardIndices = new int [treeList.Count * 6]; int j = 0; int i = 0; foreach (Vector3 currentV3 in treeList) { // Create vertices billboardVertices[i + 0] = new VertexPositionTexture(currentV3, new Vector2(0, 0)); billboardVertices[i + 1] = new VertexPositionTexture(currentV3, new Vector2(0, 1)); billboardVertices[i + 2] = new VertexPositionTexture(currentV3, new Vector2(1, 1)); billboardVertices[i + 3] = new VertexPositionTexture(currentV3, new Vector2(1, 0));

Page 158: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

158

//Create indices billboardIndices[j++] = i + 0; billboardIndices[j++] = i + 3; billboardIndices[j++] = i + 2; billboardIndices[j++] = i + 2; billboardIndices[j++] = i + 1; billboardIndices[j++] = i + 0; i += 4; } treeVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, billboardVertices.Length, BufferUsage.WriteOnly); treeVertexBuffer.SetData(billboardVertices); treeIndexBuffer = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, treeList.Count * 6, BufferUsage.WriteOnly); treeIndexBuffer.SetData(billboardIndices); } #endregion

Now we need to define the VertexBuffer and IndexBuffer, and the treeList in our Game1 instance

variables, as follows:

VertexBuffer treeVertexBuffer; IndexBuffer treeIndexBuffer; List<Vector3> treeList;

In order to use the List type, we need to add a using statement to the top of Game1.cs: using System.Collections.Generic;

The billboard vertices will be transformed to the correct location by our vertex shader, so that the

resulting billboards will always be facing the camera. We will create this shader code in a moment, but

first we will complete the Game1 code. If you forgot to do so earlier, import tree.dds into your content

project.

Let’s also add a texture declaration:

Texture2D treeTexture;

And load this texture in the LoadTextures method:

treeTexture = Content.Load<Texture2D>("tree");

Now, let’s create a simple method in our Trees region that will generate a list with four trees:

private void GenerateTreePositions(VertexMultitextured[] terrainVertices) { treeList = new List<Vector3>();

Page 159: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

159

treeList.Add(terrainVertices[3310].Position); treeList.Add(terrainVertices[3315].Position); treeList.Add(terrainVertices[3320].Position); treeList.Add(terrainVertices[3325].Position); }

This method creates a list, using four terrain positions chosen randomly. In the next section, we will

extend this method, for now we will just try to render these four trees to the screen. We need to call both

of these new methods from within LoadContent (place this code after the call to SetUpWaterVertices:

GenerateTreePositions(vertices);

CreateBBVertsAndIntsFromList();

Finally, we need a method to draw our billboards. Add a new method in the Tree Methods region to do

this:

private void DrawTrees(Matrix currentViewMatrix)

{ effect.CurrentTechnique = effect.Techniques["CylBillboard"]; effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xView"].SetValue(currentViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0)); effect.Parameters["xBillboardTexture"].SetValue(treeTexture); int numVertices = treeList.Count * 4; int numTriangles = treeList.Count * 2; device.SetVertexBuffer(treeVertexBuffer); device.Indices = treeIndexBuffer; // Turn on alpha blending GraphicsDevice.BlendState = BlendState.AlphaBlend; GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; effect.CurrentTechnique.Passes[0].Apply(); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numTriangles); // Reset graphics adapter to default state GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; }

We select the cylindrical billboarding technique (which we will create soon), and set the necessary

MonoGame-to-HLSL variables. The only one that needs explanation is the xAllowedRotDir variable,

which specifies the axis around which the 2D images are allowed to rotate. This is the difference between

spherical and cylindrical billboarding: in spherical billboarding, the 2D image is allowed to rotate about

any axis to rotate the image to face the camera. Since we are dealing with trees, we want the tree only to

be rotated along its trunk, which is around the (0,1,0) Up axis.

Now call this method at the very end of our Draw method (after DrawWater):

Page 160: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

160

DrawTrees(viewMatrix);

Finally, let’s create the new shader code. Add the following code to effects.fx. First, declare a new

global constant:

float3 xAllowedRotDir;

And add a texture sampler for the billboard texture:

Texture xBillboardTexture; sampler textureSampler = sampler_state { texture = <xBillboardTexture> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = CLAMP; AddressV = CLAMP;};

Then add the actual technique to the end of the file:

//------- Technique: CylBillboard -------- struct BBVertexToPixel { float4 Position : POSITION; float2 TexCoord : TEXCOORD0; }; struct BBPixelToFrame { float4 Color : COLOR0; }; BBVertexToPixel CylBillboardVS(float3 inPos: POSITION0, float2 inTexCoord : TEXCOORD0) { BBVertexToPixel Output = (BBVertexToPixel)0; float3 center = (float3) mul(float4(inPos,0.0), xWorld); float3 eyeVector = center - xCamPos; float3 upVector = xAllowedRotDir; upVector = normalize(upVector); float3 sideVector = cross(eyeVector, upVector); sideVector = normalize(sideVector); float3 finalPosition = center; finalPosition += (inTexCoord.x - 0.5f)*sideVector; finalPosition += (1.5f - inTexCoord.y*1.5f)*upVector; float4 finalPosition4 = float4(finalPosition, 1); float4x4 preViewProjection = mul(xView, xProjection); Output.Position = mul(finalPosition4, preViewProjection); Output.TexCoord = inTexCoord; return Output; } BBPixelToFrame BillboardPS(BBVertexToPixel PSIn)

Page 161: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

161

{ BBPixelToFrame Output = (BBPixelToFrame)0; Output.Color = tex2D(textureSampler, PSIn.TexCoord); return Output; } technique CylBillboard { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 CylBillboardVS(); PixelShader = compile ps_4_0_level_9_1 BillboardPS(); } }

Most of this code is stuff we have seen before. A few words of explanation are in order. In the vertex

shader, we have to account for the fact that all of the vertices are in the middle-bottom position. The

finalPosition computation deals with this. The upVector computation endures that we will only rotate

about the Y axis.

Run this code. You should see the four pathetic-looking trees shown below:

Move your camera around the trees, and your graphics card will render them so they are always facing the

camera. How cool is that? However, if you position the camera directly above the trees, you will see that

they are 2D images. If you place the camera above the trees and a little bit off center you can watch the

trees rotate as the camera moves up and down.

Page 162: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

162

Adding More Trees

In this section, we will populate our world with forests. To get the most realistic result, instead of simply

putting trees randomly on our terrain, we are going to selectively add small patches of forest.

How should we create the boundaries of such a forest? The solution is to use a noisemap. We already

know how to create noisemaps, so to speed things up we will use a ready-made one (feel free to create

your own, just as we did for clouds). The image below depicts our ready-made noise map.

Our basic approach to using this noise map is as follows: for each pixel in the noisemap where the white

value is above a certain threshold, we will add a tree to our terrain. We will also take into account the

height of the terrain (forests are not likely in the middle of a river, or on top of a mountain), and the slope

of the terrain (forests are less likely on a steep mountain side). If you have not already done so, begin by

importing treeMap.jpg into your content project.

We have a lot of changes to make to the GenerateTreePositions method, so let’s look at the revised code,

and then work through it. Replace the existing GenerateTreePositions code with this code:

private void GenerateTreePositions(VertexMultitextured[] terrainVertices) { Color[] treeMapColors = new Color[treeMap.Width * treeMap.Height]; treeMap.GetData(treeMapColors); int[,] noiseData = new int[treeMap.Width, treeMap.Height]; for (int x = 0; x < treeMap.Width; x++) for (int y = 0; y < treeMap.Height; y++) noiseData[x, y] = treeMapColors[y + x * treeMap.Height].R; treeList = new List<Vector3>(); Random random = new Random(); for (int x = 0; x < terrainWidth; x++)

Page 163: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

163

{ for (int y = 0; y < terrainLength; y++) { float terrainHeight = heightData[x, y]; if ((terrainHeight > TREE_MIN_HT) && (terrainHeight < TREE_MAX_HT)) { float flatness = Vector3.Dot(terrainVertices[x + y * terrainWidth].Normal, new Vector3(0, 1, 0)); float minFlatness = (float)Math.Cos(MathHelper.ToRadians(TREE_MAX_SLOPE)); if (flatness > minFlatness) { float relx = (float)x / (float)terrainWidth; float rely = (float)y / (float)terrainLength; float noiseValueAtCurrentPosition = noiseData[(int)(relx * treeMap.Width), (int)(rely * treeMap.Height)]; float treeDensity; if (noiseValueAtCurrentPosition > TREEMAP_HI_THOLD) treeDensity = HI_NUM_TREES; else if (noiseValueAtCurrentPosition > TREEMAP_MED_THOLD) treeDensity = MED_NUM_TREES; else if (noiseValueAtCurrentPosition > TREEMAP_LOW_THOLD) treeDensity = LOW_NUM_TREES; else treeDensity = 0; for (int currDetail = 0; currDetail < treeDensity; currDetail++) { float rand1 = (float)random.Next(1000) / 1000.0f; float rand2 = (float)random.Next(1000) / 1000.0f; Vector3 treePos = new Vector3((float)x - rand1, 0, -(float)y - rand2); treePos.Y = heightData[x, y]; treeList.Add(treePos); } } } } } }

We begin by converting the treeMap image into a 1D array of colors: first create the 1D array, large

enough to store all data and then copy the data from the texture into the array using the GetData method.

Next, the 1D array is reshaped into a 2D array of integers. For each pixel, the red component (which is a

value between 0 and 255) is extracted. Because the current noisemap is a greyscale map, the red

component will be the same as the blue and green components.

With our 2D array ready, we create the list of Vector3s to hold the final output of the method, plus a

random number generator (to give some realism to our tree location choices).

Page 164: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

164

The final double for loop scans our terrain grid, and at each vertex we will decide whether we need to add

trees. For each vertex, we ask:

1. Is the height acceptable? We don’t want to put some trees in the middle of the river, or on top of

our mountains.

2. Is the terrain flat? We don’t want trees on steep hillsides.

3. What should be the density of trees at this location? We want many trees at the center of the

forests, while at the borders of the forest the density should be lower.

Let’s tackle these issues one by one. The first one is easy: we will simply look up the height of our terrain

at the current position, and check if it’s inside an acceptable region. This makes sure that trees only get

planted when the height of the terrain is between TREE_MIN_HT and TREE_MAX_HT. We need to add

some constants to define these boundaries:

//Tree Constants public const int TREE_MIN_HT = 8; // Min terrain height for trees public const int TREE_MAX_HT = 14; // Max terrain height for trees

Next, we address terrain steepness. Here we can use the normals defined for each vertex. The normal

indicates the direction perpendicular to the terrain at each vertex. So on a flat part of the terrain, the

normal will point upward. So we simply need to check how close the normal is to the Up vector. As we

have seen before, a good way to measure this is by taking the dot product of both vectors. If the vertex

and the (0,1,) Up vector are the same, the dot product will be 1. If they are perpendicular to each other, it

will be 0. The discerning reader will notice that the dot product is nothing more than the cosine of the

angle between the two vectors, so we can check to see that the inclination of the terrain is no more than

TREE_MAX_SLOPE degrees:

public const int TREE_MAX_SLOPE = 15; // Max terrain slope on which to put trees

Finally, we need to check whether the noise map indicates there should be a tree at the current position.

Therefore, we will sample the noisemap at the position corresponding to the current location on the

terrain. Since we want the resolutions of our terrain and noisemap to be independent of each other, we

work with relative coordinates. The relx and rely values will be between 0 and 1, allowing us to sample

the noisemap at the correct location. The noise value at the current location is stored in the

noiseValueAtCurrentPosition variable, which contains a value between 0 and 255. From this value, we

decide how many trees should be added at the current location. If the value is really high (above

TREEMAP_HI_THOLD), we’re in the middle of a forest and we want to add HI_NUM_TREES trees around

the current terrain vertex. This number decreases corresponding to the current noise value, and drops to

zero below a certain threshold. Lets define the constants that characterize this behavior:

public const int TREEMAP_HI_THOLD = 200; // Noise map value for HI_NUM_TREES trees public const int TREEMAP_MED_THOLD = 150; // Noise map value for MED_NUM_TREES trees public const int TREEMAP_LOW_THOLD = 100; // Noise map value for LOW_NUM_TREES trees public const int HI_NUM_TREES = 5; // Num trees at TREEMAP_HI_THOLD noise value

Page 165: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

165

public const int MED_NUM_TREES = 4; // Num trees at TREEMAP_MED_THOLD noise value public const int LOW_NUM_TREES = 3; // Num trees at TREEMAP_LOW_THOLD noise value

At this point, we know exactly how many trees we should add at the current terrain vertex. The next bit of

code actually generates the locations for the new trees. First we generate two random numbers, which we

scale to the range [0,1]. We use these to add to the X and Z coordinates of the current vertex, and we use

the height of the terrain as Y coordinate. Once these values are computed, we add the new position to the

treeList, and we’re done.

The last thing we need to do is to load the treeMap.jpg texture. First declare the necessary Game1

instance variable:

Texture2D treeMap;

Then, in LoadTextures, load it.

treeMap = Content.Load<Texture2D>("treeMap");

Run the code at this point. You should see forests of trees. Experiment with different values for noise

and height thresholds. When you move your camera around the world, there are two flaws that you might

notice:

1. The most critical flaw is apparent when you move the camera above the highest mountain and

look at the trees. Trees behind other trees on certain terrain and for certain camera positions may

be occluded in odd ways. We will address this problem in the next section.

2. The second problem is that the trees are not reflected in the water. This is because we are not

rendering any trees in our DrawReflectionMap method, so let’s fix this. Add the following call

after the call to the DrawSkyDome method (in DrawReflectionMap).

DrawTrees(reflectionViewMatrix);

Now we should see at some points you will see trees reflected in the water (the camera angle has to be

right, just like in real life).

A Few Last Details

Our trees look good from most angles, but sometimes trees behind other trees look like they have been cut

in half, as depicted in the image below.

Page 166: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

166

Why is this happening? To answer this question, consider what the graphics card is doing:

1. The skydome and terrain are rendered, saving their color in the backbuffer and their distance in

the Z buffer.

2. Our program enters the DrawTrees method, and asks the graphics card to render the trees.

3. The first tree is rendered to the screen. Let’s say this is the tree shown in front in the image above.

The whole rectangle of the 2D image is rendered. The transparent pixels are also rendered; in this

case the color already present in the backbuffer remains. However, since these pixels are actually

rendered, they change the value in the Z buffer for these pixels.

4. The second billboard is rendered. Let’s say this is the tree to the right of the front tree. For all

pixels of this rectangle, the graphics card will check whether the distance to the camera is closer

than the value stored in the Z buffer. This is where the problem arises: the first tree has already

written its distance in the Z buffer, so the pixels of the left part of the second tree have a larger

distance to the camera than the value already stored in the Z buffer. The graphics card, thinking it

is helping us, does not render pixels of objects that are behind other objects.

There are two solutions to this problem. The first is 100% accurate, but impractical for large numbers of

billboards, unless you have the computer resources available to George Lucas: render the trees, starting

from back to front. This way, all pixels of all trees will be rendered, allowing perfect blending. The only

drawback, however, is that we would need to order the billboards from back to front before rendering

them. This order depends entirely on the position of your camera, so we would need to do this reordering

each time the camera (or the position of a tree) moves. Since this reordering would be done by the CPU,

we cannot employ this approach.

Page 167: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

167

The second approach is not 100% accurate, but it is close, and it is very fast. This is how we will render

our trees:

1. Start by rendering all billboards, but only the pixels of the trees that are not transparent. This will

not render the full rectangle of the images, but only those pixels of the core of trees and leaves.

Also, the Z buffer will be updated only for those pixels. You can see the result of this first step in

the left image below: only the core of the trunks and trees are rendered, but the order of the trees

is correct.

2. In the second step, we will render our billboards again, but this time only the (partly) transparent

pixels. While doing this, we will disable updating the Z buffer, because otherwise we will run

into the same trouble. We can disable the updating of the Z buffer, because the cores of the trees

have already been drawn correctly, and thus the most important pixels already have the correct Z

value. The result after the second step is shown in the right image below.

Step 1 Step 2

To implement this approach we need to change the DrawTrees method, and make a slight modification to

our pixel shader. Here is the new DrawTrees code, which we will explain below:

private void DrawTrees(Matrix currentViewMatrix) { effect.CurrentTechnique = effect.Techniques["CylBillboard"]; effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xView"].SetValue(currentViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xCamPos"].SetValue(cameraPosition);

Page 168: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

168

effect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0)); effect.Parameters["xBillboardTexture"].SetValue(treeTexture); effect.Parameters["xBBAlphaTestValue"].SetValue(BB_ALPHA_TEST_VALUE); int numVertices = treeList.Count * 4; int numTriangles = treeList.Count * 2; device.SetVertexBuffer(treeVertexBuffer); device.Indices = treeIndexBuffer; GraphicsDevice.BlendState = BlendState.AlphaBlend; // First draw the "solid" part of the tree GraphicsDevice.DepthStencilState = DepthStencilState.Default; effect.Parameters["xAlphaTestGreater"].SetValue(true); effect.CurrentTechnique.Passes[0].Apply(); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numTriangles); // Now draw the rest of the tree GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; effect.Parameters["xAlphaTestGreater"].SetValue(false); effect.CurrentTechnique.Passes[0].Apply(); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numTriangles); // Reset graphics adapter to default state GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; }

The effect parameters are the same as before, except for new parameter xBBAlphaTestValue. This is the

value that we pass to the pixel shader to represent the alpha threshold of pixels of the core of trees and

leaves. We need to define a new constant to hold this value:

public const float BB_ALPHA_TEST_VALUE = 0.6f; // useful values are .5 to .9

After setting all but one of the effect parameters, we pre-compute the number of triangles and the number

of vertices, so we only have to do this once, and set the appropriate vertex and index buffers. Then we

turn on alpha blending (so transparent pixels will not be rendered). The next four lines are where we

draw the core trees and leaves. To do this, we have to set the DepthStencilState to allow depth buffer

writes. The xAlphaTestGreater parameter will be used to tell the pixel shader whether we are drawing the

core (alpha values greater than xBBAlphaTestValue), or the (mostly) transparent pixels (alpha values less

than or equal to xBBAlphaTestValue). After drawing the core trees and leaves, we draw the (mostly)

transparent pixels by setting xAlphaTestGreater to “false” and DepthStencilState to read-only before

rendering. Finally, we restore the default graphics card settings.

We are almost done, but we still need to update our pixel shader in effects.fx. First, declare two new

global constants to hold the new passed-in values: float xBBAlphaTestValue; bool xAlphaTestGreater;

Now we need to add a single line to our pixel shader, shown in yellow below:

Page 169: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

169

BBPixelToFrame BillboardPS(BBVertexToPixel PSIn) : COLOR0 { BBPixelToFrame Output = (BBPixelToFrame)0; Output.Color = tex2D(textureSampler, PSIn.TexCoord); clip((Output.Color.a - xBBAlphaTestValue) * (xAlphaTestGreater ? 1 : -1)); return Output; }

This line uses the HLSL intrinsic function clip, which we have used before. The clip function discards

pixels for which the argument is zero. By subtracting xBBAlphaTestValue from the alpha value of the

current pixel, and multiplying the result by either 1 (if we want to keep pixels whose alpha value is

greater than xBBAlphaTestValue) , or -1 (if we want to keep pixels whose alpha value is less than or equal

to xBBAlphaTestValue), the clip function does exactly what we need.

Running this code should give you something like the right image above, but this time, the trees always

look good, no matter where the camera is positioned. How cool is that? That’s it for billboards, and in

fact for Lab 8. To those of you who stuck it out this far, congratulations! I hope you think it was time

well spent. If you have comments, or if you find errors, please contact me at [email protected]. Finally,

I want to again thank Riemer Grootjans for creating and sharing the XNA 3.1 on-line tutorials upon which

this lab was originally based.

Here is our final bucolic scene:

Page 170: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

170

Optional: Two other things that you might want to do at this point:

1. Incorporate Lab 9 (which will measure your performance in frames per second), and

2. Use a much larger heightmap (e.g., heightmap5.png, available in the Lab8-Mono files), which

will have an interesting effect on performance.

Here is our final Game1.cs and effects.fx code (without the optional items above):

Game1.cs

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; namespace Lab8_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game {

Page 171: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

171

#region Constants //Lighting Constants Vector3 LIGHT_DIRECTION = new Vector3(1.0f, -1.0f, 1.0f); //use (1.0f, -1.0f, 1.0f) to flip light public const float AMBIENT_LIGHT_LEVEL = 0.7f; // Camera control constants public const float ROTATION_SPEED = 0.3f; public const float MOVE_SPEED = 30.0f; // Terrain texture mapping constants public const float MAX_HT = 30.0f; public const float MIN_HT = 0.0f; public const float SAND_UPPER = 0.266f * MAX_HT; // 8 public const float GRASS_MID = 0.4f * MAX_HT; // 12 public const float GRASS_RANGE = 0.2f * MAX_HT; // 12 +/- 6 public const float ROCK_MID = 0.666f * MAX_HT; // 20 public const float ROCK_RANGE = 0.2f * MAX_HT; // 20 +/- 6 public const float SNOW_LOWER = 0.8f * MAX_HT; // 24 // Water Constants const float WATER_HEIGHT = 5.0f; public const float WAVE_LENGTH = 0.2f; public const float WAVE_HEIGHT = 0.3f; public Vector4 DULL_COLOR = new Vector4(0.3f, 0.4f, 0.5f, 1.0f); public const float WATER_DIRTINESS = 0.2f; // Increase to make the water "dirtier" public const float WIND_FORCE = 0.0005f; // It doesn't take much public Vector3 WIND_DIRECTION = new Vector3(0, 0, 1); //Cloud constants public const int CLOUD_SIZE = 32; //use 64 to make smaller clouds public const float OVERCAST_FACTOR = 1.2f; // Increase to make more cloudy public const float TIME_DIV = 4000.0f; // Increase to make clouds move more slowly public Vector4 SKY_TOP_COLOR = new Vector4(0.3f, 0.3f, 0.8f, 1); //Tree Constants public const int TREE_MIN_HT = 8; // Min terrain height for trees public const int TREE_MAX_HT = 14; // Max terrain height for trees public const int TREE_MAX_SLOPE = 15; // Max terrain slope on which to put trees public const int TREEMAP_HI_THOLD = 200; // Noise map value for HI_NUM_TREES trees public const int TREEMAP_MED_THOLD = 150; // Noise map value for MED_NUM_TREES trees public const int TREEMAP_LOW_THOLD = 100; // Noise map value for LOW_NUM_TREES trees public const int HI_NUM_TREES = 5; // Num trees at TREEMAP_HI_THOLD noise value public const int MED_NUM_TREES = 4; // Num trees at TREEMAP_MED_THOLD noise value public const int LOW_NUM_TREES = 3; // Num trees at TREEMAP_LOW_THOLD noise value public const float BB_ALPHA_TEST_VALUE = 0.6f; // useful values are .5 to .9 #endregion #region Vertex Structs public struct VertexPositionColorNormal {

Page 172: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

172

public Vector3 Position; public Color Color; public Vector3 Normal; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0), new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); } #endregion #region Vertex Structs public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public readonly static VertexDeclaration vertexDeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0), new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1) ); } #endregion #region Game1 Instance Vars GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; VertexMultitextured[] vertices; int[] indices; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer myVertexBuffer; IndexBuffer myIndexBuffer; VertexBuffer waterVertexBuffer; private int terrainWidth; private int terrainLength;

Page 173: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

173

private float[,] heightData; Matrix worldMatrix = Matrix.Identity; Matrix worldTranslation = Matrix.Identity; Matrix worldRotation = Matrix.Identity; Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D treeTexture; Texture2D cloudMap; Texture2D treeMap; Model skyDome; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; Matrix reflectionViewMatrix; Texture2D waterBumpMap; RenderTarget2D cloudsRenderTarget; Texture2D cloudStaticMap; VertexPositionTexture[] fullScreenVertices; VertexBuffer treeVertexBuffer; IndexBuffer treeIndexBuffer; List<Vector3> treeList; #endregion #region Class Game1 Constructor public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } #endregion #region Terrain Methods private void SetUpVertices() { vertices = new VertexMultitextured[terrainWidth * terrainLength];

Page 174: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

174

for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / MAX_HT; vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / MAX_HT; vertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MIN_HT) / SAND_UPPER, 0, 1); vertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - GRASS_MID) / GRASS_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - ROCK_MID) / ROCK_RANGE, 0, 1); vertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - MAX_HT) / SNOW_LOWER, 0, 1); float total = vertices[x + y * terrainWidth].TexWeights.X; total += vertices[x + y * terrainWidth].TexWeights.Y; total += vertices[x + y * terrainWidth].TexWeights.Z; total += vertices[x + y * terrainWidth].TexWeights.W; vertices[x + y * terrainWidth].TexWeights.X /= total; vertices[x + y * terrainWidth].TexWeights.Y /= total; vertices[x + y * terrainWidth].TexWeights.Z /= total; vertices[x + y * terrainWidth].TexWeights.W /= total; } } } private void SetUpIndices() { indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } } private void CalculateNormals()

Page 175: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

175

{ for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); } private void CopyToBuffers() { myVertexBuffer = new VertexBuffer(device, VertexMultitextured.vertexDeclaration, vertices.Length, BufferUsage.WriteOnly); myVertexBuffer.SetData(vertices); myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); myIndexBuffer.SetData(indices); } private void LoadHeightData(Texture2D heightMap) { terrainWidth = heightMap.Width; terrainLength = heightMap.Height; float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++)

Page 176: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

176

heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * MAX_HT; } private void DrawTerrain(Matrix currentViewMatrix) { LIGHT_DIRECTION.Normalize(); effect.Parameters["xLightDirection"].SetValue(LIGHT_DIRECTION); effect.Parameters["xAmbient"].SetValue(AMBIENT_LIGHT_LEVEL); effect.Parameters["xEnableLighting"].SetValue(true); effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.Indices = myIndexBuffer; device.SetVertexBuffer(myVertexBuffer); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indices.Length / 3); } } #endregion #region Texture Loads private void LoadTextures() { grassTexture = Content.Load<Texture2D>("grass"); sandTexture = Content.Load<Texture2D>("sand"); rockTexture = Content.Load<Texture2D>("rock"); snowTexture = Content.Load<Texture2D>("snow"); cloudMap = Content.Load<Texture2D>("cloudMap"); waterBumpMap = Content.Load<Texture2D>("waterbump"); cloudStaticMap = CreateStaticMap(CLOUD_SIZE); treeTexture = Content.Load<Texture2D>("tree"); treeMap = Content.Load<Texture2D>("treeMap"); } #endregion #region Camera Methods private void SetUpCamera() { UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,

Page 177: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

177

device.Viewport.AspectRatio, 0.3f, 1000.0f); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + WATER_HEIGHT * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + WATER_HEIGHT * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= ROTATION_SPEED * xDifference * amount; updownRot -= ROTATION_SPEED * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0);

Page 178: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

178

if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += MOVE_SPEED * rotatedVector; UpdateViewMatrix(); } #endregion #region Skydome Methods private void DrawSkyDome(Matrix currentViewMatrix) { GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(500) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix mworldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; //currentEffect.CurrentTechnique = currentEffect.Techniques["SimpleTextured"]; currentEffect.CurrentTechnique = currentEffect.Techniques["SkyDome"]; currentEffect.Parameters["xSkyTopColor"].SetValue(SKY_TOP_COLOR); currentEffect.Parameters["xAmbient"].SetValue(1.0f); currentEffect.Parameters["xWorld"].SetValue(mworldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } GraphicsDevice.DepthStencilState = DepthStencilState.Default; } private Texture2D CreateStaticMap(int resolution) { Random rand = new Random(); Color[] noisyColors = new Color[resolution * resolution]; for (int x = 0; x < resolution; x++) for (int y = 0; y < resolution; y++)

Page 179: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

179

noisyColors[x + y * resolution] = new Color(new Vector3((float)rand.Next(1000) / 1000.0f, 0, 0)); Texture2D noiseImage = new Texture2D(device, resolution, resolution, true, SurfaceFormat.Color); noiseImage.SetData(noisyColors); return noiseImage; } private void SetUpFullscreenVertices() { VertexPositionTexture[] vertices = new VertexPositionTexture[4]; vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 1)); vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 1)); vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 0)); vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 0)); fullScreenVertices = vertices; } private void GeneratePerlinNoise(float time) { device.SetRenderTarget(cloudsRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["PerlinNoise"]; effect.Parameters["xTexture"].SetValue(cloudStaticMap); effect.Parameters["xOvercast"].SetValue(OVERCAST_FACTOR); effect.Parameters["xTime"].SetValue(time / TIME_DIV); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.DrawUserPrimitives(PrimitiveType.TriangleStrip, fullScreenVertices, 0, 2); } device.SetRenderTarget(null); cloudMap = cloudsRenderTarget; } #endregion #region Water Methods private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1;

Page 180: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

180

Matrix worldViewProjection = currentViewMatrix * projectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } private void DrawRefractionMap() { Plane refractionPlane = CreatePlane(WATER_HEIGHT + 1.5f, new Vector3(0, 1, 0), viewMatrix, false); effect.Parameters["ClipPlane0"].SetValue(new Vector4(refractionPlane.Normal, refractionPlane.D)); // Enable clipping for the purpose of creating a refraction map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); refractionMap = refractionRenderTarget;

// write out our refraction map to see what it looks like // DO NOT leave this code active /* using (Stream stream = File.OpenWrite("refractionmap.png")) { { refractionMap.SaveAsPng(stream, refractionMap.Width, refractionMap.Height); } stream.close(); } */ } private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(WATER_HEIGHT - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); effect.Parameters["ClipPlane0"].SetValue(new Vector4(-reflectionPlane.Normal, -reflectionPlane.D)); // Enable clipping for the purpose of creating a reflection map effect.Parameters["Clipping"].SetValue(true); device.SetRenderTarget(reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

Page 181: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

181

DrawSkyDome(reflectionViewMatrix); DrawTrees(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); // Turn clipping off effect.Parameters["Clipping"].SetValue(false); device.SetRenderTarget(null); reflectionMap = reflectionRenderTarget;

// write out our reflection map to see what it looks like // DO NOT leave this code active /* using (Stream stream = File.OpenWrite("reflectionmap.png")) { { reflectionMap.SaveAsPng(stream, reflectionMap.Width, reflectionMap.Height); } stream.close(); } */ } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, WATER_HEIGHT, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, WATER_HEIGHT, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, waterVertices.Length, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(WAVE_LENGTH);

Page 182: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

182

effect.Parameters["xWaveHeight"].SetValue(WAVE_HEIGHT); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xDirtyWaterFactor"].SetValue(WATER_DIRTINESS); effect.Parameters["xDullColor"].SetValue(DULL_COLOR); effect.Parameters["xTime"].SetValue(time); effect.Parameters["xWindForce"].SetValue(WIND_FORCE); effect.Parameters["xWindDirection"].SetValue(WIND_DIRECTION); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); device.SetVertexBuffer(waterVertexBuffer); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); } } #region Tree Methods private void CreateBBVertsAndIntsFromList() { VertexPositionTexture[] billboardVertices = new VertexPositionTexture[treeList.Count * 4]; int[] billboardIndices = new int[treeList.Count * 6]; int j = 0; int i = 0; foreach (Vector3 currentV3 in treeList) { // Create vertices billboardVertices[i + 0] = new VertexPositionTexture(currentV3, new Vector2(0, 0)); billboardVertices[i + 1] = new VertexPositionTexture(currentV3, new Vector2(0, 1)); billboardVertices[i + 2] = new VertexPositionTexture(currentV3, new Vector2(1, 1)); billboardVertices[i + 3] = new VertexPositionTexture(currentV3, new Vector2(1, 0)); //Create indices billboardIndices[j++] = i + 0; billboardIndices[j++] = i + 3; billboardIndices[j++] = i + 2; billboardIndices[j++] = i + 2; billboardIndices[j++] = i + 1; billboardIndices[j++] = i + 0; i += 4; } treeVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, billboardVertices.Length, BufferUsage.WriteOnly); treeVertexBuffer.SetData(billboardVertices); treeIndexBuffer = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, treeList.Count * 6, BufferUsage.WriteOnly); treeIndexBuffer.SetData(billboardIndices);

Page 183: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

183

} private void DrawTrees(Matrix currentViewMatrix) { effect.CurrentTechnique = effect.Techniques["CylBillboard"]; effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xView"].SetValue(currentViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0)); effect.Parameters["xBillboardTexture"].SetValue(treeTexture); effect.Parameters["xBBAlphaTestValue"].SetValue(BB_ALPHA_TEST_VALUE); int numVertices = treeList.Count * 4; int numTriangles = treeList.Count * 2; device.SetVertexBuffer(treeVertexBuffer); device.Indices = treeIndexBuffer; GraphicsDevice.BlendState = BlendState.AlphaBlend; // First draw the "solid" part of the tree GraphicsDevice.DepthStencilState = DepthStencilState.Default; effect.Parameters["xAlphaTestGreater"].SetValue(true); effect.CurrentTechnique.Passes[0].Apply(); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numTriangles); // Now draw the rest of the tree GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; effect.Parameters["xAlphaTestGreater"].SetValue(false); effect.CurrentTechnique.Passes[0].Apply(); device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numTriangles); // Reset graphics adapter to default state GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; } private void GenerateTreePositions(VertexMultitextured[] terrainVertices) { Color[] treeMapColors = new Color[treeMap.Width * treeMap.Height]; treeMap.GetData(treeMapColors); int[,] noiseData = new int[treeMap.Width, treeMap.Height]; for (int x = 0; x < treeMap.Width; x++) for (int y = 0; y < treeMap.Height; y++) noiseData[x, y] = treeMapColors[y + x * treeMap.Height].R; treeList = new List<Vector3>(); Random random = new Random(); for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { float terrainHeight = heightData[x, y]; if ((terrainHeight > TREE_MIN_HT) && (terrainHeight < TREE_MAX_HT))

Page 184: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

184

{ float flatness = Vector3.Dot(terrainVertices[x + y * terrainWidth].Normal, new Vector3(0, 1, 0)); float minFlatness = (float)Math.Cos(MathHelper.ToRadians(TREE_MAX_SLOPE)); if (flatness > minFlatness) { float relx = (float)x / (float)terrainWidth; float rely = (float)y / (float)terrainLength; float noiseValueAtCurrentPosition = noiseData[(int)(relx * treeMap.Width), (int)(rely * treeMap.Height)]; float treeDensity; if (noiseValueAtCurrentPosition > TREEMAP_HI_THOLD) treeDensity = HI_NUM_TREES; else if (noiseValueAtCurrentPosition > TREEMAP_MED_THOLD) treeDensity = MED_NUM_TREES; else if (noiseValueAtCurrentPosition > TREEMAP_LOW_THOLD) treeDensity = LOW_NUM_TREES; else treeDensity = 0; for (int currDetail = 0; currDetail < treeDensity; currDetail++) { float rand1 = (float)random.Next(1000) / 1000.0f; float rand2 = (float)random.Next(1000) / 1000.0f; Vector3 treePos = new Vector3((float)x - rand1, 0, -(float)y - rand2); treePos.Y = heightData[x, y]; treeList.Add(treePos); } } } } } } #endregion #endregion #region Initialize /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // JKB Note: This ordering and repetition of graphics profile commands addresses a

Page 185: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

185

// current bug in MonoGame 3.7. If we don't do it this way the window defaults to // a (small) fixed size. Also, MonoGame says it defaults to Reach, but it doesn't, so // we have to set HiDef explicitily here in order to use 32-bit index buffers. graphics.GraphicsProfile = GraphicsProfile.HiDef; graphics.IsFullScreen = false; graphics.ApplyChanges(); graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 800; graphics.ApplyChanges(); Window.Title = "Lab8_Mono - Terrain Tutorial II"; base.Initialize(); } #endregion #region LoadContent /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { device = graphics.GraphicsDevice; effect = Content.Load<Effect>("effects"); skyDome = Content.Load<Model>("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(); Texture2D heightMap = Content.Load<Texture2D>("heightmap2"); LoadHeightData(heightMap); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); SetUpCamera(); SetUpVertices(); SetUpIndices(); CalculateNormals(); CopyToBuffers(); LoadTextures(); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat);

Page 186: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

186

cloudsRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); SetUpWaterVertices(); GenerateTreePositions(vertices); CreateBBVertsAndIntsFromList(); SetUpFullscreenVertices(); } #endregion #region UnloadContent /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } #endregion #region Update /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); //Rotation if (keyState.IsKeyDown(Keys.PageUp)) { worldRotation = Matrix.CreateRotationY(0.01f); } else if (keyState.IsKeyDown(Keys.PageDown)) { worldRotation = Matrix.CreateRotationY(-0.01f); } else { worldRotation = Matrix.CreateRotationY(0); } float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference);

Page 187: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

187

worldMatrix *= worldTranslation * worldRotation; base.Update(gameTime); } #endregion #region Draw /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap(); GeneratePerlinNoise(time); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); DrawTrees(viewMatrix); base.Draw(gameTime); } #endregion } }

effects.fx

//---------------------------------------------------- //-- This effect file derived from: -- //-- www.riemers.net -- //-- Basic shaders -- //-- -- //-- Modified for MonoGame by John K. Bennett -- //-- -- //-- Use/modify as you like -- //-- -- //---------------------------------------------------- struct VertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0;

Page 188: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

188

float2 TextureCoords: TEXCOORD1; }; struct PixelToFrame { float4 Color : COLOR0; }; //------- Constants -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; bool xShowNormals; bool Clipping; float4 ClipPlane0; float4x4 xReflectionView; float xWaveLength; float xWaveHeight; float3 xCamPos; float xDirtyWaterFactor; float4 xDullColor; float xTime; float3 xWindDirection; float xWindForce; float xOvercast; float4 xSkyTopColor; float3 xAllowedRotDir; float xBBAlphaTestValue; bool xAlphaTestGreater; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = wrap; AddressV = wrap; }; Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xRefractionMap;

Page 189: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

189

sampler RefractionSampler = sampler_state { texture = <xRefractionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xBillboardTexture; sampler textureSampler = sampler_state { texture = <xBillboardTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; }; //------- Technique: Pretransformed -------- VertexToPixel PretransformedVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; Output.Position = inPos; Output.Color = inColor; return Output; } PixelToFrame PretransformedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique Pretransformed { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PretransformedVS(); PixelShader = compile ps_4_0_level_9_1 PretransformedPS(); } } //------- Technique: Colored -------- VertexToPixel ColoredVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld));

Page 190: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

190

Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame ColoredPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Colored { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredVS(); PixelShader = compile ps_4_0_level_9_1 ColoredPS(); } } //------- Technique: ColoredNoShading -------- // No lighting or shading, so no normal info passed in VertexToPixel ColoredNoShadingVS( float4 inPos : POSITION, float4 inColor: COLOR) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Color = inColor; return Output; } PixelToFrame ColoredNoShadingPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = PSIn.Color; return Output; } technique ColoredNoShading { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 ColoredNoShadingVS(); PixelShader = compile ps_4_0_level_9_1 ColoredNoShadingPS(); } }

Page 191: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

191

//------- Technique: Textured -------- VertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = (float3) normalize(mul(normalize(float4(inNormal, 0.0)), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = dot(Normal, -xLightDirection); return Output; } PixelToFrame TexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient; return Output; } technique Textured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 TexturedVS(); PixelShader = compile ps_4_0_level_9_1 TexturedPS(); } }

//------- Technique: SimpleTextured -------- VertexToPixel SimpleTexturedVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; return Output; } PixelToFrame SimpleTexturedPS(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient;

Page 192: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

192

return Output; } technique SimpleTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SimpleTexturedVS(); PixelShader = compile ps_4_0_level_9_1 SimpleTexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION0; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; float4 clipDistances : TEXCOORD5; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS(float4 inPos : POSITION, float3 inNormal : NORMAL, float2 inTexCoords : TEXCOORD0, float4 inTexWeights : TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = (float3) mul(normalize(float4(inNormal, 0.0)), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z / Output.Position.w; Output.clipDistances = dot(inPos, ClipPlane0); return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; if (Clipping) clip(PSIn.clipDistances); float lightingFactor = 1; float blendDistance = 0.99f;

Page 193: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

193

float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth - blendDistance) / blendWidth, 0, 1); if (xEnableLighting) lightingFactor = saturate(saturate(dot(float4(PSIn.Normal, 0.0), PSIn.LightDirection)) + xAmbient); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords * 3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 MultiTexturedVS(); PixelShader = compile ps_4_0_level_9_1 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3; float4 Position3D : TEXCOORD4; }; struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex : TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection);

Page 194: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

194

float4x4 preReflectionViewProjection = mul(xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul(xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); float3 windDir = normalize(xWindDirection); float3 perpDir = cross(xWindDirection, float3(0, 1, 0)); float ydot = dot(inTex, xWindDirection.xz); float xdot = dot(inTex, perpDir.xz); float2 moveVector = float2(xdot, ydot); moveVector.y += xTime * xWindForce; Output.BumpMapSamplingPos = moveVector / xWaveLength; Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y / PSIn.ReflectionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords); float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y / PSIn.RefractionMapSamplingPos.w / 2.0f + 0.5f; float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords); float3 eyeVector = (float3) normalize(float4(xCamPos,0.0) - PSIn.Position3D); float3 normalVector = (bumpColor.rbg - 0.5f)*2.0f; float fresnelTerm = dot(eyeVector, normalVector); float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm); Output.Color = lerp(combinedColor, xDullColor, xDirtyWaterFactor);

Page 195: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

195

// add specular highlights float3 reflectionVector = -reflect(xLightDirection, normalVector); float specular = abs(dot(normalize(reflectionVector), normalize(eyeVector))); specular = pow(specular, 256); Output.Color.rgb += specular; return Output; } technique Water { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 WaterVS(); PixelShader = compile ps_4_0_level_9_1 WaterPS(); } } //------- Technique: PerlinNoise -------- struct PNVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; }; struct PNPixelToFrame { float4 Color : COLOR0; }; PNVertexToPixel PerlinVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD) { PNVertexToPixel Output = (PNVertexToPixel)0; Output.Position = inPos; Output.TextureCoords = inTexCoords; return Output; }

PNPixelToFrame PerlinPS(PNVertexToPixel PSIn) { PNPixelToFrame Output = (PNPixelToFrame)0; float2 move = float2(0, 1); float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords) + xTime * move) / 2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 2 + xTime * move) / 4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 4 + xTime * move) / 8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 8 + xTime * move) / 16; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 16 + xTime * move) / 32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords) * 32 + xTime * move) / 32; Output.Color.rgb = 1.0f - pow(abs(perlin.r), xOvercast)*2.0f; Output.Color.a = 1; return Output; }

Page 196: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

196

technique PerlinNoise { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 PerlinVS(); PixelShader = compile ps_4_0_level_9_1 PerlinPS(); } } //------- Technique: SkyDome -------- struct SDVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; float4 ObjectPosition : TEXCOORD1; }; struct SDPixelToFrame { float4 Color : COLOR0; }; SDVertexToPixel SkyDomeVS(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0) { SDVertexToPixel Output = (SDVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; Output.ObjectPosition = inPos; return Output; } SDPixelToFrame SkyDomePS(SDVertexToPixel PSIn) { SDPixelToFrame Output = (SDPixelToFrame)0; float4 bottomColor = 1; float4 baseColor = lerp(bottomColor, xSkyTopColor, saturate((PSIn.ObjectPosition.y) / 0.4f)); float4 cloudValue = tex2D(TextureSampler, PSIn.TextureCoords).r; Output.Color = lerp(baseColor, 1, cloudValue); return Output; } technique SkyDome { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 SkyDomeVS(); PixelShader = compile ps_4_0_level_9_1 SkyDomePS(); } }

Page 197: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

197

//------- Technique: CylBillboard -------- struct BBVertexToPixel { float4 Position : POSITION; float2 TexCoord : TEXCOORD0; }; struct BBPixelToFrame { float4 Color : COLOR0; }; BBVertexToPixel CylBillboardVS(float3 inPos: POSITION0, float2 inTexCoord : TEXCOORD0) { BBVertexToPixel Output = (BBVertexToPixel)0; float3 center = (float3) mul(float4(inPos,0.0), xWorld); float3 eyeVector = center - xCamPos; float3 upVector = xAllowedRotDir; upVector = normalize(upVector); float3 sideVector = cross(eyeVector, upVector); sideVector = normalize(sideVector); float3 finalPosition = center; finalPosition += (inTexCoord.x - 0.5f)*sideVector; finalPosition += (1.5f - inTexCoord.y*1.5f)*upVector; float4 finalPosition4 = float4(finalPosition, 1); float4x4 preViewProjection = mul(xView, xProjection); Output.Position = mul(finalPosition4, preViewProjection); Output.TexCoord = inTexCoord; return Output; } BBPixelToFrame BillboardPS(BBVertexToPixel PSIn) { BBPixelToFrame Output = (BBPixelToFrame)0; Output.Color = tex2D(textureSampler, PSIn.TexCoord); clip((Output.Color.a - xBBAlphaTestValue) * (xAlphaTestGreater ? 1 : -1)); return Output; } technique CylBillboard { pass Pass0 { VertexShader = compile vs_4_0_level_9_1 CylBillboardVS(); PixelShader = compile ps_4_0_level_9_1 BillboardPS(); } }

Page 198: IWKS 3400 LAB 81 JK Bennett - University of Colorado Denver › jkb › iwks3400 › Labs › Lab... · 1 IWKS 3400 LAB 81 JK Bennett ... learn, for example, how to change to a HiDef

198