Archive for the 'graphics' Category
Here's another technical article. The last one was about rendering; this time I'm switching to animation (specifically, rotation). Euler angles have well-known limitations, such as gimbal lock. Another slightly-less-well-known issue is the way in which there are many different (rx,ry,rz) triplets that all encode the same orientation, and writing a program to choose the "right" triplet for a given orientation is sometimes a lot more difficult than it would seem. This problem can come up when implementing a user-interface control for instance, or a dynamics engine. Any time that a program has an non-Euler orientation (expressed as a quaternion, a matrix, and axis and an angle, or whatever) and it has to convert it to a triplet of Euler angles that must fit in smoothly with a sequence of other Euler angles, you have to be careful how you choose which triplet to use. It's not hard to find convert a quaternion to a valid triplet triplet of Euler angles, of course. Ken Shoemake's article in Graphics Gems IV (for instance) demonstrates how to do that. But applying his recipe to a sequence of smoothly-varying orientations doesn't necessarily produce a sequence of smoothly-varying Euler triplets. For instance, because you can add or subtract any multiple of 360 degrees to any of the three Euler angles and the triplet still describes the same orientation, our valid sequence of Euler triplets might have objectionable 360-degree discontinuities in it. We could make a rule that indicates which value is preferred (like, the angle with the smallest absolute value), but that would only serve to define where the discontinuities occur, not eliminate them. For instance, imagine that we're writing a physics simulator and we run it on an object with a angular velocity around the x-axis. We would expect the x-angle to be a linear function of time, but if compute it by applying Ken's formulae to quaternions, we get a sawtooth function instead: This problem is solved easily enough by taking the previous frame's Euler angles into account when converting each frame's orientation from a quaternion to Euler angles. After finding a triplet that corresponds to the given quaternion (which probably has angles in some nominal range like -π ... π), we can add or subtract some multiple of 2π to each angle so that it's as close to the previous values as possible. That'll restore our x-angle function to the linear shape that we'd expect it to have. There's one more problem though, and this one's subtler. If you implement a rotation manipulator and you notice that 180-degree "flips" are creeping into your results, you'll realize there's another choice (like the ±2π one described above) that our conversion has to make. To understand this one, let's start by visualizing an example. Suppose we have an object that's rotated by 180 degrees around its z axis, and -90 degrees around its x axis (assuming ZXY rotation order). That object would be facing up, with its head towards us. Now suppose we took a different object and rotated it by -90 degrees around its x axis, and 180 degrees around its y axis. The second object would be in exactly the same position: facing up, with its head towards us. But these two sets of angles (-90,0,180 and -90,180,0) aren't merely different by multiples of 360 degrees... something else is going on. With a little bit more spatial visualization, we notice that it seems we can generalize it with the following relation:
Y(φ) X(θ) Z(ψ) = Y(π + φ) X(π - θ) Z(π + ψ)
where X, Y, and Z are rotation operators, and [θ, φ, ψ] is our ZXY Euler triplet. But if we're going to use this "180 degree identity", we need more than spatial intuition... we must show that it's really correct. We can do that by converting the rotation operators to quaternions, so that we have an algebra with which to manipulate them. Because we can express rotation θ around a unit vector a using the quaternion
q = (cos(θ/2), a sin(θ/2)),
our three rotation operators become
X(θ) = cos(θ/2) + i sin(θ/2)
Y(φ) = cos(φ/2) + j sin(φ/2) Z(ψ) = cos(ψ/2) + k sin(ψ/2) And the two sides of the 180-degree identity can be written
(cos(φ/2) + j sin(φ/2))
(cos(θ/2) + i sin(θ/2))
(cos(ψ/2) + k sin(ψ/2))
and
(-sin(φ/2) + j cos(φ/2))
(sin(θ/2) + i cos(θ/2))
(-cos(ψ/2) + k cos(ψ/2)).
If you multiply-out both of those expressions (keeping in mind that i2 = j2 = k2 = i j k = -1) then you'll find them equal. That's more algebra than I'm going to include in this article; I verified it using Mathematica and you should feel free to convince yourself, too. The point is that you can use this identity (in combination with the 360-degree property mentioned above) to convert quaternions to Euler angles gracefully, as follows: AlgorithmHere's a summary of this technique, then. If you have a set of "reference" angles [θ, φ, ψ] and a quaternion q′ that you want to convert to "similar" angles [θ′, φ′, ψ′], then first convert the quaternion q′ to a triplet [θ0, φ0, ψ0]. Then add/subtract multiples of 360 degrees:
θ1 = θ0 + nθ1 2π
φ1 = φ0 + nφ1 2π ψ1 = ψ0 + nψ1 2π (where nθ1, nφ1, and nψ1 are integers) in order to minimize the distance from the reference angles,
δ1 = | θ1 - θ | +
| φ1 - φ | +
| ψ1 - ψ |,
do the same thing for the angles produced by the 180-degree identity:
θ2 = π + θ1 + nθ2 2π
φ2 = π - φ1 + nφ2 2π ψ2 = π + ψ1 + nψ2 2π δ2 = | θ2 - θ | + | φ2 - φ | + | ψ2 - ψ |, and pick whichever triplet ([&theta1, &phi1, &psi1] or [&theta2, &phi2, &psi2]) is closer to the reference triplet. Further readingBy the way, if you're interested in this kind of thing, check out James Diebel's "quick reference guide". (Just kidding, it's anything but quick... it's exhaustive!). Also, Andrew J. Hanson is the biggest fan of quaternions that I've ever met and he has a book on visualizing them. I haven't read it, but if it's half as enthusiastic as his lectures then it's probably an insightful read.
As a computer graphics programmer, gamma correction seems like something that we shouldn't have to worry about. And even after being told that it is something that must be addressed in order to produce good images, it seems like it probably isn't that big a deal. How non-linear could computer monitors be, anyway? It often seems like we can get away with the assumption that a linear change in an "RGB value" produces a linear change in on-screen brightness. Most programs can get away with it. But if you're hoping to garner the respect of creative professionals, handling gamma correctly is the sort of thing that really separates the world-class solutions from the rest. There are a surprising number of places where the gamma issue creeps into an imaging pipeline, and one of them is shading. For instance, how do you render a diffuse surface that's lit by several lights? If you were to ignore the fact that computer displays aren't linear, then you wouldn't need any power functions at all... you could simply add the result of the lambertian shader for each light. That's what OpenGL's fixed-function implementation does, and it looks like this:
It looks kind of right, but an artist (or a visual effects supervisor!) will notice that it's a bit off. The area in the center where the two lights overlap seems too bright (brighter than the two lights put together) and as a result it looks a little bigger than it should be. (Notice how the top and bottom points of that center oval seem to stick out a little too far.) The problem comes from the fact that we're pretending our monitor has a gamma of 1.0, where in reality it has a gamma of 1.8 (or something else, if you're not lucky enough to be using a Macintosh :-P). How do we fix this problem? We want our lights to look natural, and to do that we have to compensate for the way our monitor distorts the image we're trying to display. The result of our lighting calculation is linear (because if it weren't, the calculation wouldn't be valid), but the monitor isn't going to show us brightnesses that are proportional to the values we request. It's going to show something that's as bright as our pixel values raised to the power 1.8 (or 2.2 or something else, depending on the details of the display hardware). So let's cancel that out by raising each of the R, G, and B components of the final color by the power 1/1.8 (that is, 0.5555). Then the displayed brightness will be proportional to what we compute it should be, and everything should work right. Right?
Sure enough the image looks more natural, but now it's too bright! That's no good. We set the intensity of our lights to 100%, gave them zero fall-off, and made the shape white, so we'd expect the color of a spotlight to be the same as the color channel of the light that made it. The problem is that if our lighting pipeline is now linear, and the inputs to it (like the light's color channel) are expressed in non-linear "monitor colors" then we need to gamma-correct our inputs too, not just our output. We need to do the same thing to colors that the monitor does: raise them to 1.8. If we do that, our render finally looks correct:
The individual spotlight discs are the same color as our lights' color channels, and the area in the center looks natural... like it should look if two lights illuminate the same surface. Yay! Happy clients make happy artists. If we're gamma-correcting the color of lights, should we gamma-correct the brightness of those lights, too? No! A brightness control is linear by definition. Users might expect a color parameter to match all of the other applications on the computer that aren't gamma-aware, but it's important for brightness to be linear because when the user drops it to 50%, the intensity of the light must look like it's half as bright. Some users might notice that setting the brightness slider to 50% doesn't do the same thing as dividing the color component values in half, but that's how it goes. It's a small price to pay compared to the messy alternatives. What else? Are there other areas where we need to worry about gamma? Oh yeah, definitely. There are a lot of them. You have to account for it in order to do a good job anti-aliasing the edges of shapes. Similarly, you have to linearize before resampling an image. And of course gamma is crucial if you're trying to match colors, but that's way beyond the scope of this article. :-)
The first thing I noticed was that the meeting room was packed. Lots of people are interested in OpenGL... more than the organizers were expecting. Then when the meeting got underway, I learned that this transfer paves the way to effectively coordinate OpenGL with OpenGL ES (the cell-phone version of the standard) which is already under the Khronos roof. Moreover, it may open some doors to coordinate OpenGL with COLLADA, the XML-based 3D interchange format. So, this is really about keeping standards coherent and compatible. There were several other good announcements, too. For one thing, OpenGL is going to be a first-class citizen on Microsoft Windows Vista, not just a compatibility layer on top of D3D. That's welcome news because it'll help keep Microsoft from marginalizing OpenGL in an effort to monopolize the domain. Also, NVIDIA says they're actively developing a geometry shader extension, which promises to add some very useful programmability in between the vertex and fragment stages of the pipeline. I can't wait!
We all know that the human visual system is remarkably adaptive, but here's an illusion that might surprise you (it surprised me). In the illustration below, the squares marked "A" and "B" are the same shade of gray. Crazy, huh?
That's why, when creating graphics, it's very important to use a software application that provides a neutral (preferably dark) visual environment. Otherwise it's hard to be precise about the color of your work.
SIGGRAPH's conference this year in Boston is going to include a Teapot Exhibit, celebrating the Utah Teapot. That got me thinking, "I wonder if I could build a teapot out of Legos?" Round objects aren't exactly Lego's forte of course, but I figured a computer program that translated the model into a voxel lattice would go a long way towards getting me started. I wrote that in C++, and then wrote another program in Python for turning the lattice into an arrangement of Lego bricks.
In the process of pursuing this project, I'm learning just how serious some people take their Legos. There are several CAD programs to choose from, and I ended up using Bricksmith because it has a nice Mac OpenGL implementation. (Thanks Allen!)
Then there's the question of how to buy Lego bricks to actually build the teapot with in real life. BrickLink provides a huge online market for Lego pieces. Wow! You can search for exactly what part you need, and you get a list of people across the country who will sell it to you. So I bought a pile of all kinds of white bricks, plates, and slopes, and started trying to put my masterpiece together. It's harder than I thought... I'm definitely not a "master builder" as they call it. But I'm getting there. I may have to go back to Python for some more help from the computer. :-)
|
|||||||||||



