手绘与码绘的比较——实战之梵高《星空》
版权声明:本文为博主原创文章,未经博主允许不得转载。
最近博主的老师在交互媒体设计课上布置了一个作业,是关于“运动”主题的作品创作,恰巧博主之前就有想过要实现一下类似希腊艺术家Petros Vrellis做的基于梵高名作《星空》的交互程序,叫做“Starry Night”。这款程序在苹果的App Store中可以买到,价格是15块钱。原作者用的工具是openframeworks,而博主为了节省时间(作业老多了),选择的是processing。因为本文的主题是“手绘与码绘的比较”,所以整篇文章会以两者异同之处的梳理过程作为主要框架,中间穿插博主实现码绘作品的关键代码。
作品展示
首先,博主先放上自己手绘作品和码绘作品的结果展示(虽然有点丑),原作是由纽约艺术家Aja Kusick模仿梵高的作品创作的。
原作:
手绘作品:
码绘作品:
思考
在看完上面两组作品之后,博主想请大家思考一个问题:为什么要用码绘的方式进行作画?对于“绘画”,编程能够做些什么?其实答案很简单:编程可以实现画不出的效果,接轨前沿艺术。我们通过编程,可以高效率地做出自己想要的图形效果,而这种图形效果的创作过程和原理完全不同于传统的手绘方式:前者本质上是创造了一个小型的空间,我们就是这个空间的主宰者,这个空间中的物体都是客观存在的,它们都有自身的客观属性,都服从我们所定好的规则;后者则不然,手绘所能表现的内容深度可能比码绘要强,但其广度是不如码绘的,比如如果想要以手绘的方式做出动画效果,它所能依赖的只能是人眼本身的视觉暂留机理,我们甚至可以粗鲁地认为两幅内容相同的手绘作品“并不相同”,因为这两者并不存在必然的客观联系,我们所认为的一些联系其实都只是自己的主观臆想而已。
比较
接下来的这一个部分,博主准备从工具、技法、创作体验、呈现效果、载体、局限性、应用这个几个方面进行手绘创作和码绘创作的比较。
(1)工具
工具原指工作时所需用的器具,后引申为达到、完成或促进某一事物的手段。博主的手绘作品是使用了一款叫做Procreate的手绘软件,其功能十分强大,包含了丰富的画笔效果,不好看只是因为博主的手绘能力不行。至于博主所用的码绘工具,当然就是前文所提到的java版的processing。
(2)技法
技法是指艺术家运用他们的工具和材料以获得表现性效果的方法或技术。这一部分也是博主本文所要描述的重点。常用的手绘技法包括对各种色笔的灵活使用、利用例如尺子等工具的作图手段······专业的手绘创作者能够利用这些工具将线条、色调、色彩运用到极致,这是码绘目前很难企及的,像博主所作的码绘作品在对色彩的运用上就远不如手绘灵活和丰富。而对于码绘而言,其技法多基于所使用的编程语言的特性,例如,像博主使用的processing是基于java语言的(当然processing也有js版、python版),而java语言一个很重要的概念就是类。通过类的继承和多态性,我们甚至可以创造出世界上所有的物体,因为所有的物体都是“物体”,而“物体”就是物质世界的根类。在博主的码绘作品中,一共定义了7个类,其中跟粒子有关的3个类之间的关系如下:
粒子类的具体代码如下:
//Particle父类
class Particle{
PVector location; //位置
PVector velocity; //速度
PVector acceleration; //加速度
color p_color; //颜色
float lifespan; //寿命
float p_scale; //缩放大小
PShape s,s1,s2,s3; //形状
//构造函数
Particle(color C, float lp, float sc){
lifespan = lp;
p_color = C;
p_scale = sc;
noStroke();
fill(p_color);
s = createShape(GROUP);
s1 = createShape(RECT,0,0,40,10);
s2 = createShape(ELLIPSE,0,5,10,10);
s3 = createShape(ELLIPSE,40,5,10,10);
s.addChild(s1);
s.addChild(s2);
s.addChild(s3);
}
void run(){
update();
display();
}
void update(){
}
void display(){
}
boolean isDead() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
void applyForce(PVector force) {
PVector f = force.get();
acceleration.add(f);
}
}
//Circle_Particle类,继承于Particle类
class Circle_Particle extends Particle{
PVector center; //旋转中心
PVector radius; //旋转半径
PVector now_dir; //当前位置
float palstance; //角速度
float r_max; //接受向心力的最大半径
Circle_Particle(PVector l,PVector c, color C, float rmin, float rmax, float lp, float sc){
super(C,lp,sc); //调用父类的构造函数
location = l.get();
center = c.get();
location.x = center.x-(center.x-location.x)*random(rmin,rmax);
location.y = center.y-(center.y-location.y)*random(rmin,rmax);
palstance = 0.02;
radius = PVector.sub(location,center);
r_max = radius.mag() + 20;
float velocity_size = radius.mag()*palstance;
PVector tangent = radius.copy();
tangent = tangent.rotate(0.5*PI);
velocity = tangent.normalize().mult(sqrt(velocity_size));
acceleration = radius.normalize().mult(-palstance*sqrt(radius.mag()));
}
//重写updata()函数
void update(){
velocity.add(acceleration);
location.add(velocity);
now_dir = PVector.sub(center,location);
if(now_dir.mag()<= r_max){
acceleration = now_dir.normalize().mult(palstance*sqrt(now_dir.mag()));
}
else{
acceleration = new PVector(0,0);
}
lifespan -= 1;
}
//重写display()函数
void display(){
pushMatrix();
translate(location.x,location.y);
rotate(-(HALF_PI-now_dir.heading()));
scale(p_scale,p_scale);
shape(s,0,0);
popMatrix();
}
}
//Flow_Particle类,继承于Particle类
class Flow_Particle extends Particle{
Flow_Particle(PVector l, PVector v, color C, float rmin, float rmax, float lp, float sc){
super(C,lp,sc);
location = l.get();
location.x = location.x*random(rmin,rmax);
location.y = location.y*random(rmin,rmax);
velocity = v.get();
acceleration = new PVector(0,0);
}
void update(){
velocity.add(acceleration);
location.add(velocity);
lifespan -= 1;
acceleration.x = 0;
acceleration.y = 0;
}
void display(){
pushMatrix();
translate(location.x,location.y);
rotate(velocity.heading());
scale(p_scale,p_scale);
shape(s,0,0);
popMatrix();
}
}
两个子类Circle_Particle和Flow_Particle都继承了它们父类Particle的成员变量和方法,并利用多态性进行了某些方法的重写,以及添加新的成员方法。在这两个类中还用到了random方法,为粒子的初始位置和颜色添加了随机性。如果我们还想继续丰富我们的粒子类,只需要不断地继承和修改、添加就可以了。当然,既然有了粒子类,就少不了管理粒子的粒子系统类(Particle System)。我们可以将所有对粒子的操作全都交给粒子系统去执行,这样就大大降低了代码的复杂度。最终,我们只需要创建几个粒子系统去管理几堆粒子就可以实现片头码绘作品中所呈现的动态效果了。
粒子系统类的具体代码如下:
import java.util.Iterator;
//父类ParticleSystem
class ParticleSystem{
PVector origin; //所管理的粒子的初始位置
color[] Color; //粒子的颜色
int c_length; //Color数据的长度
float rmin;
float rmax;
float lifespan; //粒子寿命
float p_scale; //缩放程度
float range = 50; //进行交互时的作用范围
ParticleSystem(PVector l, color[] C, float min, float max, float lp, float sc){
origin = l.get();
Color = C;
c_length = Color.length;
rmin = min;
rmax = max;
lifespan = lp;
p_scale = sc;
}
//添加粒子
void addParticle(){
}
//对粒子添加作用力
void applyForce() {
}
void run(){
}
}
import java.util.Iterator;
//Flow_PS类,继承于父类ParticleSystem
class Flow_PS extends ParticleSystem{
ArrayList<Flow_Particle> particles;
PVector velocity;
Flow_PS(PVector l, PVector v, color[] C, float min, float max, float lp, float sc){
super(l,C,min,max,lp,sc);
velocity = v.get();
particles = new ArrayList<Flow_Particle>();
}
void addParticle() {
int temp_c;
temp_c = floor(random(0,c_length));
color p_color = Color[temp_c];
particles.add(new Flow_Particle(origin, velocity, p_color, rmin, rmax, lifespan, p_scale));
}
//重写
void applyForce(PVector f, PVector clicked) {
for (Flow_Particle p: particles) {
float temp = PVector.sub(clicked,p.location).mag();
if(temp < range){
p.applyForce(f);
}
}
}
//子类新添加的成员函数
void applyAttractor(Attractor a, int t, int range) {
for (Flow_Particle p: particles) {
PVector force = a.attract(p,t,range);
p.applyForce(force);
}
}
//重写
void run() {
Iterator<Flow_Particle> it = particles.iterator();
while (it.hasNext()) {
Flow_Particle p = it.next();
p.run();
if (p.isDead()) {
it.remove();
}
}
}
}
还有一个Circle_PS子类的代码并未贴出,留给感兴趣的童鞋去思考。
不难看出,码绘设计物体的思路是不同于手绘的。通过分析并挖掘出物体之间的某些共性,我们就能利用代码快速设计出不同属性的物体。除了类的继承和多态之外,码绘还能利用条件语句、循环语句等基本手段来减少绘画的复杂性。例如,通过运用循环语句,我们只要写几行代码就可以绘制成千上万个粒子,这要是换成手绘,其工作量将变得十分巨大。
(3)创作体验
无论是手绘还是码绘,在创作过程中都免不了修改。对于前者来说,“修改”是在原来的作品上除去不满意的部分并进行重新绘制;而对于后者而言,“修改”是抛弃原来所创造的物体,在修改掉世界规则后重新生成一批全新的物体。就博主个人而言,博主觉得前者会带给人一种类似悉心照顾小树苗直至其长成大树的成就感,而后者带给人的则是更加客观、直接的创造感。
(4)呈现效果
根据文章开头的三张图片,我们可以看出虽然手绘作品已经尽量在图形、色彩等方面花心思来表现想要的流动的动态效果,但其本质上仍是静止的;然而码绘作品却能够轻松写意地将星空转动起来。不过,博主并不觉得两者的呈现效果有何高低之分,反而觉得它们各有自己独特的魅力和韵味。
(5)载体
手绘作品的载体包括纸张、墙壁、存储像素数据的图像矩阵等等;而码绘作品的载体则是计算机、显示器等等。
(6)局限性
显然,手绘在表达动态效果的能力上是比较欠缺的,而码绘在对图形、色彩的运用程度上不如手绘灵活。
(7)应用
我们可以通过手绘和码绘的方式进行设计创作,或者用于表现自身的思想情感······码绘甚至能做出各种各样的交互方式,将人们带入到画中去。博主这次也做了简单的交互方式:用户只要用鼠标在画面上进行拖拽,就可以带动附近的粒子朝着鼠标移动的方向运动。
交互部分代码如下(写在draw方法内):
if(mousePressed){
float force = dist(mouseX,mouseY,pmouseX,pmouseY);
PVector temp = PVector.sub(new PVector(mouseX,mouseY),new PVector(pmouseX,pmouseY));
temp.x = temp.x * force * 0.001;
temp.y = temp.y * force * 0.001;
fps1.applyForce(temp ,new PVector(mouseX,mouseY));
fps2.applyForce(temp ,new PVector(mouseX,mouseY));
}
效果如下:
结语
由于这是博主第一次写东西,可能很多语言都不太连贯恰当,论证方式也不够严谨,还望大家多多包含。希望大家在看了博主的文章之后,能够对手绘和码绘这两种方式有了自己的理解和思考。文章中贴的代码也并不全,如果有需要的童鞋可以在下面留言。
参考链接
(1)0.1 用代码画画——搞艺术的学编程有啥用?https://blog.csdn.net/magicbrushlv/article/details/77922119.
(2)1.1 开始第一幅“码绘”——以编程作画的基本方法
https://blog.csdn.net/magicbrushlv/article/details/77840565
(3)The Nature Of Code
https://natureofcode.com/