跳转至

4.1 照明简介

Introduction to Lighting

照明是实现逼真3D图形最重要的考虑因素之一。目标是模拟光源以及它们发出的光与场景中物体的互动方式。OpenGL 默认禁用照明计算。这意味着当 OpenGL 应用颜色到顶点时,它简单地使用由 glColor* 函数设置的当前颜色值。为了让 OpenGL 进行照明计算,您需要通过调用 glEnable(GL_LIGHTING) 来启用照明。如果您只做到这一点,您会发现您的对象全是完全黑色的。如果您想看到它们,您必须打开一些光源。

决定表面与光互动方式的属性被称为表面材质。一个表面可以有几种不同的材质属性。在我们学习 OpenGL 光照和材质的 API 之前,您需要了解一些关于光和材质属性的一般概念。这些概念在本节中介绍。我们将在下一节中讨论 OpenGL 1.1 中实际如何进行照明。

Lighting is one of the most important considerations for realistic 3D graphics. The goal is to simulate light sources and the way that the light that they emit interacts with objects in the scene. Lighting calculations are disabled by default in OpenGL. This means that when OpenGL applies color to a vertex, it simply uses the current color value as set by the one of the functions glColor*. In order to get OpenGL to do lighting calculations, you need to enable lighting by calling glEnable(GL_LIGHTING). If that's all you do, you will find that your objects are all completely black. If you want to see them, you have to turn on some lights.

The properties of a surface that determine how it interacts light are referred to as the material of the surface. A surface can have several different material properties. Before we study the OpenGL API for light and material, there are a few general ideas about light and material properties that you need to understand. Those ideas are introduced in this section. We postpone discussion of how lighting is actually done in OpenGL 1.1 until the next section.

4.1.1 光与材料

Light and Material

当光线照射到表面上时,一部分光线会被反射。反射的具体方式复杂地依赖于表面的性质,也就是我所说的表面材质属性。在 OpenGL 1.1 中,这种复杂性被非常粗略地近似为两种通用类型的反射:镜面反射散射反射。这两种反射类型在其他3D图形系统中也很重要。(但见第8.2节以了解材料的更现代观点。)

Diffuse vs. Specular

在完美的镜面反射(“镜子般的”)中,入射光线从表面完整地反射出来。反射光线与表面形成与入射光线相同的角度。只有当观察者处于反射光线路径上的完全正确的位置时,才能看到反射光线。即使整个表面被光源照亮,观察者也只能在表面几何形状正确的那些点上看到光源的反射。这种反射被称为镜面高光。在实践中,我们认为光线不是作为一个单一的完美光线被反射,而是作为一个可以更宽或更窄的光锥。

Reflection Cone

来自非常光滑表面的镜面反射会产生非常窄的反射光锥;这种材料上的镜面高光小而尖锐。较暗的表面会产生更宽的反射光锥和更大、更模糊的镜面高光。在 OpenGL 中,决定镜面高光大小和清晰度的材质属性称为光泽度OpenGL 中的光泽度是一个范围在 0 到 128 之间的数字。随着数字的增加,镜面高光变得更小。这张图片展示了八个球体,它们唯一的区别在于光泽度材质属性的值:

Specular Highlights

最左边的球体的光泽度为 0,导致一个难看的镜面“高光”,几乎覆盖了整个半球。从左到右,每个球体的光泽度增加 16。

在纯粹的散射反射中,入射光线会均匀地向所有方向散射。观察者将看到来自表面的所有点反射的光线。如果入射光以平行光线均匀照亮表面,那么表面对观察者来说看起来是均匀照明的。(如果不同的光线以不同的角度击中表面,就像它们来自附近的灯或表面是弯曲的,那么光线在该点的照明量取决于光线在该点的入射角度,而不是从该点到用户的线的角度。)

当光线照射到表面时,一部分光可以被吸收,一部分可以被散射反射,一部分可以被镜面反射。反射量对不同波长可以不同。材料对各种波长光的反射程度构成了材料的颜色。现在我们看到了,材料可以有两种不同的颜色——一个散射颜色,它告诉我们材料如何散射反射光,以及一个镜面颜色,它告诉我们它如何镜面反射光。散射颜色是物体的基本颜色。镜面颜色决定了镜面高光的颜色。散射和镜面颜色可以相同;例如,这通常适用于金属表面。或者它们可以不同;例如,塑料表面通常无论散射颜色如何都会有白色镜面高光。

这里有一个演示,让您可以尝试我们迄今讨论的材质属性。有关更多信息,请阅读演示中的帮助文本。


