您的位置:首页 > 其它

谈谈Processing 3D世界 四

2016-08-10 11:05 495 查看
这里回顾前三节知识

今天我们来聊一聊摄影机(camera)以及透视矩阵(perspective)。

摄影机

通过第一节的知识,我们已经知道了如何对模型矩阵进行变换,从而可以使物体可以做各种运动。但更多的时候,我们希望的是自己能够控制视角去观察物体的变化。引入摄影机的概念,便帮助我们解决了这个需求。

摄影机也叫观察空间(View Space)。这个概念不用过多解释,在Processing的3D空间中就相当于我们的眼睛。观察矩阵把所有的世界坐标转换到观察坐标,这些新的坐标是相对摄影机的位置和方向的。

定义一个摄影机,我们需要摄影机在世界空间中的:

位置
观察方向
一个指向它的右侧的向量(右轴)
一个指向它上方的向量(上轴)



step 1 摄像机位置 

真实世界的摄影机可以前后左右上下满世界乱跑。摄像机位置简单来说就是世界空间中代表摄像机位置的向量。Processing中摄影机的默认位置为屏幕中央并向我们(屏幕外)退后少许,这样才能拍到屏幕中的网格对象嘛。(多边形组成的模型对象称为网格,有单个网格组成的模型,也有几个网格合并组成的模型)
我们来看看定义:

cx =width / 2.0;
cy =height / 2.0;
// PI*30.0 / 180.0是度转弧的一种写法,就是tan(30°)
cz =(height / 2.0) / tan(PI*30.0 / 180.0);

PVector cameraPos = new PVector(cx, cy, cz);



step 2 摄影机方向

我们的视线需要一个方向,摄影机也需要一个方向。Processing中摄影机的方向指向屏幕中央。

PVector cameraTarget = new PVector(width/2, height/2, 0);

// 附带的,这里我们会得到一个方向向量(这里只需要了解)
PVector cameraDirection = PVector.sub(cameraPos, cameraTarget);
cameraDirection.normalize();


step 3 摄影机上向量

人为定义一个上向量(Up Vector)。Processing默认为:

PVector up  = new PVector(0, 1, 0);

上向量是用来干吗的呢?有了上向量,我们可以求出摄影机的右轴上轴,从而创建一个LookAt矩阵帮助我们实现摄影机的功能。

