#region File Description
//-----------------------------------------------------------------------------
// PerPixelLighting.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace PerPixelLightingSample
{
///
/// The central class for the sample Game.
///
public class PerPixelLighting : Microsoft.Xna.Framework.Game
{
#region Sample Fields
private GraphicsDeviceManager graphics;
private SampleArcBallCamera camera;
private Vector2 safeBounds;
private Vector2 debugTextHeight;
private Model[] sampleMeshes;
private SampleGrid grid;
private int activeMesh, activeEffect, activeTechnique, activeCombination;
private int[,] effectTechniqueCombinations =
{
{0, 0}, {1, 0}, {0, 1}, {1, 1}, {1, 2}
};
private int effectTechniqueCombinationCount = 5;
private SpriteBatch spriteBatch;
private SpriteFont debugTextFont;
private GamePadState lastGpState;
private KeyboardState lastKbState;
#endregion
#region Specular Constant Fields
private const float specularPowerMinimum = 0.5f;
private const float specularPowerMaximum = 128f;
private const float specularIntensityMinimum = 0.01f;
private const float specularIntensityMaximum = 10f;
#endregion
///
/// Example 1.1: Effect objects used for this example
///
#region Effect Fields
private Effect[] effects;
private EffectParameter[] worldParameter = new EffectParameter[2];
private EffectParameter[] viewParameter = new EffectParameter[2];
private EffectParameter[] projectionParameter = new EffectParameter[2];
private EffectParameter[] cameraPositionParameter = new EffectParameter[2];
private EffectParameter[] specularPowerParameter = new EffectParameter[2];
private EffectParameter[] specularIntensityParameter = new EffectParameter[2];
#endregion
///
/// Example 1.2: Data fields corresponding to the effect paramters
///
#region Uniform Data Fields
private Matrix world;
private float specularPower, specularIntensity;
#endregion
#region Initialization and Cleanup
public PerPixelLighting()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
///
/// Initialize the sample.
///
protected override void Initialize()
{
// create a default world and matrix
world = Matrix.Identity;
// create the mesh array
sampleMeshes = new Model[5];
// Set up the reference grid
grid = new SampleGrid();
grid.GridColor = Color.LimeGreen;
grid.GridScale = 1.0f;
grid.GridSize = 32;
// Set the grid to draw on the x/z plane around the origin
grid.WorldMatrix = Matrix.Identity;
// set up the sample camera
camera = new SampleArcBallCamera(SampleArcBallCameraMode.RollConstrained);
camera.Distance = 3;
// orbit the camera so we're looking down the z=-1 axis,
// at the "front" of the object
camera.OrbitRight(MathHelper.Pi);
// orbit up a bit for perspective
camera.OrbitUp(.2f);
// set the initial effect, technique, and mesh
activeMesh = 1;
activeCombination = 0;
activeEffect = effectTechniqueCombinations[activeCombination, 0];
activeTechnique = effectTechniqueCombinations[activeCombination, 1];
// set the initial specular values
specularPower = 16;
specularIntensity = 1;
base.Initialize();
}
///
/// Load the graphics content.
///
protected override void LoadContent()
{
// Set up the reference grid and sample camera
grid.LoadGraphicsContent(graphics.GraphicsDevice);
// create the spritebatch for debug text
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
// load meshes
sampleMeshes[0] = Content.Load("Cube");
sampleMeshes[1] = Content.Load("SphereHighPoly");
sampleMeshes[2] = Content.Load("SphereLowPoly");
sampleMeshes[3] = Content.Load("Cylinder");
sampleMeshes[4] = Content.Load("Cone");
// load the sprite font for debug text
debugTextFont = Content.Load("DebugText");
debugTextHeight = new Vector2(0, debugTextFont.LineSpacing + 5);
// load the effects
effects = new Effect[2];
effects[0] = Content.Load("VertexLighting");
effects[1] = Content.Load("PerPixelLighting");
for (int i = 0; i < 2; i++)
{
// cache the effect parameters
worldParameter[i] = effects[i].Parameters["world"];
viewParameter[i] = effects[i].Parameters["view"];
projectionParameter[i] = effects[i].Parameters["projection"];
cameraPositionParameter[i] = effects[i].Parameters["cameraPosition"];
specularPowerParameter[i] = effects[i].Parameters["specularPower"];
specularIntensityParameter[i] = effects[i].Parameters["specularIntensity"];
cameraPositionParameter[i] = effects[i].Parameters["cameraPosition"];
//
// set up some basic effect parameters that do not change during the
// course of execution
//
// set the light colors
effects[i].Parameters["ambientLightColor"].SetValue(
Color.DarkSlateGray.ToVector4());
effects[i].Parameters["diffuseLightColor"].SetValue(
Color.CornflowerBlue.ToVector4());
effects[i].Parameters["specularLightColor"].SetValue(
Color.White.ToVector4());
// Set the light position to a fixed location.
// This will place the light source behind, to the right, and above the
// initial camera position.
effects[i].Parameters["lightPosition"].SetValue(
new Vector3(30f, 30f, 30f));
}
// Recalculate the projection properties on every LoadGraphicsContent call.
// That way, if the window gets resized, then the perspective matrix will be
// updated accordingly
float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
float fieldOfView = aspectRatio * MathHelper.PiOver4 * 3f / 4f;
grid.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
fieldOfView, aspectRatio, .1f, 1000f);
// calculate the safe left and top edges of the screen
safeBounds = new Vector2(
(float)graphics.GraphicsDevice.Viewport.X +
(float)graphics.GraphicsDevice.Viewport.Width * 0.1f,
(float)graphics.GraphicsDevice.Viewport.Y +
(float)graphics.GraphicsDevice.Viewport.Height * 0.1f
);
}
#endregion
#region Update and Render
///
/// Update the game world.
///
/// Provides a snapshot of timing values.
protected override void Update(GameTime gameTime)
{
GamePadState gpState = GamePad.GetState(PlayerIndex.One);
KeyboardState kbState = Keyboard.GetState();
// Check for exit
if ((gpState.Buttons.Back == ButtonState.Pressed) ||
kbState.IsKeyDown(Keys.Escape))
{
Exit();
}
// Handle inputs for the sample camera
camera.HandleDefaultGamepadControls(gpState, gameTime);
camera.HandleDefaultKeyboardControls(kbState, gameTime);
// Handle inputs specific to this sample
HandleInput(gameTime, gpState, kbState);
// The built-in camera class provides the view matrix
grid.ViewMatrix = camera.ViewMatrix;
// The camera position should also be updated for the
// Phong specular component to be meaningful
cameraPositionParameter[activeEffect].SetValue(camera.Position);
// replace the "last" gamepad and keyboard states
lastGpState = gpState;
lastKbState = kbState;
base.Update(gameTime);
}
private void HandleInput(GameTime gameTime, GamePadState gpState,
KeyboardState kbState)
{
float elapsedTime = (float) gameTime.ElapsedGameTime.TotalSeconds;
//Handle input for selecting meshes
if (((gpState.Buttons.X == ButtonState.Pressed) &&
(lastGpState.Buttons.X == ButtonState.Released)) ||
(kbState.IsKeyDown(Keys.Tab) && lastKbState.IsKeyUp(Keys.Tab)))
{
//switch the active mesh
activeMesh = (activeMesh + 1) % sampleMeshes.Length;
}
//Handle input for selecting the active effect
if (((gpState.Buttons.Y == ButtonState.Pressed) &&
(lastGpState.Buttons.Y == ButtonState.Released)) ||
(kbState.IsKeyDown(Keys.Space) && lastKbState.IsKeyUp(Keys.Space)))
{
activeCombination = (activeCombination + 1) %
effectTechniqueCombinationCount;
activeEffect = effectTechniqueCombinations[activeCombination, 0];
activeTechnique = effectTechniqueCombinations[activeCombination, 1];
}
//handle mesh rotation inputs
float dx =
SampleArcBallCamera.ReadKeyboardAxis(kbState, Keys.Left, Keys.Right) +
gpState.ThumbSticks.Left.X;
float dy =
SampleArcBallCamera.ReadKeyboardAxis(kbState, Keys.Down, Keys.Up) +
gpState.ThumbSticks.Left.Y;
//apply mesh rotation to world matrix
if (dx != 0)
{
world *= Matrix.CreateFromAxisAngle(camera.Up, elapsedTime * dx);
}
if (dy != 0)
{
world *= Matrix.CreateFromAxisAngle(camera.Right, elapsedTime * -dy);
}
//handle specular power and intensity inputs
float dPower = SampleArcBallCamera.ReadKeyboardAxis(kbState,
Keys.Multiply, Keys.Divide);
if (gpState.DPad.Right == ButtonState.Pressed)
{
dPower = 1;
}
if (gpState.DPad.Left == ButtonState.Pressed)
{
dPower = -1;
}
float dIntensity = SampleArcBallCamera.ReadKeyboardAxis(kbState,
Keys.Add, Keys.Subtract);
if (gpState.DPad.Up == ButtonState.Pressed)
{
dIntensity = 1;
}
if (gpState.DPad.Down == ButtonState.Pressed)
{
dIntensity = -1;
}
if (dPower != 0)
{
specularPower *= 1 + (elapsedTime * dPower);
specularPower = MathHelper.Clamp(specularPower,
specularPowerMinimum, specularPowerMaximum);
}
if (dIntensity != 0)
{
specularIntensity *= 1 + (elapsedTime * dIntensity);
specularIntensity = MathHelper.Clamp(specularIntensity,
specularIntensityMinimum, specularIntensityMaximum);
}
}
///
/// Example 1.4
///
/// The effect parameters set in this function
/// are shared between all of the rendered elements in the scene.
///
private void SetSharedEffectParameters()
{
worldParameter[activeEffect].SetValue(world);
viewParameter[activeEffect].SetValue(grid.ViewMatrix);
projectionParameter[activeEffect].SetValue(grid.ProjectionMatrix);
specularPowerParameter[activeEffect].SetValue(specularPower);
specularIntensityParameter[activeEffect].SetValue(specularIntensity);
}
///
/// Draw the current scene.
///
/// Provides a snapshot of timing values.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.Black);
// the SpriteBatch added below to draw the debug text is changing some
// needed render states, so they are reset here.
graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
// draw the reference grid so it's easier to get our bearings
grid.Draw();
// always set the shared effects parameters
SetSharedEffectParameters();
// draw the mesh itself
DrawSampleMesh(sampleMeshes[activeMesh]);
// draw the technique name and specular settings
spriteBatch.Begin();
spriteBatch.DrawString(debugTextFont,
effects[activeEffect].CurrentTechnique.Name,
safeBounds, Color.White);
spriteBatch.DrawString(debugTextFont, "Specular Power: " +
specularPower.ToString("0.00"),
safeBounds + (1f * debugTextHeight), Color.White);
spriteBatch.DrawString(debugTextFont, "Specular Intensity: " +
specularIntensity.ToString("0.00"),
safeBounds + (2f * debugTextHeight), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
///
/// Example 1.6
///
/// Draws a sample mesh using a single effect with a single technique.
/// This pattern is very common in simple effect usage.
///
///
public void DrawSampleMesh(Model sampleMesh)
{
if (sampleMesh == null)
return;
// our sample meshes only contain a single part, so we don't need to bother
// looping over the ModelMesh and ModelMeshPart collections. If the meshes
// were more complex, we would repeat all the following code for each part
ModelMesh mesh = sampleMesh.Meshes[0];
ModelMeshPart meshPart = mesh.MeshParts[0];
// set the vertex source to the mesh's vertex buffer
graphics.GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
// set the current index buffer to the sample mesh's index buffer
graphics.GraphicsDevice.Indices = meshPart.IndexBuffer;
// determine the current effect and technique
effects[activeEffect].CurrentTechnique =
effects[activeEffect].Techniques[activeTechnique];
// now we loop through the passes in the teqnique, drawing each
// one in order
int passCount = effects[activeEffect].CurrentTechnique.Passes.Count;
for (int i = 0; i < passCount; i++)
{
// EffectPass.Apply will update the device to
// begin using the state information defined in the current pass
effects[activeEffect].CurrentTechnique.Passes[i].Apply();
// sampleMesh contains all of the information required to draw
// the current mesh
graphics.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList, 0, 0,
meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
}
}
#endregion
#region Entry Point
///
/// The main entry point for the application.
///
static void Main()
{
using (PerPixelLighting game = new PerPixelLighting())
{
game.Run();
}
}
#endregion
}
}