forked from cybercser/OpenGL_3_3_Tutorial_Translation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTutorial 8 Basic shading opengl-tutorial.org.txt
201 lines (166 loc) · 33 KB
/
Tutorial 8 Basic shading opengl-tutorial.org.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
在第八课中,我们将学习光照模型的基础知识。包括:In this 8th tutorial, we will learn how to do some basic shading. This includes :
<ul>
<li>物体离光源越近会越亮Beeing more bright when closer to a light source</li>
<li>直视反射光时会有亮点(镜面反射光)Having highlights when looking in the reflection of a light (specular lighting)</li>
<li>当光没有直接照射物体时,物体会更暗(漫反射光)Beeing darker when light is not directly towards the model (diffuse lighting)</li>
<li>环境光较多地影响效果(环境光)Cheating a lot (ambient lighting)</li>
</ul>
不包括:This does NOT include :
<ul>
<li>阴影。这是一个宽泛的话题,需要单独放在一课或多课中讲Shadows. This is a broad topic that deserves its own tutorial(s)</li>
<li>镜面反射(包括水)Mirror-like reflections (this includes water)</li>
<li>任何复杂的光物质的作用,像下层面反射(比如蜡)Any sophisticated light-matter interaction like subsurface scattering (like wax)</li>
<li>各向异性材料(比如雾面金属)Anisotropic materials (like brushed metal)</li>
<li>基于实物的光照模型,尝试模拟现实Physically based shading, which tries to mimic the reality closely</li>
<li>环境光遮蔽(在笼子里更黑)Ambient Occlusion (it's darker in a cave)</li>
<li>颜色溢出(一块红色的地毯会映得白色天花板带红色)Color Bleeding (a red carpet will make a white ceiling a litte bit red)</li>
<li>透明度Transparency</li>
<li>任何时候任何种类的全局光照(所有之前提到的重组后的名称)Any kind of Global Illumination whatsoever (it's the name that regroups all previous ones)</li>
</ul>
总而言之:基础。In a word : Basic.
<h1>法向Normals</h1>
过去的几个教程中我们一直在处理法向,但是并不知道法向到底是什么。During the last few tutorials you've been dealing with normal without really knowing what they were.
<h2>三角形法向Triangle normals</h2>
一个平面的法向是一个长度为1并且垂直于这个平面的向量。The normal of a plane is a vector of length 1 that is perpendicular to this plane.
一个三角形的法向是一个长度为1并且垂直于这个三角形的向量。通过简单地将三角形两条边进行叉乘计算(向量a和b的叉乘结果是一个同时垂直于a和b的向量,记得?),然后归一化:使长度为1.伪代码如下:The normal of a triangle is a vector of length 1 that is perpendicular to this triangle. It is easily computed by taking the cross product of two of its edges (the cross product of a and b produces a vector that is perpendicular to both a and b, remember ?), and normalized : its length is brought back to 1. In pseudo-code :
<pre>triangle ( v1, v2, v3 )
edge1 = v2-v1
edge2 = v3-v1
triangle.normal = cross(edge1, edge2).normalize()</pre>
不要将normal和normalize()混淆。Normalize()是对一个向量(任意向量,不一定必须是normal)除以其长度,从而使新长度为1。normal只是一类向量为了表达一个意思所取的名字。Don't mix up normal and normalize(). Normalize() divides a vector (any vector, not necessarily a normal) by its length so that its new length is 1. normal is just the name for some vectors that happen to represent, well, a normal.
<h2>顶点法向Vertex normals</h2>
引申开来,定义一个顶点的法向为以这个顶点为公共点的所有三角形法向的平均值。这样处理起来更方便,可以只处理一些顶点,而不是很多三角形。并且不管用什么方式,在OpenGL中,我们无法获得三角形信息。伪代码如下:By extension, we call the normal of a vertex the combination of the normals of the surroundings triangles. This is handy because in vertex shaders, we deal with vertices, not triangles, so it's better to have information on the vertex. And any way, we can't have information on triangles in OpenGL. In pseudo-code :
<pre>vertex v1, v2, v3, ....
triangle tr1, tr2, tr3 // all share vertex v1
v1.normal = normalize( tr1.normal + tr2.normal + tr3.normal )</pre>
<h2>在OpenGL中使用顶点法向Using vertex normals in OpenGL</h2>
在OpenGL中使用法向很简单。一个法向是一个顶点的属性,就像它的位置,颜色,UV坐标系等等一样,所以只需做一些固定的事情。第七课使用的loadOBJ函数已经将它们从OBJ文件中读出来了。To use normals in OpenGL, it's very easy. A normal is an attribute of a vertex, just like its position, its color, its UV coordinates... so just do the usual stuff. Our loadOBJ function from Tutorial 7 already reads them from the OBJ file.
<pre class="brush: cpp">GLuint normalbuffer;
glGenBuffers(1, &normalbuffer);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);</pre>
和and
<pre class="brush: cpp"> // 3rd attribute buffer : normals
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // attribute
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);</pre>
这些准备后就可以开始了。and this is enough to get us started.
<h1>漫反射部分The Diffuse part</h1>
<h2>表面法向的重要性The importance of the surface normal</h2>
当光源照射一个物体,其中重要的一小部分光向各个方向反射。这就是“漫反射部分”。(我们不久将会看到光的其他部分去哪里了)When light hits an object, an important fraction of it is reflected in all directions. This is the "diffuse component". (We'll see what happens with the other fraction soon)
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseWhite1.png"><img class="alignnone size-full wp-image-225" title="diffuseWhite" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseWhite1.png" alt="" width="500" height="303" /></a>
当一部分光通量到达表面,这个表面根据光到达时不同角度而不同程度地被照亮。When a certain flux of light arrives at the surface, this surface is illuminated differently according to the angle at which the light arrives.
如果光线垂直于表面,它会聚集在一小片表面。如果它以一个倾斜角到达表面,相同的光强度照亮更大一片表面:If the light is perpendicular to the surface, it is concentrated on a small surface. If it arrives at a gazing angle, the same quantity of light spreads on a greater surface :
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseAngle.png"><img class="alignnone size-full wp-image-227" title="diffuseAngle" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseAngle.png" alt="" width="600" height="500" /></a>
这意味着表面的每一个点在斜射下会较黑(但是记住,更多的点会被照射到,总光强度仍然是一样的)This means that each point of the surface will look darker with gazing light (but remember, more points will be illuminated, to the total quantity of light will remain the same)
也就是说当计算一个像素点的颜色时,角度在入射光线和表面法向中间。因此有:This means that when we compute the colour of a pixel, the angle between the incoming light and the surface normal matters.We thus have :
<pre class="brush: fs">// Cosine of the angle between the normal and the light direction,
// clamped above 0
// - light is at the vertical of the triangle -> 1
// - light is perpendicular to the triangle -> 0
float cosTheta = dot( n,l );
color = LightColor * cosTheta;</pre>
在这段代码中,n是表面法向,l是从表面到光源的单位向量(并不是相反方向,尽管是非直觉反应。可使数学计算更简单)。In this code, n is the surface normal and l is the unit vector that goes from the surface to the light (and not the contrary, even if it's non intuitive. It makes the math easier).
<h2>当心这个标志Beware of the sign</h2>
在求cosTheta的公式中有些没考虑到。如果光源在三角形后面,n和l方向相反,那么n.l是负值。这意味着colour = 一个负数,没有意义的一个值。因此这种情况须用clamp()将cosTheta赋值为0:mething is missing in the formula of our cosTheta. If the light is behind the triangle, n and l will be opposed, so n.l will be negative. This would mean that colour = someNegativeNumber, which doesn't mean much. So we have to clamp cosTheta to 0 :
<pre class="brush: fs">// Cosine of the angle between the normal and the light direction,
// clamped above 0
// - light is at the vertical of the triangle -> 1
// - light is perpendicular to the triangle -> 0
// - light is behind the triangle -> 0
float cosTheta = clamp( dot( n,l ), 0,1 );
color = LightColor * cosTheta;</pre>
<h2>材质颜色Material Color</h2>
当然,输出颜色也依赖于材质颜色。在这幅图像中,白光由绿、红、蓝光组成。当光碰到红色材质时,绿光和蓝光被吸收,只有红光保留着。Of course, the output colour also depends on the colour of the material. In this image, the white light is made out of green, red and blue light. When colliding with the red material, green and blue light is absorbed, and only the red remains.
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseRed.png"><img class="alignnone size-full wp-image-226" title="diffuseRed" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuseRed.png" alt="" width="500" height="303" /></a>
我们可以通过一个简单的乘法来模拟:We can model this by a simple multiplication :
<pre class="brush: fs">color = MaterialDiffuseColor * LightColor * cosTheta;</pre>
<h2>模拟光源Modeling the light</h2>
首先假设在空间中有一个确定的光源照射到所有方向,像一根蜡烛。We will first assume that we have a punctual light that emits in all directions in space, like a candle.
有了这样一个光源,表面收到的光通量依赖于表面到摄像机的距离:越远光通量越少。实际上,光通量反比于距离的平方:With such a light, the luminous flux that our surface will receive will depend on its distance to the camera : the further away, the less light. In fact, the amount of light will diminish with the square of the distance :
<pre class="brush: fs">color = MaterialDiffuseColor * LightColor * cosTheta / (distance*distance);</pre>
最后,需要另一个参数来控制光的强度。这可以被编码到LightColor中(将在随后的课程中讲到),但是现在只有一个颜色值(如白色)和一个强度(如60瓦)。Lastly, we need another parameter to control the power of the light. This could be encoded into LightColor (and we will in a later tutorial), but for now let's just have a color (e.g. white) and a power (e.g. 60 Watts).
<pre class="brush: fs">color = MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance);</pre>
<h2>把它们放在一起Putting it all together</h2>
为了让这段代码运行,需要一些参数(各种颜色和强度)和更多代码。For this code to work, we need a handful of parameters (the various colours and powers) and some more code.
MaterialDiffuseColor简单地从纹理中获取。MaterialDiffuseColor is simply fetched from the texture.
LightColor和LightPower通过GLSL准则在着色器中设置。LightColor and LightPower are set in the shader through GLSL uniforms.
cosTheta由n和l决定。只要它们同处一个空间,可以在提供的任何空间中描述它们。这里选择摄像机坐标系空间是因为在这个空间下计算光源的位置很简单:cosTheta depends on n and l. We can express them in any space provided it's the same for both. We choose the camera space because it's easy to compute the light's position in this space :
<pre class="brush: vs">// Normal of the computed fragment, in camera space
vec3 n = normalize( Normal_cameraspace );
// Direction of the light (from the fragment to the light)
vec3 l = normalize( LightDirection_cameraspace );</pre>
并且Normal_cameraspace和LightDirection_cameraspace在顶点着色器中计算,然后传给片断着色器:with Normal_cameraspace and LightDirection_cameraspace computed in the Vertex shader and passed to the fragment shader :
<pre class="brush: vs">// Output position of the vertex, in clip space : MVP * position
gl_Position = MVP * vec4(vertexPosition_modelspace,1);
// Position of the vertex, in worldspace : M * position
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;
// Vector that goes from the vertex to the camera, in camera space.
// In camera space, the camera is at the origin (0,0,0).
vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;
EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;
// Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity.
vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;
// Normal of the the vertex, in camera space
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not.</pre>
这段代码看起来印象深刻,它就是在第三课中学习到的东西:矩阵。在每一个向量的名字中写上所使用的空间名,这样在跟踪时会更简单。 <strong>你也应该这样做。</strong>This code can seem impressive but it's nothing we didn't learn in Tutorial 3 : Matrices. I paid attention to write the name of the space in each vector's name, so that keeping track of what is happening is much easier. <strong>You should do that, too.</strong>
M和V分别是模型和视图矩阵,并且是用与MVP完全相同的方式传给着色器。M and V are the Model and View matrices, which are passed to the shader in the exact same way as MVP.
<h2>运行时间Time for work</h2>
现在有了一个漫反射光源的所有需要条件。向前吧,刻苦努力地尝试 :) You've got everything you need to code a diffuse lighting. Go ahead, and learn the hard way :)
<h2>结果Result</h2>
只包含漫反射部分,我们得到下面这个结果(对又一次采用无趣的纹理感到抱歉):With only the Diffuse component, we have the following result (sorry for the lame texture again) :
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_only.png"><img class="alignnone size-large wp-image-228" title="diffuse_only" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_only-1024x793.png" alt="" width="640" height="495" /></a>
这次结果比之前好,但是仍然感觉少了一些东西。特别地,Suzanne的背后完全是黑色的,因为我们使用clamp()。It's better than before, but there is still much missing. In particular, the back of Suzanne is completely black since we used clamp().
<h1>环境反射部分The Ambient component</h1>
环境反射是最大的骗子。The Ambient component is the biggest cheat ever.
我们期望的是Suzanne的背后有一点亮度,因为在现实生活中灯泡会照亮它背后的墙,而墙会反过来(微弱地)照亮物体的背后。We expect the back of Suzanne to be receive more light because in real life, the lamp would light the wall behind it, which would in turn (slightly less) light the back of the object.
要计算这个有可怕的代价。This is awfully expensive to compute.
因此通常简单地制造一些光。实际上,只是简单地让3D模型发出光,使它看起来不是完全黑。So the usual hack is to simply fake some light. In fact, is simply makes the 3D model <em>emit </em>light so that it doesn't appear completely black.
可以通过这种方式来完成:This can be done this way :
<pre class="brush: fs">vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;</pre>
<pre class="brush: fs">color =
// Ambient : simulates indirect lighting
MaterialAmbientColor +
// Diffuse : "color" of the object
MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;</pre>
来看看会产生什么样的结果Let's see what it gives
<h2>结果Results</h2>
好的,效果比之前好一些。如果想要更好的结果,可以调整(0.1, 0.1, 0.1)值。Ok so that's a little bit better. You can adjust the (0.1, 0.1, 0.1) if you want better results.
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_ambiant.png"><img class="alignnone size-large wp-image-229" title="diffuse_ambiant" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_ambiant-1024x793.png" alt="" width="640" height="495" /></a>
<h1>镜面反射部分The Specular component</h1>
光的绝大部分是在表面有确定方向的反射。这就是镜面反射部分。The other part of light that is reflected is reflected mostly in the direction that is the reflection of the light on the surface. This is the specular component.
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/specular.png"><img class="alignnone size-full wp-image-232" title="specular" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/specular.png" alt="" width="500" height="251" /></a>
如你在图像中看到的,它形成一种波瓣。在极端的情况下,漫反射部分可以为零,这样波瓣非常非常窄(所有的光从一个方向反射),这就是镜子。As you can see in the image, it forms a kind of lobe. In extreme cases, the diffuse component can be null, the lobe can be very very very narrow (all the light is reflected in a single direction) and you get a mirror.
<em>(的确可以弱化参数值得到一面镜子,但是在这个例子这面镜子中我们唯一考虑的事情是灯泡。所以这将产生一面奇怪的镜子)</em>
(<em>we can indeed tweak the parameters to get a mirror, but in our case, the only thing we take into account in this mirror is the lamp. So this would make for a weird mirror)</em>
<pre class="brush: fs">// Eye vector (towards the camera)
vec3 E = normalize(EyeDirection_cameraspace);
// Direction in which the triangle reflects the light
vec3 R = reflect(-l,n);
// Cosine of the angle between the Eye vector and the Reflect vector,
// clamped to 0
// - Looking into the reflection -> 1
// - Looking elsewhere -> < 1
float cosAlpha = clamp( dot( E,R ), 0,1 );
color =
// Ambient : simulates indirect lighting
MaterialAmbientColor +
// Diffuse : "color" of the object
MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;
// Specular : reflective highlight, like a mirror
MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);</pre>
R是光线反射的角度,E是视线的反方向(就像之前对“l”的假设);如果这两个角夹角很小,意味着视线与反射光线重合。R is the direction in which the light reflects. E is the inverse direction of the eye (just like we did for "l"); If the angle between these two is little, it means we are looking straight into the reflection.
pow(cosAlpha,5)是用来控制镜面反射的波瓣。可以增大5来获得更大的波瓣。pow(cosAlpha,5) is used to control the width of the specular lobe. Increase 5 to get a thinner lobe.
<h2>最终结果Final result</h2>
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_ambiant_specular.png"><img class="alignnone size-large wp-image-233" title="diffuse_ambiant_specular" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/diffuse_ambiant_specular-1024x793.png" alt="" width="640" height="495" /></a>
注意到镜面反射使鼻子和眉毛更亮。Notice the specular highlights on the nose and on the eyebrows.
这个光照模型因为简单已经使用了很多年。但它有一些问题,所以它被像microfacet BRDF一类的物理基础模型代替,后面将会讲到。This shading model has been used for years due to its simplicity. It has a number of problems, so it is replaced by physically-based models like the microfacet BRDF, but we will see this later.
在下节课中,我们将学习怎么提高VBO的性能。将是第一节提高课!In the next tutorial, we'll learn how to improve the performance of your VBO. This will be the first Intermediate tutorial !