Mastering openFrameworks_第二章_2d绘图

2d绘图

绘图是openFrameworks的主要功能之一。在这里,我们讨论二维图形的基础知识,包括绘制几何元素、使用颜色以及在屏幕外缓冲区中绘制。在这一章中,我们将讨论:

几何基元

使用ofPoint

坐标系转换

颜色

Fbo在幕后绘图中的应用

玩弄数字不稳定性

抓屏

绘画基础

现代计算机的屏幕由许多小方块组成,称为像素(图像元素)。每个像素可以以一种颜色发光。您可以通过改变像素的颜色在屏幕上创建图片。

注意:基于像素的图形称为光栅图形。另一种图形是矢量图形,它基于诸如直线和圆等原始图形。今天,大多数计算机屏幕都是像素数组,代表光栅图形。但是基于矢量图形(矢量图像)的图像仍然在计算机图形学中使用(图像基础部分在第4章,图像和纹理)。矢量图像使用光栅化程序绘制在光栅屏幕上。

Openframeworks项目可以在整个屏幕上绘制(当它处于全屏模式时)或者仅在窗口中绘制(当全屏模式被禁用时)。请参阅如何在第1章openFrameworks基础中的main.cpp和setup()部分设置屏幕模式。为了简单起见,我们将openFrameworks可以绘制的区域称为屏幕。屏幕的当前宽度和高度(像素)可以使用以下公式获得GetWidth()和GetHeight()函数。为了指向像素,openFrameworks使用了屏幕的坐标系。这个坐标系的原点在屏幕的左上角。测量单位是一个像素。因此,屏幕上宽度为w和高度为h的每个像素都可以通过其坐标(x,y)指向,其中x和y分别是0到w-1和0到h-1范围内的整数值。

在本章中,我们将讨论二维图形(2D),这是通过指定两个坐标(x,y)在屏幕上绘制对象的许多方法和算法。

注意:另一类图形是三维图形,它使用三个坐标(x,y,z)表示三维空间中的物体,并使用某种空间(3D)投影到屏幕(2D)上在屏幕上进行渲染。有关3d图形的详细信息,请参阅第7章3d绘图。

屏幕的背景颜色

Openframeworks屏幕上的绘图应该在testApp::draw()函数中执行(参见第1章openFrameworks基础中的testApp.cpp部分)。在openFrameworks调用这个函数之前,整个屏幕都填充了一个固定的颜色,这个颜色由ofSetBackground(r,g,b)设置。这里r、g和b是整数值,对应于背景颜色的红色、绿色和蓝色分量,范围为0到255。注意,每个ofSetBackground()函数调用都会立即用指定的颜色填充屏幕。

注意:您可以使用ofBackgroundGradient()函数创建一个渐变背景。请参阅第七章三角形云例子部分的描述,3d绘图。

您可以在testApp::setup()函数中只设置一次背景颜色,但是我们通常在testApp::draw()函数的开头调用ofSetBackground(),以避免混淆设置阶段和绘图阶段。

脉动背景例子

你可以把ofSetBackground()看作是一个机会,可以画出最简单的图形,就好像屏幕由一个大像素组成一样。考虑一个例子,其中的背景颜色缓慢变化,从黑色到白色,并回使用正弦波。

注意:这是例子02-2D/01-PulsatingBackground。

该项目基于openFrameworksemptyExample示例。用示例复制文件夹并重命名它。然后用以下代码填充testApp::draw()函数的主体:

float time = ofGetElapsedTimef(); //Get time in seconds 
//Get periodic value in [-1,1], with wavelength equal to 1 second 
float value = sin( time * M_TWO_PI ); 
//Map value from [-1,1] to [0,255] 
float v = ofMap( value, -1, 1, 0, 255 ); 
ofBackground( v, v, v ); //Set background color

这段代码使用ofGetElapsedTimef()函数获取从项目开始到结束的时间,并使用这个值计算value= sin(time*M_TWO_PI)。这里,M_TWO_PI是一个openFrameworks常量,等于2PI;也就是说,大约是6.283185。所以,time*M_TWO_PI每秒增加2PI。值2PI等于正弦波函数sin()的周期。因此,sin(...)的论点将在一秒内通过它的波长,因此值sin(...)将从-1运行到1,然后返回。最后,我们将值映射到v,使用ofMap()函数将范围从0到255变化,并将背景设置为一种颜色,其中红色、绿色和蓝色分量等于v。

注意:请参阅第一章openFrameworks基础中的基本实用函数部分对于getalepsedtimef()和ofMap()函数的描述。

运行该项目;您将看到屏幕颜色如何通过平滑地改变其脉动,从黑色到白色再到背面。

注意:替换最后一行,该行将背景颜色设置为ofBackground(v,0,0);并且颜色将从黑色变为红色。

