查看原文
其他

写给设计师的 OF 编程指南- 打造 Photoshop笔刷系统

2016-12-26 Wenzy InsLab

从这节开始,内容会变得有趣一些。不再用大篇幅去阐述基础概念,而是更多地探寻图像世界背后的构筑原理。

下面将从数字绘画的角度切入,剖析 Photoshop 上的笔刷系统,并最终在 Openframeworks 上进行模拟。

在此之前,我们在 Openframeworks 上绘图更多是使用默认的函数,这些绘图函数往往是边缘锐利,造型工整的点线面,计算机生成的痕迹非常重。若掌握以下内容,就能把数字绘画的经验复用到程序上。作为一种补充,可以创造各种肌理,模拟各类手绘效果,让画面更有呼吸感。

纸上绘画与数字绘画

在对笔刷进行剖析之前,必须要了解数字绘画到底是什么。对于很多人来说,电脑画图工具的基本印象,最早来源于 Windows 画板。


这画板麻雀虽小,但里面已经包含绘画工具的许多基本要素:橡皮,可调节大小的画笔,色板,吸管等等。在久远的年代,使用这类“朴素”工具创作的数字绘画作品,风格往往是这样的。


在此之后,伴随各种工具的进步和普及。数字绘画的发展十分迅速。现在已经能用电脑绘制出这样的作品。





(作者:Craig Mullins)

数字绘画风格之多,就不再一一列举。这里只选了概念设计领域的 Craig Mullins 作为代表。他本身在传统绘画上就有很深的造诣,并且和数字绘画结合得非常好。它在笔刷方面的研究和应用,影响了一批后来者。

当你看过足够多的数字绘画作品之后,会发现只要有时间和技巧,几乎没有用电脑画不出的画面。这种从画风到精致度的重大转变,不是由于创作者们的整体绘画水平提高了。而是源于以下两个方面。

硬件的进步

有了手绘板数位屏等工具,使得运笔可以更精确,有了压感可以捕捉下笔力度。同时显示器,CPU,GPU 的更新迭代,可以显示色彩更艳丽,分辨率更高的图片。

(Wacom手写板)


(微软的 SurfaceStudio)

软件的进步

设计理念的改变,最重大的变化我认为是强化了笔刷系统,可以更灵活地模拟各种肌理材质。

(Photoshop)


(Sai)

(Painter)

这两方面综合起来,使得数字绘画的创作效率与质量都得到了空前提高。

Photoshop 中的笔刷使用方法

这节的重心是要理解笔刷的工作原理,并最终用代码去实现。我们得先从老祖宗 Photoshop 上去取经。

Photoshop 不仅仅是设计师用来“P图”的。得益于它强大的笔刷库,对原画师,插画师而言,几乎已经成为首选的数字绘画工具。不论你之前是否有接触和使用,下面都会从零开始,介绍它的基本使用方法。

1.安装并打开 Photoshop。在左侧的工具栏选择笔刷工具(可按快捷键 B)。

2.在选择笔刷工具之后,按右键会弹出笔刷面板。点击下方的列表就能够选择不同类型的笔刷。

3.点击右上角的“小齿轮”图标,会弹出一个选项卡。上方的选项可以切换显示模式。下方选项则可以切换不同的笔刷库。PS 中自带好几套笔刷,如“基本画笔”,“混合画笔”等等。

4.选中笔刷后。就可以按住左键在画布上拖动,就能开始绘制了。下面是使用Photoshop自带笔刷中的基本画笔进行的测试。分别为“硬边机械”和“柔边机械”。硬边机械顾名思义,边缘是硬的。而柔边机械有点像喷枪,可以做一些柔和的过渡。

从外部加载笔刷

PS 内置笔刷的绘制效果其实还是有一定局限。很多人在数字绘画上进行了很多摸索,积累了大量经验。为了方便创作,他们会自行定制各种笔刷来提高创作效率。这里推荐几位笔刷作者 CraigMullins,Andrewjones,杨雪果。初学者可以从杨雪果老师制作的 blur‘s good brush 笔刷库入门,它分类细致,比较推荐。