OpenGL 更进一步。实际上,与材质相关还有另外两种颜色。第三种颜色是材质的环境光颜色,它告诉我们表面如何反射环境光。环境光指的是不是直接来自光源的一般照明水平。它由反射和再反射过多次的光组成,以至于不再来自任何特定方向。环境光就是阴影不是绝对黑色的原因。事实上,环境光只是对多次反射光的现实的一种粗略近似,但这比完全忽略多次反射要好。材质的环境光颜色决定了它将如何反射各种波长的漫反射光。环境光颜色通常设置为与散射颜色相同。

与材质相关联的第四种颜色是自发光颜色,它并不是真正意义上的与前三种颜色属性相同的颜色。也就是说,它与表面如何反射光无关。自发光颜色是不来自任何外部来源的颜色,因此似乎是由材料本身发出的。这并不意味着物体发出的光会照亮其他物体,而是意味着即使没有光源(甚至没有环境光),物体也可以被看到。在光线存在的情况下,物体会比仅由照明光解释的更亮,从这个意义上讲,它看起来会发光。自发光颜色通常是黑色的;也就是说,物体根本没有自发光。

四种材质颜色属性中的每一种都是通过三个数字来指定的,这些数字给出了颜色的 RGB(红色、绿色和蓝色)分量。真实的光可以包含无限数量的不同波长。RGB 颜色只由三个分量组成,但由于人类色觉的特性,这在大多数目的中是一个相当好的近似。(见小节2.1.4。)材质颜色也可以有 alpha 分量,但在 OpenGL 中唯一使用的 alpha 分量是散射材质颜色的 alpha 分量。

在环境光、散射或镜面颜色的红色、蓝色和绿色分量的情况下,“颜色”这个术语实际上意味着反射率。也就是说,颜色的红色分量给出了击中表面的红光的比例,该表面被反射,绿色和蓝色也是如此。有三种不同类型的反射颜色,因为 OpenGL 中有三种不同类型的光,而材料可以对每种类型的光有不同的反射率。

When light strikes a surface, some of it will be reflected. Exactly how it reflects depends in a complicated way on the nature of the surface, what I am calling the material properties of the surface. In OpenGL 1.1, the complexity is approximated—very crudely—by two general types of reflection, specular reflection and diffuse reflection. These two types of reflection are important in other 3D graphics systems as well. (But see Section 8.2 for a more modern view of materials.)

123

In perfect specular ("mirror-like") reflection, an incoming ray of light is reflected from the surface intact. The reflected ray makes the same angle with the surface as the incoming ray. A viewer can see the reflected ray only if the viewer is in exactly the right position, somewhere along the path of the reflected ray. Even if the entire surface is illuminated by the light source, the viewer will only see the reflection of the light source at those points on the surface where the geometry is right. Such reflections are referred to as specular highlights. In practice, we think of a ray of light as being reflected not as a single perfect ray, but as a cone of light, which can be more or less narrow.

123

Specular reflection from a very shiny surface produces very narrow cones of reflected light; specular highlights on such a material are small and sharp. A duller surface will produce wider cones of reflected light and bigger, fuzzier specular highlights. In OpenGL, the material property that determines the size and sharpness of specular highlights is called shininess. Shininess in OpenGL is a number in the range 0 to 128. As the number increases, specular highlights get smaller. This image shows eight spheres that differ only in the value of the shininess material property:

123

For the sphere on the left, the shininess is 0, which leads to an ugly specular "highlight" that almost covers an entire hemisphere. Going from left to right, the shininess increases by 16 from one sphere to the next.

In pure diffuse reflection, an incoming ray of light is scattered in all directions equally. A viewer would see reflected light from all points on the surface. If the incoming light arrives in parallel rays that evenly illuminate the surface, then the surface would appear to the viewer to be evenly illuminated. (If different rays strike the surface at different angles, as they would if they come from a nearby lamp or if the surface is curved, then the amount of illumination at a point depends on the angle at which the ray hits the surface at that point, but not on the angle of the line from that point to the user.)

When light strikes a surface, some of the light can be absorbed, some can be reflected diffusely, and some can be reflected specularly. The amount of reflection can be different for different wavelengths. The degree to which a material reflects light of various wavelengths is what constitutes the color of the material. We now see that a material can have two different colors—a diffuse color that tells how the material reflects light diffusely, and a specular color that tells how it reflects light specularly. The diffuse color is the basic color of the object. The specular color determines the color of specular highlights. The diffuse and specular colors can be the same; for example, this is often true for metallic surfaces. Or they can be different; for example, a plastic surface will often have white specular highlights no matter what the diffuse color.

here is a demo that lets you experiment with the material properties that we have discussed so far. Read the help text in the demo for more information.