把sin(...)函数的论元换成公式time*M_TWO_PI*2,脉动的速度增加两倍。

我们将返回到绘图的背景与未清除的背景部分。现在我们将考虑如何绘制几何元素。

几何基元

在这一章中,我们将讨论二维图形。2d图形可以通过以下方式创建:

1.绘制诸如直线、圆形等几何元素,以及诸如三角形和长方形等其他曲线和形状。这是通过编程创建图形的最自然的方式。生成性艺术和创造性编码项目往往是基于这种图形方法。我们将在本章的其余部分考虑这个问题。

2.绘制图像可以让您添加更多的现实主义的图形,这是考虑在第4章,图像和纹理。

3.直接设置屏幕的内容,一个像素一个像素地设置,是生成图形最强大的方法。但是对于像绘制曲线这样的简单的事情来说就很难使用了。因此,这种方法通常与以前的两种方法一起使用。一种快速的屏幕逐像素绘制技术包括用像素颜色填充一个数组,加载到图像中,然后在屏幕上绘制图像(参见第四章图像和纹理的创建图像部分的描述)。最快,但有点困难的技术,是使用片段着色器(见第8章,使用着色器的a简单片段着色器示例部分的解释)。

openFrameworks具有以下绘制原语的功能:

1.ofLine(x1,y1,x2,y2):该函数绘制一条线段,连接点(x1,y1)和(x2,y2);

2.ofRect(x,y,w,h):该函数绘制一个带有左上角(x,y)、宽度w和高度h;

3.ofTriangle(x1,y1,x2,y2,x3,y3):该函数绘制一个带有顶点(x1,y1)、(x2,y2)和(x3,y3)的三角形

4.ofCircle(x,y,r):此函数绘制一个圆心为(x,y)的圆,半径r;

注意:openframeworks没有改变单独像素颜色的特殊功能。为此,可以将像素(x,y)绘制为宽度和高度等于1像素的矩形,即ofRect(x,y,1,1)。

这是一个非常缓慢的方法,但我们有时用它来教育和调试目的。

这些函数中的所有坐标都是float类型。虽然屏幕上特定像素的坐标(x,y)是整数值,但openFrameworks使用浮点数来绘制几何基元。这是因为视频卡可以使用建模方法绘制具有浮点坐标的对象,就好像线位于像素之间一样。因此,使用浮点坐标绘制的结果图像比使用整数坐标绘制的图像更平滑。

使用这些函数,可以创建简单的绘图。

一朵花最简单的例子

让我们考虑一个示例,它绘制了一个圆、一条直线和两个三角形,这些形式

最简单的一种花。

注意:这是02-2D/02-花最简单的例子。

此示例项目基于openFrameworksemptyExample项目。用以下代码填充testApp::draw()函数的主体:

ofBackground( 255, 255, 255 ); //Set white background 
ofSetColor( 0, 0, 0 ); //Set black color 
ofCircle( 300, 100, 40 ); //Blossom 
ofLine( 300, 100, 300, 400 ); //Stem 
ofTriangle( 300, 270, 300, 300, 200, 220 ); //Left leaf 
ofTriangle( 300, 270, 300, 300, 400, 220 ); //Right leaf

在运行此代码时,您将看到下面的“flower”图片:

控制元素的绘制

有许多函数用于控制绘制基元的参数。

1.ofSetColor(r,g,b):这个函数设置绘制基元的颜色,其中r、g和b是整数值,对应于0到255范围内的颜色的红色、绿色和蓝色分量。在调用ofSetColor()之后,所有元素都将使用此颜色绘制,直到另一个调用ofSetColor()为止。我们将在颜色部分更详细地讨论颜色。

2.ofFill()和ofNoFill():这些函数启用和禁用圆形、矩形和三角形等填充形状。在调用ofFill()或ofNoFill()之后,将绘制所有元素的填充或未填充,直到调用下一个函数。默认情况下,形状呈现为填充颜色。添加ofNoFill();ofCircle()之前;在前面的示例中,您将查看所有未填充的形状,如下所示:

3.ofSetLineWidth(lineWidth):此函数将渲染行的宽度设置为lineWidth值,该值具有float类型。默认值是1.0,调用此函数时,如果使用较大的值,则会产生粗线条。它只影响绘制未填充的形状。根据视频卡的不同,线条粗细可以改变到某个限制。通常,这个限制不小于8.0。

注意:添加ofSetLineWidth(7)线;在前面示例中的线条绘制之前,您将看到带有粗垂直线的花,而所有填充形状将保持不变。注意,我们使用值7;这是一个奇数,所以它使对称的线增粗。

请注意,这种获取粗线条的方法很简单,但并不完美,因为相邻线条画得很粗糙。为了获得光滑的粗线条,你应该把它们画成填充形状。

