这次通过对绘画系统的制作、各式笔刷的制作、以及自己去通过系统创作的过程让我感觉到工具在作品实现过程中起到的巨大作用。传统的绘画工具就是一个静止的,本身没有思想和运作规律支撑的一个载体,创作完完全全交给主体,工具或许会体现出很多种的效果,但也完全看主体怎么应用,与它本身的内在部分是毫无关联的。但用码绘的时候的绘画系统则是另一回事儿了,工具本身带着强烈的生命力,激烈的动态变化,多样的颜色,背后靠算法支撑起来的让人赏心悦目的自然规律拟生,它不再是完全被掌控的那个了,主动权,或者说在完成绘画的过程,它和创作者的重要性渐渐持平,差距渐渐减少,所以带给人的视觉冲击力在某种程度上是1+1远远大于2的效果。
理念上其实艺术到底是为谁所生的为谁所用的,这种宏大的命题我不想多加阐述,猎奇是人类的本欲,我不否认这样的码绘因为其独特性和冲击性带来的很多追求新奇的人的追捧很多都是虚无缥缈的,但它的价值其实要留给后人评判,我们既然赶在这个浪尖上就是该去完成试错的工作的,反反复复,但固然是有价值的。
既然都这么说了理念那下面就说说局限性吧,因为它的可能性,潜力变得更大,但其实隐患也在成倍数的增大,最重要的一点我觉得他会给人们带来太多快娱乐产生的精力与灵感过度耗费,将某些创作的质感减少很多,但其实这些只是我的猜想,它才刚刚起步,谁也说不准未来的路是什么样。
绘画系统界面:
绘画系统主体是一块黑色面板,左边可选择颜色、宽度 以及十几种不同的笔刷效果,下方有一个清除按钮。
下面选择几种不同的笔刷大致看一下效果:
a=dist(mouseX, mouseY, pmouseX, pmouseY);
strokeWeight(random(1, 5));
stroke(70,random(0,255));
line(mouseX, mouseY, pmouseX, pmouseY);
rd=int(random(0, 15));
if (rd<2) {
fill(153,204,255, random(100, 255));
}
else if (rd==3) {
fill(71,181,255, random(200, 255));
}
else {
fill(193,83,100, random(100, 255));
}
r=random(1, 15);
noStroke();
ellipse(mouseX+random(3-a, a-3), mouseY+random(-3-a, a-3), r-a/3, r-a/3);
var ox, oy, nx, ny, spd, spd0, spd00, spd00, spd000, spd0000;
var angle = 0, oldAngle = 0, angleDiff, weight, theCanvas;
function Dragged2() {
var adj = 0;
var minWeight = 2;
var maxWeight = 30;
var weightIncrement = 4;
var num,size,distance;
if (typeof ox === 'undefined') {
ox = mouseX;
oy = mouseY;
}
nx = mouseX;
ny = mouseY;
spd = dist(ox, oy, nx, ny) ;
avgSpd = (spd + spd0 + spd00 + spd000 + spd0000) / 5;
if (between2(avgSpd,1,2)) { weight += (weightIncrement * 2) }
if (between2(avgSpd,3,5)) { weight += (weightIncrement) }
if (between2(avgSpd,6, 40)) { weight -= weightIncrement }
if (between2(avgSpd,61,999)) { weight -= (weightIncrement * 2) }
if(avgSpd < 1) { weight += 1 }
weight = Math.max(weight, minWeight);
weight = Math.min(weight, maxWeight);
stroke(255, 250);
strokeWeight(weight);
line(ox, oy, nx, ny);
angle = atan2(ny - oy, nx - ox);
angleDiff = abs(angle, oldAngle);
push();
noStroke();
fill(255,254);
num = Number(angleDiff > 1);
distance = spd * random(1);
for(let i = 0; i < 4; i++) {
size = random(0.5, distance / 8);
ellipse(wobble2(nx, distance), wobble2(ny, distance), size, size);
}
pop();
oldAngle = angle;
ox = nx;
oy = ny;
spd0000 = spd000;
spd000 = spd00;
spd00 = spd0;
sped0 = spd;
}
function wobble2(num, mag) {
return random(0 - mag, mag) + num;
}
function between2(num, a, b) {
return(num >= a && num <= b);
}
var weight = dist(mouseX, mouseY, pmouseX, pmouseY);
strokeWeight(weight)
if (mouseIsPressed == true){
stroke(BrushColor);
line(mouseX, mouseY, pmouseX, pmouseY);
}
stroke(206,21,0,5);
strokeWeight (random(10));
fill (162,16,0,80);
ellipse (mouseX,mouseY,random(0,30),random(0,20));
fill (206,21,0,180);
ellipse (mouseX,mouseY,random(0,30),random(0,20));
ellipse (mouseX+random(-20,25),mouseY+random(-20,25),random(0,5),random(0,5));
stroke(0,0,0,5);
strokeWeight (30);
fill (0,0,0,30);
ellipse (mouseX,mouseY,random(0,30),random(0,20));
rect (mouseX,mouseY,random(15,30),random(-10,20));
var ox, oy, nx, ny, spd, spd0, spd00, spd00, spd000, spd0000;
var angle = 0, oldAngle = 0, angleDiff, weight, theCanvase;
function Dragged10(){
var adj = 0;
var minWeight = 2;
var maxWeight = 32;
var weightIncrement = 2;
var num,size,distance;
if (typeof ox === 'undefined') {
ox = mouseX;
oy = mouseY;
}
line(ox+wobble(10), oy+wobble(10), nx, ny);
nx = quantize(mouseX,20);
ny = quantize(mouseY,20);
spd = dist(ox, oy, nx, ny) ;
avgSpd = (spd + spd0 + spd00 + spd000 + spd0000) / 5;
if (between(avgSpd,1,2)) { weight += (weightIncrement * 2) }
if (between(avgSpd,3,5)) { weight += (weightIncrement) }
if (between(avgSpd,6, 40)) { weight -= weightIncrement }
if (between(avgSpd,61,999)) { weight -= (weightIncrement * 2) }
if(avgSpd < 1) { weight += 1 }
angle = atan2(ny - oy, nx - ox);
angleDiff = abs(angle, oldAngle);
push();
noStroke();
fill(BrushColor);
num = Number(angleDiff > 1);
distance = spd * random(1) + weight;
for(let i = 0; i < 4; i++) {
size = random(0.5, distance / 8);
ellipse(wobble(nx, distance), wobble(ny, distance), weight, weight);
}
pop();
oldAngle = angle;
ox = nx;
oy = ny;
spd0000 = spd000;
spd000 = spd00;
spd00 = spd0;
sped0 = spd;
}
function wobble(num, mag) {
return random(0 - mag, mag) + num;
}
function between(num, a, b) {
return(num >= a && num <= b);
}
function quantize(num, multiple) {
return Math.floor(num / multiple) * multiple;
}
在设计的所有笔刷中,除了最基础的画线笔刷以外,其余笔刷有的只运用简单的函数就能呈现比较好的效果,有的运用了粒子效果。在设计的所有笔刷中,我对上面最后一张图的效果最为满意,所以单独讲一下这个笔刷的设计过程。
这个笔刷的设计灵感来源于我们日常生活中的烟花,从基本上说其实烟花的燃放效果是球形,绝大部分不同的形状效果是从此演变或组合而来的。
在码绘的设计过程中,首先声明了一个二维数组,储存每两个粒子间的距离,一个粒子数组,和一个paused暂停动画。
var distances = [];
var particles = [];
var paused = false;
声明了一个控制背景更新的参数,true时更新背景(粒子没有运动轨迹),false时不更新背景(粒子运动时有轨迹)。
var bg = true;
声明了一个引力常量,一个距离常量,一个控制粒子运动速度和扩散的范围的参数,以及一个控制粒子之间距离的参数。
var G = 0.000005;
var D = 500.0;
var EPSILON = 0.0001;
var LIMIT = 1500;
然后定义了一个粒子类,构造函数中定义了粒子的位置xy坐标以及粒子的速度质量和颜色,以及一个判断粒子是否需要销毁的标记(false为不需要销毁,true为销毁),然后绘制粒子。
class Particle {
constructor(x, y, vx, vy, mass, color){
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.vx = (vx !== undefined) ? vx : 0;
this.vy = (vy !== undefined) ? vy : 0;
this.mass = (mass !== undefined) ? mass : 0;
this.color = (color !== undefined) ? color : color(0,0,0);
this.toDestroy = false;
stroke(this.color);
strokeWeight(this.mass / 4 + 1);
point(x, y);
}
更新单个粒子函数(函数的输入为粒子的编号id,和粒子数组),先遍历粒子数组particles中的每一个粒子,更新粒子的属性(位置、速度等),然后当粒子的位置处于无法显示的区域的时候,销毁粒子(设置销毁标记为true)。
update(id, particles){
var fx = 0;
var fy = 0;
for(var i=0; i < particles.length; ++i){
var inverseDistance = findInverseDistance(particles, id, i)
fx += particles[i].mass * inverseDistance * (particles[i].x - this.x);
fy += particles[i].mass * inverseDistance * (particles[i].y - this.y);
}
this.vx += fx * G;
this.vy += fy * G;
this.px = this.x;
this.py = this.y;
this.x += this.vx;
this.y += this.vy;
if(this.x < 0 || this.x >= width || this.y < 0 || this.y >= height){
this.toDestroy = true;
}
}
获取当前粒子销毁标记的函数。
drawAndDestroy(){
stroke(this.color);
strokeWeight(this.mass / 4 + 1);
line(this.px, this.py, this.x, this.y);
return this.toDestroy;
}
确定粒子“反转距离”的函数,在距离矩阵中找到两个粒子之间的距离。首先判断如果是同一个粒子,返回“反转距离”,后面调用inverseDistance()函数,计算两个粒子之间的距离。
function findInverseDistance(particles, index1, index2){
if(index1 == index2){
return 0;
} else if(index1 < index2){
if(distances[index1][index2] == null){
distances[index1][index2] = inverseDistance(
particles[index1].x,
particles[index1].y,
particles[index2].x,
particles[index2].y
);
}
return distances[index1][index2];
} else {
if(distances[index2][index1] == null){
distances[index2][index1] = inverseDistance(
particles[index1].x,
particles[index1].y,
particles[index2].x,
particles[index2].y
);
}
return distances[index2][index1]
}
}
计算粒子“反转距离“函数。计算两个粒子之间的距离,EPSILON是一个系数,用来控制粒子的运动速度,得到一个返回值,避免粒子之间的距离太近。
function inverseDistance(x1, y1, x2, y2){
var dist = sq( sq(x1 - x2) * EPSILON + sq(y1 - y2) * EPSILON );
return (dist < LIMIT) ? D / LIMIT : D / dist;
}
在传入参数(x,y)的位置创建一个随机颜色、随机质量的粒子,此处的push()函数是JavaScript中Array对象的方法,作用是在数组的末尾添加指定值,并更新数组的长度。
function addParticle(x, y){
particles.push( new Particle(x, y, 0, 0, random(10), randomColor()) );
}
创建随机颜色。
function randomColor(){
return color(random(255), random(255), random(255));
}
键盘触发函数,点击空格键清空粒子数组,点击P键暂停动画,点击B键保留粒子的移动轨迹。
function keyTyped(){
if(key == ' '){
particles = [];
} else if(key == 'p'){
paused = !paused;
} else if(key == 'b'){
bg = !bg;
}
}
主函数中,点击鼠标,调用addParticle()函数(创建粒子)。paused初始值为false,点击“P”键取反,用来控制后面代码的执行与否(暂停画面),if函数中,bg初始值为true,点击“B”键取反,用来控制背景的更新,更新了距离矩阵,在for循环中建立距离矩阵放当前粒子与其他所有粒子之间的距离,然后更新粒子的位置和速度,判断粒子是否需要销毁,将不需要销毁的粒子放到粒子数组的末尾,等待下一轮的更新。最后更新此时的粒子数组。
if(Brush2){
if(mouseIsPressed){
addParticle(mouseX, mouseY);
}
if( !paused ){
if(bg) background(0, 20);
let pcount = particles.length;
var newParticles = [];
distances = new Array(pcount);
for(var i=0; i < pcount ; ++i){
distances[i] = new Array(pcount);
particles[i].update(i, particles);
if(!particles[i].drawAndDestroy()){
newParticles.push(particles[i]);
}
}
particles = newParticles;
}
}
这张图只截取了运行过程中快速画了一个圈之后的运动轨迹中的一瞬间。
然后看一下动图。
更复杂的情况下是这样的。
如果保留运动轨迹的话,观察运动轨迹。
前面的两篇博文中以及这篇博文的开头已经从各个方面对比了手绘和码绘的区别,在这里进行一个总结。
手绘有着自己的独特魅力。宫崎骏一再强调,有生之年他都会一直用彩色铅笔来创作。设计师们通过手绘传达自己的设计思想,自由又直接的表达自己的情感 ,必须依靠深厚的功底。手绘是一个人完整的表达设计思想最直接有效的方法,不论是思想上的创意功底还是手头上的高科技运用技术,都必须建立在有一定的艺术审美能力和一定程度绘画基础上,需要实践练习慢慢积累成。
手绘更多的时候指的是徒手绘画,在不依赖其他工具的情况下,手绘过程中对表现内容的表达没有限制性,并带有一定的主观随意性,可以是瞬间灵感的记录也可以是对场景人物等的绘制,所用的可以是任何可描绘的纸张,或者任何三维的场地,码绘使用代码以及鼠标等等代替了传统的纸和笔。
手绘更多的是人对画的表达,包含了一个人的个人情感以及创意思维等,而码绘则可以在瞬间内完成操作,在手绘过程中不断重复的环节在码绘中变得几行代码就能够解决,所以码绘的发展空间还是很大的。
码绘制作出的效果工整,具有感染力,而手绘则是一种独特的表现形式和手段,是一种设计思考途径,也是一种过程,码绘不及笔来得快速和灵活,同时也缺少了一点情绪的积累。它们是互补的也是一致的。手绘是一个创意的过程,强调表达作者的理念,也可以为码绘提供参考,码绘可以对手绘进行进一步的延伸,手绘与码绘的目标是一致的,都是进行某种视觉方式的传达,从思维的角度来看,两者同为作者展示的创造性思维,没有高低优劣之分,只有采取的手段不同,不管是通过手绘还是码绘其目标都是创造性的,二者有一定的差别也有联系,不管过程是否曲折变化,目标较为一致。
码绘的广泛运用可能带来一些设计方法和观念的变革,可能会成为设计和创新的新趋势和新动向,它具有直观、真实、准确的特点,比手绘更加理性,也比手绘更加更加注重效果,在后期处理上面可能会让作品锦上添花。但码绘普遍应用并不太会影响到手绘本身的魅力。手绘具有一定的主观随意性、艺术性、以及记录实践的特点,它的缺点是相对来说耗时且不易修改,不能体验空间的感受,码绘的特点是快速,准确,高效(有代码的情况下,写代码的过程也是非常漫长的..),它可以结合一些其他的技术体验空间,所以也并不缺少艺术性,但可能相对来说程式化。
手绘和码绘都是技法。任何的表现技法都是为了完美的表现设计理念,要运用好手绘和码绘,善于运用各种媒介、材料、技巧和表现手段,生动直观的表达设计方案构思,最大程度上的传达设计情感等信息。
参考资料:
https://github.com/magicbrush/PixelArray
https://github.com/magicbrush/DrawingByCodingTutorialDemos
https://www.openprocessing.org/