Chapter 1

Computer Graphics: From Then to Now

To predict the future and appreciate the present, you must understand the past.

—Probably said by someone sometime

Computer graphics have always been the darling of the software world. Laypeople can appreciate computer graphics more easily than, say, increasing the speed of a sort algorithm by 3 percent or adding automatic tint control to a spreadsheet program. You are likely to hear more people say “Cooooolllll!” at your nicely rendered image of Saturn on your iPad than at a Visual Basic script in Microsoft Word (unless, of course, a Visual Basic script in Microsoft Word can render Saturn, then that really would be cool). The cool factor goes up even more so when said renderings are on a device you can carry around in your back pocket. Let's face it—Steve Jobs has made the life of art directors on science-fiction films very difficult. After all, imagine how hard it must be to design a prop that looks more futuristic than an iPad. (Even before the iPhone was available for sale, the prop department at ABC's LOST borrowed some of Apple's screen iconography for use in a two-way radio carried by a helicopter pilot.)

If you are reading this book, chances are you have an iOS-based device or are considering getting one in the near future. If you have one, put it in your hand now and consider what a miracle it is of 21st-century engineering. Millions of man-hours, billions of dollars of research, centuries of overtime, plenty of all-nighters, and an abundance of Jolt-drinking, T-shirt–wearing, comic-book-loving engineers coding into the silence of the night have gone into making that little glass and plastic miracle-box so you could play DoodleJump when Mythbusters is in reruns.

Your First OpenGL ES Program

Some software how-to titles will carefully build up the case for their specific topic (“the boring stuff”) only to get to the coding and examples (“the fun stuff”) by around page 655. Others will jump immediately into some exercises to address your curiosity and save the boring stuff for a little later. This book will be of the latter category.

Note OpenGL ES is a 3D graphics standard based on the OpenGL library that emerged from the labs of Silicon Graphics in 1992. It is widely used across the industry in everything from pocketable machines running games up to supercomputers running fluid dynamics simulations for NASA (and playing really, really fast games). The ES variety stands for Embedded Systems, meaning small, portable, low-power devices. Unless otherwise noted, I'll use OpenGL and OpenGL ES interchangeably.

When developing any apps for iOS, it is customary to let Xcode do the heavy lifting at the beginning of any project via its various wizards. With Xcode (this book uses Xcode 4 as reference), you can easily create an example OpenGL ES project and then add on your own stuff to eventually arrive at something someone might want to buy from the App Store.

With Xcode 4 already running, go to File Image New Image New Project, and you should see something that looks like Figure 1-1.

images

Figure 1-1. Xcode project wizard

Select the OpenGL Game template, and fill in the needed project data. It doesn't matter whether it is for the iPhone or iPad.

Now compile and run, making sure you have administrative privileges. If you didn't break anything by undue tinkering, you should see something like Figure 1-2.

images

Figure 1-2. Your first OpenGL ES project. Give yourself a high five.

The code will be examined later. And don't worry, you'll build stuff fancier than a couple of rotating cubes. The main project will be to construct a simple solar-system simulator based on some of the code used in Distant Suns 3. But for now, it's time to get to the boring stuff: where computer graphics came from and where it is likely to go.

A Spotty History of Computer Graphics

To say that 3D is all the rage today is at best an understatement. Although forms of “3D” imagery go back to more than a century ago, it seems that it has finally come of age. First let's look at what 3D is and what it is not.

3D in Hollywood

In 1982 Disney released Tron, the first movie to widely use computer graphics depicting life inside a video game. Although the movie was a critical and financial flop (not unlike the big-budget sequel released in 2011), it would eventually join the ranks of cult favorites right up there with Showgirls and The Rocky Horror Picture Show. Hollywood had taken the bite out of the apple, and there was no turning back.

Stretching back to the 1800s, what we call “3D” today was more commonly referred to as stereo vision. Popular Victorian-era stereopticons would be found in many parlors of the day. Consider this technology an early Viewmaster. The user would hold the stereopticon up to their face with a stereo photograph slipped into the far end and see a view of some distant land, but in stereo rather than a flat 2D picture. Each eye would see only one half of the card, which carried two nearly identical photos taken only a couple of inches apart.

