提要

3D游戏中最基本的一个功能就是3D漫游了,玩家可以通过键盘或者鼠标控制自己的视角。

之前我们也学习过一个相关的函数,glLookAt,用来制定摄像机的位置,摄像机观察目标位置,还有摄像机的放置方式,我们可以通过不断地调用这个函数来实现3D漫游,但更方便的是抽象出一个摄像机类,实现一些摄像机的方法。

UVN相机

UVN使用三个相互垂直的向量来表示相机的位置与朝向:

1) 相机注视的向量N
2) 相机的上方向向量V
3) 相机的右方向向量U

如下图,是在世界坐标系下的UVN相机的向量表示:

绿色轴为N,蓝色轴为V,红色轴为U。

当要改变相机位置和朝向的时候,只需要将uvn矩阵和相应的变换矩阵相乘即可。

代码实现

这里借助了一个第三方矩阵向量库 - eigen。Ubuntu下的安装的过程非常简单,下载源码之后,解压,cd进目录:

mkdir build
cd build 
cmake ..
sudo make install

写一个头文件:

eigen.h

#ifndef EIGEN_H
#define EIGEN_H #include "eigen3/Eigen/Dense"
#include "eigen3/Eigen/LU"
#include "eigen3/Eigen/Core" #endif // EIGEN_H

放在工程目录下面,使用的时候包含进来就可以了。

看类声明:glcamera.h

#ifndef GLCAMERA_H
#define GLCAMERA_H
#include "eigen.h"
#include <GL/glu.h>
#include <iostream> using namespace Eigen;
class GLCamera
{
public:
GLCamera();
GLCamera(const Vector3d& pos, const Vector3d& target, const Vector3d& up);
void setModelViewMatrix();
void setShape(float viewAngle,float aspect,float Near,float Far);
void slide(float du, float dv, float dn);
void roll(float angle);
void yaw(float angle);
void pitch(float angle);
float getDist(); private:
Vector3d m_pos;
Vector3d m_target;
Vector3d m_up;
Vector3d u,v,n; }; #endif // GLCAMERA_H

setModelViewMatrix:加载将当前MV矩阵。

setShape:设置摄像机的视角。

roll,yaw,pitch相当于绕N,V,U轴的旋转,如下图:

下面是相机的实现:

#include "glcamera.h"

GLCamera::GLCamera()
{ } GLCamera::GLCamera(const Vector3d &pos, const Vector3d &target, const Vector3d &up)
{
m_pos = pos;
m_target = target;
m_up = up;
n = Vector3d( pos.x()-target.x(), pos.y()-target.y(), pos.z()-target.z());
u = Vector3d(up.cross(n).x(), up.cross(n).y(), up.cross(n).z());
v = Vector3d(n.cross(u).x(),n.cross(u).y(),n.cross(u).z()); n.normalize();
u.normalize();
v.normalize(); setModelViewMatrix();
} void GLCamera::setModelViewMatrix()
{
double m[16];
m[0]=u.x(); m[4]=u.y(); m[8]=u.z(); m[12]=-m_pos.dot(u);
m[1]=v.x(); m[5]=v.y(); m[9]=v.z(); m[13]=-m_pos.dot(v);
m[2]=n.x(); m[6]=n.y(); m[10]=n.z(); m[14]=-m_pos.dot(n);
m[3]=0; m[7]=0; m[11]=0; m[15]=1.0;
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(m); //用M矩阵替换原视点矩阵
} void GLCamera::setShape(float viewAngle, float aspect, float Near, float Far)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); //设置当前矩阵模式为投影矩阵并归一化
gluPerspective(viewAngle,aspect, Near, Far); //对投影矩阵进行透视变换
} void GLCamera::slide(float du, float dv, float dn)
{
//std::cout<<"u.x:"<<u.x()<<std::endl;
m_pos(0) = m_pos(0) + du*u.x()+dv*v.x()+dn*n.x();
m_pos(1) = m_pos(1) + du*u.y() +dv*v.y()+dn*n.y();
m_pos(2) = m_pos(2) + du*u.z()+dv*v.z()+dn*n.z();
m_target(0) = m_target(0)+du*u.x()+dv*v.x()+dn*n.x();
m_target(1) = m_target(0)+du*u.y()+dv*v.y()+dn*n.y();
m_target(2) = m_target(0)+du*u.z()+dv*v.z()+dn*n.z();
setModelViewMatrix();
} void GLCamera::roll(float angle)
{
float cs=cos(angle*3.14159265/180);
float sn=sin(angle*3.14159265/180);
Vector3d t(u);
Vector3d s(v);
u.x() = cs*t.x()-sn*s.x();
u.y() = cs*t.y()-sn*s.y();
u.z() = cs*t.z()-sn*s.z(); v.x() = sn*t.x()+cs*s.x();
v.y() = sn*t.y()+cs*s.y();
v.z() = sn*t.z()+cs*s.z(); setModelViewMatrix(); //每次计算完坐标轴变化后调用此函数更新视点矩阵
} void GLCamera::pitch(float angle)
{
float cs=cos(angle*3.14159265/180);
float sn=sin(angle*3.14159265/180);
Vector3d t(v);
Vector3d s(n); v.x() = cs*t.x()-sn*s.x();
v.y() = cs*t.y()-sn*s.y();
v.z() = cs*t.z()-sn*s.z(); n.x() = sn*t.x()+cs*s.x();
n.y() = sn*t.y()+cs*s.y();
n.z() = sn*t.z()+cs*s.z(); setModelViewMatrix();
} void GLCamera::yaw(float angle)
{
float cs=cos(angle*3.14159265/180);
float sn=sin(angle*3.14159265/180);
Vector3d t(n);
Vector3d s(u); n.x() = cs*t.x()-sn*s.x();
n.y() = cs*t.y()-sn*s.y();
n.z() = cs*t.z()-sn*s.z(); u.x() = sn*t.x()+cs*s.x();
u.y() = sn*t.y()+cs*s.y();
u.z() = sn*t.z()+cs*s.z(); setModelViewMatrix();
} float GLCamera::getDist()
{
float dist = pow(m_pos.x(),2)+pow(m_pos.y(),2)+pow(m_pos.z(),2);
return pow(dist,0.5);
}