在网上下载完笔刷后,可以通过“小齿轮”图标弹出的选项卡中,选“载入画笔”或“替换画笔”。

下面是使用外部笔刷进行的一些测试


寥寥几笔,就能产生复杂的肌理效果。

在 Photoshop 中制作笔刷

要真正地理解笔刷,仅会使用是远远不够的。要透彻理解更好的方式是自己动手制作一个。

方法非常简单。先在 PS 中新建一个图层,绘制一个基本形状。在绘制过程可以使用任何颜色,但程序最终都会进行灰度化处理。颜色越浅,笔刷的透明度就越低。当绘制的颜色是黑色,黑色部分则是不透明的。

这里尝试绘制了几个圆点,再用选区工具框选。这个区域,就会作为笔刷的基本型。

接着在菜单栏选择-编辑-自定义画笔预设。就可以开始给笔刷取名。


按确定后,自制的笔刷就会自动收录到现有笔刷库的末尾。

可以选中它进行试画
在绘制过程中,PS 会将笔刷的形状作为基本的图形单元进行重复。现在简单的一笔,就能产生多根线条。除此之外,我们还可以调出控制面板进行修改。选择菜单栏上的“窗口”-“画笔”(快捷键为 F5)。以下是面板的默认设置。

面板非常重要,所有笔刷都是根据图片素材,再结合调节面板上的参数制作而来的。

例如,在“画笔笔尖形状” 上调节间距,就会改变笔刷元素之间的距离


从中可以发现。原来我们一直以为是连续的画笔,本质是由一个个点串联而成的。当间距比较小的时,看起来就是连续的。如果调大,就会成为一个个独立散布的点。

(间距为 30 时)

除了从“画笔笔尖形状”可以通过调节间距来影响笔刷效果。下方的“形状动态”也有同样的功能。勾选它以后,便会增加更多调控参数

从面板上可以看到很多个“抖动”参数。所谓的抖动其实就是随机。大小抖动是指笔刷在绘制过程中会随机变化,角度抖动是指笔刷会随机旋转,圆度抖动是指笔刷的形状的随机拉伸。其中数值的大小会影响随机的范围。

下面是使用角度抖动后的效果

请试着自己创建一个笔刷,折腾各种参数去熟悉用法

编程前的准备工作-制作笔刷图片

相信经过上面的实验。你已经对笔刷的工作原理有基本的了解。在程序中模拟笔刷,必须要从外部载入 png 图片素材。

由于 Openframeworks 中图片的着色机制,是根据各个通道色光的放出比例来显示图片的。所以若想笔刷可以自如地设置各种颜色,必须先进行“白化处理”。通过快捷键 Ctrl + U,把明度调到最大。

图片保存时,请记得带上 alpha 通道并保存为 png 图片。

除了用手动的方式去画,你也可以“站在巨人的肩膀上”,直接提取 PS 中现有的笔刷形状。通过使用第三方程序,比如 abrViewver。就可以批量导出笔刷。


但是由于导出图片都是黑色,无法直接使用,使用前依然要在 PS 中进行白化处理

动手创建笔刷系统

基本笔刷

有了笔刷图片之后,就可以开始在 Openframeworks 中大显身手。一开始很容易就能想到,只要载入笔刷图片,再在 mouseDragged 中把绘图函数写进去,拖动鼠标就能在窗口上留下笔刷形状。

代码示例(01):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofImage brush;        float r; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(700,400);    ofBackground(255);    ofSetBackgroundAuto(false);    r = 50;    brush.load("brush.png");    brush.setAnchorPercent(0.5,0.5); } void ofApp::update(){ } void ofApp::draw(){ } void ofApp::mouseDragged(int x, int y, int button){    ofSetColor(0,150);    brush.draw(mouseX,mouseY,r * 2,r * 2); }

运行效果:

但这样有一个非常严重的问题。当绘制速率不稳定,笔刷间的疏密就会很不均匀。为了解决这个问题,需要设定一个参数,让每个笔刷元素之间的间距成为一个定值。

间距控制

