Other Posts in Image Editing

  1. Perlin Noise
  2. Fault Formation
  3. Cellular Textures
  4. Resizing an Image in C#
  5. Box Blur and Gaussian Blur... Sort of...
  6. Thermal Erosion
  7. Using Mid Point Displacement to Create Cracks
  8. Fluvial Erosion
  9. Creating Marble Like Textures Procedurally
  10. Procedural Textures and Dilation
  11. Converting Image to Black and White in C#
  12. Getting an HTML Based Color Palette from an Image in C#
  13. Adding Noise/Jitter to an Image in C#
  14. Creating Pixelated Images in C#
  15. Edge detection in C#
  16. Using Sin to Get What You Want... In C#...
  17. Noise Reduction of an Image in C# using Median Filters
  18. Image Dilation in C#
  19. Sepia Tone in C#
  20. Kuwahara Filter in C#
  21. Matrix Convolution Filters in C#
  22. Symmetric Nearest Neighbor in C#
  23. Bump Map Creation Using C#
  24. Normal Map Creation Using C#
  25. Creating Negative Images using C#
  26. Red, Blue, and Green Filters in C#
  27. Converting an Image to ASCII Art in C#
  28. Adjusting Brightness of an Image in C#
  29. Adding Noise to an Image in C#
  30. Adjusting the Gamma of an Image Using C#
  31. Adjusting Contrast of an Image in C#
  32. Drawing a Box With Rounded Corners in C#
  33. Anding Two Images Together Using C#
  34. Motion Detection in C#
  35. Creating Thermometer Chart in C#
  36. Colorizing a Black and White Image in C#
  37. Extracting an Icon From a File
  38. Setting the Pixel Format and Image Format of an Image in .Net
  39. Using Unsafe Code for Faster Image Manipulation
  40. Sobel Edge Detection and Laplace Edge Detection in C#

Normal Map Creation Using C#