Stereovision is what gives us the notion of a depth component to our field of view. Our two eyes deliver two slightly different images to the brain that then interprets them in a way that we understand as depth perception. A single image will not have that effect. Eventually this moved to movies, with a brief and unsuccessful dalliance as far back as 1903 (the short L'arrivée du Train is said to have had viewers running from the theater to avoid the train that was clearly heading their way) and a resurgence in the early 1950s, with Bwana Devil being perhaps the best known.

The original form of 3D movies generally used the “anaglyph” technique that required the viewers to wear cheap plastic glasses with a red filter over one eye and a blue one over the other. Polarizing systems were incorporated in the early 1950s and permitted color movies to be seen in stereo, and they are still very much the same as today. Afraid that television would kill off the movie industry, Hollywood needed some gimmick that was impossible on television in order to keep selling tickets, but because both the cameras and the projectors required were much too impractical and costly, the form fell out of favor, and the movie industry struggled along just fine.

With the advent of digital projection systems in the 1990s and fully rendered films such as Toy Story, stereo movies and eventually television finally became both practical and affordable enough to move it beyond the gimmick stage. In particular, full-length animated features (Toy Story being the first) made it a no-brainer to convert to stereo. All one needed to do was simply rerender the entire film but from a slightly different viewpoint. This is where stereo and 3D computer graphics merge.

The Dawn of Computer Graphics

One of the fascinating things about the history of computer graphics, and computers in general, is that the technology is still so new that many of the giants still stride among us. It would be tough to track down whoever invented the buggy whip, but I'd know whom to call if you wanted to hear firsthand how to program the Apollo Lunar Module computers from the 1960s.

Computer graphics (frequently referred to as CG) come in three overall flavors: 2D for user interface, 3D in real time for flight or other forms of simulation as well as games, and 3D rendering where quality trumps speed for non-real-time use.

MIT

In 1961, an MIT engineering student named Ivan Sutherland created a system called Sketchpad for his PhD thesis using a vectorscope, a crude light pen, and a custom-made Lincoln TX-2 computer (a spin-off from the TX-2 group would become DEC). Sketchpad's revolutionary graphical user interface demonstrated many of the core principles of modern UI design, not to mention a big helping of object-oriented architecture tossed in for good measure.

Note For a video of Sketchpad in operation, go to YouTube and search for Sketchpad or Ivan Sutherland.

A fellow student of Sutherland's, Steve Russell, would invent perhaps one of the biggest time sinks ever made, the computer game. Russell created the legendary game of Spacewar in 1962, which ran on the PDP-1, as shown in Figure 1-3.

images

Figure 1-3. The 1962 game of Spacewar resurrected at the Computer History Museum in Mountain View, California, on a vintage PDP-1. Photo by Joi Itoh, licensed under the Creative Commons Attribution 2.0 Generic license (http://creativecommons.org/licenses/by/2.0/deed.en).

By 1965, IBM would release what is considered the first widely used commercial graphics terminal, the 2250. Paired with either the low-cost IBM-1130 computer or the IBM S/340, the terminal was meant largely for use in the scientific community.

Perhaps one of the earliest known examples of computer graphics on television was the use of a 2250 on the CBS news coverage of the joint Gemini 6 and Gemini 7 missions in December 1965 (IBM built the Gemini's onboard computer system). The terminal was used to demonstrate several phases of the mission on live television from liftoff to rendezvous. At a cost of about $100,000 in 1965, it was worth the equivalent of a very nice home. See Figure 1-4.

images

Figure 1-4. IBM-2250 terminal from 1965. Courtesy NASA.

University of Utah

Recruited by the University of Utah in 1968 to work in its computer science program, Sutherland naturally concentrated on graphics. Over the course of the next few years, many computer graphics visionaries in training would pass through the university's labs.

Ed Catmull, for example, loved classic animation but was frustrated by his inability to draw—a requirement for artists back in those days as it would appear. Sensing that computers might be a pathway to making movies, Catmull produced the first-ever computer animation, which was of his hand opening and closing. This clip would find its way into the 1976 film Future World.

During that time he would pioneer two major computer graphics innovations: texture mapping and bicubic surfaces. The former could be used to add complexity to simple forms by using images of texture instead of having to create texture and roughness using discrete points and surfaces, as shown in Figure 1-5. The latter is used to generate algorithmically curved surfaces that are much more efficient than the traditional polygon meshes.

images

Figure 1-5. Saturn with and without texture

Catmull would eventually find his way to Lucasfilm and, later, Pixar and eventually serve as president of Disney Animation Studios where he could finally make the movies he wanted to see. Not a bad gig.

Many others of the top names in the industry would likewise pass through the gates of University of Utah and the influence of Sutherland:

  • John Warnock, who would be instrumental in developing a device-independent means of displaying and printing graphics called PostScript and the Portable Document Format (PDF) and would be cofounder of Adobe.
  • Jim Clark, founder of Silicon Graphics (SGI), which would supply Hollywood with some of the best graphics workstations of the day and create the 3D software development framework now known as OpenGL. After SGI, he co-founded Netscape Communications, which would lead us into the land of the World Wide Web.
  • Jim Blinn, inventor of both bump mapping, which is an efficient way of adding true 3D texture to objects, and environment mapping, which is used to create really shiny things. Perhaps he would be best known creating the revolutionary animations for NASA's Voyager project, depicting their flybys of the outer planets, as shown in Figure 1-6 (compare that with Figure 1-7 using modern devices). Of Blinn, Sutherland would say, “There are about a dozen great computer graphics people, and Jim Blinn is six of them.” Blinn would later lead the effort to create Microsoft's competitor to OpenGL, namely, Direct3D.
images

Figure 1-6. Jim Blinn's depiction of Voyager II's encounter with Saturn in August of 1981. Notice the streaks formed of icy particles while crossing the ring plane. Courtesy NASA.

images

Figure 1-7. Compare Figure 1-6, using some of the best graphics computers and software at the time, with a similar view of Saturn from Distant Suns 3 running on a $500 iPad.

Coming of Age in Hollywood

Computer graphics would really start to come into their own in the 1980s thanks both to Hollywood and to machines that were increasingly powerful while at the same time costing less. For example, the beloved Commodore Amiga that was introduced in 1985 cost less than $2,000, and it brought to the consumer market an advanced multitasking operating system and color graphics that had been previously the domain of workstations costing upwards of $100,000. See Figure 1-8.

images

Figure 1-8. Amiga 1000, circa 1985. Photo by Kaivv, licensed under the Creative Commons Attribution 2.0 Generic license (http://creativecommons.org/licenses/by/2.0/deed.en).

Compare this to the original black-and-white Mac that was released a scant 18 months earlier for about the same cost. Coming with a very primitive OS, flat file system, and 1-bit display, it was fertile territory for the “religious wars” that broke out between the various camps as to whose machine was better (wars that would also include the Atari ST).

Note One of the special graphics modes on the original Amiga could compress 4,096 colors into a system that would normally max out at 32. Called Hold and Modify (HAM mode), it was originally included on one of the main chips for experimental reasons by designer Jay Miner. Although he wanted to remove the admitted kludge that produced images with a lot of color distortion, the results would have left a big empty spot on the chip. Considering that unused chip landscape was something no self-respecting engineer could tolerate, he left it in, and to Miner's great surprise, people started using it.

A company in Kansas called NewTek pioneered the use of Amigas for rendering high-quality 3D graphics when coupled with its special hardware named the Video Toaster. Combined with a sophisticated 3D rendering software package called Lightwave 3D, NewTek opened up the realm of cheap, network-quality graphics to anyone who had a few thousand dollars to spend. This development opened the doors for elaborate science-fiction shows such as Babylon 5 or Seaquest to be financially feasible considering their extensive special effects needs.

During the 1980s, many more techniques and innovations would work their way into common use in the CG community:

  • Loren Carpenter developed a technique to generate highly detailed landscapes algorithmically using something called fractals. Carpenter was hired by Lucasfilm to create a rendering package for a new company named Pixar. The result was REYES, which stood for Render Everything You Ever Saw.
  • Turner Whitted developed a technique called ray tracing that could produce highly realistic scenes (at a significant CPU cost), particularly when they included objects with various reflective and refractive properties. Glass items were common subjects in various early ray-tracing efforts, as shown in Figure 1-9.
  • Frank Crow developed the first practical method of anti-aliasing in computer graphics. Aliasing is the phenomenon that generates jagged edges because of the relatively poor resolution of the display. Crow's method would smooth out everything from lines to text, producing far more natural and pleasing imagery. Note that one of Lucasfilm's early games was called Rescue on Fractalus. The bad guys were named jaggies (another term for anti-aliasing).
  • Star Trek II: The Wrath of Khan brought with it the first entirely computer-generated sequence used to illustrate how a device called the Genesis Machine could generate life on a lifeless planet. That one simulation was called “the effect that wouldn't die” because of its groundbreaking techniques in flame and particle animation, along with the use of fractal landscapes.
    images

    Figure 1-9. Sophisticated images such as this are within the range of hobbyists with programs such as the open source POV-Ray. Photo by Gilles Tran, 2006.

The 1990s brought the T1000 “liquid metal” terminator in Terminator 2: Judgment Day, the first completely computer-generated full-length feature film of Toy Story, believable animated dinosaurs in Jurassic Park, and James Cameron's Titanic, all of which helped solidified CG as a common tool in the Hollywood director's arsenal.

By the decade's end, it would be hard to find any films that didn't have computer graphics as part of the production in either actual effects or in postproduction to help clean up various scenes. New techniques are still being developed and applied in ever more spectacular fashion, as in Disney's delightful Up! or James Cameron's beautiful Avatar.

Now, once again, take out your i-device and realize what a little technological marvel it is. Feel free to say “wow” in hushed, respectful tones.

Toolkits

All of the 3D wizardry referenced earlier would never have been possible without software. Many CG software programs are highly specialized, and others are more general purpose, such as OpenGL ES, the focus of this book. So, what follows are a few of the many toolkits available.

OpenGL

Open Graphics Library (OpenGL) came out of the pioneering efforts of SGI, the maker of high-end graphics workstations and mainframes. Its own proprietary graphics framework, IRIS-GL, had grown into a de-facto standard across the industry. To keep customers as competition increased, SGI opted to turn IRIS-GL into an open framework so as to strengthen their reputation as the industry leader. IRIS-GL was stripped of non-graphics-related functions and hardware-dependent features, renamed OpenGL, and released in early 1992. As of this writing, version 4.1 is the most current one available.

As small handheld devices became more common, OpenGL for Embedded Systems (OpenGL ES) was developed, which was a stripped-down version of the desktop version. It removed many of the more redundant API calls while simplifying other elements. making it run efficiently on lower-power CPUs. As a result, it has been widely adopted across many platforms, such as Android, iOS, Nintendo 3DS, and BlackBerry (OS 5.0 and newer).

There are two main flavors of OpenGL ES, 1.x and 2.x. Many devices support both. 1.x is the higher-level variant, based on the original OpenGL specification. Version 2.x (yes, I know it's confusing) is targeted toward more specialized rendering chores that can be handled by programmable graphics hardware.

Direct3D

Direct3D (D3D) is Microsoft's answer to OpenGL and is heavily oriented toward game developers. In 1995, Microsoft bought a small company called RenderMorphics that specialized in creating a 3D framework named RealityLab for writing games. RealityLab was turned into Direct3D and first released in the summer of 1996. Even though it was proprietary to Windows-based systems, it has a huge user base across all of Microsoft's platforms: Windows, Windows 7 Mobile, and even Xbox. There are constant ongoing debates between the OpenGL and Direct3D camps as to which is more powerful, flexible, and easier to use. Other factors include how quickly hardware manufacturers can update their drivers to support new features, ease of understanding (Direct3D uses Microsoft's COM interface that can be very confusing for newcomers), stability, and industry support.

The Other Guys

While OpenGL and Direct3D remain at the top of the heap when it comes to both adoption and features, the graphics landscape is littered with numerous other frameworks, many which are supported on today's devices.

In the computer graphics world, graphics libraries come in two very broad flavors: low-level rendering mechanisms represented by OpenGL and Direct3D and high-level systems typically found in game engines that concentrate on resource management with special extras that extend to common gameplay elements (sound, networking, scoring, and so on). The latter are usually built on top of one of the former for the 3D portion. And if done well, the higher-level systems might even be abstracted enough to make it possible to work with both GL and D3D.

QuickDraw 3D

An example of a higher-level general-purpose library is QuickDraw 3D (QD3D). A 3D sibling to Apple's 2D QuickDraw (used in pre-OS-X days), QD3D had an elegant means of generating and linking objects in an easy-to-understand hierarchical fashion (a scene-graph). It likewise had its own file format for loading 3D models and a standard viewer and was platform independent. The higher-level part of QD3D would calculate the scene and determine how each object and, in turn, each piece of each object would be shown on a 2D drawing surface. Underneath QD3D there was a very thin layer called RAVE that would handle device-specific rendering of these bits.

Users could go with the standard version of RAVE, which would render the scene as expected. But more ambitious users could write their own that would display the scene in a more artistic fashion. For example, one company generated the RAVE output so as to look like their objects were hand-painted on the side of a cave. It was very cool when you could take this modern version of a cave drawing and spin it around. The plug-in architecture also made QD3D highly portable to other machines. When potential users balked at using QD3D since it had no hardware solution on PCs, a version of RAVE was produced that would use the hardware acceleration available for Direct3D by actually using its competitor as its rasterizer. Sadly, QD3D was almost immediately killed on the second coming of Steve Jobs, who determined that OpenGL should be the 3D standard for Macs in the future. This was an odd statement because QD3D was not a competitor to the other but an add-on that made the lives of programmers much easier. After Jobs refused requests to make QD3D open source, the Quesa project was formed to re-create as much as possible the original library, which is still being supported at the time of this writing. And to nobody's surprise, Quesa uses OpenGL as its rendering engine.

A disclaimer here: I wrote the RAVE/Direct3D layer of QD3D only to have the project canceled a few days after going “gold master” (ready to ship).

OGRE

Another scene-graph system is Object-oriented Rendering Engine (OGRE). First released in 2005, OGRE can use both OpenGL and Direct3D as the low-level rasterizing solution, while offering users a stable and free toolkit used in many commercial products. The size of the user community is impressive. A quick peek at the forums shows more than 6,500 topics in the General Discussion section alone at the time of this writing.

OpenSceneGraph

Recently released for iOS devices, OpenSceneGraph does roughly what QuickDraw 3D did, by providing a means of creating your objects on a higher level, linking them together, and performing scene management duties and extra effects above the OpenGL layer. Other features include importing multiple file formats, text support, particle effects (used for sparks, flames, or clouds), and the ability to display video content in your 3D applications. Knowledge of OpenGL is highly recommended, because many of the OSG functions are merely thin wrappers to their OpenGL counterparts.

Unity3D

Unlike OGRE, QD3D, or OpenSceneGraph, Unity3D is a full-fledged game engine. The difference lies in the scope of the product. Whereas the first two concentrated on creating a more abstract wrapper around OpenGL, game engines go several steps further, supplying most if not all of the other supporting functionality that games would typically need such as sound, scripting, networked extensions, physics, user interface, and score-keeping modules. In addition, a good engine will likely have tools to help generate the assets and be platform independent.

Unity3D has all of these so would be overkill for many smaller projects. Also, being a commercial product, the source is not available, and it is not free to use, costing a modest amount (compared to other products in the past that could charge $100,000 or more).

And Still Others

Let's not ignore A6, Adventure Game Studio, C4, Cinder, Cocos3d, Crystal Space, VTK, Coin3D, SDL, QT, Delta3D, Glint3D, Esenthel, FlatRedBall, Horde3D, Irrlicht, Leadwerks3D, Lightfeather, Raydium, Panda3D (from Disney Studios and CMU), Torque (available for iOS), and many others. Although they're powerful, one drawback of using game engines is that more often than not, your world is executed in their environment. So if you need a specific subtle behavior that is unavailable, you may be out of luck. That brings me back to the topic of this book.

Back to the Waltz of the Two Cubes

Up through iOS4, Apple saw OpenGL as more of a general-purpose framework. But starting with iOS5, they wanted to emphasize it as a perfect environment for game development. That is why, for example, the project icon in the wizard is titled “OpenGL Game,” where previously it was “OpenGL ES Application.” That also explains why the example exercise pushes the better performing—but considerably more cumbersome—OpenGL ES 2 environment, while ignoring the easier version that is the subject of this book.

Note Also starting with iOS5, Apple has added a number of special helper-objects in their new GLKit framework that take over some of the common duties developers had to do themselves early on. These tasks include image loading, 3D-oriented math operations, creating a special OpenGL view, and managing special effects.

With that in mind, I'll step into 2.0-land every once in a while, such as via the example app described below, because that's all we have for now. Detailed discussions of 2.0 will be reserved for the last chapter, because it really is a fairly advanced topic for the scope of this book.

A Closer Look

The wizard produces six main files not including those of the plist and storyboards. Of these, there are the two for the view controller, two for the application delegate, and two mysterious looking things called shader.fsh and shader.vsh.

The shader files are unique to OpenGL ES 2.0 and are used to fine-tune the look of your scenes. They serve as small and very fast programs that execute on the graphics card itself, using their own unique language that resembles C. They give you the power to specify exactly how light and texture should show up in the final image. Unfortunately, OpenGL ES 2.0 requires shaders and hence a somewhat steeper learning curve, while the easier and more heavily used version 1.1 doesn't use shaders, settling for a few standard lighting and shading effects (called a “fixed function” pipeline). The shader-based applications are most likely going to be games where a visually rich experience is as important as anything else, while the easier 1.1 framework is just right for simple games, business graphics, educational titles, or any other apps that don't need to have perfect atmospheric modeling.

The application delegate has no active code in it, so we can ignore it. The real action takes place in the viewController via three main sections. The first initializes things using some of the standard view controller methods we all know and love, the second serves to render and animate the image, and the third section manages these shader things. Don't worry if you don't get it completely, because this example is merely intended to give you a general overview of what a basic OpenGL ES program looks like.

Note All of these exercises are available on the Apress site, including additional bonus exercises that may not be in the book.

You will notice that throughout all of the listings, various parts of the code are marked with a numbered comment. The numbers correspond to the descriptions following the listing and that highlight various parts of the code.

Listing 1-1. The initialization of the wizard-generated view controller.

#import "TwoCubesViewController.h"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

// Uniform index.
Enum                                                                                //1
{
    UNIFORM_MODELVIEWPROJECTION_MATRIX,
    UNIFORM_NORMAL_MATRIX,
    NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];

// Attribute index.
enum
{
    ATTRIB_VERTEX,
    ATTRIB_NORMAL,
    NUM_ATTRIBUTES
};

GLfloat gCubeVertexData[216] =                                                      //2
{
    // Data layout for each line below is:
    // positionX, positionY, positionZ,     normalX, normalY, normalZ,
    0.5f, -0.5f, -0.5f,        1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, 0.5f,          1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,         1.0f, 0.0f, 0.0f,
    
    0.5f, 0.5f, -0.5f,         0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 1.0f, 0.0f,
    0.5f, 0.5f, 0.5f,          0.0f, 1.0f, 0.0f,
    0.5f, 0.5f, 0.5f,          0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 1.0f, 0.0f,
    
    -0.5f, 0.5f, -0.5f,        -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,       -1.0f, 0.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         -1.0f, 0.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,       -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        -1.0f, 0.0f, 0.0f,
    
    -0.5f, -0.5f, -0.5f,       0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, -0.5f,        0.0f, -1.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, -1.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, -0.5f,        0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         0.0f, -1.0f, 0.0f,
    
    0.5f, 0.5f, 0.5f,          0.0f, 0.0f, 1.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    0.5f, -0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    0.5f, -0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, 0.0f, 1.0f,
    
    0.5f, -0.5f, -0.5f,        0.0f, 0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,       0.0f, 0.0f, -1.0f,
    0.5f, 0.5f, -0.5f,         0.0f, 0.0f, -1.0f,
    0.5f, 0.5f, -0.5f,         0.0f, 0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,       0.0f, 0.0f, -1.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 0.0f, -1.0f
};

@interface TwoCubesViewController () {
    GLuint _program;

    GLKMatrix4 _modelViewProjectionMatrix;                                          //3
    GLKMatrix3 _normalMatrix;
    float _rotation;
    
    GLuint _vertexArray;
    GLuint _vertexBuffer;
}
@property (strong, nonatomic) EAGLContext *context;
@property (strong, nonatomic) GLKBaseEffect *effect;

- (void)setupGL;
- (void)tearDownGL;

- (BOOL)loadShaders;
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file;
- (BOOL)linkProgram:(GLuint)prog;
- (BOOL)validateProgram:(GLuint)prog;
@end
@implementation TwoCubesViewController

@synthesize context = _context;
@synthesize effect = _effect;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];    //4

    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }
    
    GLKView *view = (GLKView *)self.view;                                           //5
    view.context = self.context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;                        //6
    
    [self setupGL];
}

- (void)viewDidUnload
{    
    [super viewDidUnload];
    
    [self tearDownGL];
    
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
self.context = nil;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc. that aren't in use.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations.
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}
- (void)setupGL
{
    [EAGLContext setCurrentContext:self.context];                                   //7
    
    [self loadShaders];
    
    self.effect = [[GLKBaseEffect alloc] init];                                     //8
    self.effect.light0.enabled = GL_TRUE;                                           //9
    self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f);       //10
    
    glEnable(GL_DEPTH_TEST);                                                        //11
    
    glGenVertexArraysOES(1, &_vertexArray);                                         //12
    glBindVertexArrayOES(_vertexArray);
    
    glGenBuffers(1, &_vertexBuffer);                                                //13
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);                                            
    glBufferData(GL_ARRAY_BUFFER,
sizeof(gCubeVertexData), gCubeVertexData, GL_STATIC_DRAW); //14
    
    glEnableVertexAttribArray(GLKVertexAttribPosition);                             //15
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
    glEnableVertexAttribArray(GLKVertexAttribNormal);                                    
    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
    
    glBindVertexArrayOES(0);                                                        //16
}

- (void)tearDownGL                                                                  //17
{
    [EAGLContext setCurrentContext:self.context];
    
    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteVertexArraysOES(1, &_vertexArray);
    
    self.effect = nil;
    
    if (_program) {
        glDeleteProgram(_program);
        _program = 0;
    }
}

So, what is happening here?

  • In lines 1ff (the ff means “and the lines following”), some funky-looking enums are defined. These hold “locations” of various parameters in the shader code. We'll get to this later in the book.
  • Lines 2ff actually define the data used to describe the two cubes. You will rarely have to define anything in code like this. Usually, primitive shapes (spheres, cubes, and cones, for example) are generated on the fly, while more complicated objects are loaded in from a file generated by a 3D authoring tool.

    Both cubes actually use the same dataset but just operate on it in a slightly different fashion. There are six sections of data, one for each face, with each line defining a vertex or corner of the face. The first three numbers are the x, y and z values in space, and the second three have the normal of the face (the normal being a line that specifies the direction the face is aiming and that is used to calculate how the face is illuminated). If the normal is facing a light source, it will be lit; if away, it would be in shadow.

    You will notice that the cube's vertices are either 0.5 or -0.5. There is nothing magical about this, merely defining the cube's size as being 1.0 unit on a side.

    The faces are actually made up of two triangles. The big-brother of OpenGL ES can render four-sided faces, but not this version, which can do only three sides. So we have to fake it. That is why there are six vertices defined here: three for each triangle. Notice that two of the points are repeated. That is not really necessary, because only four unique vertices will do just fine.

  • Lines 3ff specify the matrices that are used to rotate and translate (move) our objects.  In this use, a matrix is a compact form of trigonometric expressions that describe various transformations for each object and how their geometry in 3 dimensions is eventually mapped to a two-dimensional surface of our screens. In OpenGL ES 1.1, we rarely have to refer to the actual matrices directly because the system keeps them hidden from us, while under 2.0, we see all of the inner workings of the system and must handle the various transformations ourselves.  And it is not a pretty sight at times.
  • Line 4 allocates an OpenGL context. This is used to keep track of all of our specific states, commands, and resources needed to actually render something on the screen. This line actually allocates a context for OpenGL ES 2, as specified via the parameter passed via initWithAPI. Most of the time we'll be using kEAGLRenderingAPIOpenGLES1.
  • In line 5, we grab the view object of this controller. What makes this different is the use of a GLKView object, as opposed to the more common UIView that you are probably familiar with. New to iOS5, the GLKView takes the place of the much messier EAGLView. With the former, it takes only a couple of lines of code to create a GLKView and specify various properties, whereas in those dark and unforgiving days before iOS5, it could take dozens of lines of code to do only basic stuff. Besides making things easier to set up, the GLKView also handles the duties of calling your update and refresh routines and adds a handy snapshot feature to get screen grabs of your scene.
  • Line 6 states that we want our view to support full 24-bit colors.
  • Line 7 features the first 2.0-only call. As mentioned above, shaders are little C-like programs designed to execute on the graphics hardware. They exist in either a separate file, as in this exercise, or as some people prefer, embedded in text strings in the main body of the code.
  • Line 8 illustrates another new feature in the GLKit: effect objects. The effect objects are designed to hold some date and presentation information, such as lighting, materials, images, and geometry that are needed to create a special effect. On iOS5's initial release, only two effects were available, one to do reflections in objects and the other to provide full panoramic images: Both are commonly used in graphics, so they are welcomed by developers who would otherwise have to code their own. I expect libraries of effects to eventually become available, both from Apple and from third parties.

    In this case, the example is using the “base effect” to render one of the two cubes. You'd likely never use an effect class to draw just basic geometry like this, but it demonstrates how the effect encapsulates a miniature version of OpenGL ES 1.1. That is, it has a lot of the missing functionality, mainly in lights and materials, that you'd otherwise have to reimplement when porting 1.1 code over to 2.0.

  • Also a part of the setup of the effect, line 9 shows us how to turn on the lights, followed by line 10, which actually specifies the color of the light by using a four-component vector. The fields are ordered as red, green, blue, and alpha. The colors are normalized between 0 and 1, so here red is the main color, with green and blue both at only 40%. If you guessed this is the color of the reddish cube, you'd be right. The fourth component is alpha, which is used to specify transparency, with 1.0 being completely opaque.
  • Depth-testing is another important part of 3D worlds. It is used in line 11, in what is otherwise a very nasty topic, for occluding or blocking stuff that is hidden behind other stuff. What depth-testing does is to render each object on your screen with a depth component. Called a z-buffer, this lets the system know, as it renders an object, whether something is in front of that object. If so, the object (or pieces of it) is not rendered. In earlier days, z-buffering was so slow and took up so much extra memory that it was invoked only when absolutely necessary, but nowadays there is rarely any reason not to use it, except for some special rendering effects.
  • Lines 12f (the single f meaning “the line following”) sets the system up for something called Vertex Array Objects (VAOs). VAOs enable you to cache your models and their attributes in the GPU itself, cutting down a lot of overhead otherwise incurred by copying the data across the bus for each frame. Up until iOS4, VAOs were available only on OpenGL ES 2 implementations, but now both versions can use them.

    Seen here, we first get a “name” (actually just a unique handle) used to identify our array of data to the system. Afterwards, we take that and “bind” it, which merely makes it the currently available array for any calls that need one. It can be unbound, either by binding a new array handle or by using 0. This process of naming and binding objects is a common one used across all of OpenGL.

  • In lines 13ff, the same process is repeated, but this time on a vertex buffer. The difference is that a vertex buffer is the actual data, and in this case, it points to the statically defined data for the cube at the very top of this file.
  • Line 14 supplies the cube's data to the system now, specifying both the size and the location of the data, which is then sent up to the graphics subsystem.
  • Remember how both the 3D xyz coordinates of each corner were embedded with the normals of the faces (the things that say where a face is pointing)? You can actually embed almost any data in these arrays, as long as the data format doesn't change. Lines 15f tell the system which data is which. The first line says that we're using GLKVertexAttribPosition data made up of three floating point values (the x, y, and z components), offset by 0 bytes from the start of the data supplied in line 14,  and a total of 24 bytes long for each structure. That means when it comes time to draw this cube, it will grab three numbers from the very start of the buffer, jump 24 bytes, grab the next three, and so on.

    The normals are treated almost identical, except they are called GLKVertexAttribNormal, and start at an offset of 12 bytes, or immediately after the xyz data.

  • Line 16 “closes” the vertex array object. Now, whenever we want to draw one of these cubes, we can just bind this specific VAO and give a draw command without having to supply the format and offset information again.
  • Finally, in line 17, the buffers are deleted.

If your head hurts, it's understandable. This is a lot of fussing around to draw a couple of cubes. But a visual world is a rich one, and needs a lot of stuff to define it. And we're far from done yet. But the principles remain the same.

Showing the Scene

In Listing 1-2, we can now actually draw the data to the screen and see some pretty pictures. This uses two different approaches to display things. The first hides everything under the new GLKit available from iOS5 and beyond. It hides all of the shaders and other stuff that OpenGL ES 2 normally exposes, and does so under the new GLKBaseEffect class. The second way is just straight 2.0 stuff. Together, the both of them show how the two different approaches can be part of the same rendering loop. But remember, using the effects classes to render a simple cube is overkill, sort of like hopping in the car to drive 6 feet to the mailbox.

Note Apple has pitched the use of GLKBaseEffect as a means to get 1.1 users to port their code to 2.0, because it has lights, materials, and other features that 2.0 doesn't have. But it really doesn't work well for a simple migration because it has far too many limitations to host the entire 1.1 environment of most OpenGL apps.

Listing 1-2. Rendering the scene to the display.

- (void)update                                                                      //1
{
                                                                                    //2
    float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);
    
    self.effect.transform.projectionMatrix = projectionMatrix;                      //3
    
                                                                                                                                               
    GLKMatrix4 baseModelViewMatrix =                                                //4
                   GLKMatrix4MakeTranslation(0.0f, 0.0f, -4.0f);                                      
    baseModelViewMatrix =                                                           //5
                   GLKMatrix4Rotate(baseModelViewMatrix, _rotation, 0.0f, 1.0f, 0.0f);
    
    // Compute the model view matrix for the object rendered with GLKit.
                                                                                                                                               

    GLKMatrix4 modelViewMatrix =                                                    //6
                  GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.5f);

    modelViewMatrix =                                                               //7
                  GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f);

    modelViewMatrix =                                                               //8
                  GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix);
    
    self.effect.transform.modelviewMatrix = modelViewMatrix;                        //9
    
    // Compute the model view matrix for the object rendered with ES2.

    modelViewMatrix =
                 GLKMatrix4MakeTranslation(0.0f, 0.0f, 1.5f);                       //10
    modelViewMatrix =
                  GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f);
    modelViewMatrix =
                  GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix);
    
    _normalMatrix =                                                                 //11
                  GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
    
                                                                                    //12
    _modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
    
    _rotation += self.timeSinceLastUpdate * 0.5f;                                   //13
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect                               
{
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);                                        //14
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                          

    glBindVertexArrayOES(_vertexArray);                                             //15

    // Render the object with GLKit.

    [self.effect prepareToDraw];                                                    //16

    glDrawArrays(GL_TRIANGLES, 0, 36);                                              //17

    // Render the object again with ES2.

    glUseProgram(_program);                                                         //18

    glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m);

    glDrawArrays(GL_TRIANGLES, 0, 36);                                              //19
}