OpenGL goes even further. In fact, there are two more colors associated with a material. The third color is the ambient color of the material, which tells how the surface reflects ambient light. Ambient light refers to a general level of illumination that does not come directly from a light source. It consists of light that has been reflected and re-reflected so many times that it is no longer coming from any particular direction. Ambient light is why shadows are not absolutely black. In fact, ambient light is only a crude approximation for the reality of multiply reflected light, but it is better than ignoring multiple reflections entirely. The ambient color of a material determines how it will reflect various wavelengths of ambient light. Ambient color is generally set to be the same as the diffuse color.

The fourth color associated with a material is an emission color, which is not really a color in the same sense as the first three color properties. That is, it has nothing to do with how the surface reflects light. The emission color is color that does not come from any external source, and therefore seems to be emitted by the material itself. This does not mean that the object is giving off light that will illuminate other objects, but it does mean that the object can be seen even if there is no source of light (not even ambient light). In the presence of light, the object will be brighter than can be accounted for by the light that illuminates it, and in that sense it appears to glow. The emission color is usually black; that is, the object has no emission at all.

Each of the four material color properties is specified in terms of three numbers giving the RGB (red, green, and blue) components of the color. Real light can contain an infinite number of different wavelengths. An RGB color is made up of just three components, but the nature of human color vision makes this a pretty good approximation for most purposes. (See Subsection 2.1.4.) Material colors can also have alpha components, but the only alpha component that is ever used in OpenGL is the one for the diffuse material color.

In the case of the red, blue, and green components of the ambient, diffuse, or specular color, the term "color" really means reflectivity. That is, the red component of a color gives the proportion of red light hitting the surface that is reflected by that surface, and similarly for green and blue. There are three different types of reflective color because there are three different types of light in OpenGL, and a material can have a different reflectivity for each type of light.

4.1.2 光属性

Light Properties

不考虑环境光,环境中的光来自光源,例如灯或太阳。实际上,灯和太阳是两种本质上不同的光源的例子:点光源方向光。点光源位于3D空间中的一个点,并从该点向所有方向发射光。对于方向光,所有光都来自同一方向,使光线平行。太阳被认为是一个方向光源,因为它距离如此之远,以至于当太阳光到达地球时,光线实际上是平行的。

Point vs. Directional Light

光可以有颜色。实际上,在OpenGL中,每个光源都有三种颜色:环境色、散射色和镜面色。正如材料的颜色更准确地称为反射率,光的颜色更准确地称为强度或能量。更准确地说,颜色指的是光的能量如何在不同波长之间分布。真正的光可以包含无限数量的不同波长;当这些波长被分离时,你会得到一个包含一系列颜色的光谱或彩虹。在计算机上通常模拟的光只包含三种基本颜色:红色、绿色和蓝色。所以,就像材料颜色一样,光的颜色是通过给出代表光的红色、绿色和蓝色强度的三个数字来指定的。

光的散射强度是与散射材料颜色互动的光的方面,光的镜面强度是与镜面材料颜色互动的部分。通常,散射和镜面光强度是相同的。

光的环境强度的工作原理略有不同。回想一下,环境光是不可直接追溯到任何光源的光。尽管如此,它必须来自某个地方,我们可以想象打开一个光源应该增加环境中的环境光的总体水平。在OpenGL中,光的环境强度被添加到环境光的总体水平中。(也可以有全局环境光,它与场景中的任何光源都不相关。)环境光与材料的环境色互动,这种互动不依赖于光源或观察者的位置。所以,光不必照射在物体上才能使物体的环境色受到光源的影响;只要打开光源即可。

我再次强调,这只是一种近似,而且在这个例子中,它并没有基于现实世界物理的基础。真正的光源没有分开的环境、散射和镜面颜色,许多计算机图形系统使用单一颜色来模拟光源。

Leaving aside ambient light, the light in an environment comes from a light source such as a lamp or the sun. In fact, a lamp and the sun are examples of two essentially different kinds of light source: a point light and a directional light. A point light source is located at a point in 3D space, and it emits light in all directions from that point. For a directional light, all the light comes from the same direction, so that the rays of light are parallel. The sun is considered to be a directional light source since it is so far away that light rays from the sun are essentially parallel when they get to the Earth .

123

A light can have color. In fact, in OpenGL, each light source has three colors: an ambient color, a diffuse color, and a specular color. Just as the color of a material is more properly referred to as reflectivity, color of a light is more properly referred to as intensity or energy. More exactly, color refers to how the light's energy is distributed among different wavelengths. Real light can contain an infinite number of different wavelengths; when the wavelengths are separated, you get a spectrum or rainbow containing a continuum of colors. Light as it is usually modeled on a computer contains only the three basic colors, red, green, and blue. So, just like material color, light color is specified by giving three numbers representing the red, green, and blue intensities of the light.