4.ofSetCircleResolution(res):这个函数设置圆的分辨率,也就是用于绘制圆到res的线段的数量。默认值是20,但是使用这样的设置,只有小圆圈看起来不错。对于较大的圆,建议增加圆的分辨率;例如,40或60。添加ofSetCircleResolution(40);在ofCircle(...)之前;在前面的例子中,您将看到一个更平滑的圆。请注意,大的res值会降低项目的性能,因此如果您需要画很多小圆,可以考虑使用小的res值。

5.ofDisableSmoothing():这些函数启用和禁用行平滑。这样的设置可以由显卡控制。在我们的示例中,调用这些函数不会产生任何效果。

性能考虑因素

所讨论的函数适用于包含不超过1000个元素的绘图。当绘制更多的元素时,项目的性能会降低(这取决于视频卡)。原因是每个命令,例如ofSetColor()或ofLine()都是分别发送到绘图的,这需要时间。因此,对于绘制10,000,100,000,甚至100万个元素,您应该使用先进的方法,这些方法可以同时绘制许多元素。在openFrameworks中,您可以使用ofMesh和ofVboMesh类来实现这一点(详细信息请参阅第7章的3D绘图中的使用ofMesh部分)。

使用ofPoint

也许您在考虑前面的花卉示例时注意到了一个问题:通过指定所有顶点的坐标来绘制基元有点麻烦。代码中有太多的数字,所以很难理解元素之间的关系。为了解决这个问题,我们将学习如何使用ofPoint类,然后使用控制点绘制元素。

ofPoint是一个表示二维点坐标的类。它有两个主要的x和y,它们是float类型。

注意:实际上,ofPoint有第三个字段z,所以ofPoint也可以用来表示3D点(我们在第7章“3D绘图”中使用了这个功能)。如果你不指定z,它默认设置为零,所以在这种情况下,你可以认为ofPoint是一个二维点。

带分数的操作

要表示某个点,只需声明ofPoint类的一个对象。

ofPoint p;

若要初始化该点,请设置其坐标。

p.x = 100.0; 
p.y = 200.0;

或者,也可以使用构造函数。

p = ofPoint( 100.0, 200.0 );

你可以像处理数字一样处理点数。如果你有一个点q,下面的操作是有效的:

1.p+q或p-q用坐标(p.x+q.x,p.y+q.y)或(p.x-q.x,p.y-q.y)

2.p*k或p/k提供点(p.x*k,p.y*k)或(p.x/k,p.y/k)

3.p+=q或p-=q 

下面是一些简化二维矢量数学的有用函数:

4.p.length():这个函数返回向量p的长度等于sqrt(p.x*p.x+p.y*p.y)。

5.p.normalize():该函数对点进行规范化,使其具有单位长度p=p/p.length()。此外,当p.length()等于零时,此函数可以正确处理大小写。

注意:请参阅libs/openframeworks/math/ofvec3f.h文件中ofPoint函数的完整列表。实际上,ofPoint只是另一个ofVec3f类的名字,代表3D向量和相应的函数。

所有函数的绘图元素都重载了与ofPoint一起工作的版本:

ofLine(p1,p2)绘制一条线段连接p1和p2;

ofRect(p,w,h)的绘制一个具有左上角p、宽w和高h的矩形;

ofTriangle(p1,p2,p3)绘制一个三角形(p1,p2,p3)的顶点p1、p2和p3;

ofCircle(p,r )绘制一个具有中心p和半径r的圆

使用控制点示例

我们已经准备好解决在使用ofPoint部分开头提到的问题。为了避免在绘图代码中使用大量的数字,我们可以声明一些点并使用它们作为基元绘图的顶点。在计算机图形学,这样的点被称为控制点。

让我们在最简单的花例子中为花指定以下控制点:

现在我们在代码中实现它。

注意:这是示例02-2D/03-FlowerControlPoints。

在testApp.h文件中的testApp类声明中添加以下控制点声明:

ofPoint stem0,stem1,stem2,stem3,leftLeaf,rightLeaf;

然后在testApp::update()函数中为点设置如下值:

stem0 = ofPoint( 300, 100 );

stem1 = ofPoint( 300, 270 );

stem2 = ofPoint( 300, 300 );

stem3 = ofPoint( 300, 400 );

leftLeaf = ofPoint( 200, 220 );

rightLeaf = ofPoint( 400, 220 );

最后,使用这些控制点在testApp::draw()函数中绘制花朵:

ofBackground( 255, 255, 255 ); //Set white background 
ofSetColor( 0, 0, 0 ); //Set black color 
ofCircle( stem0, 40 ); //Blossom 
ofLine( stem0, stem3 ); //Stem 
ofTriangle( stem1, stem2, leftLeaf ); //Left leaf 
ofTriangle( stem1, stem2, rightLeaf ); //Right leaf

您将看到,当使用控制点绘制代码时,代码要容易理解得多。