Let's take a look at what's going on here:

  • Line 1, the start of the update method, is actually one of the delegate calls from the new GLKViewController object. This supports frame-rate hints, as in, “I'd love to have my new game Dangerous Poodles update at 100 fps, if you can do so please.” It will also let you know what its real frame rate is, the number of frames since starting the session, and it handles pause and resume functions.
  • In line 2, besides defining the objects to show, we need to define the viewing frustum. This simply specifies how big of a swath of area you want to see in your world. Think of it as a camera's zoom lens, where you can zoom in or out. This then gets converted into a projection-matrix, similar to a transformation matrix that we saw earlier. This encapsulates the information to project your object up against you device's display.

    Note that the first value supplied to GLKMatrix4MakePerspective is 65, meaning that we want our “lens” to have a 65 degree field-of-view.

    This is generated using one of the many new math library calls that also form a part of the GLKit. The calls include support for vectors, matrices, and quaternions (covered later), exclusively for 3D scene manipulation.

  • The GLKBaseEffect used to contain one of the cubes needs to be told to use this matrix in line 3.
  • Line 4 generates a translation matrix. This describes how to move, or translate, your object through space. In this case, the -4 value moves it away from our eyepoint by 4 units. By default, the OpenGL coordinate system has the X-axis, left and right, the Y-axis up and down, and the Z-axis, forward and back. We are looking towards –Z.

    The matrix, baseModelViewMatrix, gets its name from OpenGL's “ModelView” matrix, which the one invoked more frequently than any others.

    By applying it first, we are actually moving our entire world away by 4 units. Below we add separate motions to the individual cubes.

  • Now we want to rotate the cube. Line 5 shows that transformations can be concatenated by multiplying them together. Here we reuse the baseModelView matrix from the previous line.
  • “What?” you are no doubt asking, “another one of these silly matrix things?” Even seemingly simple motions sometimes require a convoluted assortment of rotations and translations. Here in line 6 the cube is moved -1.5 units away from its own origin. That's why neither is actually centered in the screen but orbit around an invisible something.
  • Line 7 applies a rotation to each axis of the cube's world. The rotation value is updated each time through this loop.
  • Line 8 applies the baseModelViewMatrix done earlier to this one moving it away from our eyepoint. This combined matrix is then assigned to the GLKBaseEffect object along with the projection matrix in line 9.
  • In line 10, much of the same is repeated for the OpenGL ES 2-only code block that draws the blue cube. Lines 10ff, are exactly like lines 6, 7, and 8, except the translation is in a positive direction, not a negative one.
  • Now, in line 11, we need another matrix, this one for the face normals described earlier. Normals are generally at their happiest when exactly 1 unit in length, otherwise known as being “normalized.” This counteracts any distortions the previous transformation matrices might otherwise introduce.
  • Line 12 combines the model view matrix with the projection matrix done earlier.
  • In line 13, the rotational value is bumped up a bit. Multiplying it against the value timeSinceLastUpdate ensures that the rotation rates are smooth.
  • The second method, drawInRect(), is the one that actually renders the objects. Lines 14f clear the screen's background. Here glClearColor() is set to display 65% values of all three colors, to give the light gray you see. glClear() actually performs the clearing operation but only on buffers you specify—in this case, the “color buffer,” which is the main one, and the depth buffer, which holds the z-values for hidden surface removal.
  • In line 15, we can finally use the VAO created way back in the day. Binding it to the system means to use the collection of stuff previously uploaded to the GPU.
  • The first cube rendered is the one managed by the GLKBaseEffect object. Line 16 tells it to prepare to render, and line 17 actually commands it to do so.
  • Now in lines 18ff, we start using the shader stuff for the other cube. glUseProgram() tells it to use the two mysterious shader files, Shader.fsh and Shader.vsh, which had previously been loaded, while the two glUniform calls hand off the model view and the projection matrices to them.
  • Now a second call to glDrawArrays() in line 19, and that does it!