没什么好说的,都是矩阵的一些计算。

这样就可以将你的摄像机融入到OpenGL工程中了,比如说放进一个Qt的工程,用一个GLWifget类来显示OpenGL。

在initializeGL() 中,初始化camera

  Vector3d pos(0.0, 0.0, 12.0);
Vector3d target(0.0, 0.0, 0.0);
Vector3d up(0.0, 1.0, 0.0);
camera = new GLCamera(pos, target, up);

在paintGL的时候,设置当前矩阵:

 glLoadIdentity();
camera->setModelViewMatrix();

在resizeGL中调整视角:

camera->setShape(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);

添加相应的鼠标事件:

void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
} void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
int dx = event->x() - lastPos.x();
int dy = event->y() - lastPos.y();
if (event->buttons() & Qt::LeftButton)
{
RotateX(dx);
RotateY(dy);
}
else if(event->buttons() & Qt::RightButton)
{
camera->roll(dx);
//camera->pitch(dy);
//camera->slide(0,0,-dy);
}
else if(event->buttons() & Qt::MiddleButton)
{
camera->slide(-dx,dy,0);
}
lastPos = event->pos();
updateGL();
} void GLWidget::RotateX(float angle)
{
float d=camera->getDist();
int cnt=100;
float theta=angle/cnt;
float slide_d=-2*d*sin(theta*3.14159265/360);
camera->yaw(theta/2);
for(;cnt!=0;--cnt)
{
camera->slide(slide_d,0,0);
camera->yaw(theta);
}
camera->yaw(-theta/2);
} void GLWidget::RotateY(float angle)
{
float d = camera->getDist();
int cnt=100;
float theta=angle/cnt;
float slide_d=2*d*sin(theta*3.14159265/360);
camera->pitch(theta/2);
for(;cnt!=0;--cnt)
{
camera->slide(0,slide_d,0);
camera->pitch(theta);
}
camera->pitch(-theta/2);
}

效果就像这样(gif 有点大,耐心等待):

参考

openGL中camera类的设计以及使用 - http://blog.csdn.net/hobbit1988/article/details/7956838