此外,使用控制点还有一个优点:我们可以很容易地改变控制点的位置,从而获得动画图纸。请参阅02-2D/03-FlowerControlPoints中的完整示例代码。除了已经解释过的代码之外,它还包含了一个根据时间移动leftLeaf和rightLeaf点的代码。因此,当您运行代码时,您将看到带有活动叶子的花朵。

坐标系转换

有时我们需要平移、旋转和调整图形大小。例如,街机游戏是基于人物在屏幕上移动。

当我们使用控制点进行绘图时,用于平移、旋转和调整图形大小的直接解决方案是使用相应的数学公式对控制点应用期望的变换。这样的想法是可行的,但有时会导致代码中的复杂公式(特别是当我们需要旋转图形时)。更优雅的解决方案是使用坐标系/值转换。这是一种在绘图过程中临时改变坐标系的方法,它允许你在不改变绘图算法的情况下平移、旋转和调整绘图大小。

注意:当前的坐标系框架是用一个矩阵表示的openFrameworks。所有的坐标系变换都是通过以某种方式改变这个矩阵来实现的。当openFrameworks使用修改后的坐标系矩阵绘制某些内容时,它执行的计算量与原始矩阵完全相同。这意味着您可以应用任意多的坐标系转换,而不会降低绘图的性能。

坐标系转换在openFrameworks中管理,具有以下功能:

ofPushMatrix():此函数将当前坐标系推入矩阵堆栈。这个堆栈是一个特殊的容器,用于保存坐标系统矩阵。它使您能够恢复坐标系,当你不需要它们时进行转换。

ofPopMatrix()这个函数从矩阵堆栈中弹出最后添加的坐标系,并使用它作为当前的坐标系。您应该注意保证ofPopMatrix()调用的数量不超过ofPushMatrix()调用的数量。

注意:虽然在testApp::draw()调用之前已经恢复了坐标系调用,但是我们建议您的项目中的

ofPushMatrix()和ofPopMatrix()调用的数量应该完全相同。它将简化项目的调试和进一步的开发。

ofTranslate(x,y)或ofTranslate(p):此函数将当前坐标系移动到向量(x,y),或者等效地移动到向量p。如果x和y等于零,坐标系不变。

ofScale(scaleX,scaleY)这个函数在x轴上缩放scaleX的当前坐标系,在y轴上缩放scaleY。如果两个参数都等于1.0,那么坐标系保持不变。值-1.0表示反转坐标轴在相反的方向。

ofRotate(angle):这个函数将电流坐标系围绕其原点顺时针旋转度。如果角度值等于0,或者k*360与k为整数,坐标系值保持不变。

所有转换都可以以任何顺序应用;例如,平移、缩放、旋转,再次转动,等等。

这些功能的典型用法如下:

1.使用ofPushMatrix()存储当前变换矩阵。

2.通过调用以下任意一个函数来改变坐标系:

ofTranslate(),ofScale()或者ofRotate()。

3.画点东西。

4.使用ofPopMatrix()恢复原来的变换矩阵。

注意:步骤3可以再次包括步骤1到步骤4。

例如,要将坐标系的原点移动到屏幕中央,可以在testApp中使用以下代码::draw():

ofPushMatrix(); 
ofTranslate( ofGetWidth() / 2, ofGetHeight() / 2 ); 
//Draw something 
ofPopMatrix();

如果你替换//画一些注释到ofCircle(0,0,100);,你会看到在屏幕中心的圆圈。

注意:这种转换极大地简化了,应该位于屏幕中央的图纸。

现在,让我们使用坐标系变换为花朵添加三角形花瓣。

注意:要进一步探索坐标系/图像转换,请参阅第4章图像和纹理旋转图像部分的例子。

花瓣的例子

在这个例子中,我们从02-2D/03-FlowerControlPoints示例中为花朵绘制花瓣,在Usingcontrolpoints示例部分中进行了描述。

注意:这是02-2D/04-FlowerWithPetals的例子。

我们要在这里绘制未填充的形状,所以在开头添加以下线条testApp::draw () :

ofNoFill(); //Draw shapes unfilled

现在在testApp::draw()的末尾添加下面的代码来绘制花瓣:

ofPushMatrix(); //Store the coordinate system 
//Translate the coordinate system center to stem0 
ofTranslate( stem0 ); 
//Rotate the coordinate system depending on the time 
float angle = ofGetElapsedTimef() * 30; 
ofRotate( angle ); 
int petals = 15; //Number of petals 
for (int i=0; i<petals; i++) { 
//Rotate the coordinate system 
ofRotate( 360.0 / petals ); 
//Draw petal as a triangle 
ofPoint p1( 0, 20 ); 
ofPoint p2( 80, 0 ); 
ofTriangle( p1, -p1, p2 ); 
} 
//Restore the coordinate system 
ofPopMatrix();

