查看原文
其他

写给设计师的 OF 编程指南(13) - 3D绘图

2016-10-31 Wenzy InsLab

三维是二维的进阶。在学习新的知识点前,希望你已经在二维绘图上足够娴熟。

用代码在三维空间绘图,与 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 系列文章

[1][2][3][4][5][6]

[7][8][9][10][11][12]

资源索引

CreativeCoding学习资源索引

Twitter资源索引


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存