The diffuse intensity of a light is the aspect of the light that interacts with diffuse material color, and the specular intensity of a light is what interacts with specular material color. It is common for the diffuse and specular light intensities to be the same.

The ambient intensity of a light works a little differently. Recall that ambient light is light that is not directly traceable to any light source. Still, it has to come from somewhere and we can imagine that turning on a light should increase the general level of ambient light in the environment. The ambient intensity of a light in OpenGL is added to the general level of ambient light. (There can also be global ambient light, which is not associated with any of the light sources in the scene.) Ambient light interacts with the ambient color of a material, and this interaction has no dependence on the position of the light sources or viewer. So, a light doesn't have to shine on an object for the object's ambient color to be affected by the light source; the light source just has to be turned on.

I should emphasize again that this is all just an approximation, and in this case not one that has a basis in the physics of the real world. Real light sources do not have separate ambient, diffuse, and specular colors, and many computer graphics systems model light sources using just one color.

4.1.3 法向量

Normal Vectors

光线照射在表面上产生的视觉效应取决于表面和光线的属性。但它也在很大程度上取决于光线与表面接触的角度。角度对于镜面反射至关重要,也会影响散射反射。这就是为什么即使曲面的颜色均匀,不同点上的弯曲、发光表面看起来也会不同。为了计算这个角度,OpenGL 需要知道表面面向的方向。这个方向由一个垂直于表面的向量指定。"垂直"的另一个词是 "法向",一个非零向量如果在给定点上垂直于表面,则称为该表面的 法向量。在使用照明计算时,法向量的长度必须等于一。长度为一的法向量称为 单位法向量。为了在 OpenGL 中进行适当的照明计算,必须为每个顶点指定单位法向量。然而,给定任何法向量,都可以通过将其除以其长度来计算单位法向量(见第3.5节关于向量及其长度的讨论)。

由于表面可能是弯曲的,它在不同点上可能面向不同的方向。所以,法向量与表面上的一个特定点相关联。在 OpenGL 中,法向量实际上只分配给原语的顶点。原语顶点上的法向量用于对整个原语进行照明计算。

特别要注意的是,你可以为多边形的每个顶点分配不同的法向量。现在,你可能会问自己,"多边形的所有法向量不是都指向同一方向吗?"毕竟,多边形是平的;多边形的垂直方向不会随着点的变化而变化。这是真的,如果你的目标是显示一个由平面多边形构成的多面体对象,那么确实,这些多边形的每个法向量都应该指向同一方向。另一方面,多面体经常用来近似球体等曲面。如果你真正的目标是制作看起来像曲面的东西,那么你想使用的法向量应该垂直于实际表面,而不是近似它的多面体。看这个例子:

Flat vs. Smooth

这张图片中的两个对象由一系列矩形带组成。这两个对象具有完全相同的几何形状,但它们看起来非常不同。这是因为在每种情况下使用了不同的法向量。对于上面的对象,矩形带应该近似一个平滑表面。矩形的顶点是该表面上的点,我根本不想看到矩形——我想看到的是曲面,或者至少是一个很好的近似。所以对于上面的对象,当我在每个顶点指定法向量时,我使用的向量是垂直于表面而不是矩形的。另一方面,对于下面的物体,我认为它真的是一个矩形带,我使用的法向量实际上垂直于矩形。这是一个二维插图,显示了用于两幅图片的法向量:

Flat vs. Smooth

粗蓝线表示矩形,从上方边缘看。箭头表示法向量。每个矩形有两个法向量,每个端点一个。每个顶点是两个矩形的一部分,因此在每个顶点指定了两个法向量。

在插图的下半部分,两个在点上相遇的矩形在该点上有不同的法向量。矩形的法向量实际上垂直于矩形。当你从一块矩形移动到下一块时,方向会发生突然变化,所以当一块矩形与下一块矩形相遇时,两个矩形的法向量是不同的。在渲染图像上的视觉效应是突然的阴影变化,被感知为两个矩形之间的角落或边缘。

另一方面,在上半部分,向量垂直于通过矩形端点的曲面。当两个矩形共享一个顶点时,它们也在该顶点共享相同的法向量。在视觉上,这消除了阴影的突然变化,产生了更像平滑曲面的外观。

分配法向量的两种方式称为 平面着色平滑着色。平面着色使表面看起来像由平面侧面或面组成。平滑着色使它看起来更像一个平滑表面。这个演示将帮助您理解这些概念。它显示了一个多边形网格被用来近似一个球体,可以选择平滑或平面着色。使用滑块控制网格中的多边形数量。

所有这些的结果是,您可以随意选择适合您目的的任何法向量。顶点处的法向量是您所说的任何东西,它不必真的垂直于多边形。您选择的法向量应该取决于您试图建模的对象。