The only other section is that which handles the loading and using of the shaders. This process is to load them first in memory, compile, and then link them. If all works as planned, they can be turned on with the call to glUseProgram() above.

One of the files, Shader.vsh, intercepts the vertices as the hardware starts processing them, while the other, Shader.fsh, in effect lets you play with each individual pixel before it's sent to the display hardware.

Tweak and Tweak Some More

Whenever I learn some new technology, I start tweaking the values to see what happens. If it happens the way I expect, I feel as if I've just acquired a new super-power. So, let's play here.

Let's tweak a couple of the values just for kicks. First, go to the gCubeVertexData a few pages up, and change the very first value from 0.0 to 1.0. What do you think you'll see? How about Figure 1-10?

images

Figure 1-10. With one vertex moved out.

What About the Shaders?

Here is not the place to get into a detailed breakdown of shader design and the language, but let's remove a little of the mystery by playing with those as well. Listing 1-3 is the vertex shader.

Listing 1-3. Shader.vsh that preprocesses the vertices.

attribute vec4 position;
attribute vec3 normal;

varying lowp vec4 colorVarying;

uniform mat4 modelViewProjectionMatrix;
uniform mat3 normalMatrix;

void main()
{
    vec3 eyeNormal = normalize(normalMatrix * normal);
    vec3 lightPosition = vec3(0.0, 0.0, 1.0);                                       //1
    vec4 diffuseColor = vec4(0.4, 0.4, 1.0, 1.0);

    float nDotVP = max(0.0, dot(eyeNormal, normalize(lightPosition)));
                 
    colorVarying = diffuseColor * nDotVP;

    gl_Position = modelViewProjectionMatrix * position;                             //2
}

