Ipphones

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Sunday, 14 December 2008

Surface Normals in OpenGL

Posted on 06:05 by Unknown
Before we can learn to load objects in from a 3D program, we need to talk about another feature of OpenGL - Surface Normals (usually referred to just as "normals"). A surface normal is nothing more than a vector (or line) that is perpendicular to the surface of a given polygon. Here's a nice illustration of the concept (this one is from Wikipedia, not from me:)

OpenGL doesn't need to know the normals to render a shape, but it does need them when you start using directional lighting. OpenGL needs the surface normals to know how the light interacts with the individual polygons.

Normals can be calculated, but OpenGL ES won't do it for you. To find the normal for a triangle, simply calculate the vector cross product of the three vertices. That's easy enough, right? Well, maybe it's been a while since you took Geometry, so let's dive in a little deeper here (as much for my benefit as for yours - my last Geometry class was over fifteen years ago).

Before we look at the code for calculating the surface normal, let's do a little setup. So far, throughout our code, we've just been dealing with vertex arrays where the first element is the X position, the second is the Y position, and the third is the Z position, then it starts over with the fourth being the X position for the next vertex, etc.. For clarity in our code sample, however, let's define a struct to hold a single vertex:
typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D, Vector3D;
That shouldn't need much explanation, right? Three floating point values to represent a point in space. Notice, however, that we've actually defined two types based on this struct,: Vertex3D and Vector3D. Computationally speaking, vectors and vertices are the same - they are represented by a single point in three-dimensional space. Conceptually, they are different, however. A vertex represents a single point in space, while a vector represents both a direction and a distance (usually called "magnitude").

This is one of those little things that people who work with 3D graphics and Euclidean geometry take for granted but rarely bother to explain to newcomers. How can a single point in space represent a distance? It takes two points to make a line, right? Everybody knows that.

The answer is simple - the other point is assumed to be the origin. A vector is a line segment drawn from the origin to the point in space represented by the data structure. As a practical matter, the distinction between a vector and a vertex is often academic, since the data structures are the same. Normals are vectors, not vertices, so we've defined a separate type for each just to be semantically accurate.

Anyway, back from that little tangent, to make the code readable, let's also define a struct to hold a single triangle:
typedef struct {
Vertex3D v1;
Vertex3D v2;
Vertex3D v3;
} Triangle3D;
So, given a triangle, here is how we calculate the surface normal:
Vector3D calculateTriangleSurfaceNormal(Triangle3D triangle)
{
Vector3D surfaceNormal;
surfaceNormal.x = (triangle.v1.y * triangle.v2.z) - (triangle.v1.z * triangle.v2.y);
surfaceNormal.y = -((triangle.v2.z * triangle.v1.x) - (triangle.v2.x * triangle.v1.z));
surfaceNormal.z = (triangle.v1.x * triangle.v2.y) - (triangle.v1.y * triangle.v2.x);
return surfaceNormal;
}
Surface normals, however, are usually "normalized" so that they have a length of one. This makes calculations faster. So, to normalize a normal (say that ten times fast standing on your head), we have to first figure out the magnitude (length) of the surface normal:
GLfloat vectorMagnitude(Vector3D vector)
{
return sqrt((vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z));
}
Eww... now you're starting to see why OpenGL doesn't do this for us at runtime - performance. Square root is a costly calculation, and it would have to do it for every polygon. But we're still not done. Nope, once we have the length of the vector, then we can normalize it like so:
void normalizeVector(Vector3D *vector)
{
GLfloat vecMag = vectorMagnitude(*vector);
vector->x /= vecMag;
vector->y /= vecMag;
vector->z /= vecMag;
}
Note: Things can get confusing with the terminology here. "Normalizing" a vector has nothing to do with a surface "normal". A surface normal is called a "normal" based on the use of the word "normal" as a synonym for "perpendicular". On the other hand, when you're "normalizing" a vector, you're reducing that vector to a standard (or "normal") magnitude. So, normalizing a normal is cool - a normal doesn't have to be normalized, but it often is, and with OpenGL, they should be to avoid causing the shader additional work.

Only, if you look at the illustration above, there are actually two normals, pointing in opposite directions - triangles have two sides, and therefore two potential normals. The polygons in most 3D computer objects only have one normal, and it's pointing outwards from the object. There's no point in defining the normal for the side that light will never hit because it's inside an object (the side without a surface normal is often called a "backface"). OpenGL would have no way of knowing which normal to use if it tried to calculate them for you, so it would have to use both - another performance hit, since it would be doing calculations on polygons that could never be seen.