在选择法向量时还有另一个问题:表面上的一个点总是有两个可能的单位法向量,指向相反的方向。3D 中的多边形有两个面,面向相反的方向。OpenGL 将其中一个视为前表面,另一个视为后表面。OpenGL 通过指定顶点的顺序来区分它们。(见小节3.4.1。)默认规则是,当观察前表面时,顶点的顺序是逆时针的,观察后表面时是顺时针的。当多边形在屏幕上绘制时,这个规则让 OpenGL 知道正在显示的是前表面还是后表面。在为多边形指定法向量时,向量应该指向多边形的前表面。这是右手规则的另一个例子。如果您将右手的手指卷曲在多边形顶点指定的方向上,那么法向量应该指向您的拇指方向。注意,当您观察多边形的前表面时,法向量应该指向您。如果您观察后表面,法向量应该指向远离您。

为对象找到正确的法向量可能是一个难题。复杂的几何模型通常附带必要的法向量。例如,GLUT 库绘制的实体形状就是如此。

The visual effect of a light shining on a surface depends on the properties of the surface and of the light. But it also depends to a great extent on the angle at which the light strikes the surface. The angle is essential to specular reflection and also affects diffuse reflection. That's why a curved, lit surface looks different at different points, even if its surface is a uniform color. To calculate this angle, OpenGL needs to know the direction in which the surface is facing. That direction is specified by a vector that is perpendicular to the surface. Another word for "perpendicular" is "normal," and a non-zero vector that is perpendicular to a surface at a given point is called a normal vector to that surface. When used in lighting calculations, a normal vector must have length equal to one. A normal vector of length one is called a unit normal. For proper lighting calculations in OpenGL, a unit normal must be specified for each vertex. However, given any normal vector, it is possible to calculate a unit normal from it by dividing the vector by its length. (See Section 3.5 for a discussion of vectors and their lengths.)

Since a surface can be curved, it can face different directions at different points. So, a normal vector is associated with a particular point on a surface. In OpenGL, normal vectors are actually assigned only to the vertices of a primitive. The normal vectors at the vertices of a primitive are used to do lighting calculations for the entire primitive.

Note in particular that you can assign different normal vectors at each vertex of a polygon. Now, you might be asking yourself, "Don't all the normal vectors to a polygon point in the same direction?" After all, a polygon is flat; the perpendicular direction to the polygon doesn't change from point to point. This is true, and if your objective is to display a polyhedral object whose sides are flat polygons, then in fact, all the normals of each of those polygons should point in the same direction. On the other hand, polyhedra are often used to approximate curved surfaces such as spheres. If your real objective is to make something that looks like a curved surface, then you want to use normal vectors that are perpendicular to the actual surface, not to the polyhedron that approximates it. Take a look at this example:

123

The two objects in this picture are made up of bands of rectangles. The two objects have exactly the same geometry, yet they look quite different. This is because different normal vectors are used in each case. For the top object, the band of rectangles is supposed to approximate a smooth surface. The vertices of the rectangles are points on that surface, and I really didn't want to see the rectangles at all—I wanted to see the curved surface, or at least a good approximation. So for the top object, when I specified the normal vector at each of the vertices, I used a vector that is perpendicular to the surface rather than one perpendicular to the rectangle. For the object on the bottom, on the other hand, I was thinking of an object that really is a band of rectangles, and I used normal vectors that were actually perpendicular to the rectangles. Here's a two-dimensional illustration that shows the normal vectors that were used for the two pictures:

123

The thick blue lines represent the rectangles, as seen edge-on from above. The arrows represent the normal vectors. Each rectangle has two normals, one at each endpoint. Each vertex is part of two rectangles, and so two normal vectors are specified at each vertex.

In the bottom half of the illustration, two rectangles that meet at a point have different normal vectors at that point. The normal vectors for a rectangle are actually perpendicular to the rectangle. There is an abrupt change in direction as you move from one rectangle to the next, so where one rectangle meets the next, the normal vectors to the two rectangles are different. The visual effect on the rendered image is an abrupt change in shading that is perceived as a corner or edge between the two rectangles.

In the top half, on the other hand, the vectors are perpendicular to a curved surface that passes through the endpoints of the rectangles. When two rectangles share a vertex, they also share the same normal at that vertex. Visually, this eliminates the abrupt change in shading, resulting in something that looks more like a smoothly curving surface.

The two ways of assigning normal vectors are called flat shading and smooth shading. Flat shading makes a surface look like it is made of flat sides or facets. Smooth shading makes it look more like a smooth surface. This demo will help you to understand these concepts. It shows a polygonal mesh being used to approximate a sphere, with your choice of smooth or flat shading. Use the sliders to control the number of polygons in the mesh.