代码示例(02):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofImage brush;          float r,brushDist;      float lastX, lastY; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(700,400);    ofBackground(255);    ofSetBackgroundAuto(false);    r = 50;    brushDist = 10;    brush.load("brush.png");    brush.setAnchorPercent(0.5,0.5); } void ofApp::update(){ } void ofApp::draw(){ } void ofApp::mouseDragged(int x, int y, int button){    int brushNum;    float dist = ofDist(mouseX, mouseY, lastX, lastY);    float angle = atan2(mouseY - lastY,mouseX - lastX);    if (dist > brushDist) {        brushNum = int(dist/brushDist);        float newX,newY;        for (int i = 1; i <= brushNum; i++) {            float length = i * brushDist;            newX = lastX + cos(angle) * length;            newY = lastY + sin(angle) * length;            ofSetColor(0,150);            brush.draw(newX,newY,r * 2,r * 2);        }        lastX = newX;        lastY = newY;    } } void ofApp::mousePressed(int x, int y, int button){    if (lastX == 0 && lastY == 0) {        lastX = mouseX;        lastY = mouseY;    } } void ofApp::mouseReleased(int x, int y, int button){    lastX = 0;    lastY = 0; } void ofApp::keyPressed(int key){    if(key == ' '){        ofBackground(255);    } }

运行效果:

(brushDist 为 10 时)

(brushDist 设为 20 时)

代码说明:

  • 现在无论速度怎么变化,都会非常均匀。变量 brushDist 用于设置笔刷的间距。有了这个数值作为参照,就能决定绘制的时机。变量 lastX,lastY 用于保存上个笔刷的坐标位置。而 dist 代表上个笔刷坐标到当前鼠标坐标的距离。当 dist 小于笔刷间距 brushDist,就说明距离太靠近,不应该绘制下一个笔刷。这可以避免绘制过慢时导致笔刷过密。而当 dist 大于等于 brushDist,才会开始绘制。

  • 变量 brushNum 代表在一帧里面绘制多少个笔刷图形,它可以避免速度过快而导致中途“丢笔刷”。做一个极端的假设,假定笔刷间距为 20。程序上一帧的笔刷绘制坐标在(0,0),下一帧鼠标坐标却瞬间跳到(0,400)。那在 mouseDragged 函数里,一帧的时间内只画一个笔刷显然是不行的。它需要在这一帧内,填补 20 个笔刷图形才可能使得前后整个笔刷造型是连贯的。具体的填补位置,通过计算运笔的角度以及结合三角函数就能得出

  • 若是不添加 mousePressed 和 mouseReleased 函数中的语句,那么绘制一笔后,再画第二笔时。程序就会自动把第一笔提笔的地方,和第二笔下笔的地方相连。为了解决这个问题,可以在提笔时将lastX,lastY 的坐标设成 0,将它作为结束的标志。而当新的一笔正式下笔,就重新把数值替换为 mouseX,mouseY,就能避免这个情况发生

模拟角度抖动,大小抖动,圆度抖动

要实现 PS 笔刷中的角度抖动,大小抖动,圆度抖动非常简单。在上段代码中,只需要修改 mouseDragged 函数的一小段即可

角度抖动模拟

代码示例(03):

if (dist > brushDist) {    brushNum = int(dist/brushDist);    float newX,newY;    for (int i = 1; i <= brushNum; i++) {        float length = i * brushDist;        newX = lastX + cos(angle) * length;        newY = lastY + sin(angle) * length;        ofSetColor(0,150);        ofPushMatrix();        ofTranslate(newX,newY);        ofRotate(ofRandom(360));        brush.draw(0,0,r * 2,r * 2);        ofPopMatrix();    }    lastX = newX;    lastY = newY; }

运行效果:

(角度抖动 360 度)

(角度抖动 20 度)

代码说明:

  • 为了要让图片绕中心旋转,需要用到 ofTranslate 函数

大小抖动模拟

代码示例(04):

if (dist > brushDist) {    brushNum = int(dist/brushDist);    float newX,newY;    for (int i = 1; i <= brushNum; i++) {        float length = i * brushDist;        newX = lastX + cos(angle) * length;        newY = lastY + sin(angle) * length;        ofSetColor(0,150);        ofPushMatrix();        ofTranslate(newX,newY);        float randomSize = ofRandom(0,1);        brush.draw(0,0,r * 2 * randomSize,r * 2 * randomSize);        ofPopMatrix();    }    lastX = newX;    lastY = newY; }