The two surface normals for a triangle (or any polygon) point in completely opposite directions - 180° - so it's easy to "flip a normal" so that it's representing the other side of the triangle:
void flipVector(Vector3D *vector)
{
vector->x = -vector->x;
vector->y = -vector->y;
vector->z = -vector->z;
}
Now that I've shown you how to calculate, normalize, and flip vectors, forget all about it. In general, you won't need to worry about calculating surface normals because 3D computer modeling programs like Maya, 3D Max, LightWave, and Blender will calculate those normals for your objects, and you can export your 3D data so that you have the pre-calculated, normalized normals available as part of your data model (something we'll look at before too long)

Here is the basic process for using normals in OpenGL. To tell OpenGL that you are going to provide it with normals, you have to call
glEnableClientState(GL_NORMAL_ARRAY);
This is usually done once during startup, but could also be turned on and off during run time if you only had normals for some objects.

Normal data, like pretty much everything else we've worked in with OpenGL ES 1.1, have to be packed into an array (a "normal array"). Before calling glDrawElements(), you have to feed OpenGL that array using the glNormalPointer() function, like:
glNormalPointer(GL_FLOAT, 0, myNormalArray);
The first parameter tells OpenGL that our array is an array of GLfloats. The second... just ignore the second for now - it's that same one I keep telling you to ignore, the one that allows you to skip data in the array. The third and final argument is a pointer to the actual normal data.

That's all there is to it. You won't notice any difference in the drawing of the object with or without surface normals until you start adding some directional lighting (which we'll do in a future blog posting), but I wanted you to have a good grasp on what normals were and why we need them before we get into trying to load a 3D model.

One last note - if you're wondering why we're working so much with C structs and C functions rather than declaring Objective-C classes, the answer once again is performance. There is a bit of additional overhead associated with instantiating Objective-C objects and with dynamic messaging. That overhead is trivial in most applications, but a 3D model will usually be made up of thousands of triangles. You do NOT want the additional instantiation or dispatch overhead in this situation, believe me, especially not on a small device like the iPhone. It might make sense to declare a class to hold a 3D model, but not to hold the individual polygons or vertices. Too much cost, too little benefit.
Email ThisBlogThis!Share to XShare to Facebook
Posted in iPhone SDK, OpenGL ES | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • Making OpenGL ES Screenshot
    The Bit-101 Blog has an entry that shows how to take a screenshot when using OpenGL ES . I tested this in my much-delayed particle-generato...
  • Adding CLANG to Your Build Process
    Frasier Spiers has a nifty piece this morning on using Git pre-commit hooks to automatically run the CLANG Static Analyzer. I'm not a G...
  • CLANG Static Analyzer
    If you aren't using the LLVM/Clang Static Analyzer , you really should be. The Clang Project is an attempt to write a front end for the...
  • A Little Help
    I'm having a problem with OpenGL ES, and it's keeping me from finishing my particle engine post. I was hoping someone here could see...
  • WWDC Accommodations
    Staying downtown in San Francisco is very expensive in the summertime. Bu, if you're going to WWDC, you really want to stay downtown. Yo...
  • Xcode File Templates and a Mystery
    One of the things that confuses many newcomers to Xcode is how to set it up so that your company name gets automatically filled in when you ...
  • Brain Surgery?
    Craig Hockenberry has an interesting post on his blog today about the iPhone background processing issue. Craig speaks from personal experi...
  • Book's Almost Done
    I just finished Chapter 16. I'll give it another read-over in the morning then it will go off to my writing partner for his review, then...
  • iPhone Alley
    Looks like Dave and I are going to make an appearance on the iPhone Alley Podcast next week. We're recording on Sunday night, so I woul...
  • Shuffling Arrays
    Ever want to randomize an array of items? It's a task that, for some reason, I've had to do a lot in recent programs. So, I wrote a ...

Categories

  • 3D Models
  • Ad Hoc Distribution
  • ADC
  • Address Book
  • Amazon
  • Anaglyphs
  • App Store
  • Apple
  • Apple DTS
  • Apple Store
  • Application Store
  • articles
  • Award
  • Background Processing
  • Barcodes
  • Beta
  • Blog
  • Blogger
  • Blogging
  • Blogs
  • Blogspot
  • Book project
  • Bug Reporting
  • Captain Obvious
  • Categories
  • Censorship
  • CFFoundation
  • CGAffineTransform
  • Clang Static Analyzer
  • Cocoa
  • Cocoa Touch
  • Code Reuse
  • Code Signing
  • Computer
  • conferences
  • Controller Classes
  • Core Animation
  • Daring Fireball
  • Database
  • Debugging
  • Defect
  • Delegates
  • Design Awards
  • Developer Certifications
  • Discussion Forums
  • Edit Mode
  • employment opportunities
  • Encryption
  • Enterprise
  • Errata
  • free code
  • Free software
  • Full Screen
  • Game Programming
  • Gestures
  • Getting Started
  • goof
  • Google Code
  • Google Maps
  • Gotcha
  • Help
  • HIG
  • HTTP PUT
  • Idiots
  • Idle Timer
  • Images
  • Instruments
  • Interface Builder
  • iPHone
  • iPhone Applications
  • iPhone Dev Center
  • iPhone Developers
  • iPhone OS 3.0
  • iPhone SDK
  • iPhone SDK PNG
  • iPhone Simulator
  • iPhoneSDK
  • iPod
  • Job Opportunities.
  • k
  • Key Value Observing
  • Keynote
  • KVO
  • Landscape Mode
  • Learn Cocoa
  • Learn Cocoa on the Mac
  • libxml
  • Licensing
  • Mac Developers
  • Mac OS X
  • Macworld Expo
  • Microsoft
  • NDA
  • NeHe
  • New Category
  • New Release
  • NSFileHandle
  • NSMutableArray
  • NSMutableURLRequest
  • NSXML
  • Object-Oriented Design
  • Objective-C
  • Open Source
  • OpenGL ES
  • Optimizations
  • Other blogs
  • Paired Arrays
  • Parsing
  • Particle Engine
  • Party
  • PeopleSoft
  • Performance
  • Persistence
  • Pink Screen of Death
  • Piracy
  • Pixar
  • Podcasts
  • Press Release WTF
  • Press Releases WTF
  • private APIs Google
  • Project Template
  • Properties
  • Random Numbers
  • Rant
  • Rejected
  • Resources
  • Responder Chain
  • REST
  • Reverse Engineering
  • Rumors
  • Runtime
  • Sample Code
  • Screencast
  • screenshot
  • Scroll Views
  • snippet
  • Snow Leopard.
  • SOAP
  • Sockets
  • Source
  • Splash Screen
  • SQLite
  • SQLitePersistentObjects
  • Steve Jobs
  • Steve-Note
  • Strings
  • Stupidity
  • Subversion
  • Table Views
  • Taps
  • Template
  • Tip
  • Tips
  • Tririga
  • tutorials
  • Twitter
  • UIAlertView
  • UIColor
  • UIImage
  • UIPickerView
  • UIScrollView
  • UITextField
  • UIView
  • UIWebView
  • Update
  • Utilities
  • UUID
  • Vacation
  • Version Control
  • Web Services
  • Writing
  • WTF
  • WWDC
  • Xcode
  • XML

Blog Archive

  • ►  2009 (141)
    • ►  May (14)
    • ►  April (30)
    • ►  March (48)
    • ►  February (26)
    • ►  January (23)
  • ▼  2008 (163)
    • ▼  December (46)
      • Coming Soon - My OpenGL Particle Engine
      • Updated OpenGL ES Xcode Project Template
      • Another Wavefront OBJ loader
      • PVRTC Textures & OpenGL resources from Apple
      • A Reusable Date Drill-Down Controller
      • Learn Objective-C on the Mac is Shipping
      • We broke Amazon's Top 100 Books
      • Reusable Controllers
      • Wavefront OBJ Loader - Another Update
      • Almost There...
      • Texture Coordinate Arrays or Things Nobody Tells You
      • Happy Holidays
      • OpenGL ES Extensions on the iPhone
      • Texture Mapping is Coming
      • And we have a Winner
      • More on UIAlertView and Landscape Problem
      • UIAlertView & Landscape Mode
      • More on OpenGL and Normals
      • Wavefront OBJ Loader - Normals Done...
      • Progress on the Wavefront OBJ Loader
      • Andy Ihnatko on Apple & the Macworld Expo
      • gluLookAt()
      • MacWorld 2009
      • 3D Models as C Header Files
      • OpenGL Postings
      • The Start of a WaveFront OBJ File Loader Class
      • More on Outlets - Instance Variables vs. Properties
      • Just for Fun: OpenGL Icosahedron
      • Surface Normals in OpenGL
      • NeHe Tutorials, Lesson 6 Ported to iPhone (sort of)
      • Lesson 5 Code Fixed
      • OpenGL Warning!
      • Preparations for Porting NeHe Lesson 06
      • NeHe Tutorials, Lesson 5 Ported to iPhone
      • Captain Obvious Discusses OpenGL Function Document...
      • NeHe Tutorials, Lesson 4 Ported to iPhone
      • NeHe Tutorials, Lesson 3 Ported to iPhone
      • OpenGL Project Template Redux
      • NeHe Tutorials, Lesson 2 Ported to iPhone
      • gluPerspective
      • An OpenGL Project Template for Xcode
      • Back in town
      • Next Few Days
      • Outlets - Property vs. Instance Variable
      • Speaking of Errata...
      • Happy Belated Thanksgiving
    • ►  November (25)
    • ►  October (44)
    • ►  September (2)
    • ►  August (5)
    • ►  July (2)
    • ►  June (9)
    • ►  May (2)
    • ►  April (11)
    • ►  March (17)
Powered by Blogger.

About Me

Unknown
View my complete profile