这里说说过程,你只需了解就好,因为他们都由Processing帮助我们计算完成。我们把上向量和第二步得到的摄影机方向向量叉乘。两个向量叉乘的结果就是同时垂直于两向量的向量,因此我们会得到指向x轴正方向的那个向量(如果我们交换两个向量的顺序就会得到相反的指向x轴负方向的向量:

PVector cameraRight = up.cross(cameraDirection);
cameraRight.normalize();

实现摄影机

Processing帮我们把这一摊子事情都封装成了一个小小的函数: camera()

我们只需要向camera()函数中填入摄影机的位置,方向及上向量,就可以一切ok。

// 设置摄影机
PVector cameraPos    = new PVector(width/2.0,  height/2.0, (height/2.0) / tan(PI*30.0 / 180.0));
PVector cameraTarget = new PVector(width/2.0, height/2.0, 0.0);
PVector up           = new PVector(0, 1, 0);

camera(cameraPos.x,    cameraPos.y,    cameraPos.z,
cameraTarget.x, cameraTarget.y, cameraTarget.z,
up.x,           up.y,           up.z);


控制摄影机移动

配置好了摄影机,我们当然希望能操作摄影机~。
这十分简单,我们分别控制摄影机的位置cameraPos, 和摄影机的目标cameraTarget即可。

void do_movement() {
float cameraSpeed = 5.0;
// 移动摄影机(同时移动目标)
if (keyPressed) {
// 前后
if (key == 'w') {
cameraPos.z -= cameraSpeed;
cameraTarget.z -= cameraSpeed;
}
if (key == 's') {
cameraPos.z += cameraSpeed;
cameraTarget.z += cameraSpeed;
}
// 左右
if (key == 'a') {
cameraPos.x -= cameraSpeed;
cameraTarget.x -= cameraSpeed;
}
if (key == 'd') {
cameraPos.x += cameraSpeed;
cameraTarget.x += cameraSpeed;
}
}

// 转动摄影机视角(即移动摄影机目标)
// 偏航
cameraTarget.x += mouseX - pmouseX;
// 俯仰
cameraTarget.y += mouseY - pmouseY;
}


注意移动摄影机时,要连带移动摄影机目标,否则我们的摄影机只会盯住目标不放= =!。
很多同学不知道如何让物体自旋。有了摄影机这个观察者视角,你想让物体怎么折腾都可以了。
这里有完整代码(这里先简单的改变摄像机):

int w = 800;
int h = 800;

// 定义顶点
PVector[] ver;
PVector[] face;
PVector[][] uv;
PVector[] cubesPos;
PImage tex;

PVector cameraPos, cameraTarget, up;
PVector camTranslate, camRotate;

void settings() {
//fullScreen();
size(w, h, P3D);
}

void setup() {
// 设置顶点
ver = new PVector[8];
ver[0] = new PVector(-0.5, -0.5,  0.5); // 顶点1
ver[1] = new PVector(-0.5, -0.5, -0.5); // 顶点2
ver[2] = new PVector( 0.5, -0.5, -0.5); // ...
ver[3] = new PVector( 0.5, -0.5,  0.5);

ver[4] = new PVector(-0.5,  0.5,  0.5);
ver[5] = new PVector(-0.5,  0.5, -0.5);
ver[6] = new PVector( 0.5,  0.5, -0.5);
ver[7] = new PVector( 0.5,  0.5,  0.5);

// 设置顶点索引
face = new PVector[12];
// top
face[0]  = new PVector(0, 1, 2);
face[1]  = new PVector(0, 2, 3);
// front
face[2]  = new PVector(0, 3, 7);
face[3]  = new PVector(0, 7, 4);
// back
face[4]  = new PVector(1, 2, 6);
face[5]  = new PVector(1, 6, 5);
// right
face[6]  = new PVector(3, 2, 7);
face[7]  = new PVector(2, 6, 7);
// left
face[8]  = new PVector(0, 4, 5);
face[9]  = new PVector(1, 5, 0);
// bottom
face[10] = new PVector(4, 5, 7);
face[11] = new PVector(5, 6, 7);

// 设置UV
// 目前有一点苦力活需要干,但很快,将会有东西拯救我们。
uv = new PVector[12][3]; // 12个面,每个面3个顶点,为每个顶点描述UV
float max = 2.0;
// top
uv[0][0]  = new PVector(0,   max); // face0
uv[0][1]  = new PVector(0,   0);
uv[0][2]  = new PVector(max, 0);
uv[1][0]  = new PVector(0,   max); // face1
uv[1][1]  = new PVector(max, 0);
uv[1][2]  = new PVector(max, max);
// front
uv[2][0]  = new PVector(0,   0); // face2
uv[2][1]  = new PVector(max, 0);
uv[2][2]  = new PVector(max, max);
uv[3][0]  = new PVector(0,   0); // face3
uv[3][1]  = new PVector(max, max);
uv[3][2]  = new PVector(0,   max);
// back
uv[4][0]  = new PVector(0,   0); // face4
uv[4][1]  = new PVector(max, 0);
uv[4][2]  = new PVector(max, max);
uv[5][0]  = new PVector(0,   0); // face5
uv[5][1]  = new PVector(max, max);
uv[5][2]  = new PVector(0,   max);
// right
uv[6][0]  = new PVector(0,   0); // face6
uv[6][1]  = new PVector(max, 0);
uv[6][2]  = new PVector(0,   max);
uv[7][0]  = new PVector(max, 0); // face7
uv[7][1]  = new PVector(max, max);
uv[7][2]  = new PVector(0,   max);
// left
uv[8][0]  = new PVector(0,   0); // face8
uv[8][1]  = new PVector(0,   max);
uv[8][2]  = new PVector(max, max);
uv[9][0]  = new PVector(max, 0); // face9
uv[9][1]  = new PVector(max, max);
uv[9][2]  = new PVector(0,   0);
// bottom
uv[10][0] = new PVector(0,   max); // face10
uv[10][1] = new PVector(0,   0);
uv[10][2] = new PVector(max, max);
uv[11][0] = new PVector(0,   0); // face11
uv[11][1] = new PVector(max, 0);
uv[11][2] = new PVector(max, max);

// 设置cubes的位置坐标
cubesPos = new PVector[3];
cubesPos[0] = new PVector( 0,   0,  0);
cubesPos[1] = new PVector( 500, 0,  0);
cubesPos[2] = new PVector( 0,   0,  400);

// 设置图形模式
noStroke();

// 载入贴图
tex = loadImage("t3.jpg");
tex.resize(int(256/max), 0);

// 设置纹理属性
textureMode(NORMAL);
textureWrap(REPEAT);

// 设置摄影机
cameraPos    = new PVector(width/2.0,  height/2.0, (height/2.0) / tan(PI*30.0/180.0));
cameraTarget = new PVector(width/2.0, 0.0, 0.0);
up           = new PVector(0, 1, 0);

camTranslate = new PVector(0, 0, 0);
camRotate    = new PVector(0, 0, 0);

//noCursor();

}

float angle1 = 0;
float angle2 = 0;
void draw() {
// 清楚缓冲区
background(0);

// 事件处理
do_movement();

camera(cameraPos.x,    cameraPos.y,    cameraPos.z,
cameraTarget.x, cameraTarget.y, cameraTarget.z,
up.x,           up.y,           up.z);

// 绘制图形
for (int n = 0; n < cubesPos.length; n++) {
// 模型矩阵变换
translate(cubesPos
.x, cubesPos
.y, cubesPos
.z);

pushMatrix();
// 单个模型矩阵变换
translate(width/2, height/2, -100.0);
scale(200);

// 绘制单个网格物体
//fill(255, 127, 39);
for (int i = 0; i < face.length; i++) {
beginShape();
texture(tex);
//     x                    , y                    , z                    , u         , v
vertex(ver[int(face[i].x)].x, ver[int(face[i].x)].y, ver[int(face[i].x)].z, uv[i][0].x, uv[i][0].y);
vertex(ver[int(face[i].y)].x, ver[int(face[i].y)].y, ver[int(face[i].y)].z, uv[i][1].x, uv[i][1].y);
vertex(ver[int(face[i].z)].x, ver[int(face[i].z)].y, ver[int(face[i].z)].z, uv[i][2].x, uv[i][2].y);
endShape(TRIANGLES);
}
popMatrix();
}
}

void do_movement() {
float cameraSpeed = 5.0;
// 移动摄影机(同时移动目标)
if (keyPressed) {
// 前后
if (key == 'w') {
cameraPos.z -= cameraSpeed;
cameraTarget.z -= cameraSpeed;
}
if (key == 's') {
cameraPos.z += cameraSpeed;
cameraTarget.z += cameraSpeed;
}
// 左右
if (key == 'a') {
cameraPos.x -= cameraSpeed;
cameraTarget.x -= cameraSpeed;
}
if (key == 'd') {
cameraPos.x += cameraSpeed;
cameraTarget.x += cameraSpeed;
}
}

// 转动摄影机视角
// 偏航
cameraTarget.x += mouseX - pmouseX;
// 俯仰
cameraTarget.y += mouseY - pmouseY;
}

透视

现实生活中离你越远的东西看起来越小,铁轨公路什么的就是最好的例子。这种视觉效果我们称之为透视(Perspective)。我们将使用透视投影(Perspective Projection)来模仿这样的效果,它是使用透视矩阵来完成的。这个矩阵修改了每个顶点的w值,从而使得离观察者越远的顶点坐标w分量越大。

out = (x/w, y/w, z/w);

在Processing中创建投影矩阵很简单:perspective() 函数。我们来看看具体的方法,投影矩阵要放在摄影机矩阵之后实现:

// 投影矩阵(透视)
float fov    = PI/3.0;                     // 视野(Field of View)
float aspect = float(width)/float(height); // 画幅比例
float zNear  = cameraPos.z/10.0;           // 近焦平面
float zFar   = cameraPos.z*10.0;           // 远焦平面
perspective(fov, aspect, zNear, zFar);




fov也就是视野(Field of View),相信玩过3D类游戏的朋友应该都清楚。通常设定在45,60,75,90这几个档位。你可以自己改着玩玩。fov值太高的视野画面空间会被拉长,视野狭窄;fov值太低,空间会被压平,视野比较大。玩过相机的同学应该有体验过广角镜头。
超出摄影机远焦平面和近焦平面的画面内容都会被剪裁掉,也就是不显示。

我们看看在程序中的具体实现(顺便把摄影机的移动和旋转方法更新到较新的版本):

// 窗口属性
int w = 720;
int h = 480;

// 定义顶点
PVector[] ver;
PVector[] face;
PVector[][] uv;
PVector[] cubesPos;
PImage tex;

// 摄影机属性
PVector cameraPos, cameraTarget, up, cameraFront;
float focalLength; // 焦距
float fov, aspect, zNear, zFar;
float yaw, pitch;  // 偏航、俯仰

void settings() {
//fullScreen();
size(w, h, P3D);
}

void setup() {
// 设置顶点
ver = new PVector[8];
ver[0] = new PVector(-0.5, -0.5,  0.5); // 顶点1
ver[1] = new PVector(-0.5, -0.5, -0.5); // 顶点2
ver[2] = new PVector( 0.5, -0.5, -0.5); // ...
ver[3] = new PVector( 0.5, -0.5,  0.5);

ver[4] = new PVector(-0.5,  0.5,  0.5);
ver[5] = new PVector(-0.5,  0.5, -0.5);
ver[6] = new PVector( 0.5,  0.5, -0.5);
ver[7] = new PVector( 0.5,  0.5,  0.5);

// 设置顶点索引
face = new PVector[12];
// top
face[0]  = new PVector(0, 1, 2);
face[1]  = new PVector(0, 2, 3);
// front
face[2]  = new PVector(0, 3, 7);
face[3]  = new PVector(0, 7, 4);
// back
face[4]  = new PVector(1, 2, 6);
face[5]  = new PVector(1, 6, 5);
// right
face[6]  = new PVector(3, 2, 7);
face[7]  = new PVector(2, 6, 7);
// left
face[8]  = new PVector(0, 4, 5);
face[9]  = new PVector(1, 5, 0);
// bottom
face[10] = new PVector(4, 5, 7);
face[11] = new PVector(5, 6, 7);

// 设置UV
// 目前有一点苦力活需要干,但很快,将会有东西拯救我们。
uv = new PVector[12][3]; // 12个面,每个面3个顶点,为每个顶点描述UV
float max = 2.0;
// top
uv[0][0]  = new PVector(0,   max); // face0
uv[0][1]  = new PVector(0,   0);
uv[0][2]  = new PVector(max, 0);
uv[1][0]  = new PVector(0,   max); // face1
uv[1][1]  = new PVector(max, 0);
uv[1][2]  = new PVector(max, max);
// front
uv[2][0]  = new PVector(0,   0); // face2
uv[2][1]  = new PVector(max, 0);
uv[2][2]  = new PVector(max, max);
uv[3][0]  = new PVector(0,   0); // face3
uv[3][1]  = new PVector(max, max);
uv[3][2]  = new PVector(0,   max);
// back
uv[4][0]  = new PVector(0,   0); // face4
uv[4][1]  = new PVector(max, 0);
uv[4][2]  = new PVector(max, max);
uv[5][0]  = new PVector(0,   0); // face5
uv[5][1]  = new PVector(max, max);
uv[5][2]  = new PVector(0,   max);
// right
uv[6][0]  = new PVector(0,   0); // face6
uv[6][1]  = new PVector(max, 0);
uv[6][2]  = new PVector(0,   max);
uv[7][0]  = new PVector(max, 0); // face7
uv[7][1]  = new PVector(max, max);
uv[7][2]  = new PVector(0,   max);
// left
uv[8][0]  = new PVector(0,   0); // face8
uv[8][1]  = new PVector(0,   max);
uv[8][2]  = new PVector(max, max);
uv[9][0]  = new PVector(max, 0); // face9
uv[9][1]  = new PVector(max, max);
uv[9][2]  = new PVector(0,   0);
// bottom
uv[10][0] = new PVector(0,   max); // face10
uv[10][1] = new PVector(0,   0);
uv[10][2] = new PVector(max, max);
uv[11][0] = new PVector(0,   0); // face11
uv[11][1] = new PVector(max, 0);
uv[11][2] = new PVector(max, max);

// 设置cubes的位置坐标
cubesPos = new PVector[10];
randomSeed(9999);
for (int i = 0; i < cubesPos.length; i++) {
cubesPos[i] = new PVector(random(-400, 400), random(-100, 100), random(-400, 400));
}

// 设置图形模式
noStroke();

// 载入贴图
tex = loadImage("t3.jpg");
tex.resize(int(256/max), 0);

// 设置纹理属性
textureMode(NORMAL);
textureWrap(REPEAT);

// 设置摄影机
focalLength  = (height/2.0) / tan(PI*30.0/180.0);
cameraPos    = new PVector(width/2.0,  height/2.0, focalLength);
cameraFront  = new PVector(0.0, 0.0, -1.0);
up           = new PVector(0, 1, 0);

// 投影矩阵参数
fov    = radians(60);                // 视野(Field of View)
aspect = float(width)/float(height); // 画幅比例
zNear  = cameraPos.z/10.0;           // 近焦平面
zFar   = cameraPos.z*10.0;           // 远焦平面
// 摄影机旋转属性
yaw    = -90.0;
pitch  =  0.0;

//noCursor();
}

void draw() {

// 清楚缓冲区
background(0);
// 事件处理
do_movement();

// 设置摄影机
cameraTarget = PVector.add(cameraPos, cameraFront);
camera(cameraPos.x,    cameraPos.y,    cameraPos.z,
cameraTarget.x, cameraTarget.y, cameraTarget.z,
up.x,           up.y,           up.z);
perspective(fov, aspect, zNear, zFar);

// 绘制图形
for (int n = 0; n < cubesPos.length; n++) {

// 分配模型网格位置
translate(cubesPos
.x, cubesPos
.y, cubesPos
.z);

pushMatrix();
// 单个模型矩阵变换
translate(width/2, height/2, -100.0);
scale(200);

// 绘制单个网格物体
//fill(255, 127, 39);
for (int i = 0; i < face.length; i++) {
beginShape();
texture(tex);
//     x                    , y                    , z                    , u         , v
vertex(ver[int(face[i].x)].x, ver[int(face[i].x)].y, ver[int(face[i].x)].z, uv[i][0].x, uv[i][0].y);
vertex(ver[int(face[i].y)].x, ver[int(face[i].y)].y, ver[int(face[i].y)].z, uv[i][1].x, uv[i][1].y);
vertex(ver[int(face[i].z)].x, ver[int(face[i].z)].y, ver[int(face[i].z)].z, uv[i][2].x, uv[i][2].y);
endShape(TRIANGLES);
}
popMatrix();
}
}

void do_movement() {
float cameraSpeed = 5.0;
// 移动摄影机(同时移动目标)
if (keyPressed) {
// 前后
if (key == 'w') {
cameraPos.z -= cameraSpeed;
cameraTarget.z -= cameraSpeed;
}

if (key == 's') {
cameraPos.z += cameraSpeed;
cameraTarget.z += cameraSpeed;
}
// 左右
if (key == 'a') {
cameraPos.x -= cameraSpeed;
cameraTarget.x -= cameraSpeed;
}
if (key == 'd') {
cameraPos.x += cameraSpeed;
cameraTarget.x += cameraSpeed;
}
}

// 转动摄影机视角
float sensitivity = 0.05; // 灵敏度
if (mousePressed) {
// 偏航
yaw += (mouseX - pmouseX)*sensitivity;
// 俯仰
pitch += (mouseY - pmouseY)*sensitivity;
// 限值俯仰值
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;
// 更新摄影机目标
cameraFront.x = cos(radians(yaw))*cos(radians(pitch));
cameraFront.y = sin(radians(pitch));
cameraFront.z = sin(radians(yaw))*cos(radians(pitch));
cameraFront.normalize();

// 另一种方法,异曲同工,相对理解简单。
// 这种方法不需要cameraFront, 直接修改cameraTarget
// cameraTarget初始化为:(width/2, height/2, 0)
// yaw, pitch 的起始值为:180,180
//cameraTarget.x = sin(radians(yaw))*focalLength + cameraPos.x;
//cameraTarget.y = sin(radians(pitch))*focalLength + cameraPos.y;
//cameraTarget.z = (cos(radians(yaw)) + cos(radians(pitch))) * focalLength + cameraPos.z;
}

// 重置
if (keyPressed) {

if (key == 'r') {
yaw   = -90.0;
pitch =  0.0;

cameraFront.x = cos(radians(yaw))*cos(radians(pitch));
cameraFront.y = sin(radians(pitch));
cameraFront.z = sin(radians(yaw))*cos(radians(pitch));
cameraFront.normalize();
}
}
}


关于摄影机的方向控制要花点心思去理解数学。
控制摄影机的方向,如果焦距不变的话,实际上就是在一个球面上运动。
想想这应该也可以用距离场(distance field)来实现。
这里我有空(恢复点力气)再更新吧,连同位置控制。。。
毕竟我们希望看哪走哪。

好啦,填坑完毕!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  processing 3d 图形