该代码将坐标系原点移动到stem0点(花朵的中心),并根据当前时间旋转它。然后它旋转坐标系在一个固定的角度和绘制一个三角形花瓣倍。因此,我们得到了许多围绕着点stem0缓慢旋转的三角形。

颜色

到目前为止,我们使用ofSetColor(r,g,b)和ofBackground(r,g,b)函数处理颜色。通过调用这些函数,我们将当前绘图和背景的颜色指定为r、g和b值,对应于红色、绿色和蓝色组件,其中r、g和b是0到255之间的整数值。

注意:当您需要指定灰色时,您可以使用这些函数的重载版本,只有一个参数:ofSetColor(gray)和ofBackground(gray),其中灰色在0到255之间。

这些功能很简单,但还不够。有时,您需要将颜色作为函数中的单个参数传递,还需要进行颜色修改,比如更改亮度。为了解决这个问题,openFrameworks提供了color类。它可以让我们像操作单个实体一样操作颜色,并修改这些实体。

ofColor是一个代表颜色的类。它有四个float字段:r、g、b和a。这里r、g和b是颜色的红色、绿色和蓝色组成部分,a是alpha组成部分,表示颜色的不透明度。阿尔法分量与透明度有关,在第4章图像和纹理的透明部分有详细讨论。

在本章中,我们将不考虑alpha分量。默认情况下,它的值等于255,这意味着真正不透明的颜色,所以本章中考虑的所有颜色都是不透明的。

注意:ofSetColor()、ofBackground()和ofColor()函数包含alpha组。作为可选的最后一个参数,因此可以在需要的时候指定。

带颜色的操作

要表示某种颜色,只需声明ofColor类的一个对象。

ofColor color;

若要初始化颜色,请设置其组件。

color.r = 0.0;

color.g = 128.0;

color.b = 255.0;

或者,等效地,使用构造函数。

color = ofColor( 0.0, 128.0, 255.0 );

您可以在ofSetColor()和ofBackground()。例如,ofSetColor(color)和ofBackground(color)。

openFrameworks有许多预定义的颜色,包括白色、灰色、黑色、红色、绿色、蓝色、青色、洋红色和黄色。请参阅libs/openframeworks/types/ofcolors.h文件中的完整颜色列表。若要使用预定义的颜色,请在这些名称之前添加ofColor::前缀。例如,ofSetColor(ofColor::yellow)将当前绘图颜色设置为黄色。

你可以使用以下函数来修改颜色:

1.setHue(hue)、setSaturation(saturation)和setBrighness(brighness):这些函数将颜色的色调、饱和度和亮度改变为指定值。所有参数都是0到255范围内的浮点值。

2.setHsb(hue、saturation、brighness):这个函数通过指定颜色的色调、饱和度和亮度值创建颜色,其中参数是0到255范围内的浮点值。

3.getHue()和getSaturation():这些函数返回颜色的色调和饱和度值。

4.getBrightness():此函数返回最亮的颜色分量。

5.getLightness():此函数返回颜色组件的平均值。

6.invert():这个函数将颜色分量反转,也就是说,颜色的r、g和b字段分别变为255-r、255-g和255-b。

让我们考虑一个演示颜色修改的示例。

颜色修改示例

在这个例子中,我们将通过改变整个范围内的亮度、饱和度和色调来修改红色,并绘制三条结果条纹。

注意:这是例子02-2D/05-Colors。

此示例项目基于openFrameworksemptyExample项目。用以下代码填充testApp::draw()函数的主体:

ofBackground( 255, 255, 255 ); //Set white background 
//Changing brightness 
for ( int i=0; i<256; i++ ) { 
ofColor color = ofColor::red; //Get red color 
color.setBrightness( i ); //Modify brightness 
ofSetColor( color ); 
ofLine( i, 0, i, 50 ); 
} 
//Changing saturation 
for ( int i=0; i<256; i++ ) { 
ofColor color = ofColor::red; //Get red color 
color.setSaturation( i ); //Modify saturation 
ofSetColor( color ); 
ofLine( i, 80, i, 130 ); 
} 
//Changing hue 
for ( int i=0; i<256; i++ ) { 
ofColor color = ofColor::red; //Get red color 
color.setHue( i ); //Modify hue 
ofSetColor( color ); 
ofLine( i, 160, i, 210 ); 
}

注意:运行该项目,你会看到三个条纹组成的红色与改变亮度、饱和度和色调。

如您所见,更改亮度、饱和度和色调类似于AdobePhotoshop和Gimp等照片编辑器中使用的颜色校正方法。从设计者的角度来看,与直接指定红色、绿色和蓝色组件相比,这是一种更强大的控制颜色的方法。

注意:在第3章,构建一个简单的粒子系统,定义粒子函数一节中,我们可以看到一个使用描述的颜色修改方法的例子。

