写给设计师的 OF 编程指南(13) - 3D绘图
三维是二维的进阶。在学习新的知识点前,希望你已经在二维绘图上足够娴熟。
用代码在三维空间绘图,与 3dMax ,Maya 这类三维软件相比,有完全不一样的体验。由于里面没有直观的界面来控制和调整物体的位置,所以写起来会比较抽象。其中有一难点,就要熟悉空间坐标系以及对应的各种变换操作。
在平面上绘图只需考虑两个维度,x 轴和 y 轴。但在三维空间中,会多出一个 z 轴。
三维空间中的坐标系
下面先来看三维空间中的坐标分布情况
对比原来的平面坐标系可以发现,x,y 轴的方向与原来是一致。新增加的 z 轴,正方向朝向的是观察者。它表示深度,物体的远近也取决于 z 轴上的数值。
而三维空间中的坐标系原点,与平面二维坐标系的坐标原点位置是一致的。都处于窗口的左上方,上图为了便于演示各个坐标轴的朝向,才对原点进行了平移。下图才是坐标系的默认状态
注:添加了旋转动画效果展示深度
在绘图时,这个坐标系在头脑中必须非常明晰,包括各轴的正方向、负方向。为了加深印象,便于理解。下面再针对具体的坐标点做一些举例。
假设网格之间的距离为 50,某个空间坐标数值为(300,0,0),它便会处于以下位置。
注:坐标原点进行过平移处理
当空间坐标值为 (-300,0,0) 时,便会往左偏移
坐标值为 (0,300,0)
坐标值为 (0,0,300)
坐标值为 (300,300,300)
绘制正方体
前面用了较多篇幅去讲坐标系。这是 3D 绘图的基础。只有熟悉它,才能在空间随心所欲地按自己的构想画出点线面。
下面正式进入绘图部分。从最简单的图形开始,在窗口中绘制一个正方体。
代码示例(13-1):
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
bool showWire;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
ofSetLineWidth(3);
}
void ofApp::draw(){
ofBackground(0);
if(showWire){
// 关闭填充,只显示线框
ofNoFill();
}else{
// 开启填充
ofFill();
}
ofTranslate(ofGetWidth()/2,ofGetHeight()/2,mouseX);
ofDrawBox(100);
}
void ofApp::mousePressed(int x, int y, int button){
showWire = !showWire;
}
运行效果:
代码说明:
和二维平面的显示方式一致。三维空间中的图形也有两种显示模式。一种是填充模式,另一种是线框模式。当前的显示模式取决于是否使用了 ofFill 和 ofNoFill 函数。例子中为了方便演示,设置了一个布尔变量 showWire 用于切换显示模式。当点击鼠标时,showWire 的值会进行取反。从而影响到 draw 函数中的判定语句,实现切换效果
ofTranslate 的作用大家并不陌生,是对坐标系进行平移。它同时是一个可重载的函数,当输入的参数个数为 3 时,第 3 个参数便会影响 z 轴。前两个参数分别设置成了 ofGetWidth()/2 和 ofGetHeight()/2。所以坐标原点就恰好居中到屏幕中央,最后的参数由 mouseX 控制。因此左右移动鼠标,可以看到图形的远近发生变化
这里看似物体的位置在移动。但实质上仅仅是坐标系的位置发生了变化,物体的坐标其实仍处于原点
与 Processing 的异同对比
如果你在 Processing 中尝试过绘制三维图形。那在写绘图函数之前,必须要在 size 中将显示模式先设置成 P3D 又或者是 OPENGL。Openframeworks 无需进行类似的操作。它默认便是在三维空间上绘制图形。由于前几章的例子里在绘图时没有设置过 z 轴坐标,程序会默认 z 值为 0,因此图形也就处于同一个平面,看上去就像“二维”的。
旋转正方体
前面由于摄像机的视角是固定的,物体也没有旋转,所以很不立体,更像是平面的。下面介绍的 ofRotate() 函数,可以让正方体旋转起来。
前面并没有介绍,ofRotate 函数其实有几种变式。分别是 ofRotateX,ofRotateY,ofRotateZ。它们会绕不同的坐标轴进行旋转。
其中,ofRotate 函数始终是以坐标原点为中心进行旋转的。所以若想某物体以自身为中心旋转,需要先使用 ofTranslate 将坐标原点变换到原物体的坐标上。
代码示例(13-2):
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
int rotateMode;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(0);
ofPushMatrix();
ofTranslate(ofGetWidth()/2,ofGetHeight()/2);
if(rotateMode == 0){
ofRotateX(ofGetElapsedTimef() * 50);
}else if(rotateMode == 1){
ofRotateY(ofGetElapsedTimef() * 50);
}else{
ofRotateZ(ofGetElapsedTimef() * 50);
}
ofNoFill();
ofSetLineWidth(3);
ofDrawBox(300);
ofPopMatrix();
}
void ofApp::mousePressed(int x, int y, int button){
rotateMode++;
if(rotateMode == 3){
rotateMode = 0;
}
}
运行效果:
代码说明:
鼠标单击便可切换不同的旋转模式,分别绕 x 轴,y 轴,z 轴进行旋转。整型变量 rotateMode 作为中介
在绘制三维图形时,ofTranslate() 允许只传入两个参数。此时 z 坐标默认值为 0
关于 ofRotate 的旋转方向
ofRotate 函数的旋转方向是有固定规律的。当传入的参数为递增时,坐标系便会面朝旋转轴的正方向,进行逆时针旋转。与之相反,递减时则呈顺时针旋转。
下面是数值递增时的旋转情况。
ofRotateX(ofGetElapsedTimef() * 30);
ofRotateY(ofGetElapsedTimef() * 30);
ofRotateZ(ofGetElapsedTimef() * 30);
除了用常规的方式记忆以外,还可以借鉴中学物理课上提到的右手螺旋定则(也叫安培定则)
所谓的右手螺旋定则,是一种判定电流和电流磁场的磁感线方向的方法。右手握住通电导线,大拇指朝向电流方向,其余四指的指向便会对应磁感线方向。
程序中没有电流,没有磁场。但可以用类似的方式来描述方向。例如,当 ofRotateX 传入的参数递增时,就可以采用“左手螺旋定则”。左手握住旋转的坐标轴 X 轴,大拇指朝向坐标轴的正方向,其余四指的指向便会对应旋转方向。当数值递减时,则采用“右手螺旋定则”,判定方法是一致的。右手握住旋转的坐标轴,大拇指朝向坐标轴的正方向,其余四指的指向便会对应旋转方向。(可对照前面的动图理解此法则)
3D 绘图的相关函数
前面只介绍了一个绘图函数 - ofDrawBox,接下来再看其他函数。以下列举了 Openframeworks 中最常用的绘图函数以及对应的各种重载形式。
函数名 | 函数功能 | 参数说明 |
---|---|---|
ofDrawSphere(float r) | 绘制球体 | r:球体半径 |
ofDrawSphere(float x,float y,float r) | 绘制球体 | x、y 分别代表球体的 x、y坐标,r:球体半径 |
ofDrawSphere(float x,float y,float z,float r) | 绘制球体 | x、y 、z 分别代表球体的 x、y、z坐标,r:球体半径 |
ofDrawBox(float size) | 绘制正方体 | size:正方体边长 |
ofDrawBox(float w,float h,float d) | 绘制长方体 | w、h、d 分别代表长方体的宽度,高度,深度 |
ofDrawBox(float x,float y,float z,float size) | 绘制正方体 | size:正方体边长,x、y、z 分别代表长方体的x、y、z坐标 |
ofDrawBox(float x,float y,float z,float w,float h,float d) | 绘制长方体 | w、h、d 分别代表长方体的宽度,高度,深度,x、y、z 分别代表长方体的 x、y、z坐标 |
ofSetConeResolution(int radiusSegments,int heightSegments) | 设置圆锥精度 | radiusSegments:半径精度,heightSegments 高度精度 |
ofDrawCone(float r,float h) | 绘制圆锥 | r 表示圆锥底面半径,h 表示圆锥的高 |
ofDrawCone(float x,float y,float r,float h) | 绘制圆锥 | x、y 分别代表圆锥的 x、y 坐标,r 表示圆锥底面半径,h 表示圆锥的高 |
ofDrawCone(float x,float y,float z,float r,float h) | 绘制圆锥 | x、y、z分别代表圆锥的 x、y、z 坐标,r 表示圆锥底面半径,h 表示圆锥的高 |
ofSetCylinderResolution(int radiusSegments,int heightSegments) | 设置圆柱精度 | radiusSegments:半径精度,heightSegments 高度精度 |
ofDrawCylinder(float r,float h) | 绘制圆柱 | r 表示圆柱底面半径,h 表示圆柱的高 |
ofDrawCylinder(float x,float y,float r,float h) | 绘制圆柱 | x、y分别代表圆柱的 x、y 坐标,r 表示圆柱底面半径,h 表示圆柱的高 |
ofDrawCylinder(float x,float y,float z,float r,float h) | 绘制圆柱 | x、y、z分别代表圆柱的 x、y、z 坐标,r 表示圆柱底面半径,h 表示圆柱的高 |
ofDrawPlane(float w,float h) | 绘制平面 | w、h 分别代表平面的宽高 |
ofDrawPlane(float x,float y,float w,float h) | 绘制平面 | x、y 分别代表平面的 x、y 坐标,w、h 分别代表平面的宽高 |
ofDrawPlane(float x,float y,float z,float w,float h) | 绘制平面 | x、y、z 分别代表平面的 x、y、z 坐标,w、h 分别代表平面的宽高 |
Openframeworks 相比 Processing,内置的绘图函数可以说是异常丰富,可修改设置的参数也更多。例如绘制函数可以允许物体拥有自身的坐标值。
如前面的 ofDrawBox 函数,它有四种重载形式。当参数个数为 1 时,数值就代表正方体的边长。参数个数为 3 时,可以分别控制长方体的长宽高。参数个数为 4 时,前 3 个参数代表正方体的空间坐标,最后一个参数代表正方体的边长。当参数个数为 6 时,前 3 个参数代表正方体的空间坐标。
重载形式看似复杂,但几乎所有的绘图函数,都有这样的组合规律-[坐标参数 + 比例参数]。其中“坐标参数”部分是可以省略的,并且大多数情况下还可以选择是否忽略 z 轴坐标。而“比例参数”部分,往往是固定的,除了 ofDrawBox 函数比较特殊。既可以只用一个参数控制长宽高,也能分别控制。
另外,每种三维图形都有一个对应的 resolution 函数用于控制图形精度。精度越高,就会使用越多的面来构成立体图形,呈现效果也会更细腻。特别是当启用灯光照明时,面数高会让过渡更自然,但相应的会占用更多运算资源。
除了这些有体积的绘图函数,如 ofDrawBox,ofDrawSphere。前面在平面视图下提到过的 ofDrawLine,ofDrawCircle 也能在三维空间中正常显示的。只是传入参数的个数会有所差别,坐标部分可以允许多一个 z 坐标。
函数名 | 函数功能 | 参数说明 |
---|---|---|
ofDrawLine(float x1,float y1,float z1,float x2,float y2,float z2) | 绘制直线 | 前三个参数代表直线端点 A 的空间坐标,后三个参数代表端点 B 的空间坐标 |
ofDrawCircle(float x,float y,float z,float r) | 绘制圆形 | 前三个参数代表圆形的空间坐标,r 代表圆的半径 |
ofDrawEllipse(float x,float y,float z, float w,float h) | 绘制椭圆 | 前三个参数代表矩形的空间坐标,后两个参数代表椭圆的宽高 |
ofDrawTriangle(float x1,float y1,float z1,float x2,float y2,float z2,float x3,float y3,float z3) | 绘制三角形 | 分别代表三角形三个顶点的空间坐标 |
ofDrawRectangle(float x,float y,float z, float w,float h) | 绘制矩形 | 前三个参数代表矩形的空间坐标,后两个参数代表矩形的宽高 |
若不输入 z 坐标的参数,默认为 0。
3D绘图函数-综合示例01
由于图形的绘制都大同小异,这里就不独立列举每个函数的使用方法。下面整合了一个实例。
代码示例(13-3):
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
int index;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
index = 0;
}
void ofApp::draw(){
ofBackground(0);
ofTranslate(ofGetWidth()/2,ofGetHeight()/2);
ofRotateX(ofGetElapsedTimef() * 30);
ofRotateY(ofGetElapsedTimef() * 40);
ofNoFill();
ofSetLineWidth(3);
switch (index) {
case 0:
ofDrawBox(300);
break;
case 1:
ofSetSphereResolution(8);
ofDrawSphere(180);
break;
case 2:
ofDrawCone(150,300);
break;
case 3:
ofDrawCylinder(150, 300);
break;
case 4:
ofDrawPlane(300,300);
break;
case 5:
ofDrawLine(-200,0,200,0);
break;
case 6:
ofDrawTriangle(150, -200, 0, 150, -150, -200);
break;
case 7:
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(0,0,300,300);
break;
case 8:
ofDrawCircle(0,0,200);
break;
default:
break;
}
}
void ofApp::keyPressed(int key){
if(key == OF_KEY_LEFT){
index--;
if(index < 0){
index = 8;
}
}
if(key == OF_KEY_RIGHT){
index++;
if(index > 8){
index = 0;
}
}
}
运行效果:
代码说明:
ofRotateX 和 ofRotateY 的旋转速度略有不同,目的是为了让视角变化更丰富。
当开启 ofFill,三维图像会以填充方式显示,但只是简单的平涂,并不利于观察空间体积,所以使用了 ofNoFill 只显示线框
OF_KEY_LEFT,OF_KEY_RIGHT 作为特殊键值,分别代表方向键的左键和右键
变量 index 代表了不同的模式。方向键的左键和右键,可以切换不同的图形
摄像机的控制
下面将介绍摄像机的用法。使用它可以更灵活地控制窗口视角。
代码示例(13-4):
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
ofEasyCam cam;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(0);
cam.begin();
ofNoFill();
ofSetLineWidth(3);
ofDrawBox(300);
cam.end();
}
运行效果:
代码说明:
ofEasyCam 是 Openframeworks 中自带的一个类,通过它可以创建一个摄像机对象。里面集成了很多方便的功能,使用也非常便捷。当创建了 ofEasyCam 的对象后,就可以使用成员函数 begin 和 end 对绘图部分进行包裹。被包裹的部分,就会受对象影响。只要用鼠标进行相关操作,便会切换视角
操作方式一览:
鼠标左键拖动(笔记本触摸板三指移动):会以原点为目标,旋转摄像机视角
鼠标右键上下拖动(笔记本触摸板双指移动):推拉摄像机,远近变化
鼠标左键拖动 + Alt 键:平移摄像机
鼠标左键双击:恢复摄像机初始视角
使用 ofEasyCam 有一个好处,坐标原点会默认居中到屏幕中央。这样就无需每次使用 ofTranslate 。但它和默认的坐标系有一个很大的不同:y 轴的正方向不再朝下,变成了朝上。我们可以从下图了解坐标系的朝向
当坐标系方向改变后,旋转的角度方向也发生了变化。当参数递增时,原本是采用的“左手螺旋定则”。现在需要换成“右手螺旋定则”。递减时,则采用“左手螺旋定则”。
下面是数值递增时的旋转情况。
ofRotateX(ofGetElapsedTimef() * 30);
ofRotateY(ofGetElapsedTimef() * 30);
ofRotateZ(ofGetElapsedTimef() * 30);
灯光系统
谈到三维绘图,不得不提到灯光。要使物体具有立体感,灯光是不可获缺的。Openframeworks 中,就提供多种类型的灯光。如果你之前使用过 3dMax,Maya 这类三维动画软件,一定不会陌生。
在 Openframeworks 中可以使用的灯光类型有这几类。
环境光:光在环境中进行了充分散射,没有光源位置和方向,强度均匀。
平行光:当某个光源距离物体接近无限远,就会产生平行光。在程序中无需设定位置,只有方向,常用于模拟太阳光照
点光源:点光源的光都会从某个点放出,它对各个方向的光照强度是一致的,并会随距离衰减。常用于模拟灯光
聚光灯:聚光灯是一种舞台灯光,投出的灯光近似于锥形。它有多个参数。如角度,集中度等。
面光源:光源从某个平面发出,会随距离衰减
下面先以点光源为例。
代码示例(13-5):
—- ofApp.h 内
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
...
ofEasyCam cam;
ofLight pointLight;
};
—- ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
pointLight.setPointLight();
pointLight.setDiffuseColor(ofFloatColor(1.0,1.0,1.0));
pointLight.setPosition(-150, 150, 150); ofEnableDepthTest();
}
void ofApp::draw(){
ofBackground(0);
cam.begin();
// 绘制灯光位置
ofDisableLighting();
ofSetColor(255);
pointLight.draw();
ofRotateX(ofGetElapsedTimef() * 30);
ofRotateY(ofGetElapsedTimef() * 40);
// 启用灯光
pointLight.enable();
// 绘图正方体
ofFill();
ofSetColor(255,100,0);
ofDrawBox(250);
cam.end();
// 文字绘制
ofDisableLighting();
ofSetColor(255);
ofDrawBitmapString("It's a point light", ofGetWidth()/2 - 50, ofGetHeight() - 30);
}
运行效果:
代码说明:
可通过 ofLight 创建灯光对象,setPointLight() 函数的作用是将对象设置成点光源。当 ofLight 对象创建后,程序会默认将灯光类型设置成点光源,因此这句代码可以省略
setDiffuseColor() 作用是设置光源的颜色( 漫反射颜色 )。其中传入的颜色变量必须要使用 ofFloatColor。它与变量 ofColor 有所不同,传入参数为小数,并且最大值为 1。因此 ofFloatColor(1.0,1.0,1.0) 等价于 ofColor(255,255,255)。ofFloatColor(1,0,0.5)等价于 ofColor(255,0,128)。当不对灯光对象设置漫反射参数时,默认光源为白色。
setPosition() 作用是设置光源位置。不主动设置,默认位置在坐标原点。若希望创建一个可运动的光源,就可以把它写在 update,并改变传入变量。
ofEnableDepthTest() 作用是开启深度检测,在 3D 绘图中非常重要。3D 图形本质是由一个个面组成的。调用此函数可以保证面的前后关系以正确的方式显示,不开启则会出现奇怪的透视。
“绘制灯光位置”这部分的代码对于开启灯光效果而言不是必须的。pointLight.draw() 的作用只是用球体绘制出灯光的位置,它会自带三根不同颜色的直线标示坐标系各轴的朝向,红绿蓝分别对应 x,y,z。
ofDisableLighting() 作用是关闭当前系统中的所有灯光,让物体以平涂的方式进行着色。若不开启,图形便会以光照模式进行着色。例如通过 pointLight.draw() 函数绘制出的“灯光球体”,由于程序里点光源的位置恰好处在球心当中,所以外表面就不会被照亮,这不利于观察,就需要在开头调用此函数。同理,“文字绘制”部分也选择关闭灯光,这就能使文字以白色填充色清晰地显示出来。下图是取消 ofDisableLighting() 后的效果
pointLight.enable() 的作用是开启点光源,它必须写在 ofDrawBox() 之前,ofDisableLighting()之后,这样才能保证光源能作用到物体上
ofDrawBitmapString() 写在 cam.end() 之外,便能让文字部分不受摄像机的影响
有一点值得注意,光源的位置是固定的,并不会随 ofRotate ,ofTranslate 而变化。因此 pointLight.draw() 需写在 ofRotate 之前,这样才能保证光源正确显示。
深入灯光系统
下面通过一个综合实例了解各种类型的灯光用法。
代码示例(13-6):
由于文章字数限制,具体代码可到主页查看。(点击文末的阅读原文)
运行效果:
(点光源模式下切换物体)
(切换灯光模式)
代码说明:
键盘方向键的左右键可切换物体,上下键可切换光照模式
ofLight 中,没有相关的函数可以将灯光对象设置成环境光。环境光是作为独立的属性依附到灯光对象上的,就像漫反射光,作为光源的颜色,它同样是灯光对象独立的属性。在程序中任何一种光源,只要通过 setAmbientColor 就可以加入环境光属性。当此属性启用后,物体表面的颜色便会同时受环境光和漫反射光的影响,色彩会进行叠加。由于创建 ofLight 对象后,默认是光源色为白色的点光源,所以需要使用 setDiffuseColor 讲它设置成 0,这样就只看到环境光对物体的影响。例子中在环境光模式下,物体各面呈现的是均匀的绿色。这是因为环境光经过充分的散射,会从各个方向放出均匀的光线照亮物体,颜色因此也一样。
函数 setDirectional() 可以将灯光设置成平行光。光线的方向默认是朝向 z 轴的正方向(相当于物体背光)。若希望改变方向,可以通过函数 setOrientation()。它能对平行光进行旋转操作,从而改变光源方向。传入的参数类型必须为 ofPoint。它是 OF 中常见的,用于保存点坐标的数据类型,相当于Processing 中的 PVector。ofPoint 里的三个参数分别控制 x,y,z 轴。当参数递增,则以对应旋转轴的正方向顺时针旋转相应角度(右手螺旋定则)。因此当参数设为 (0, 90, 0) 时,便会以 y 轴作为旋转轴旋转 90 度。光源方向变成从物体的左方发出。红色部分便是平行光。
函数 setSpotlight() 可以将灯光设置成聚光灯。setup 中的成员函数 lookAt() 可以设定投射目标,实例中投射目标为坐标原点。 update 中的 setSpotConcentration() 函数可以设置灯光的集中度。左右移动鼠标即可实时调控
函数 setAreaLight 可以将灯光设置成面光源。
成员函数 disable 与 enable 相反,作用是关闭灯光。
与 Processing 的异同对比
Processing 的灯光必须写在 draw 函数中,并且灯光的位置会受 pushMatrix,popMatrix 函数影响。Openframeworks 只要调用成员函数 enable 即可,它可在 setup ,update 或 draw 函数中使用,并且不受 ofPushMatrix,ofPopMatrix 影响。
灯光的综合实例01
代码示例(13-7):
由于文章字数限制,具体代码可到主页查看。(点击文末的阅读原文)
运行效果:
代码说明:
像现实中的灯光一样,程序中的光照效果也是可以叠加的。这里就设置了两个不同颜色,不同位置的点光源,这样可以使物体更有层次感
局部变量 w 代表的是整个矩阵的宽度,通过间距和方体的数量可以计算得出。获得了 w 的数值之后,就可以计算偏移量,让整个矩阵恰好居中到屏幕中央
灯光的综合实例02
代码示例(13-8):
由于文章字数限制,具体代码可到主页查看。(点击文末的阅读原文)
运行效果:
代码说明:
ofEnableLighting() 函数会开启当前系统中已有的灯光。虽然 setup 中已经使用 enable 开启了灯光。但由于后面在描绘实线时,使用了 ofDisableLighting()。所以从末尾回到开头,整个 draw 循环里灯光都处于关闭状态。因此要调用 ofEnableLighting() 重新开启,让长方体可以正常显示光照效果。除此以为,也能使用 pointLight1.enable() 和 pointLight2.enable() 替换 ofEnableLighting() 的位置,效果是一样的
第二个 for 循环,作用是对方体之间随机进行连线。使用 ofDisableLighting(),线条的着色方式便是平涂,不会受光源影响
灯光系统总结
无论是 Processing 还是 Openframeworks。它们内置的灯光系统有两个特性。一是没有投影,二是物体之间不会相互影响,光没有反射也没有折射。所以在真实感上比不上 Unity 这类游戏引擎,画面的精致程度也很难达到 C4D,Maya 等软件渲染出的效果。
尽管如此,作为一个不那么仿真,阉割版的灯光,只要参数调节得当,还是可以有不错的三维表现力。若是觉得无法满足创作需求了,就可以尝试学习一些更高阶的内容 - Shader Programming , 使用它就不会有以上的限制,可以用 GPU 充分挖掘图形的性能。
若想更多地了解 Shader,推荐 Patricio 的入门教程(
shadertoy 网站可以查看更多绚丽作品,你能从中领略 shader 的魅力。(
END
至此,基础部分已经完结。回顾前半部分的写作。整个过程并不轻松,作为基础教程,必须在有条件限制的情况下将核心概念讲清楚。保证通俗的同时,实例要简炼有趣。作为初稿,自我评价还算过关。
编程技能的修炼之路上,遵循某种“二八定律”。只要掌握 20 % 的核心概念,就能解决 80 % 的问题。虽然上部分涉及的知识点还远远不到 20 %,但只要把基础巩固好。就能更游刃有余地往下深入。
后面会开始进入新的章节。不谈技术的细枝末节,更多地围绕图形创作展开。敬请期待~
Openframeworks 系列文章
资源索引