2/26/2009

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:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System.Drawing;
  24: using System.Drawing.Imaging;
  25: #endregion
  26:  
  27: namespace Utilities.Media.Image
  28: {
  29:     /// <summary>
  30:     /// Class for creating a normal map
  31:     /// </summary>
  32:     public class NormalMap
  33:     {
  34:         #region Constructors
  35:  
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         public NormalMap()
  40:         {
  41:             InvertX = false;
  42:             InvertY = false;
  43:         }
  44:  
  45:         #endregion
  46:  
  47:         #region Properties
  48:  
  49:         /// <summary>
  50:         /// Determines the direction of the normal map in the x direction
  51:         /// </summary>
  52:         public bool InvertX { get; set; }
  53:  
  54:         /// <summary>
  55:         /// Determines the direction of the normal map in the y direction
  56:         /// </summary>
  57:         public bool InvertY { get; set; }
  58:  
  59:         /// <summary>
  60:         /// X filter
  61:         /// </summary>
  62:         protected BumpMap FilterX { get; set; }
  63:  
  64:         /// <summary>
  65:         /// Y filter
  66:         /// </summary>
  67:         protected BumpMap FilterY { get; set; }
  68:  
  69:         #endregion
  70:  
  71:         #region Protected Functions
  72:  
  73:         /// <summary>
  74:         /// Sets up the edge detection filter
  75:         /// </summary>
  76:         protected void CreateFilter()
  77:         {
  78:             FilterX = new BumpMap();
  79:             FilterY = new BumpMap();
  80:             FilterX.Invert = InvertX;
  81:             FilterY.Invert = InvertY;
  82:             FilterX.Direction = Direction.LeftRight;
  83:             FilterY.Direction = Direction.TopBottom;
  84:         }
  85:  
  86:         #endregion
  87:  
  88:         #region Public Functions
  89:  
  90:         /// <summary>
  91:         /// Creates the bump map
  92:         /// </summary>
  93:         public Bitmap Create(Bitmap ImageUsing)
  94:         {
  95:             CreateFilter();
  96:             using (Bitmap TempImageX = FilterX.Create(ImageUsing))
  97:             {
  98:                 using (Bitmap TempImageY = FilterY.Create(ImageUsing))
  99:                 {
 100:                     Bitmap ReturnImage = new Bitmap(TempImageX.Width, TempImageX.Height);
 101:                     Math.Vector3 TempVector = new Utilities.Math.Vector3(0.0, 0.0, 0.0);
 102:                     BitmapData TempImageXData = Image.LockImage(TempImageX);
 103:                     BitmapData TempImageYData = Image.LockImage(TempImageY);
 104:                     BitmapData ReturnImageData = Image.LockImage(ReturnImage);
 105:                     int TempImageXPixelSize = Image.GetPixelSize(TempImageXData);
 106:                     int TempImageYPixelSize = Image.GetPixelSize(TempImageYData);
 107:                     int ReturnImagePixelSize = Image.GetPixelSize(ReturnImageData);
 108:                     for (int y = 0; y < TempImageX.Height; ++y)
 109:                     {
 110:                         for (int x = 0; x < TempImageX.Width; ++x)
 111:                         {
 112:                             Color TempPixelX = Image.GetPixel(TempImageXData, x, y, TempImageXPixelSize);
 113:                             Color TempPixelY = Image.GetPixel(TempImageYData, x, y, TempImageYPixelSize);
 114:                             TempVector.X = (double)(TempPixelX.R) / 255.0;
 115:                             TempVector.Y = (double)(TempPixelY.R) / 255.0;
 116:                             TempVector.Z = 1.0;
 117:                             TempVector.Normalize();
 118:                             TempVector.X = ((TempVector.X + 1.0) / 2.0) * 255.0;
 119:                             TempVector.Y = ((TempVector.Y + 1.0) / 2.0) * 255.0;
 120:                             TempVector.Z = ((TempVector.Z + 1.0) / 2.0) * 255.0;
 121:                             Image.SetPixel(ReturnImageData, x, y,
 122:                                 Color.FromArgb((int)TempVector.X,
 123:                                     (int)TempVector.Y,
 124:                                     (int)TempVector.Z),
 125:                                 ReturnImagePixelSize);
 126:                         }
 127:                     }
 128:                     Image.UnlockImage(TempImageX, TempImageXData);
 129:                     Image.UnlockImage(TempImageY, TempImageYData);
 130:                     Image.UnlockImage(ReturnImage, ReturnImageData);
 131:                     return ReturnImage;
 132:                 }
 133:             }
 134:         }
 135:  
 136:         #endregion
 137:     }
 138: }

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:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21: #region Usings
  22: using System;
  23: using System.Xml.Serialization;
  24: #endregion
  25:  
  26: namespace Utilities.Math
  27: {
  28:     /// <summary>
  29:     /// Vector class (holds three items)
  30:     /// </summary>
  31:     [Serializable()]
  32:     public class Vector3
  33:     {
  34:         #region Constructor
  35:  
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         /// <param name="X">X direction</param>
  40:         /// <param name="Y">Y direction</param>
  41:         /// <param name="Z">Z direction</param>
  42:         public Vector3(double X, double Y, double Z)
  43:         {
  44:             this.X = X;
  45:             this.Y = Y;
  46:             this.Z = Z;
  47:         }
  48:  
  49:         #endregion
  50:  
  51:         #region Public Functions
  52:  
  53:         /// <summary>
  54:         /// Normalizes the vector
  55:         /// </summary>
  56:         public void Normalize()
  57:         {
  58:             double Normal = Magnitude;
  59:             if (Normal > 0)
  60:             {
  61:                 Normal = 1 / Normal;
  62:                 X *= Normal;
  63:                 Y *= Normal;
  64:                 Z *= Normal;
  65:             }
  66:         }
  67:  
  68:         #endregion
  69:  
  70:         #region Public Overridden Functions
  71:  
  72:         /// <summary>
  73:         /// To string function
  74:         /// </summary>
  75:         /// <returns>String representation of the vector</returns>
  76:         public override string ToString()
  77:         {
  78:             return "(" + X + "," + Y + "," + Z + ")";
  79:         }
  80:  
  81:         /// <summary>
  82:         /// Gets the hash code
  83:         /// </summary>
  84:         /// <returns>The hash code</returns>
  85:         public override int GetHashCode()
  86:         {
  87:             return (int)(X + Y + Z) % Int32.MaxValue;
  88:         }
  89:  
  90:         /// <summary>
  91:         /// Determines if the items are equal
  92:         /// </summary>
  93:         /// <param name="obj">Object to compare</param>
  94:         /// <returns>true if they are, false otherwise</returns>
  95:         public override bool Equals(object obj)
  96:         {
  97:             if (obj is Vector3)
  98:             {
  99:                 return this == (Vector3)obj;
 100:             }
 101:             return false;
 102:         }
 103:  
 104:         #endregion
 105:  
 106:         #region Public Properties
 107:  
 108:         /// <summary>
 109:         /// Used for converting this to an array and back
 110:         /// </summary>
 111:         public double[] Array
 112:         {
 113:             get { return new double[] { X, Y, Z }; }
 114:             set
 115:             {
 116:                 if (value.Length == 3)
 117:                 {
 118:                     X = value[0];
 119:                     Y = value[1];
 120:                     Z = value[2];
 121:                 }
 122:             }
 123:         }
 124:  
 125:         /// <summary>
 126:         /// Returns the magnitude of the vector
 127:         /// </summary>
 128:         public double Magnitude
 129:         {
 130:             get { return System.Math.Sqrt((X * X) + (Y * Y) + (Z * Z)); }
 131:         }
 132:  
 133:         /// <summary>
 134:         /// X value
 135:         /// </summary>
 136:         [XmlElement]
 137:         public double X { get; set; }
 138:  
 139:         /// <summary>
 140:         /// Y Value
 141:         /// </summary>
 142:         [XmlElement]
 143:         public double Y { get; set; }
 144:  
 145:         /// <summary>
 146:         /// Z value
 147:         /// </summary>
 148:         [XmlElement]
 149:         public double Z { get; set; }
 150:  
 151:         #endregion
 152:  
 153:         #region Public Static Functions
 154:  
 155:         public static Vector3 operator +(Vector3 V1, Vector3 V2)
 156:         {
 157:             return new Vector3(V1.X + V2.X, V1.Y + V2.Y, V1.Z + V2.Z);
 158:         }
 159:  
 160:         public static Vector3 operator -(Vector3 V1, Vector3 V2)
 161:         {
 162:             return new Vector3(V1.X - V2.X, V1.Y - V2.Y, V1.Z - V2.Z);
 163:         }
 164:  
 165:         public static Vector3 operator -(Vector3 V1)
 166:         {
 167:             return new Vector3(-V1.X, -V1.Y, -V1.Z);
 168:         }
 169:  
 170:         public static bool operator <(Vector3 V1, Vector3 V2)
 171:         {
 172:             return V1.Magnitude < V2.Magnitude;
 173:         }
 174:  
 175:         public static bool operator <=(Vector3 V1, Vector3 V2)
 176:         {
 177:             return V1.Magnitude <= V2.Magnitude;
 178:         }
 179:  
 180:         public static bool operator >(Vector3 V1, Vector3 V2)
 181:         {
 182:             return V1.Magnitude > V2.Magnitude;
 183:         }
 184:  
 185:         public static bool operator >=(Vector3 V1, Vector3 V2)
 186:         {
 187:             return V1.Magnitude >= V2.Magnitude;
 188:         }
 189:  
 190:         public static bool operator ==(Vector3 V1, Vector3 V2)
 191:         {
 192:             return V1.X == V2.X && V1.Y == V2.Y && V1.Z == V2.Z;
 193:         }
 194:  
 195:         public static bool operator !=(Vector3 V1, Vector3 V2)
 196:         {
 197:             return !(V1 == V2);
 198:         }
 199:  
 200:         public static Vector3 operator /(Vector3 V1, double D)
 201:         {
 202:             return new Vector3(V1.X / D, V1.Y / D, V1.Z / D);
 203:         }
 204:  
 205:         public static Vector3 operator *(Vector3 V1, double D)
 206:         {
 207:             return new Vector3(V1.X * D, V1.Y * D, V1.Z * D);
 208:         }
 209:  
 210:         public static Vector3 operator *(double D, Vector3 V1)
 211:         {
 212:             return new Vector3(V1.X * D, V1.Y * D, V1.Z * D);
 213:         }
 214:         /// <summary>
 215:         /// Does a cross product
 216:         /// </summary>
 217:         public static Vector3 operator *(Vector3 V1, Vector3 V2)
 218:         {
 219:             Vector3 TempVector = new Vector3(0.0, 0.0, 0.0);
 220:             TempVector.X = (V1.Y * V2.Z) - (V1.Z * V2.Y);
 221:             TempVector.Y = (V1.Z * V2.X) - (V1.X * V2.Z);
 222:             TempVector.Z = (V1.X * V2.Y) - (V1.Y * V2.X);
 223:             return TempVector;
 224:         }
 225:  
 226:         /// <summary>
 227:         /// Does a dot product
 228:         /// </summary>
 229:         /// <param name="V1">Vector 1</param>
 230:         /// <param name="V2">Vector 2</param>
 231:         /// <returns>a dot product</returns>
 232:         public static double DotProduct(Vector3 V1, Vector3 V2)
 233:         {
 234:             return (V1.X * V2.X) + (V1.Y * V2.Y) + (V1.Z * V2.Z);
 235:         }
 236:  
 237:         /// <summary>
 238:         /// Interpolates between the vectors
 239:         /// </summary>
 240:         /// <param name="V1">Vector 1</param>
 241:         /// <param name="V2">Vector 2</param>
 242:         /// <param name="Control">Percent to move between 1 and 2</param>
 243:         /// <returns>The interpolated vector</returns>
 244:         public static Vector3 Interpolate(Vector3 V1, Vector3 V2, double Control)
 245:         {
 246:             Vector3 TempVector = new Vector3(0.0, 0.0, 0.0);
 247:             TempVector.X = (V1.X * (1 - Control)) + (V2.X * Control);
 248:             TempVector.Y = (V1.Y * (1 - Control)) + (V2.Y * Control);
 249:             TempVector.Z = (V1.Z * (1 - Control)) - (V2.Z * Control);
 250:             return TempVector;
 251:         }
 252:  
 253:         /// <summary>
 254:         /// The distance between two vectors
 255:         /// </summary>
 256:         /// <param name="V1">Vector 1</param>
 257:         /// <param name="V2">Vector 2</param>
 258:         /// <returns>Distance between the vectors</returns>
 259:         public static double Distance(Vector3 V1, Vector3 V2)
 260:         {
 261:             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)));
 262:         }
 263:  
 264:         /// <summary>
 265:         /// Determines the angle between the vectors
 266:         /// </summary>
 267:         /// <param name="V1">Vector 1</param>
 268:         /// <param name="V2">Vector 2</param>
 269:         /// <returns>Angle between the vectors</returns>
 270:         public static double Angle(Vector3 V1, Vector3 V2)
 271:         {
 272:             V1.Normalize();
 273:             V2.Normalize();
 274:             return System.Math.Acos(Vector3.DotProduct(V1, V2));
 275:         }
 276:  
 277:         #endregion
 278:     }
 279: }

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. Well that and you'll notice that the normal map creation code has references to Image.Lock, Unlock, etc. These are functions from my utility library to help speed up things but can be removed/replaced with built in GetPixel/SetPixel functions (or simply download my library as it has all of this code plus a bunch more). So try it out, leave feedback, and happy coding.



Comments