Here in the vertex shader is where the light is hidden for this particular cube; the values are the x, y, and z values. Change the middle value to 5.0, which will move it way above the scene but will affect only the blue cube.

In line 2, gl_Position is predefined object that carries the position of the current vertex. Add in the following line to the end: gl_Position.x*=.5;. Figure 1-11a shows the result.

images

Figure 1-11a,b. Changing the vertical scaling in the vertex shader on the left, and coloring in the fragment shader on the right.

Now for a quick look at the fragment shader, in Listing 1-3. This does absolutely nothing and is merely a pass-through shader. However, it is here where you can intercept the calls to each of the “fragments,” something like pixels at this level. Add the line gl_FragColor.g=1.0; at the end. This will add green to every pixel in the image, looking something like Figure 1-11b. See? That wasn't so hard was it? Now you can proudly go out and tell your friends that you've been programming shaders all day and watch the garlands pile up around your feet.

Listing 1-3. The fragment shader.

varying lowp vec4 colorVarying;

void main()
{
    gl_FragColor = colorVarying;
}

Finally, we are done with the very first example. Yes, for the 3D newcomers out there, it was likely too much information too soon. But I have a theory that if the first thing you do in the morning is to eat a cold frog, the rest of the day is bound to be much better. Consider this first example a cold frog, at least until Chapter 7 that is.