The upshot of all this is that you get to make up whatever normal vectors suit your purpose. A normal vector at a vertex is whatever you say it is, and it does not have to be literally perpendicular to the polygon. The normal vector that you choose should depend on the object that you are trying to model.

There is one other issue in choosing normal vectors: There are always two possible unit normal vectors at a point on a surface, pointing in opposite directions. A polygon in 3D has two faces, facing in opposite directions. OpenGL considers one of these to be the front face and the other to be the back face. OpenGL tells them apart by the order in which the vertices are specified. (See Subsection 3.4.1.) The default rule is that the order of the vertices is counterclockwise when looking at the front face and is clockwise when looking at the back face. When the polygon is drawn on the screen, this rule lets OpenGL tell whether it is the front face or the back face that is being shown. When specifying a normal vector for the polygon, the vector should point out of the front face of the polygon. This is another example of the right-hand rule. If you curl the fingers of your right hand in the direction in which the vertices of the polygon were specified, then the normal vector should point in the direction of your thumb. Note that when you are looking at the front face of a polygon, the normal vector should be pointing towards you. If you are looking at the back face, the normal vector should be pointing away from you.

It can be a difficult problem to come up with the correct normal vectors for an object. Complex geometric models often come with the necessary normal vectors included. This is true, for example, for the solid shapes drawn by the GLUT library.

4.1.4 OpenGL 1.1 光照方程

The OpenGL 1.1 Lighting Equation

OpenGL 进行“照明计算”实际上意味着什么?计算的目的是为表面上的一个点产生一个颜色 (r,g,b,a)。在 OpenGL 1.1 中,照明计算实际上只在原语的顶点处进行。每个顶点的颜色计算完成后,通过插值顶点颜色来获得原语内部点的颜色。

顶点颜色的 alpha 分量 a 很简单:它只是该顶点处散射材质颜色的 alpha 分量。r、g 和 b 的计算相当复杂,也比较数学化,您不一定需要理解它。但这里简要描述一下如何进行计算...

忽略 alpha 分量,假设材质的环境、散射、镜面和自发光颜色的 RGB 分量分别为 (mar,mag,mab)、(mdr,mdg,mdb)、(msr,msg,msb) 和 (mer,meg,meb)。假设全局环境强度 (gar,gag,gab) 代表与环境中任何光源无关的环境光。可以有多个点光源和方向光源,我们称之为第 0 号光源、第 1 号光源、第 2 号光源等。在这种设置下,顶点颜色的红色分量将是:

\[ r = mer + gar \times mar + I_0,r + I_1,r + I_2,r + \ldots \]

其中 \( I_0,r \) 是来自第 0 号光源对颜色的贡献的红色分量;\( I_1,r \) 是第 1 号光源的贡献;以此类推。对于颜色的绿色和蓝色分量也有类似的方程。这个方程表明,自发光颜色 mer 简单地添加到其他颜色贡献中。全局环境光的贡献是通过将全局环境强度 gar 乘以材质环境颜色 mar 来获得的。这是数学上说材质环境颜色是表面反射的环境光的一部分。

\( I_0,r, I_1,r \) 等项代表环境中各个光源对最终颜色的贡献。光源的贡献比较复杂。考虑其中一个光源。首先要注意的是,如果一个光源被禁用(即关闭),那么这个光源的贡献就是零。对于启用的光源,我们需要考虑几何形状和颜色:

Light Equation

在这个插图中,N 是我们想要计算颜色的点的法向量。L 是一个指向从表面到达的光线方向的向量。V 是一个指向观察者方向的向量。R 是反射光线的方向,即当光线从光源到达相关点的表面时,会镜面反射的方向。N 和 L 之间的角度与 N 和 R 之间的角度相同;这是关于光的物理学的一个基本事实。所有向量都是单位向量,长度为 1。回想一下,对于单位向量 A 和 B,内积 A · B 等于两个向量之间角度的余弦。内积在照明方程中有几个点出现,作为计算不同向量之间角度的一种方式。

现在,假设光具有环境、散射和镜面颜色分量 (lar,lag,lab)、(ldr,ldg,ldb) 和 (lsr,lsg,lsb)。另外,mh 是材质的光泽度属性值。然后,假设光源启用,这个光源对顶点颜色红色分量的贡献可以计算为:

\[ I_r = lar \times mar + f \times ( ldr \times mdr \times (L \cdot N) + lsr \times msr \times \max(0, V \cdot R)^{mh} ) \]

对于绿色和蓝色分量有类似的方程。第一项 \( lar \times mar \) 考虑了这个光源的环境光对表面颜色的贡献。无论表面是否面向光线,这一项都添加到颜色中。