运行效果:

圆度抖动模拟

代码示例(05):

if (dist > brushDist) {    brushNum = int(dist/brushDist);    float newX,newY;    for (int i = 1; i <= brushNum; i++) {        float length = i * brushDist;        newX = lastX + cos(angle) * length;        newY = lastY + sin(angle) * length;        ofSetColor(0,150);        ofPushMatrix();        ofTranslate(newX,newY);        float randomSize = ofRandom(0.2,1);        brush.draw(0,0,r * 2,r * 2 * randomSize);        ofPopMatrix();    }    lastX = newX;    lastY = newY; }

运行效果:

特殊笔刷-运笔方向控制角度

当我们想制作一些特殊笔刷,比如的旋转角度会跟随方向而变化。

(笔刷的基本形状)

若不希望角度不变

而是想这样

就可以用以下代码实现

代码示例(06):

if (dist > brushDist) {   brushNum = int(dist/brushDist);   float newX,newY;   for (int i = 1; i <= brushNum; i++) {       float length = i * brushDist;       newX = lastX + cos(angle) * length;       newY = lastY + sin(angle) * length;       ofSetColor(0,150);       ofPushMatrix();       ofTranslate(newX,newY);       ofRotate(ofRadToDeg(angle) + 90);       brush.draw(0,0,r * 2,r * 2 * brush.getHeight()/(float)brush.getWidth());       ofPopMatrix();   }   lastX = newX;   lastY = newY; }

代码说明:

  • 由于不是所有笔刷图片都为规则的正方形,通过 brushPic.height/(float)brushPic.width 可以保证图片使用原比例显示。

辉光画笔

PS 中的某些笔刷是需要在特殊的图层模式下才能发挥最佳效果。例如描绘火焰,星光的笔刷。在 Openframeworks中,也有和 PS 相对应的图层叠加模式。我们可以通过 ofEnableBlendMode 来进行设置。

代码示例(07):

if (dist > brushDist) {    brushNum = int(dist/brushDist);    float newX,newY;    for (int i = 1; i <= brushNum; i++) {        float length = i * brushDist;        newX = lastX + cos(angle) * length;        newY = lastY + sin(angle) * length;        ofEnableBlendMode(OF_BLENDMODE_ADD);        ofSetColor(ofColor::fromHsb(ofRandom(255),255,255,ofRandom(30,100)));        ofPushMatrix();        ofTranslate(newX,newY);        ofRotate(ofRandom(255));        float randomSize = ofRandom(0,1);        brush.draw(0,0,r * 2 * randomSize,r * 2 * brush.getHeight()/(float)brush.getWidth() * randomSize);        ofPopMatrix();    }    lastX = newX;    lastY = newY; }

运行效果:

  • 绘制前先改成深色背景,辉光的显示效果会更佳。其余部分与前面保持不变

End

原本看似复杂的笔刷,其实底层原理都非常简单。以上的实例都是使用鼠标绘制的,没有压感,而且也只用到一个笔刷形状。尽管如此,已经有足够多的变化。

如果你对效果仍不满足,Openframeworks 本身还有相关的类库支持,可以让程序支持手绘板,把压感数据传到程序中。到时,你会有更多的参数去控制画笔的深浅力度,甚至可做出一个比 PS 更加功能强劲的画笔系统。

(矢量画板实验)

本文虽然只涉及到 Photoshop 画笔面板的几个核心功能,但已经能实现 80 % 的画笔效果。这里重新用代码来模拟,不是为了重复造轮子。而是通过理解底层规则,就能创造更多新的可能性。请用好奇心去肢解一切,在图形世界中必然能有所斩获。



源文件下载链接

网盘:https://pan.baidu.com/s/1nuCICWh

Github: https://github.com/Wenzy--/OpenFrameWorksTest

Openframeworks 系列文章

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

[8][9][10][11][12][13]

资源索引

CreativeCoding学习资源索引

Twitter资源索引


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

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