forked from cybercser/OpenGL_3_3_Tutorial_Translation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTutorial 6 Keyboard and Mouse opengl-tutorial.org.txt
201 lines (184 loc) · 24.9 KB
/
Tutorial 6 Keyboard and Mouse 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
欢迎来到第六课!
Welcome for our 6th tutorial !
我们将学习如何通过鼠标和键盘来移动相机,就像在第一人称射击游戏中一样。
We will now learn how to use the mouse and the keyboard to move the camera just like in a FPS.
<h1>接口The interface</h1>
这段代码在整个课程中多次被使用,因此把它单独放在一个文件中:common/controls.cpp,然后在common/controls.hpp中声明函数接口,这样tutorial06.cpp就能使用它们了。
Since this code will be re-used throughout the tutorials, we will put the code in a separate file : common/controls.cpp, and declare the functions in common/controls.hpp so that tutorial06.cpp knows about them.
和前节课比,tutorial06.cpp里的代码变动很小。主要的变化是:每一帧都计算MVP(投影视图矩阵)矩阵,而不像之前那样只算一次。现在把这段代码加到主循环中:
The code of tutorial06.cpp doesn't change much from the previous tutorial. The major modification is that instead of computing the MVP matrix once, we now have to do it every frame. So let's move this code inside the main loop :
<pre class="brush: cpp">do{
// ...
// Compute the MVP matrix from keyboard and mouse input
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
// ...
}</pre>
这段代码需要3个新函数:
This code needs 3 new functions :
<ul>
<li>computeMatricesFromInputs()读键盘和鼠标操作,然后计算投影视图矩阵。这就是奇妙所在。
computeMatricesFromInputs() reads the keyboard and mouse and computes the Projection and View matrices. This is where all the magic happens.</li>
<li>getProjectionMatrix()返回算好的投影矩阵。
getProjectionMatrix() just returns the computed Projection matrix.</li>
<li>getViewMatrix()返回算好的视图矩阵。
getViewMatrix() just returns the computed View matrix.</li>
</ul>
这只是一种实现方式,当然,如果你不喜欢这些函数,果断地去改写它们。
This is just one way to do it, of course. If you don't like these functions, go ahead and change them.
来看看controls.cpp在做什么。
Let's see what's inside controls.cpp.
<h1>实际代码The actual code</h1>
我们需要几个变量。
We'll need a few variables.
<pre class="brush: cpp">// position
glm::vec3 position = glm::vec3( 0, 0, 5 );
// horizontal angle : toward -Z
float horizontalAngle = 3.14f;
// vertical angle : 0, look at the horizon
float verticalAngle = 0.0f;
// Initial Field of View
float initialFoV = 45.0f;
float speed = 3.0f; // 3 units / second
float mouseSpeed = 0.005f;</pre>
FoV is the level of zoom. 80° = very wide angle, huge deformations. 60° - 45° : standard. 20° : big zoom.
首先根据输入,重新计算位置,水平角,竖直角和视场角(FoV);再由它们算出视图和投影矩阵。
We will first recompute position, horizontalAngle, verticalAngle and FoV according to the inputs, and then compute the View and Projection matrices from position, horizontalAngle, verticalAngle and FoV.
<h2>方向Orientation</h2>
读取鼠标位置是容易的:
Reading the mouse position is easy :
<pre class="brush: cpp">// Get mouse position
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);</pre>
我们需要把光标放到屏幕中心,否则它将很快移到屏幕外,导致无法响应。
but we have to take care to put the cursor back to the center of the screen, or it will soon go outside the window and you won't be able to move anymore.
<pre class="brush: cpp">// Reset mouse position for next frame
glfwSetMousePos(1024/2, 768/2);</pre>
注意:这段代码假设窗口大小是1024*768,这不是必须的。你可以用glfwGetWindowSize来设定窗口大小。
Notice that this code assumes that the window is 1024*768, which of course is not necessarily the case. You can use glfwGetWindowSize if you want, too.
计算观察角度:
We can now compute our viewing angles :
<pre class="brush: cpp">// Compute new orientation
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle += mouseSpeed * deltaTime * float( 768/2 - ypos );</pre>
从右往左阅读这几行代码:
Let's read this from right to left :
<ul>
<li>1024/2 - xpos表示鼠标离窗口中心点的距离。这个值越大,转动角越大。
1024/2 - xpos means : how far is the mouse from the center of the window ? The bigger this value, the more we want to turn.</li>
<li>float(...)是浮点数转换,使乘法顺利进行
float(...) converts it to a floating-point number so that the multiplication goes well.</li>
<li>mouseSpeed用来加速或减慢旋转,可以随你调整或让用户选择。
mouseSpeed is just there to speed up or slow down the rotations. Finetune this at will, or let the user choose it.</li>
<li>+= : 如果你没移动鼠标,1024/2-xpos的值为零,horizontalAngle+=0不改变horizontalAngle的值。如果你用的是"=",每帧视角都被强制转回到原始方向,这就不好了。
+= : If you didn't move the mouse, 1024/2-xpos will be 0, and horizontalAngle+=0 doesn't change horizontalAngle. If you had a "=" instead, you would be forced back to your original orientation each frame, which isn't good.</li>
</ul>
现在,在世界坐标系下计算一个向量,代表视线方向。
We can now compute a vector that represents, in World Space, the direction in which we're looking
<pre class="brush: cpp">// Direction : Spherical coordinates to Cartesian coordinates conversion
glm::vec3 direction(
cos(verticalAngle) * sin(horizontalAngle),
sin(verticalAngle),
cos(verticalAngle) * cos(horizontalAngle)
);</pre>
这是一种标准计算,如果你不了解余弦和正弦,下面有一个简短的解释:
This is a standard computation, but if you don't know about cosine and sinus, here's a short explanation :
<img class="alignnone whiteborder" title="Trigonometric circle" src="http://www.numericana.com/answer/trig.gif" alt="" width="150" height="150" />
上面的公式,只是上图在三维空间下的推广。
The formula above is just the generalisation to 3D.
我们想算出相机的『上方向』。『上方向』不一定是Y轴正方向:你俯视时,『上方向』实际上是水平的。这里有一个例子,位置相同,视点相同的相机,却有不同的『上方向』。
Now we want to compute the "up" vector reliably. Notice that "up" isn't always towards +Y : if you look down, for instance, the "up" vector will be in fact horizontal. Here is an example of to cameras with the same position, the same target, but a different up.
本例中,唯一不变的是,『相机的右边』这个方向始终取水平方向。你可以试试:保持手臂水平伸直,向正上方看、向下看;向这之间的任何方向看(译注:『看』立刻产生视线方向)。现在定义『右方向』向量:因为是水平的,故Y坐标为零,X和Z值就像上图中的一样,只是角度旋转了90度,或Pi/2弧度。
In our case, the only constant is that the vector goes to the right of the camera is always horizontal. You can check this by putting your arm horizontal, and looking up, down, in any direction. So let's define the "right" vector : its Y coordinate is 0 since it's horizontal, and its X and Z coordinates are just like in the figure above, but with the angles rotated by 90°, or Pi/2 radians.
<pre class="brush: cpp">// Right vector
glm::vec3 right = glm::vec3(
sin(horizontalAngle - 3.14f/2.0f),
0,
cos(horizontalAngle - 3.14f/2.0f)
);</pre>
我们有一个『右方向』和一个视线方向,或者说是『前方向』。『上方向』垂直于这两者。一个很有用的数学工具可以让三者的联系变得简单:叉乘。
we have a "right" vector and a "direction", or "front" vector. The "up" vector is a vector that is perpendicular to these two. A useful mathematical tool makes this very easy : the cross product.
<pre class="brush: cpp">// Up vector : perpendicular to both direction and right
glm::vec3 up = glm::cross( right, direction );</pre>
叉乘是在做什么呢?很简单,回忆第三课讲到的右手定则。第一个向量是大拇指;第二个是食指;叉乘的结果就是中指。十分方便。
To remember what the cross product does, it's very simple. Just recall the Right Hand Rule from Tutorial 3. The first vector is the thumb; the second is the index; and the result is the middle finger. It's very handy.
<h2>位置Position</h2>
代码十分直观。顺便说下,我用上/下/右/左键而不用wsad;是因为我的azerty键盘中,美式键盘的awsd键位处实际上是zqsd。qwerZ键盘其实又不一样了,更别提韩国键盘了。我甚至不知道韩国人民用的键盘是什么布局,但我猜想肯定很不一样。
The code is pretty straightforward. By the way, I used the up/down/right/left keys instead of the awsd because on my azerty keyboard, awsd is actually zqsd. And it's also different with qwerZ keyboards, let alone korean keyboards. I don't even know what layout korean people have, but I guess it's also different.
<pre class="brush: cpp">// Move forward
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
position += direction * deltaTime * speed;
}
// Move backward
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
position -= direction * deltaTime * speed;
}
// Strafe right
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
position += right * deltaTime * speed;
}
// Strafe left
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
position -= right * deltaTime * speed;
}</pre>
这里唯一特别的是deltaTime。你不会希望每帧移动1单元的,原因很简单:
The only special thing here is the deltaTime. You don't want to move from 1 unit each frame for a simple reason :
<ul>
<li>如果你有一台快电脑,每秒能跑60帧,你每秒移动60*speed个单位。
If you have a fast computer, and you run at 60 fps, you'd move of 60*speed units in 1 second</li>
<li>如果你有一台慢电脑,每秒能跑20帧,你每秒移动20*speed个单位。
If you have a slow computer, and you run at 20 fps, you'd move of 20*speed units in 1 second</li>
</ul>
电脑性能不能成为速度不稳的借口;你需要通过“前一帧到现在的时间”或“时间间隔(deltaTime)”来控制移动步长。
Since having a better computer is not an excuse for going faster, you have to scale the distance by the "time since the last frame", or "deltaTime".
<ul>
<li>如果你有一台快电脑,每秒能跑60帧,你每帧移动1/60*speed个单位,每秒移动1*speed个单位。
If you have a fast computer, and you run at 60 fps, you'd move of 1/60 * speed units in 1 frame, so 1*speed in 1 second.</li>
<li>如果你有一台慢电脑,每秒能跑20帧,你每帧移动1/20*speed个单位,每秒移动1*speed个单位。
If you have a slow computer, and you run at 20 fps, you'd move of 1/20 * speed units in 1 second, so 1*speed in 1 second.</li>
</ul>
这就好多了。deltaTime很容易算:
which is much better. deltaTime is very simple to compute :
<pre class="brush: cpp">double currentTime = glfwGetTime();
float deltaTime = float(currentTime - lastTime);</pre>
<h2>视场角Field Of View</h2>
为了好玩,我们可以把视场角绑定到鼠标滚轮,作为简陋的缩放功能:
For fun, we can also bind the wheel of the mouse to the Field Of View, so that we can have a cheap zoom :
<pre class="brush: cpp">float FoV = initialFoV - 5 * glfwGetMouseWheel();</pre>
<h2>计算矩阵Computing the matrices</h2>
计算矩阵已经很直观了。使用和前面几乎一样的函数,仅参数不同。
Computing the matrices is now straightforward. We use the exact same functions than before, but with our new parameters.
<pre class="brush: cpp">// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
ProjectionMatrix = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 100.0f);
// Camera matrix
ViewMatrix = glm::lookAt(
position, // Camera is here
position+direction, // and looks here : at the same position, plus "direction"
up // Head is up (set to 0,-1,0 to look upside-down)
);</pre>
<h1>结果Results</h1>
<a href="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/moveanim.gif"><img class="alignnone size-full wp-image-372" title="moveanim" src="http://www.opengl-tutorial.org/wp-content/uploads/2011/05/moveanim.gif" alt="" width="206" height="159" /></a>
<h2>隐藏面消除Backface Culling</h2>
现在可以自由移动鼠标,你会注意到:如果鼠标移动到立方体里面,多边形仍然会被显示。这看起来理所当然,实则可以优化。事实上,在常见应用中,你从来不会处于立方体内。
Now that you can freely move around, you'll notice that if you go inside the cube, polygons are still displayed. This can seem obvious, but this remark actually opens an opportunity for optimisation. As a matter of fact, in a usual application, you are never _inside_ a cube.
有一个思路是让GPU检查相机在三角形的后面还是前面。如果在前面,显示该三角形;如果相机在三角形后面,且不在网格(网格必须是封闭的)内部,那么必有其他三角形在相机前面,故不显示该三角形。没有人会注意到什么,除了一切都会变快:三角形平均少了两倍!
The idea is to let the GPU check if the camera is behind, or in front of, the triangle. If it's in front, display the triangle; if it's behind, *and* the mesh is closed, *and* we're not inside the mesh, *then* there will be another triangle in front of it, and nobody will notice anything, except that everything will be faster : 2 times less triangles on average !
更妙的是,检查起来还很简单:GPU计算三角形的法向(用叉乘,记得吧?),然后检查这个法向是否朝向相机。
The best thing is that it's very easy to check this. The GPU computes the normal of the triangle (using the cross product, remember ?) and checks whether this normal is oriented towards the camera or not.
不幸的是这样做有代价:三角形的方向是隐式的。这意味着如果你在缓冲区中交换两个顶点,可能会产生洞。但一般来说,它值得做一点额外工作。一般你只要在三维建模软件中点击“反转法向”(实际是交换两个顶点,从而反转法向),一切就正常了。
This comes at a cost, unfortunately : the orientation of the triangle is implicit. This means that is you invert two vertices in your buffer, you'll probably end up with a hole. But it's generally worth the little additional work. Often, you just have to click "invert normals" in your 3D modeler (which will, in fact, invert vertices, and thus normals) and everything is just fine.
开启隐藏面消除是很轻松的:
Enabling backface culling is a breeze :
<pre class="brush: cpp">// Cull triangles which normal is not towards the camera
glEnable(GL_CULL_FACE);</pre>
<h1>练习Exercices</h1>
<ul>
<li>限制verticalAngle,使之不能颠倒方向
Restrict verticalAngle so that you can't go upside-down</li>
<li>创建一个相机,使它绕着物体旋转 ( position = ObjectCenter + ( radius * cos(time), height, radius * sin(time) ) );然后将半径/高度/时间的变化绑定到键盘/鼠标上,诸如此类。
Create a camera that rotates around the object ( position = ObjectCenter + ( radius * cos(time), height, radius * sin(time) ) ); bind the radius/height/time to the keyboard/mouse, or whatever</li>
<li>玩得开心!Have fun !</li>
</ul>