现在我们将考虑如何在未清除背景的情况下进行绘图,这在许多与2D图形相关的创造性编码项目中非常有用。

背景不清晰的绘图

默认情况下,每次在调用testApp:draw()之前都会清除屏幕,因此您需要在testApp::draw()内部一次又一次地绘制屏幕的所有内容。

这在大多数情况下是适当的,但有时我们希望屏幕积累我们的图纸。在openFrameworks中,您可以通过禁用ofSetBackgroundAuto(false)函数来清除屏幕。所有连续的图纸都将在屏幕上累积。(在这种情况下,应该很少调用ofBackground(),仅用于清除当前屏幕)。

这种方法使用起来非常简单,但是对于严肃的项目来说不够灵活。此外,目前它还存在一些问题:

在Windows中,屏幕抓取不起作用(更多关于屏幕抓取的细节可以在本章后面的屏幕抓取部分中看到)

注意:请参阅第六章与声音一起工作的反弹球例子一节中使用这种方法的一个例子。

所以,当你需要积累图纸时,我们建议你使用FBO缓冲区,我们现在会解释。

Fbo在幕后绘图中的应用

计算机图形学中的FBO代表帧缓冲区对象。这是一个离屏光栅缓冲区,openFrameworks可以像在屏幕上一样绘制。您可以在此缓冲区中绘制某些内容,然后在屏幕上绘制缓冲区内容。每次testApp::draw()调用都不会清除缓冲区中的图片,因此您可以对累积的图片使用FBO。

在openFrameworks中,FBO由类ofFBO表示。其典型用法如下:

1.在testApp类声明中声明一个ofFbo对象fbo。

ofFbo fbo;

2.在testApp:setup()函数中用一些大小初始化fbo。

int w = ofGetWidth();

int h = ofGetHeight();

fbo.allocate( w, h );

3.在fbo里画点东西。不仅可以在testApp::draw()中实现,还可以在testApp::setup()和testApp::update()中实现。要开始绘图,请调用fbo.begin()。在此之后,所有绘制命令,例如ofBackground()和ofLine(),都将绘制到fbo。要完成绘图,调用fbo.end()。例如,要用白色填充fbo,请使用以下代码:

fbo.begin(); 
ofBackground( 255, 255, 255 ); 
fbo.end();

4.使用fbo.Draw(x,y)或fbo.Draw(x,y,w,h)函数在屏幕上绘制fbo。在这里,x和y是左上角,w和h是在屏幕上渲染fbo图像的可选宽度和高度。绘图应该在testApp::draw()函数中完成。相应的代码示例如下:

ofSetColor( 255, 255, 255 ); 
fbo.draw( 0, 0 );

注意:ofFbo类具有类似于ofImage类的绘图行为。因此,这里需要使用ofSetColor(255,255,255);线来绘制没有色彩调制的fbo(详见第4章,图像和纹理中的色彩调制部分)。

您可以使用许多FBO对象,甚至可以在另一个内部绘制一个。例如,如果你有ofFbo,ofFbo2,你可以在fbo2内部画fbo如下:

fbo2.begin(); 
ofSetColor( 255, 255, 255 ); 
fbo.draw( 0, 0 ); 
fbo2.end();

注意:如果调用FBO.begin(),则应该始终调用FBO.end();在任何地方绘制FBO的内容之前都应该这样做。

下面的小贴士将有助于你更好的使用ofFbo:

1.Fbo具有纹理类型的纹理,它保存当前的图片。可以使用fbo.getTextureReference()访问纹理。关于纹理操作的细节,请参阅第4章图像和纹理中的内存优化使用ofTexture一节。

2.视频卡的设置,比如抗锯齿平滑,不会影响FBO,所以当您使用FBO执行此绘图时,屏幕上的平滑绘图可能会出现锯齿。平滑图形的一个可能的解决方案是使用fbo,它是屏幕大小的两倍,并在绘图过程中将fbo缩小到屏幕大小。

3.当您对fbo执行半透明绘制(启用alpha混合)时,在屏幕上绘制fbo本身时,最可能应该禁用alpha混合。在相反的情况下,fbo的透明像素将在屏幕上再次被混合,因此生成的图片将被过度混合。关于混合的细节,请参阅第4章图像和纹理中的透明部分。

4.默认情况下,fbo将其像素的颜色组件保存为无符号字符值。当需要更高的精度时,可以使用浮点值fbo,方法是将它与可选的最后一个参数GLrgb32farb一起分配。

fbo.allocate( w, h, GL_RGB32F_ARB );

在第3章,构建一个简单的粒子系统的项目章节中,可以看到一个使用这种方法实现粒子的例子。

让我们考虑一个使用ofFbo对象进行累积绘图的例子。

螺旋星系的例子

考虑一个包含以下步骤的绘图算法:

1.设置a=0和b=0。