f 的值如果表面背向光线是 0,如果表面面向光线是 1;也就是说,它考虑了光线只照亮表面的一面。要测试 f 是 0 还是 1,我们可以检查 \( L \cdot N \) 是否小于 0。这个点积是 L 和 N 之间角度的余弦;当角度大于 90 度时,它小于 0,这意味着法向量在表面对面光线的一侧。当 f 为零时,光线对顶点颜色没有散射或镜面贡献。

颜色的散射分量(在 f 调整之前)由 \( ldr \times mdr \times (L \cdot N) \) 给出。这代表散射光强度乘以材质的散射反射率,乘以 L 和 N 之间角度的余弦。角度涉及:

Angle of Incidence

译注

每个平方单位表面上击中的光能量量取决于光击中表面的角度。对于较大的角度,相同的光能量被分散在更大的区域。

表面面向的方向由其法向量表示,法向量是一个垂直于表面的向量。

随着角度从 0 增加到 90 度,角度的余弦从 1 减少到 0,所以角度越大,\( ldr \times mdr \times (L \cdot N) \) 的值越小,散射照明对颜色的贡献越小。

对于镜面分量,回想一下,光线以光锥的形式镜面反射。反射向量 R 位于锥体中心。观察者离锥体中心越近,镜面反射就越强烈。观察者与锥体中心的距离取决于 V 和 R 之间的角度,在方程中以点积 V·R 的形式出现。数学上,颜色的镜面贡献由 \( lsr \times msr \times \max(0, V \cdot R)^{mh} \) 给出。取 0 和 V·R 的最大值确保如果 V 和 R 之间的角度大于 90 度,镜面贡献为零。假设不是这种情况,max(0,V·R) 等于 V·R。注意,这个点积被提升到指数 mh,这是材质的光泽度属性。当 mh 为 0 时,(V·R)^{mh} 为 1,没有角度的依赖性;在这种情况下,结果是我们在光泽度为零时看到的那种巨大而不受欢迎的镜面高光。对于正值的光泽度,镜面贡献在 V 和 R 之间角度为零时最大,随着角度的增加而减少。光泽度值越大,减少的速率越快。结果是,较大的光泽度值给出更小、更清晰的镜面高光。

记住,相同的计算对每个启用的光源重复进行,结果结合起来给出最终的顶点颜色。特别是当使用几个光源时,很容易最终得到大于一的颜色分量。最后,在颜色用于给屏幕上的像素上色之前,颜色分量必须被限制在零到一的范围内。大于一的值被替换为一。这很容易产生大片区域为均匀白色的丑陋图像,因为这些区域的所有颜色值都超过了一。本应通过照明传达的所有信息已经丢失。其效果类似于曝光过度的照片。为了避免这种曝光过度,可能需要一些工作来找到合适的照明水平。

(本节中关于照明的讨论遗漏了一些因素。所呈现的方程没有考虑到点光源的效果可能取决于到光源的距离,也没有考虑到聚光灯,它们只发射一个光锥。这两者都可以在 OpenGL 1.1 中配置,但本书没有介绍如何做到这一点。还有许多光的方面没有被 OpenGL 使用的简单模型捕获。最明显的遗漏之一是阴影:对象不阻挡光!光直接穿过它们。我们将在后续章节中讨论其他图形系统时遇到模型的一些扩展。)

What does it actually mean to say that OpenGL performs "lighting calculations"? The goal of the calculation is to produce a color, (r,g,b,a), for a point on a surface. In OpenGL 1.1, lighting calculations are actually done only at the vertices of a primitive. After the color of each vertex has been computed, colors for interior points of the primitive are obtained by interpolating the vertex colors.

The alpha component of the vertex color, a, is easy: It's simply the alpha component of the diffuse material color at that vertex. The calculation of r, g, and b is fairly complex and rather mathematical, and you don't necessarily need to understand it. But here is a short description of how it's done...

Ignoring alpha components, let's assume that the ambient, diffuse, specular, and emission colors of the material have RGB components (mar,mag,mab), (mdr,mdg,mdb), (msr,msg,msb), and (mer,meg,meb), respectively. Suppose that the global ambient intensity, which represents ambient light that is not associated with any light source in the environment, is (gar,gag,gab). There can be several point and directional light sources, which we refer to as light number 0, light number 1, light number 2, and so on. With this setup, the red component of the vertex color will be:

r = mer + gar*mar + I0,r + I1,r + I2,r + ...

where I0,r is the red component of the contribution to the color that comes from light number 0; I1,r is the contribution from light number 1; and so on. A similar equation holds for the green and blue components of the color. This equation says that the emission color, mer, is simply added to any other contributions to the color. And the contribution of global ambient light is obtained by multiplying the global ambient intensity, gar, by the material ambient color, mar. This is the mathematical way of saying that the material ambient color is the fraction of the ambient light that is reflected by the surface.

