上篇 坐标系统 讲述了三大矩阵:
- model矩阵:主要指被观察物体要做的变作,变幻
- view矩阵:观察矩阵,其实就本文要说的摄像机矩阵
- projection矩阵:透视矩阵,这个矩阵主要作用是设视图远近,视图大小,可以放大缩小
本章主要讲view矩阵,即摄像机矩阵。
1、摄像机空间
要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量。
-
position:即摄像机的位置,如上图所示,摄像机位置为 (0,0,2),观察点在原点
-
direction:摄像机方向,摄像机位置是在z轴正轴,观察点是原点,按理说摄像机方向应该是原点向量减去摄像机位置向量(回忆下两个向量的差的知识,向量差可以得到向量终点之间的方向向量),但图2上方向依然是指向z轴正方向,因为方向向量其实是相反的,即真实方向向量是摄像机位置向量减去观察点方向向量
-
right:代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));
- up:已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:
glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
2、Look At
上面的概念有点多,幸运的是,接口比较简单:
glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f)
);
lookAt函数一共有三个参数:
- 摄像机位置
- 观察点位置
- 上向量(我们计算右向量使用的那个上向量)
另外,需要注意的是,lookAt产生的矩阵是view矩阵
3、旋转效果
如果我们要做一个这样的效果,该怎么做呢?
结合上面的知识,可以让摄像机位置转圈,然后观察点位置不变即可,所以可以这样地调整view矩阵:
degree = degree + 1.0f;
float radius = 8.0f;
float temp = degree * 0.01;
float camX = sin(temp) * radius;
float camZ = cos(temp) * radius;
cameraPos = glm::vec3(camX, 0.0, camZ);
view = glm::lookAt(cameraPos,
cameraPos + cameraFront,
cameraUp);
4、自由移动
自由移动的效果如下:
自由移动就是平面移动,它相当于一个人平视前方,然后这个人可以上下左右移动,上下移动就相当于缩放,左右移动就相当于人平视前方,因此被观察物体可能会被看不到。所以它和旋转最大的不同即是,观察点位置是变化的,摄像机位置也是变化的。为了实现这种效果,因此定义了如下矩阵:
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
//lookAt函数现在是
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
所以,当我们平移时,变化cameraPos向量,那观察点位置也会发生变化,就能实现平移
float cameraSpeed = 0.5f;
switch (direction) {
case Direction::Up:
cameraPos += cameraSpeed * cameraFront;
break;
case Direction::Down:
cameraPos -= cameraSpeed * cameraFront;
break;
case Direction::Left:
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp) * cameraSpeed);
break;
case Direction::Right:
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp) * cameraSpeed);
break;
}
5、视角移动
视角移动的效果大致如上,能跟随手移动实现3D旋转,其实现有两种方式:
- 欧拉角原理,通过改变view矩阵实现,相当于移动摄像机
- 修改model矩阵,让物体自己旋转
目前欧拉角这套方式,我还未在机器上完美复现,暂时使用第2种方式实现,等后续实现了我再补上这段内容
首先当然是需要处理touch事件:
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.actionMasked) {
MotionEvent.ACTION_DOWN ->{
lastX = event.x
lastY = event.y
canRotate = true
canScale = false
}
MotionEvent.ACTION_POINTER_DOWN ->{
canRotate = false
canScale = true
if (event.pointerCount > 2) {
canScale = false
return false
}
var index0 = event.getPointerId(0)
var index1 = event.getPointerId(1)
lastX = event.getX(index0)
lastY = event.getY(index0)
last2x = event.getX(index1)
last2y = event.getY(index1)
glNativeRender?.rorate(0f, 0f, 0f)
}
MotionEvent.ACTION_POINTER_UP ->{
canRotate = false
canScale = false
glNativeRender?.rorate(0f, 0f, 0f)
}
MotionEvent.ACTION_MOVE ->{
if (canRotate) {
if (event.pointerCount > 2) {
glNativeRender?.rorate(0f, 0f, 0f)
return true
}
var curX = event.x
var curY = event.y
var xoffset = curX - lastX
var yoffset = curY - lastY
lastX = curX
lastY = curY
var distance = Math.sqrt((xoffset * xoffset).toDouble() + (yoffset * yoffset).toDouble())
glNativeRender?.rorate(xoffset, yoffset, distance.toFloat())
} else if (canScale) {
val index0 = event.getPointerId(0)
val index1 = event.getPointerId(1)
val curX = event.getX(index0)
val curY = event.getY(index0)
val cur1X = event.getX(index1)
val cur1Y = event.getY(index1)
val lastDis=Math.sqrt(Math.pow((lastX-last2x).toDouble(), 2.0)+Math.pow((lastY-last2y).toDouble(), 2.0))
val curDis=Math.sqrt(Math.pow((curX-cur1X).toDouble(), 2.0)+Math.pow((curY-cur1Y).toDouble(), 2.0))
glNativeRender?.scale((curDis/lastDis).toFloat())
}
}
}
return true
}
c++中的旋转处理:
void CubeSample::rorate(float x, float y, float distance) {
distance = distance/80.0f;
if (x > 0 && y > 0) {
y = -y;
} else if (x <= 0 && y > 0) {
x = -x;
distance = -distance;
} else if (x > 0 && y < 0) {
y = -y;
} else if (x <= 0 && y < 0) {
y = -y;
}
this->xoffset = x;
this->yoffset = y;
this->distance = distance;
}
注意model矩阵的处理,model矩阵必须要乘以之前的model矩阵,否则会有顿挫,因为每个动作都是叠加之前的处理,所以model矩阵要定义成成员变量:
if (distance != 0) {
glm::vec3 cross = glm::cross(glm::vec3(0.0f, 0.0f, 1.0), glm::vec3(xoffset, yoffset, 0.0f));
glm::mat4 tmpMat = glm::mat4(1.0f);
model = glm::rotate(model, distance, cross);
tmpMat *= model;
model = tmpMat;
}
6、缩放
缩放其实就是在处理投影矩阵:
projection = glm::perspective(glm::radians(fov), rat, 0.1f, 100.0f);
一般投影矩阵的第一个参数是45f,这里我们使用fov值,它可以根据缩放距离变化
c++中对绽放的处理:
void CubeSample::scale(float scale) {
if (scale > 1) {
fov -= scale;
} else if(scale<1) {
fov += (1.0f/scale);
}
if (fov <= 20.0) {
fov = 20.0;
} else if (fov > 140.0) {
fov = 140.0;
}
}
在这里计算fov值,最后在透视矩阵中改变fov值,实现缩放,最后的效果为: