Last time I showed how to create a generic bump map. The benefits of that style is it can be used as a height map that adds some depth to an item, allowing you to use smaller poly counts. The down side is the simple fact that there is no information regarding lighting. Generally when you use a basic bump map the lighting is going to look rather uniform no matter where the light source is coming from. To fix this, people came up with normal maps. Normal maps are RGB images that use the various R, G, and B portions of each pixel to determine the direction of the normal for that location with R being the X, G being the Y, and B being the Z axis (although R, G, and B can really be any of the axis).
In order to create the normal map, we actually use two of the earlier bump maps. One that finds edges going top to bottom and one going left to right. Each of these are used to determine one of the channels, combining them into a vector in which we just leave the Z axis at 1.0, after which we normalize it, and then convert the vector to a pixel (multiply each channel by 255 and put it in a pixel).It's fairly simple, but takes a bit of code. Luckily I have the code here for you:
/*
Copyright (c) 2009 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/
#region Usings
using System;
using System.Drawing;
#endregion
namespace Utilities.Media.Image
{
public class NormalMap : IDisposable
{
#region Private Variables
private System.Drawing.Bitmap _Image = null;
private BumpMap _FilterX = null;
private BumpMap _FilterY = null;
private bool _InvertX = false;
private bool _InvertY = false;
#endregion
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public NormalMap()
{
try
{
CreateFilter();
}
catch (Exception e) { throw e; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="FileName">Name of the file</param>
public NormalMap(string FileName)
{
try
{
_Image = (Bitmap)System.Drawing.Bitmap.FromFile(FileName);
CreateFilter();
CreateNormalMap();
}
catch (Exception e) { throw e; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="Image">Image to use</param>
public NormalMap(System.Drawing.Bitmap Image)
{
try
{
_Image = new Bitmap(Image);
CreateFilter();
CreateNormalMap();
}
catch (Exception e) { throw e; }
}
#endregion
#region Public Properties
/// <summary>
/// The internal image
/// </summary>
public System.Drawing.Bitmap Image
{
get { return _Image; }
set
{
try
{
_Image = value;
CreateNormalMap();
}
catch (Exception e) { throw e; }
}
}
/// <summary>
/// Determines the direction of the normal map in the x direction
/// </summary>
public bool InvertX
{
get { return _InvertX; }
set
{
try
{
_InvertX = value;
CreateFilter();
CreateNormalMap();
}
catch (Exception e) { throw e; }
}
}
/// <summary>
/// Determines the direction of the normal map in the y direction
/// </summary>
public bool InvertY
{
get { return _InvertY; }
set
{
try
{
_InvertY = value;
CreateFilter();
CreateNormalMap();
}
catch (Exception e) { throw e; }
}
}
#endregion
#region Private Functions
/// <summary>
/// Creates the bump map
/// </summary>
private void CreateNormalMap()
{
try
{
Bitmap TempImageX = _FilterX.Image;
Bitmap TempImageY = _FilterY.Image;
_Image.Dispose();
_Image = new Bitmap(TempImageX.Width, TempImageX.Height);
Math.Vector3 TempVector = new Utilities.Math.Vector3(0.0, 0.0, 0.0);
for (int y = 0; y < TempImageX.Height; ++y)
{
for (int x = 0; x < TempImageX.Width; ++x)
{
Color TempPixelX = TempImageX.GetPixel(x, y);
Color TempPixelY = TempImageY.GetPixel(x, y);
TempVector.X = (double)(TempPixelX.R) / 255.0;
TempVector.Y = (double)(TempPixelY.R) / 255.0;
TempVector.Z = 1.0;
TempVector.Normalize();
TempVector.X = ((TempVector.X + 1.0) / 2.0) * 255.0;
TempVector.Y = ((TempVector.Y + 1.0) / 2.0) * 255.0;
TempVector.Z = ((TempVector.Z + 1.0) / 2.0) * 255.0;
_Image.SetPixel(x, y, Color.FromArgb((int)TempVector.X, (int)TempVector.Y, (int)TempVector.Z));
}
}
}
catch (Exception e) { throw e; }
}
/// <summary>
/// Sets up the edge detection filter
/// </summary>
private void CreateFilter()
{
try
{
_FilterX = new BumpMap(_Image);
_FilterY = new BumpMap(_Image);
_FilterX.Invert = InvertX;
_FilterY.Invert = InvertY;
_FilterX.Direction = Direction.LeftRight;
_FilterY.Direction = Direction.TopBottom;
}
catch (Exception e) { throw e; }
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (_FilterX != null)
{
_FilterX.Dispose();
}
if (_FilterY != null)
{
_FilterY.Dispose();
}
if (_Image != null)
{
_Image.Dispose();
}
}
#endregion
}
}
Now if you try the code out, you'll notice that it uses the BumpMap class that I created last post (which uses the Filter class) and another class called Vector3 which I haven't shown thus far. So you can actually compile, here's the code for that:
/*
Copyright (c) 2009 <a href="http://www.gutgames.com">James Craig</a>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/
#region Usings
using System;
using System.Xml.Serialization;
#endregion
namespace Utilities.Math
{
[Serializable()]
public class Vector3
{
#region Constructor
/// <summary>
/// Constructor
/// </summary>
/// <param name="X">X direction</param>
/// <param name="Y">Y direction</param>
/// <param name="Z">Z direction</param>
public Vector3(double X, double Y, double Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
#endregion
#region Public Variables
/// <summary>
/// X direction
/// </summary>
private double _X = 0.0;
/// <summary>
/// Y direction
/// </summary>
private double _Y = 0.0;
/// <summary>
/// Z direction
/// </summary>
private double _Z = 0.0;
#endregion
#region Public Functions
/// <summary>
/// Normalizes the vector
/// </summary>
public void Normalize()
{
double Normal = Magnitude;
if (Normal > 0)
{
Normal = 1 / Normal;
X *= Normal;
Y *= Normal;
Z *= Normal;
}
}
#endregion
#region Public Overridden Functions
/// <summary>
/// To string function
/// </summary>
/// <returns>String representation of the vector</returns>
public override string ToString()
{
return "(" + X + "," + Y + "," + Z + ")";
}
/// <summary>
/// Gets the hash code
/// </summary>
/// <returns>The hash code</returns>
public override int GetHashCode()
{
return (int)(X + Y + Z) % Int32.MaxValue;
}
/// <summary>
/// Determines if the items are equal
/// </summary>
/// <param name="obj">Object to compare</param>
/// <returns>true if they are, false otherwise</returns>
public override bool Equals(object obj)
{
if (obj is Vector3)
{
return this == (Vector3)obj;
}
return false;
}
#endregion
#region Public Properties
/// <summary>
/// Used for converting this to an array and back
/// </summary>
public double[] Array
{
get { return new double[] { X, Y, Z }; }
set
{
if (value.Length == 3)
{
X = value[0];
Y = value[1];
Z = value[2];
}
}
}
/// <summary>
/// Returns the magnitude of the vector
/// </summary>
public double Magnitude
{
get { return System.Math.Sqrt((X * X) + (Y * Y) + (Z * Z)); }
}
/// <summary>
/// X value
/// </summary>
[XmlElement]
public double X
{
get { return _X; }
set { _X = value; }
}
/// <summary>
/// Y Value
/// </summary>
[XmlElement]
public double Y
{
get { return _Y; }
set { _Y = value; }
}
/// <summary>
/// Z value
/// </summary>
[XmlElement]
public double Z
{
get { return _Z; }
set { _Z = value; }
}
#endregion
#region Public Static Functions
public static Vector3 operator +(Vector3 V1, Vector3 V2)
{
return new Vector3(V1.X + V2.X, V1.Y + V2.Y, V1.Z + V2.Z);
}
public static Vector3 operator -(Vector3 V1, Vector3 V2)
{
return new Vector3(V1.X - V2.X, V1.Y - V2.Y, V1.Z - V2.Z);
}
public static Vector3 operator -(Vector3 V1)
{
return new Vector3(-V1.X, -V1.Y, -V1.Z);
}
public static bool operator <(Vector3 V1, Vector3 V2)
{
return V1.Magnitude < V2.Magnitude;
}
public static bool operator <=(Vector3 V1, Vector3 V2)
{
return V1.Magnitude <= V2.Magnitude;
}
public static bool operator >(Vector3 V1, Vector3 V2)
{
return V1.Magnitude > V2.Magnitude;
}
public static bool operator >=(Vector3 V1, Vector3 V2)
{
return V1.Magnitude >= V2.Magnitude;
}
public static bool operator ==(Vector3 V1, Vector3 V2)
{
return V1.X == V2.X && V1.Y == V2.Y && V1.Z == V2.Z;
}
public static bool operator !=(Vector3 V1, Vector3 V2)
{
return !(V1 == V2);
}
public static Vector3 operator /(Vector3 V1, double D)
{
return new Vector3(V1.X / D, V1.Y / D, V1.Z / D);
}
public static Vector3 operator *(Vector3 V1, double D)
{
return new Vector3(V1.X * D, V1.Y * D, V1.Z * D);
}
public static Vector3 operator *(double D, Vector3 V1)
{
return new Vector3(V1.X * D, V1.Y * D, V1.Z * D);
}
/// <summary>
/// Does a cross product
/// </summary>
public static Vector3 operator *(Vector3 V1, Vector3 V2)
{
Vector3 TempVector = new Vector3(0.0, 0.0, 0.0);
TempVector.X = (V1.Y * V2.Z) - (V1.Z * V2.Y);
TempVector.Y = (V1.Z * V2.X) - (V1.X * V2.Z);
TempVector.Z = (V1.X * V2.Y) - (V1.Y * V2.X);
return TempVector;
}
/// <summary>
/// Does a dot product
/// </summary>
/// <param name="V1">Vector 1</param>
/// <param name="V2">Vector 2</param>
/// <returns>a dot product</returns>
public static double DotProduct(Vector3 V1, Vector3 V2)
{
return (V1.X * V2.X) + (V1.Y * V2.Y) + (V1.Z * V2.Z);
}
/// <summary>
/// Interpolates between the vectors
/// </summary>
/// <param name="V1">Vector 1</param>
/// <param name="V2">Vector 2</param>
/// <param name="Control">Percent to move between 1 and 2</param>
/// <returns>The interpolated vector</returns>
public static Vector3 Interpolate(Vector3 V1, Vector3 V2, double Control)
{
Vector3 TempVector = new Vector3(0.0, 0.0, 0.0);
TempVector.X = (V1.X * (1 - Control)) + (V2.X * Control);
TempVector.Y = (V1.Y * (1 - Control)) + (V2.Y * Control);
TempVector.Z = (V1.Z * (1 - Control)) - (V2.Z * Control);
return TempVector;
}
/// <summary>
/// The distance between two vectors
/// </summary>
/// <param name="V1">Vector 1</param>
/// <param name="V2">Vector 2</param>
/// <returns>Distance between the vectors</returns>
public static double Distance(Vector3 V1, Vector3 V2)
{
return System.Math.Sqrt(((V1.X - V2.X) * (V1.X - V2.X)) + ((V1.Y - V2.Y) * (V1.Y - V2.Y)) + ((V1.Z - V2.Z) * (V1.Z - V2.Z)));
}
/// <summary>
/// Determines the angle between the vectors
/// </summary>
/// <param name="V1">Vector 1</param>
/// <param name="V2">Vector 2</param>
/// <returns>Angle between the vectors</returns>
public static double Angle(Vector3 V1, Vector3 V2)
{
V1.Normalize();
V2.Normalize();
return System.Math.Acos(Vector3.DotProduct(V1, V2));
}
#endregion
}
}
That's another rather large bit of code... But really it's just a vector class that allows you to do various things. The portion we care about is the normalization, but it has the ability to do addition, subtraction, cross products, dot products, etc. Anyway the two classes should allow you to create a normal map. So try it out, leave feedback, and happy coding.
4715fed3-6dc5-4284-9a27-dbe9471ad755|0|.0