OpenGL Architecture

Now since we've analyzed to death a “simple” OpenGL program, let's take a brief look at what goes on under the hood at the graphics pipeline.

The term pipeline is commonly used to illustrate how a tightly bound sequence of events relate to each other, as illustrated in Figure 1-12. In the case of OpenGL ES, the process accepts a bunch of numbers in one end and outputs something really cool-looking at the other end, be it an image of the planet Saturn or the results of an MRI.

images

Figure 1-12. Basic overview of the OpenGL ES 1.x pipeline

  • The first step is to take the data that describes some geometry along with information on how to handle lighting, colors, materials, and textures and send it into the pipeline.
  • Next the data is moved and rotated, after which lighting on each object is calculated and stored. The scene—say, a solar-system model—must then be moved, rotated, and scaled based on the viewpoint you have set up. The viewpoint takes the form of a frustrum, a rectangular cone of sorts, which limits the scene to, ideally, a manageable level.

    Next the scene is clipped, meaning that only stuff that is likely to be visible is actually processed. All of the other stuff is culled out as early as possible and discarded. Much of the history of real-time graphics development has to do with object culling techniques, some of which are very complex.

    Let's get back to the example of a solar system. If you are looking at the Earth and the Moon is behind your viewpoint, there is no need whatsoever to process the Moon data. The clipping level does just this, both on an object level on one end and on a vertex level on the other. Of course, if you can pre-cull objects on your own before submitting to the pipeline, so much the better. Perhaps the easiest is to simply tell whether an object is behind you, making it completely skippable. Culling can also take place if the object is just too far away to see or is completely obscured by other objects.

  • The remaining objects are now projected against the “viewport,” a virtual display of sorts.
  • At this point is where rasterization takes place. Rasterization breaks apart the image into fragments that are in effect single pixels. Fragments are pixels bundled with additional information such as texture and fog, in preparation for the next step.
  • Now the fragments can have texture and fog effects applied to them. Additional culling can likewise take place if the fog might obscure the more distant fragments, for example.
  • The final phase is where the surviving fragments are written to the frame buffer, but only if they satisfy some last-minute operations. Here is where the fragment's alpha values are applied for translucency, along with depth tests to ensure that the closest fragments are drawn in front of further ones and stencil tests used to render to nonrectangular viewports.

