有了第一节的知识,我们其实已经可以做很多事情了。比如,
绘制一个立方体(cube)。
不过绘制立方体相对复杂。我们先从绘制一个正方形起步吧。
仍然使用beginShape(),与endShape()这对基友来绘制我们的多边形:
// 绘制图形
beginShpae(); // 开始绘制
vertex(); // 这里填入顶点位置
...
endShpae(CLOSE); // 结束绘制
step 1 定义顶点
如何定义正方形的4个顶点?
经过第一节知识洗礼,我们知道第一步要把正方形画在坐标系原点附近,最好图形的中心点能与原点重合。
这样无论图形在之后要怎么倒腾,我们只需对这个坐标系进行矩阵变换(平移、旋转、缩放)就可以应付了。
事实上,我们把这些操作称为对模型矩阵的变换操作。
上图标示了正方形4个顶点位置。我们将申请4个向量来填装。
// 设置顶点
PVector[] ver = new PVector[4];
ver[0] = new PVector(-0.5, -0.5);
ver[1] = new PVector( 0.5, -0.5);
ver[2] = new PVector( 0.5, 0.5);
ver[3] = new PVector(-0.5, 0.5);
你可能注意到了,这一次我们并没有使用具体的数值来描述顶点的位置。相比官网提供的例子,如:
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
状的变化,并方便我们后续对模型矩阵的变换操作。
translate(width/2,height/2);
然而,这时候我们任然看不见图形,因为我们需要给图形指定大小:
scale(100);
我们将图形放大100倍,正方形的边长由1.0放大成了100.0。
使用遍历顶点的结构,我们可以过程化多边形的绘制工作。
这里是完整的代码:
你可以尝试运行第25行代码。
step 2 变换矩阵
我们希望这个正方形显示在屏幕正中央,因此使用平移矩阵,将画面移到屏幕中央:translate(width/2,height/2);
然而,这时候我们任然看不见图形,因为我们需要给图形指定大小:
scale(100);
我们将图形放大100倍,正方形的边长由1.0放大成了100.0。
step 3 绘制图形
// 绘制图形
beginShape();
for(int i = 0; i < ver.length; i++) {
vertex(ver[i].x, ver[i].y);
}
endShape(CLOSE);
使用遍历顶点的结构,我们可以过程化多边形的绘制工作。
这里是完整的代码:
你可以尝试运行第25行代码。
// 定义顶点
PVector[] ver;
void setup() {
size(200, 200, P3D);
// 设置顶点
ver = new PVector[4];
ver[0] = new PVector(-0.5, -0.5);
ver[1] = new PVector( 0.5, -0.5);
ver[2] = new PVector( 0.5, 0.5);
ver[3] = new PVector(-0.5, 0.5);
// 设置图形模式
noStroke();
noLoop();
}
void draw() {
// 模型矩阵变换
// 在opengl中,我们建议您在组合矩阵时,先进行缩放操作,
// 然后是旋转,最后才是平移,否则它们会(消极地)互相影响。
// 在Processing中,恰恰相反。
translate(width/2, height/2);
//rotateZ(radians(45));
scale(100);
// 绘制图形
fill(255, 127, 39);
beginShape();
for(int i = 0; i < ver.length; i++) {
vertex(ver[i].x, ver[i].y);
}
endShape(CLOSE);
}
--------------------------这里是华丽的分割线 --------------------------------
现在我们回过头来想象立方体的顶点分布

.
现在我们回过头来想象立方体的顶点分布
step 1 定义顶点
这一次,我们的顶点将加上z轴坐标。// 设置顶点
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);
step 2 变换矩阵
没有什么变化。step 3 绘制图形
然而我们还能像刚才那样遍历顶点吗?.
.
.
真正开始时,我们似乎有一点不知从何下手,对吧?事实上这里会遇到两个问题:
.
真正开始时,我们似乎有一点不知从何下手,对吧?事实上这里会遇到两个问题:
1.描述绘制多边形的方法
关于这一点我们可以查看beginShape()的官方说明。我们可以看到说明里列举了很多绘制的方法,一
般我们选:
TRIANGLES - 绘制独立的三角面
或 TRIANGLE_FAN - 绘制连续的三角面
因为我们的显卡在处理三角面时效率会快的多。同时,使用三角面,我们也无需考虑线条穿插问题。我
TRIANGLES - 绘制独立的三角面
或 TRIANGLE_FAN - 绘制连续的三角面
因为我们的显卡在处理三角面时效率会快的多。同时,使用三角面,我们也无需考虑线条穿插问题。我
倾向选择前者。处理起来直截了当。我们的立方体有6个面,每个面可以由2个三角形构成,那么一共就
是12个三角面。
2.描述顶点的方法
当然,我们可以老老实实把12个三角面的顶点都描述一遍。不过这样的操作似乎是机器的爱好。。。想
必我们会很厌烦。仔细想想,尽管有12个三角面,但它们的顶点仍然是由上文中ver 那8个顶点构成的。
因此我们可以称ver为[顶点列表],然后添加一个[顶点索引列表],用来描述每一个三角面使用了那些
因此我们可以称ver为[顶点列表],然后添加一个[顶点索引列表],用来描述每一个三角面使用了那些
顶点:
face= new PVector[12];
face[0] = new PVector(0, 1, 2); // 三角面1
face[1] = new PVector(0, 2, 3); // 三角面2
...
3.更新绘制图形方法
既然我们添加了顶点索引列表,那么原来的绘制图形的过程就有必要更新一下了:// 绘制图形
fill(255, 127, 39);
for (int i = 0; i < face.length; i++) { // 遍历三角面
// 绘制三角面
beginShape();
vertex(ver[int(face[i].x)].x, ver[int(face[i].x)].y, ver[int(face[i].x)].z);
vertex(ver[int(face[i].y)].x, ver[int(face[i].y)].y, ver[int(face[i].y)].z);
vertex(ver[int(face[i].z)].x, ver[int(face[i].z)].y, ver[int(face[i].z)].z);
endShape(TRIANGLES);
}
现在,我们来更新一下立方体的绘制过程:
// 定义顶点
PVector[] ver;
PVector[] face;
void setup() {
size(200, 200, P3D);
// 设置顶点
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);
// 设置图形模式
noStroke();
}
void draw() {
// 清楚缓冲区
background(0);
// 模型矩阵变换
// 在opengl中,我们建议您在组合矩阵时,先进行缩放操作,
// 然后是旋转,最后才是平移,否则它们会(消极地)互相影响。
// 在Processing中,恰恰相反。
translate(width/2, height/2, -100);
float angle = frameCount%360;
rotateX(radians(angle));
rotateY(radians(angle));
scale(100);
// 绘制图形
fill(255, 127, 39);
for (int i = 0; i < face.length; i++) {
beginShape();
vertex(ver[int(face[i].x)].x, ver[int(face[i].x)].y, ver[int(face[i].x)].z);
vertex(ver[int(face[i].y)].x, ver[int(face[i].y)].y, ver[int(face[i].y)].z);
vertex(ver[int(face[i].z)].x, ver[int(face[i].z)].y, ver[int(face[i].z)].z);
endShape(TRIANGLES);
}
}
好啦,咱们的立方体终于顺利绘制到屏幕中了。 欣喜之余,你是不是会突然想到利用box()函数,不是
也可简单快速的绘制一个立方体吗?
其实不然,通过上面的努力,你已经开启了通往3D世界大门。
精彩
的世界就在眼前。