2.将pos点的位置设置为屏幕中心。

3.设置a+=b。

4.设置b+=0.5。

5.控件定义的方向上移动pos点一个固定长度的步骤角度a以度数来衡量。

6.每100步将绘图颜色改变为一种随机生成的新颜色。

7.在pos的最后位置和当前位置之间画一条线。

8.进入第三步。

该算法是一种生成式艺术算法ーー简短,能生成有趣而意想不到的图形。

该算法的结果将是屏幕上移动的词的彩色轨迹的图片。值呈线性增长,因此a值呈抛物线增长。A的值是一个角度,它定义了将要移动的步骤pos。角度呈抛物线变化时,台阶的行为很难预测,因此很难想象结果曲线会是什么样子。那么让我们来实现这个算法,看看它。

我们将使用ofFbo fbo对象来保存生成的图片。

注意:这是例子02-2D/06-螺旋。

该示例基于openFrameworks中的emptyExample项目。在testApp.h文件的testApp类声明中,为a、b、pos、fbo和一些其他变量添加声明。此外,我们声明函数draw1(),它通过执行绘图算法的步骤3至7绘制一条线段。

double a, b; //Angle and its increment 
ofPoint pos, lastPos; //Current and last drawing position 
ofColor color; //Drawing color 
int colorStep; //Counter for color changing 
ofFbo fbo; //Drawing buffer 
void draw1(); //Draw one line segment

注意,a和b被声明为double。原因是a增长很快,所以浮点数的精度不足以进行稳定的计算。然而,我们也将玩浮点数的情况下,在玩与数值不稳定节。

testApp::setup()函数初始化fbo缓冲区,用白色填充它,并为所有变量设置初始值。

void testApp::setup(){ 
ofSetFrameRate( 60 ); //Set screen frame rate 
//Allocate drawing buffer 
fbo.allocate( ofGetWidth(), ofGetHeight() ); 
//Fill buffer with white color 
fbo.begin(); 
ofBackground( 255, 255, 255 ); 
fbo.end(); 
//Initialize variables 
a = 0;
b = 0; 
pos = ofPoint( ofGetWidth() / 2, ofGetHeight() / 2 ); 
//Screen center 
colorStep = 0; 
}

testApp::update()函数通过调用draw1()函数在fbo中绘制线段。请注意,为了快速获得结果曲线,我们一次执行200个绘图。

void testApp::update(){ 
fbo.begin(); //Begin draw to buffer 
for ( int i=0; i<200; i++ ) { 
draw1(); 
} 
fbo.end(); //End draw to buffer 
}

testApp::draw()函数只是在屏幕上绘制fbo。

void testApp::draw(){ 
ofBackground( 255, 255, 255 ); //Set white background 
//Draw buffer 
ofSetColor( 255, 255, 255 ); 
fbo.draw( 0, 0 ); 
}

注意,调用background()在这里是不必要的,因为fbo占据了整个屏幕,但是我们对其他项目的调用是一致的。

最后,我们应该为draw1()函数添加一个定义。

void testApp::draw1(){ 
//Change a 
a += b * DEG_TO_RAD; 
//a holds values in radians, b holds values in degrees, 
//so when changing a we multiply b to DEG_TO_RAD constant 
//Change b 
b = b + 0.5; 
//Shift pos in direction defined by angle a 
lastPos = pos; //Store last pos value 
ofPoint d = ofPoint( cos( a ), sin( a ) ); 
float len = 20;
pos += d * len; 
//Change color each 100 steps 
if ( colorStep % 100 == 0 ) { 
//Generate random color 
color = ofColor( ofRandom( 0, 255 ), 
ofRandom( 0, 255 ), 
ofRandom( 0, 255 ) ); 
} 
colorStep++; 
//Draw line segment 
ofSetColor( color ); 
ofLine( lastPos, pos ); 
}

在最初的算法中,在部分的开头描述,a和b是度量。在openFrameworks实现中,我们决定以度数为b,以弧度为a。原因将在后面的数值不稳定性部分中解释。因此,在代码中,我们使用DEG_TO_RAD常量的乘法将度转换为弧度,该常量在openFrameworks中定义,PI/180度。

a += b * DEG_TO_RAD;

运行这个项目,你会看到一条两端呈螺旋状它们的颜色不断变化的曲线

曲线的这种特殊行为是由下面一行中的0.5参数决定的:

b = b + 0.5;

这个参数定义了增加b的速度。把这个参数改成5.4,你会看到4和12条螺旋线的曲线,如下图所示:

尝试自己的参数值。如果结果曲线太大,不适合屏幕,你可以通过改变下面一行的len值来控制它的比例:

float len = 20;

例如,如果您将len设置为10,那么结果曲线会收缩两次。

玩弄数字不稳定性

