前言
在上一篇博客中我们绘制了三维的立方体并且利用模型变换矩阵对立方体进行变换。在今天我们将会进行更加灵活的变换:用鼠标键盘来操控模型进行旋转缩放平移,此外,我们将通过阅读简单的OFF格式模型来获取更加生动的几何形状。
⚠
该部分的绘制代码基于上一篇博客:OpenGL学习(三)三维绘制与模型变换矩阵
博客内容因为篇幅关系,不会完整的列出所有的代码 完整代码会放在文章末尾
freeglut所提供的交互
freeglut,你坏事干净 ,提供了一套基础的交互函数,我们可以通过翻阅它的手册:http://freeglut.sourceforge.net/docs/api.php 来快速查阅。
与此同时,我在互联网上搜索到了两位大佬的博客,记载非常详细:
感谢两位大佬 Orz 。。。因为上面的三个链接对 freeglut 的 API 有了详细的描述,所以我就不介绍了(逃
准备工作
在开始之前,我们将 init 函数中,我们创建的着色器程序对象 program 设为全局变量,因为我们要在 display 函数中,实时地传输我们的模型变换矩阵。
此外,我们设置几个全局变量,表示模型变换矩阵的缩放,旋转,平移。最后是我们的窗口尺寸,因为我们需要利用鼠标在窗口的位置来进行操控。下面是设置的全局变量:
GLuint program; // 着色器程序对象
glm::vec3 scaleControl(1,1,1); // 缩放控制
glm::vec3 rotateControl(0, 0, 0); // 旋转控制
glm::vec3 translateControl(0, 0, 0); // 平移控制
int windowWidth = 512; // 窗口宽
int windowHeight = 512; // 窗口高
然后我们将 init 函数中,模型变换矩阵的计算代码,挪动到 display 函数中。这意味着我们的模型变换矩阵是实时地计算的:
display函数开头......
// 构造模型变换矩阵
glm::mat4 unit( // 单位矩阵
glm::vec4(1, 0, 0, 0),
glm::vec4(0, 1, 0, 0),
glm::vec4(0, 0, 1, 0),
glm::vec4(0, 0, 0, 1)
);
glm::mat4 scale = glm::scale(unit, scaleControl); // xyz缩放
glm::mat4 translate = glm::translate(unit, translateControl); // 平移
glm::mat4 rotate = unit; // 旋转
rotate = glm::rotate(rotate, glm::radians(rotateControl.x), glm::vec3(1, 0, 0)); // 绕x轴转
rotate = glm::rotate(rotate, glm::radians(rotateControl.y), glm::vec3(0, 1, 0)); // 绕y轴转
rotate = glm::rotate(rotate, glm::radians(rotateControl.z), glm::vec3(0, 0, 1)); // 绕z轴转
glm::mat4 model = translate * rotate * scale; // 变换级联 -- 生成模型变换矩阵
// 传递uniform变量
GLuint mlocation = glGetUniformLocation(program, "model"); // 名为model的uniform变量的位置索引
glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model)); // 列优先矩阵
绘制......
鼠标交互调整旋转
我们开始编写第一个交互效果:我们希望通过鼠标拖动我们的模型进行旋转变换!注意到 glutMotionFunc
函数为我们提供了良好的平台。
我们通过 glutMotionFunc
函数绑定一个鼠标回调函数 mouse
(函数名称不一定叫 mouse),每当我们按下鼠标左键并且鼠标发生移动的时候,就会调用被我们绑定的 mouse
函数。
原型:
void glutMotionFunc(void (GLUTCALLBACK *func)(int x, int y))
glutMotionFunc
的原型要求我们定义一个函数,具有形参 x 和 y,他们代表当前调用时,鼠标指针在屏幕上的像素坐标。
于是我们可以开始编写鼠标移动回调函数 mouse
:
// 鼠标运动函数
void mouse(int x, int y)
{
// 调整旋转
rotateControl.y = -100 * (x - float(windowWidth) / 2.0) / windowWidth;
rotateControl.x = -100 * (y - float(windowHeight) / 2.0) / windowHeight;
glutPostRedisplay(); // 重绘
}
首先我们根据鼠标位置(x,y)计算出它在屏幕上的百分比位置,范围 [-1, 1] 。然后我们根据鼠标的位置,调整旋转角度。
注意到 glutPostRedisplay
函数,它表示刷新我们的窗口,否则我们虽然绘制了,GPU 也确切的执行了工作,但是窗口不刷新,我们看不到变化。
我们在 main 函数中,调用 glutMotionFunc
以绑定 mouse
:
glutMotionFunc(mouse); // 左键按下并且移动
随后重新加载程序:
好耶
鼠标滚轮控制缩放
和鼠标按下的函数类似,鼠标滚轮也具有回调函数 glutMouseWheelFunc
。
和上面的代码类似,通过 glutMouseWheelFunc
可以指定当鼠标滚轮滚动时要执行的函数。glutMouseWheelFunc
的原型为:
void glutMouseWheelFunc ( void( *callback )( int wheel, int direction, int x, int y ));
要求我们定义一个函数,有四个输入,其中 wheel 是滚轮 id,鼠标的话默认 0 。唔。。。direction 为鼠标滚轮滚动方向,向前为 1 向后为 -1 。此外,x 和 y 则是触发函数时,鼠标的位置。至此,我们能够轻易的写出鼠标滚轮缩放的函数:
// 鼠标滚轮函数
void mouseWheel(int wheel, int direction, int x, int y)
{
scaleControl += 1 * direction * 0.1;
glutPostRedisplay(); // 重绘
}
别忘了 glutPostRedisplay
重新绘制!然后我们在 main 函数中调用 glutMouseWheelFunc
以绑定我们的滚轮函数:
glutMouseWheelFunc(mouseWheel); // 滚轮缩放
重新加载程序:
好耶
键盘控制平移
在开始之前,我们将会引入一些现代的键盘解决方案,我们设置全局变量,记录每一个按键的状态:
// 键盘状态数组 keyboardState[x]==true 表示按下x键
bool keyboardState[1024];
有了键盘状态表,我们只需要干两件事:
- 键盘按下某个键的时候,将 keyboardState[x] 设为 true
- 键盘松开某个键的时候,将 keyboardState[x] 设为 false
然后我们将通过 w a s d,像传统的游戏一般控制我们方块的平移。我们编写一个函数,专门根据上述四个键位来修改我们的平移控制变量:
// 根据键盘状态判断移动
void move()
{
if (keyboardState['w']) translateControl.y += 0.0005;
if (keyboardState['s']) translateControl.y -= 0.0005;
if (keyboardState['a']) translateControl.x -= 0.0005;
if (keyboardState['d']) translateControl.x += 0.0005;
glutPostRedisplay(); // 重绘
}
我们需要在 display 函数中,计算模型变化矩阵之前,调用一次 move 函数以完成我们的移动。
好了,现在的工作就剩下这两个了
- 键盘按下某个键的时候,将 keyboardState[x] 设为 true
- 键盘松开某个键的时候,将 keyboardState[x] 设为 false
和刚刚的鼠标交互一样,freeglut 的交互都是通过绑定对应的回调函数来完成的。我们通过
glutKeyboardFunc
glutKeyboardUpFunc
来绑定对应的函数,其中 glutKeyboardFunc
是绑定键盘按下时的回调函数,而 glutKeyboardUpFunc
是绑定键盘松开时的回调函数,他们的原型如下:
glutKeyboardFunc(void (GLUTCALLBACK *func)(unsigned char