The terms I0,r, I1,r, and so on, represent contributions to the final color from the various light sources in the environment. The contributions from the light sources are complicated. Consider just one of the light sources. Note, first of all, that if a light source is disabled (that is, if it is turned off), then the contribution from that light source is zero. For an enabled light source, we have to look at the geometry as well as the colors:

123

In this illustration, N is the normal vector at the point whose color we want to compute. L is a vector that points back along the direction from which the light arrives at the surface. V is a vector that points in the direction of the viewer. And R is the direction of the reflected ray, that is, the direction in which a light ray from the source would be reflected specularly when it strikes the surface at the point in question. The angle between N and L is the same as the angle between N and R; this is a basic fact about the physics of light. All of the vectors are unit vectors, with length 1. Recall that for unit vectors A and B, the inner product A · B is equal to the cosine of the angle between the two vectors. Inner products occur at several points in the lighting equation, as the way of accounting for the angles between various vectors.

Now, let's say that the light has ambient, diffuse, and specular color components (lar,lag,lab), (ldr,ldg,ldb), and (lsr,lsg,lsb). Also, let mh be the value of the shininess property of the material. Then, assuming that the light is enabled, the contribution of this light source to the red component of the vertex color can be computed as

Ir = lar*mar + f*( ldr*mdr*(L·N) + lsr*msr*max(0,V·R)mh )

with similar equations for the green and blue components. The first term, lar*mar accounts for the contribution of the ambient light from this light source to the color of the surface. This term is added to the color whether or not the surface is facing the light.

The value of f is 0 if the surface is facing away from the light and is 1 if the surface faces the light; that is, it accounts for the fact that the light only illuminates one side of the surface. To test whether f is 0 or 1, we can check whether L·N is less than 0. This dot product is the cosine of the angle between L and N; it is less than 0 when the angle is greater than 90 degrees, which would mean that the normal vector is on the opposite side of the surface from the light. When f is zero, there is no diffuse or specular contribution from the light to the color of the vertex.

The diffuse component of the color, before adjustment by f, is given by ldr*mdr*(L·N). This represents the diffuse intensity of the light times the diffuse reflectivity of the material, multiplied by the cosine of the angle between L and N. The angle is involved

123

As the angle increases from 0 to 90 degrees, the cosine of the angle decreases from 1 to 0, so the larger the angle, the smaller the value of ldr*mdr*(L·N) and the smaller the contribution of diffuse illumination to the color.

For the specular component, recall that a light ray is reflected specularly as a cone of light. The reflection vector, R, is at the center of the cone. The closer the viewer is to the center of the cone, the more intense the specular reflection. The distance of the viewer from the center of the cone depends on the angle between V and R, which appears in the equation as the dot product V·R. Mathematically, the specular contribution to the color is given by lsrmsrmax(0,V·R)mh. Taking the maximum of 0 and V·R ensures that the specular contribution is zero if the angle between V and R is greater than 90 degrees. Assuming that is not the case, max(0,V·R) is equal to V·R. Note that this dot product is raised to the exponent mh, which is the material's shininess property. When mh is 0, (V·R)mh is 1, and there is no dependence on the angle; in that case, the result is the sort of huge and undesirable specular highlight that we have seen for shininess equal to zero. For positive values of shininess, the specular contribution is maximal when the angle between V and R is zero, and it decreases as the angle increases. The larger the shininess value, the faster the rate of decrease. The result is that larger shininess values give smaller, sharper specular highlights.

Remember that the same calculation is repeated for every enabled light and that the results are combined to give the final vertex color. It's easy, especially when using several lights, to end up with color components larger than one. In the end, before the color is used to color a pixel on the screen, the color components must be clamped to the range zero to one. Values greater than one are replaced by one. This makes it easy to produce ugly pictures in which large areas are a uniform white because all the color values in those areas exceeded one. All the information that was supposed to be conveyed by the lighting has been lost. The effect is similar to an over-exposed photograph. It can take some work to find appropriate lighting levels to avoid this kind of over-exposure.

(My discussion of lighting in this section leaves out some factors. The equation as presented doesn't take into account the fact that the effect of a point light can depend on the distance to the light, and it doesn't take into account spotlights, which emit just a cone of light. Both of these can configured in OpenGL 1.1, but this book does not cover how to do that. There are also many aspects of light that are not captured by the simple model used in OpenGL. One of the most obvious omissions is shadows: Objects don't block light! Light shines right through them. We will encounter some extensions to the model in later chapters when we discuss other graphics systems.)