在openFrameworks代码中,我们将a和b声明为双值。在表示数字时,double类型比float具有更高的准确性,这在本例中是必不可少的,因为a增长很快。

但是如果我们声明a和b是浮点数会发生什么呢?动手吧!将行double a,b替换为float a,b,然后运行该项目。您将看到,在运行时间的第一秒钟,结果曲线将等于从双壳开始的曲线。然后,螺旋的中心开始移动。

渐渐地,双螺旋结构将被破坏,曲线将表现出意想不到的行为,画出不同大小的圆圈。

这种不稳定性的原因是计算值的数值是不准确的。

请注意,所利用的不稳定性效果可能依赖于您的CPU的浮点运算,因此您的结果图片可能不同于所提供的截图。

注意:在物理仿真、优化规划等许多重要任务中,需要得到准确的计算结果,这样的计算不稳定性是不允许的。但从创造性编码和生成性艺术领域的角度来看,这种不稳定性让你创造出有趣的视觉或音频效果。因此,这种不稳定性往往是允许的,也是可取的。更多关于这些过程的数学细节,请阅读确定性混沌理论。

现在将b=b+0.5中的参数0.5改为17,您将看到各种各样的形状,包括三角形、正方形、七边形和星星。然后试试4,21和你自己的值。通过这个简单的绘图算法,您将看到大量相似但不同的图片。

最后,请注意该算法的主要计算行如下:

a += b * DEG_TO_RAD; 
//... 
b = b + 0.5; 
//... 
ofPoint d = ofPoint( cos( a ), sin( a ) );

它们对任何变化都非常敏感。如果以某种方式改变它,结果曲线将会不同(在float情况下)。在这个意义上,这种创造性的编码可以被认为是艺术,因为它在很大程度上依赖于最小的代码细微差别,而这些细微差别往往是无法预测的。

抓屏

有时可以将项目绘制的图片保存到文件中。您可以使用您的操作系统的工具来完成,但是在您的项目中这样做会更舒服。因此,让我们看看如何将项目屏幕的内容保存到图像文件中。

出于这样的目的,我们需要使用ofImage类来处理图像。尽管在第4章图像和纹理中考虑了这个类,但是对于屏幕抓取来说,仅仅理解ofImage对象持有一个图像就足够了。

下面的代码在按空格键时将当前屏幕保存为文件。它应该被添加到testApp::keyPressed()函数中,如下所示:

//Grab the screen image to file 
if ( key == ' ' ) { 
ofImage image; //Declare image object 
//Grab contents of the screen 
image.grabScreen( 0, 0, ofGetWidth(), ofGetHeight() ); 
image.saveImage( "screen.png" ); //Save image to file 
}

Image.grabScreen()函数的参数指定抓取的矩形。在我们的例子中,它是整个项目的屏幕。

此代码在02-2D/06-Spirals示例中实现。运行它并按空格键;屏幕的内容将保存到项目文件夹中的bin/data/screen.png文件中。

png文件很小,而且质量很好,所以我们经常使用它们来抓屏。但是,写入PNG文件需要一些时间,因为图像必须进行压缩。根据CPU和图像大小的不同,这需要几秒钟的时间。因此,如果您需要快速保存图像,请使用BMP文件格式。

image.saveImage( "screen.bmp" );

其他话题

在这一章中,我们已经考虑了一些二维绘图的基本主题。为了进一步阅读openFrameworks2d功能,我们建议以下主题:

1.使用drawbitmapstring()函数或truettypefont类绘制文本。请参阅openFrameworks示例/graphics/fontShapesExample。

2.使用beginshape()、ofVertex()和ofEndShape()函数绘制填充形状。请参阅openFrameworks示例/graphics/polygonExample。

3.使用openFrameworks绘图创建PDF文件。这些文件将包含适合于高质量打印目的的矢量图形。请参阅openFrameworks示例/graphics/pdfexample。

为了更深入地探索2d图形世界,我们建议以下主题:

1.用柏林噪声模拟物体的仿生运动。见附录b,柏林噪音。

2.利用递归算法绘制树状分支结构。

如果你对生成艺术感兴趣,可以去openprocessing.org大学探索处理草图的庞大基地。Processing是一个用于创造性编码的免费的基于java的语言和开发环境。它与openFrameworks非常相似(在某种程度上,openFrameworks是作为Processing的c++版本创建的)。大多数Processing示例处理二维图形,是生成式艺术项目,可以很容易地移植到openFrameworks。

摘要

在这一章中,我们学习了如何使用控制点绘制几何基元,执行坐标系变换,以及使用颜色。此外,我们还研究了如何在屏幕外缓冲区积累绘画,并考虑了使用它的生成艺术实例。最后,我们学习了如何将当前屏幕图像保存到文件中。

在下一章,我们将继续学习2D图形,并将考虑一个强大的方法,生成迷人的动画和绘图粒子系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白茶等风12138

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值