Creating bump-mapped sprites in Mongame
Work-in-progress as I'm creating the blog site while adding the content
TODO: Write an example of using shaders to use a normal map for sprites in Monogame
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
public class ShadedTexture2D
{
static Dictionary lights = null;
static bool firstRun = true;
static int MAX_LIGHTS = 3;
public struct LightSource
{
public Vector2 position;
public Color color;
}
public struct VertexPositionTextureNormal
{
public Vector3 Position;
public Vector2 TextureCoordinate;
public Vector3 Normal;
public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
new VertexElement(sizeof(float) * 5, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
);
public VertexPositionTextureNormal(Vector3 position, Vector2 textureCoordinate, Vector3 normal)
{
Position = position;
TextureCoordinate = textureCoordinate;
Normal = normal;
}
}
static GraphicsDevice gfx;
static Effect effect;
static VertexBuffer vertexBuffer;
static IndexBuffer indexBuffer;
private Texture2D spaceshipTexture;
private Texture2D spaceshipNormalMap;
static VertexPositionTextureNormal[] vertices;
//static short[] indices = new short[] { 0, 1, 2, 2, 1, 3 };
static short[] indices = new short[] { 0, 2, 1, 1, 2, 3 };
public ShadedTexture2D(Texture2D spaceshipTexture, Texture2D spaceshipNormalMap)
{
this.spaceshipTexture = spaceshipTexture;
this.spaceshipNormalMap = spaceshipNormalMap;
}
internal static void Init(ContentManager content, GraphicsDevice graphicsDevice)
{
gfx = graphicsDevice;
effect = content.Load("BumpMapping");
lights = new Dictionary();
//gfx.SamplerStates[0] = new SamplerState { Filter = TextureFilter.Linear };
//gfx.SamplerStates[1] = new SamplerState { Filter = TextureFilter.Linear };
vertices = new VertexPositionTextureNormal[]
{
new VertexPositionTextureNormal(new Vector3(-0.5f, 0.5f, 0), new Vector2(0, 0), Vector3.UnitZ),
new VertexPositionTextureNormal(new Vector3(0.5f, 0.5f, 0), new Vector2(1, 0), Vector3.UnitZ),
new VertexPositionTextureNormal(new Vector3(-0.5f, -0.5f, 0), new Vector2(0, 1), Vector3.UnitZ),
new VertexPositionTextureNormal(new Vector3(0.5f, -0.5f, 0), new Vector2(1, 1), Vector3.UnitZ)
};
vertexBuffer = new VertexBuffer(gfx, VertexPositionTextureNormal.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);
indexBuffer = new IndexBuffer(gfx, IndexElementSize.SixteenBits, indices.Length, BufferUsage.WriteOnly);
indexBuffer.SetData(indices);
}
public void Draw(Vector2 position, float rotation, float scale)
{
// Bind the vertex buffer to the graphics device
gfx.SetVertexBuffer(vertexBuffer);
gfx.Indices = indexBuffer;
Matrix world = Matrix.CreateTranslation(new Vector3(position, 0)) * Matrix.CreateRotationZ(rotation) * Matrix.CreateScale(scale);
effect.Parameters["World"].SetValue(world);
effect.Parameters["View"].SetValue(Matrix.Identity);
effect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, gfx.Viewport.Width, gfx.Viewport.Height, 0, 0, 1));
effect.Parameters["Texture"].SetValue(spaceshipTexture);
effect.Parameters["NormalMap"].SetValue(spaceshipNormalMap);
// Create arrays to hold the light positions and colors
Vector3[] lightPositionsArray = new Vector3[MAX_LIGHTS];
Vector3[] lightColorsArray = new Vector3[MAX_LIGHTS];
int lightIndex = 0;
foreach (var light in lights.Values)
{
// Pass light position and color to the arrays
lightPositionsArray[lightIndex] = new Vector3(light.position, 0);
lightColorsArray[lightIndex] = light.color.ToVector3();
lightIndex++;
}
// Set the arrays of light positions and colors to the shader parameters
effect.Parameters["LightPositions"].SetValue(lightPositionsArray);
effect.Parameters["LightColors"].SetValue(lightColorsArray);
int passCount = 0;
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
if (firstRun)
{
System.Diagnostics.Debug.WriteLine($"Pass {passCount++}");
}
pass.Apply();
gfx.DrawIndexedPrimitives(
Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList,
0,
0,
2);
}
if (firstRun)
{
// Log some debug info
foreach (var light in lights.Values)
{
System.Diagnostics.Debug.WriteLine("Light position: " + light.position);
System.Diagnostics.Debug.WriteLine("Light color: " + light.color);
}
System.Diagnostics.Debug.WriteLine("World matrix: " + world);
}
firstRun = false;
}
public void Draw2(Vector2 position, float rotation, float scale)
{
BasicEffect basicEffect = new BasicEffect(gfx)
{
TextureEnabled = false,
VertexColorEnabled = true
};
VertexPositionColor[] vertices = new VertexPositionColor[]
{
new VertexPositionColor(new Vector3(-100, -100, 0), Color.Red),
new VertexPositionColor(new Vector3(100, -100, 0), Color.Green),
new VertexPositionColor(new Vector3(-100, 100, 0), Color.Blue),
new VertexPositionColor(new Vector3(100, 100, 0), Color.Yellow),
};
short[] indices = new short[] { 0, 1, 2, 2, 1, 3 };
Matrix world = Matrix.CreateTranslation(new Vector3(position, 0)) * Matrix.CreateRotationZ(rotation) * Matrix.CreateScale(scale);
basicEffect.World = world;
basicEffect.View = Matrix.Identity; // Set identity matrix for view
basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, gfx.Viewport.Width, gfx.Viewport.Height, 0, 0, 1); // Set an orthographic projection matrix
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
gfx.DrawUserIndexedPrimitives(
Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList,
vertices,
0,
vertices.Length,
indices,
0,
2
);
}
}
internal static void AddLightSource(string id, Vector2 position, Color color)
{
if (lights == null)
lights = new Dictionary();
if (!lights.ContainsKey(id))
{
LightSource light = new LightSource
{
position = position,
color = color
};
lights.Add(id, light);
}
}
}