Now to compare things, Figure 1-13 shows the pipeline for OpenGL ES 2. Somewhat simpler in design, but it can be considerably more cumbersome to code for.

images

Figure 1-13. Basic overview of the OpenGL ES 2.x pipeline

When this is done, and all the rasters have been rasterized, the vertices shaded, and the colors blended, you might actually see something that looks like that teapot shown in Figure 1-14.

Note The more you delve into computer graphics, the more you'll see a little teapot popping up here and there in examples in books all the way to television and movies (The Simpsons, Toy Story). The legend of the teapot, sometimes called the Utah Teapot (everything can be traced back to Utah), began with a PhD student named Martin Newell in 1975. He needed a challenging shape but one that was otherwise a common object for his doctoral work. His wife suggested their white teapot, at which point Newell laboriously digitized it by hand. When he released the data into the public domain, it quickly achieved the status of being the “Hello World!” of graphics programming. Even one of the early OpenGL ES examples from Apple's developer web site had a teapot demo. The original teapot now resides at the Computer History Museum in Mountain View, California, just a few blocks from Google. See the upper left image of Figure 1-14.

images

Figure 1-14. Upper left, the actual teapot used by Newell, currently on display at the Computer History Museum in Mountain View, California. Photo by Steve Baker. An example OpenGL application from Apple's developer site on the right. The green teapot at the lower left is by Hay Kranen.

Summary

In this chapter, we covered a little bit of computer graphics history, a basic example program, and, most importantly, the Utah Teapot. Next up is a deep and no doubt overly detailed look into the mathematics behind 3D imagery.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.210.151.5