OpenGL进阶(十四) - UVN Camera实现的更多相关文章

  1. J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP

    J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP 前言   搜狐畅游笔试题中有一道问答题涉及到回答谈谈对Spring IOC与AOP的理解.特将相关内容进行整理.    ...

  2. Python进阶(十四)----空间角度研究类,类与类之间的关系

    Python进阶(十四)----空间角度研究类,类与类之间的关系 一丶从空间角度研究类 对象操作对象属性 class A(): address = '沙河' def __init__(self, na ...

  3. Android Multimedia框架总结(十四)Camera框架初识及自定义相机案例

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52738492 前言:国庆节告一段 ...

  4. OpenGL(十四) 模板测试

    启用模板测试时,OpenGL会在内存中开辟一块空间作为模板缓冲区,里边保存了每个像素的"模板值",模板测试的过程就是把每一个像素的模板值与一个设定的模板参考值进行比较,符合设定条件 ...

  5. Android进阶(十四)Android Adapter详解

    Android Adapter详解 Android是完全遵循MVC模式设计的框架,Activity是Controller,layout是View.因为layout五花八门,很多数据都不能直接绑定上去, ...

  6. AngularJS进阶(十四)AngularJS灵异代码事件

    AngularJS灵异代码事件 注:请点击此处进行充电! 事情原委 router_sys.js源代码如下: 自己在html路由跳转的代码如下: 但是在实际路由过程中,却路由到了下面的状态,相应的页面中 ...

  7. mysql进阶(十四) 批量更新与批量更新多条记录的不同值实现方法

    mysql 批量更新与批量更新多条记录的不同值实现方法 在mysql中批量更新我们可能使用update,replace into来操作,下面详细介绍mysql批量更新与性能. 批量更新 mysql更新 ...

  8. Java进阶(十四)实现每天定时对数据库的操作

    Java实现每天定时对数据库操作 现在有一个很棘手的问题:客户要求实现一个功能,就是每日凌晨自动计算慢性病订单是否有需要在今日提醒的,如果有则生成一条提醒记录到lm_notice之中. 如何在Web工 ...

  9. 网站开发进阶(十四)JS实现二维码生成

    JS实现二维码生成 绪 项目开发原语:已然花费半天的时间,仍旧未能将二维码显示在订单中.但是可以在单个页面中显示二维码,结合到angularjs的控制器中就失效了,自己是真的找不到其中的原因了.费解! ...

随机推荐

  1. pycharm的安装教程及大坑

    在根据网上的教程创建新工程后,发现不能调用第三方库,网上大多给的是print('hello world'),己适python解释器用的pycharm默认的也不能发现错误.后来浏览了一篇文章才恍然大悟, ...

  2. java 读入文件 BufferedReader

    package com.mkyong; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOExcep ...

  3. Eigen学习笔记1:在VS2015下Eigen(矩阵变换)的配置

    一.Eigen简介 Eigen是一个高层次的C ++库,有效支持线性代数,矩阵和矢量运算,数值分析及其相关的算法. Eigen适用范围广,支持包括固定大小.任意大小的所有矩阵操作,甚至是稀疏矩阵:支持 ...

  4. luogu 11月月赛 斐波那契数列

    本来想作为水题刷,很快就想出了做法,结果细节实现太差改了好久... 根据题意你会发现其实就是求方程 ax+by=k解的个数. 此时 a=f[i],b=f[i+1],而(x,y)就是你要求的数对. 于是 ...

  5. 「UOJ218」火车管理

    「UOJ218」火车管理 解题思路:观察发现,在弹出 \(x\) 之前,它前面这个元素都是保持不变的,所以可以用一棵可持久化线段树维护每一个栈顶元素的插入时间,每次找到当前时间\(-1\) 的版本就可 ...

  6. AHOI2018训练日程(3.10~4.12)

    (总计:共90题) 3.10~3.16:17题 3.17~3.23:6题 3.24~3.30:17题 3.31~4.6:21题 4.7~4.12:29题 ZJOI&&FJOI(6题) ...

  7. BZOJ 4566 JZYZOJ 1547 [haoi2016T5]找相同子串 后缀数组 并查集

    http://172.20.6.3/Problem_Show.asp?id=1547 http://www.lydsy.com/JudgeOnline/problem.php?id=4566 单纯后缀 ...

  8. 【推导】zoj3981 Balloon Robot

    题意:一个桌子有m个位置(首尾相接),有n支队伍坐在其中的n个位置上.有个机器人会从某个起始位置出发,每个时刻会依次发生以下三个事件: 机器人顺时针转一个单位: 某些队伍通过了题目(如果存在): 如果 ...

  9. js 运算符 || && 妙用

    首先出个题:如图: 假设对成长速度显示规定如下:  成长速度为5显示1个箭头:  成长速度为10显示2个箭头:  成长速度为12显示3个箭头:  成长速度为15显示4个箭头:  其他都显示都显示0个箭 ...

  10. MySQL遇到的一个卡库问题及对update的学习

    近日遇到个卡库的问题,环境是MySQL5.5.12,报错信息如下 ) and was aborted. There is a chan ce that your master is inconsist ...