引言
对于绘画的定义,众说纷纭。百度词条上的绘画是这样的:绘画,是指用笔、板刷、刀、墨、颜料等工具材料,在纸、纺织物、木板、墙壁等平面(二度空间)上塑造形象的艺术形式。2016年吕澎在《论绘画》中说,“知识与感觉是评估绘画的两个基本出发点。” 而纵观中西方绘画的历史,在二战之前,西方艺术家保留着感觉与想象的空间,例如未来主义者将“时间”象征性地置于绘画平面之中。二战结束后,美国人干脆而直接地将绘画彻底解释为一个“平面”的事实,而不是一种空间甚至精神的虚拟与象征,格林伯格辩护说:平面就是平面。然而,在科技无时无刻不在迅速发展的今天,在想象力、智力与思想可以借用任何手段、材料与方法去完成艺术的今天,绘画是否可以被重新定义?
在这篇博文中,我将从我用p5.js写的一个特殊的绘画系统出发,来探讨绘画的更多可能性。
系统总体设计
功能模块划分
此绘画系统以星星为主题,分为模式及背景选择和笔刷这两个大的模块。可以选择“draw”、“play”、“move”等多个模式,可以调整背景颜色及选择背景图片,有“star”、“moon”等多个笔刷。
各功能实现效果及具体代码实现
“draw”模式
整个绘画系统的功能主要集中在“draw”模式,包括背景选择、笔刷选择和颜色选择。界面如下:
画布选择
根据绘画系统的主题,在此功能中设置了四种静态背景,也可选择纯色,可改变背景颜色。
“star”画笔
星星画笔使用了我之前做过的一个闪烁的星空的效果,鼠标点击或拖拽可在画布上绘制闪烁的星星,可选择画笔颜色,可改变星星大小。
由于要绘制闪烁的星星,所以要用到p5的粒子系统,每绘制一颗星星都要将其的位置坐标、大小、颜色、初始透明度等信息存储起来,以便后面通过改变每颗星星的透明度来实现闪烁效果。
具体代码如下:
//用于存储星星信息
var star_maxinum=300;
var st1=[];
//结构体
function star1(){
this. ori_apha;
this.x;
this.y;
this.r;
this.colorr;
this.colorg;
this.colorb;
this.flag=0;
this.star=function()
{
push();
translate(this.x,this.y);
for(var i=0;i<5;i++)
{
rotate(PI*72/180);
bezier(this.r/2,0,2,-1.5*this.r,-2,-1.5*this.r,-this.r/2,0);
}
pop();
}
}
//画数组中存储的星星并使星星闪烁
function drawstar()
{
noStroke();
for(var i=0;i<st1.length;i++)
{
if(st1[i].flag==0){
var Millis = millis();
var Second = millis()/1000;
var randl = noise(Second)*260;
randl=(st1[i].ori_apha+randl)%320;
if(randl<100)
randl=100;
fill(st1[i].colorr,st1[i].colorg,st1[i].colorb,randl);
st1[i].star();
}
}
}
“moon”笔刷
通过点击绘制月亮,可选择月亮颜色,可改变月亮大小和形状。
月亮的实现比较简单,即用画笔颜色画一个圆形,再错开位置用背景颜色画一个同样大小的圆形。改变两个圆形的大小即可改变月亮的大小,改变背景颜色的圆形的位置即可改变月亮形状。
“leaf”笔刷
拖拽可绘制树叶,绘制的树叶不会在画面上停留,而是飘落,直至离开画布。可选择树叶大小及颜色。
叶子的实现使用的是 bezierVertex()函数,用六个点来确定一片叶子,除了叶子顶点的两个点,叶子每条边上各找两个点。边上四个点的计算参考了openprocessing网站的一个例子,算法如下:
假设叶子一个顶点的点坐标为locv(locx,locy),则另一顶点坐标lv(lx,ly)根据叶子大小size和叶子角度angle(我取了固定值60°)通过三角函数求得,
lx=locx+cos(angle)*scale,
ly=locx+sin(angle)*scale,
叶子右边的两个点loc_right(loc_rightx,loc_righty),l_right(l_rightx,l_righty)同样根据大小size和角度angle用三角函数求得,定义一个角度angle_r=c_angle-abs(PI/2 - angle),其中c_angle可根据需要取值(我取的是60°),则
loc_rightx=cos(angle - angle_r)sizew+locx,
loc_righty=sin(angle - angle_r)sizew+locy,
l_rightx=cos(PI + angle + angle_r)sizew+lx,
l_righty=sin(PI + angle + angle_r)sizew+lx,
其中w是为了调整叶子的形状,可根据需要取0.3-0.5之间的值。
叶子左边两个点的计算与右边相同,将angle_r换成angle_l即可,angle_l=abs(PI/2 - angle) / 2+c_angle.
具体代码如下:
function leaf()
{
this.x;
this.y;
this.size;
this.angle=60;
this.angle2=random(0,60);
this.leaf_stroke=function(locx,locy, lx,ly, cx,cy)
{
beginShape();
vertex(locx, locy);
bezierVertex(cx[0], cy[0], cx[1], cy[1], lx, ly);
bezierVertex(cx[2], cy[2], cx[3], cy[3], locx, locy);
endShape();
}
this.chieh_leaf=function(oldlocx,oldlocy, scale, angle, c_angle, w)
{
locx = oldlocx;
locy = oldlocy;
axis1x=cos(angle)*scale;
axis1y=sin(angle)*scale;
lx=locx+axis1x;
ly=locy+axis1y;
tilt = abs(PI/2 - angle) / 2;
angle_l = (angle > PI/2) ? tilt + c_angle : c_angle - 2 * tilt;
angle_r = (angle > PI/2) ? c_angle - 2 * tilt : tilt + c_angle;
loc_leftx = cos(angle + angle_l);
loc_lefty = sin(angle + angle_l);
loc_leftx=loc_leftx*w+locx;
loc_lefty=loc_lefty*w+locy;
l_leftx = cos(PI + angle - angle_l);
l_lefty = sin(PI + angle - angle_l);
l_leftx=l_leftx*w+lx;
l_lefty=l_lefty*w+ly;
loc_rightx = cos(angle - angle_r);
loc_righty = sin(angle - angle_r);
loc_rightx=loc_rightx*w+locx;
loc_righty=loc_righty*w+locy;
l_rightx = cos(PI + angle + angle_r);
l_righty = sin(PI + angle + angle_r);
l_rightx=l_rightx*w+lx;
l_righty=l_righty*w+ly;
controlsx = [loc_rightx, l_rightx, l_leftx, loc_leftx];
controlsy = [loc_righty, l_righty, l_lefty, loc_lefty];
this.leaf_stroke(locx,locy, lx,ly, controlsx,controlsy);
}
this.chieh=function()
{
c_angle = PI/3;
w=1.0/2.5* this.size;
push();
translate(this.x,this.y);
rotate(this.angle2);
this.chieh_leaf(0,0, this.size, this.angle, c_angle, w);
pop();
}
}
叶子的飘动效果通过实时改变叶子的位置即可实现,整体飘动方向通过noise()函数计算,为了防止每片叶子的飘动轨迹一模一样,在每片叶子的位移值上加一个很小的random值来模拟更真实的树叶飘动效果。
具体代码如下:
var leaves=[];
function leavesdraw0()
{
strokeWeight(2);
stroke(ground_color);
fill(ground_color);
for(var i=0;i<leaves.length;i++)
{
leaves[i].chieh();
var Millis = millis();
var Second = millis()/1000;
var an = noise(Second)*3;
leaves[i].x-=an+noise(i);
leaves[i].y+=2;
}
fill(pen_r,pen_g,pen_b,pen_diaphaneity);
for(var i=0;i<leaves.length;i++)
{
leaves[i].chieh();
}
}
花朵笔刷
点击绘制花朵,花朵有一个动态生成的过程,可选择花朵大小和颜色。
花朵是通过对叶子做修改实现的,不同角度,不同形状的叶子按一定规律旋转即可实现花朵的效果。
“play”模式
在此模式中,可以通过点击星星把星星用线穿起来,长按释放星星,长长按可召唤流星雨。
线串星星
线串星星在我之前的博文中有详细介绍,在这里就不再赘述,感兴趣的可以去看看。传送门:https://blog.csdn.net/weixin_43751558/article/details/84325756
释放星星
释放星星的实现较简单,因为事先已经存储了每颗星星的位置等信息,所以当检测到鼠标按下的时间大于一定值时,将线上的星星全部重新在画布上绘制即可。
流星雨
流行雨的实现也是通过改变星星的位置,为了实现流行拖尾的效果,在每一颗星星后面加了大小渐小的四颗星星。
释放星星和流星雨的具体代码如下:
//释放星星
function releasestar()
{
for(var i=0;i<st1.length;i++){
st1[i].flag=0;
}
}
//流行雨
var shawerdx;
var shawerdy;
function shawer()
{
for(var i=0;i<Math.trunc(st1.length/3);i++){
if(st1[i].flag==0)
{
shawerdx=(st1[i].r/5+1000/st1[i].x)*1.2;
shawerdy=(st1[i].r/5+st1[i].y/10)/5;
st1[i].x-=shawerdx;
st1[i].y+=shawerdy;
noStroke();
fill(st1[i].colorr,st1[i].colorg,st1[i].colorb,120);
star(st1[i].x+shawerdx,st1[i].y-shawerdy,st1[i].r*0.6);
fill(st1[i].colorr,st1[i].colorg,st1[i].colorb,100);
star(st1[i].x+shawerdx*2,st1[i].y-shawerdy*2,st1[i].r*0.4);
fill(st1[i].colorr,st1[i].colorg,st1[i].colorb,80);
star(st1[i].x+shawerdx*3,st1[i].y-shawerdy*3,st1[i].r*0.2);
fill(st1[i].colorr,st1[i].colorg,st1[i].colorb,60);
star(st1[i].x+shawerdx*3.5,st1[i].y-shawerdy*3.5,st1[i].r*0.2);
}
}
}
void draw()
{
//流星雨
if(isshawer){
shawer();
}
//释放星星(长按)
if(mouseIsPressed){
ti+=1;
}
else{
if(ti>30&&ti<100){
releasestar();
}
else if(ti>100){
isshawer=true;
}
ti=0;
}
}
“play2”模式
为了增加系统的交互性与可玩性,又增加了“play2”、“move”和“3d”三个模式。“play2”模式中有黄白两种颜色的星星,星星的运动在两种不同的模式之间切换:一种模式为同颜色的星星相互靠近,其稳定状态为白色的星星聚在一起,黄色的星星聚在一起;另一种模式为不同颜色的星星相互吸引,其稳定状态为黄白星星两两配对。鼠标点击切换模式。
此功能的实现参考了openprocessing官网的例子,实现原理为:对于每一颗星星,遍历除自己之外的所有星星,找出每个星星相对于自己的方向与距离,根据方向与距离算一个位移偏移量。如果为模式一,与自己同颜色的星星对自己的位置偏移表现为吸引,与自己异色的星星对自己排斥;模式二与之相反。所有星星对自己的位置偏移相加,就可以得到这颗星星的最终位置偏移量,根据这个偏移量改变星星的位置即可。
具体实现代码如下:
var g = 0.005;
var playparticles = [];
function play2setup() {
noStroke();
for(var i=0;i<450;i++){
playparticles[i]=new playparticle();
playparticles[i].px = random(0,width);
playparticles[i].py = random(0,height);
if (i % 2 == 0)
playparticles[i].m = random() * 10;
else
playparticles[i].m = -random() * 10;
}
}
function play2draw() {
background(32);
for (var me = 0; me < playparticles.length; me++) {
playparticles[me].resetAcceleration();
for (var neighbor = 0; neighbor < playparticles.length; neighbor++) {
playparticles[me].updatePartialAcceleration( playparticles[neighbor]);
}
}
for (var i = 0; i < playparticles.length; i++) {
playparticles[i].updateVelocityAndPosition();
var opacity = sqrt( playparticles[i].vx * playparticles[i].vx + playparticles[i].vy * playparticles[i].vy) * 128;
if ( playparticles[i].m < 0)
fill(255,255,60, opacity);
else
fill(220, 220, 200, opacity);
var radius = abs( playparticles[i].m);
star(playparticles[i].px, playparticles[i].py, radius);
}
}
function mousePressed() {
g *= -1;
}
function playparticle() {
this.px;
this.py;
this.vx=0;
this.vy=0;
this.ax=0;
this.ay=0;
this.m;
this.resetAcceleration=function() {
this.ax = 0;
this.ay = 0;
}
this.updatePartialAcceleration=function(neighbor) {
if (neighbor != this) {
dx = this.px - neighbor.px;
dy = this.py - neighbor.py;
var d = dx*dx + dy*dy;
if (d < 1) d = 1;
var common = this.m * neighbor.m / d;
this.ax += common * dx;
this.ay += common * dy;
}
}
this.updateVelocityAndPosition=function() {
//计算偏移
this.vx = this.vx * 0.99 + this.ax * g;
this.vy = this.vy * 0.99 + this.ay * g;
//改变位置
this.px += this.vx;
this.py += this.vy;
//碰壁检测
if ((this.px < 0 && this.vx < 0) || (this.px > width && this.vx > 0)) this.vx = -this.vx;
if ((this.py < 0 && this.vy < 0) || (this.py > height && this.vy > 0)) this.vy = -this.vy;
}
}
“move”模式
此模式实现了一种伪3d的效果,鼠标长按进行交互。
此功能的实现较为简单,参考processing里的例子,让星星从初始位置开始绘制,绘制过程中不断进行平移旋转,并且星星大小也逐渐变大,当星星位置超出画布上的一定范围时,销毁星星重新赋初始位置与大小进行绘制,如此一直循环。星星初始位置和初始大小都是一定范围内的随机值,位置偏移的过程是先旋转,后改变位置的posx值,posy值不变,这样可以实现越靠近画布中间的星星位置偏移越小,而越靠近画布四周的星星位置偏移越大,这种运动可以形成一种类似3d的效果。
鼠标的交互效果也很简单,如果鼠标没有被按下的话,每一帧都刷新一下背景,若鼠标被按下,则背景不再刷新,鼠标被释放后重新刷新背景。
具体代码如下:
var p=[];
var diagonal;
var rotation = 0;
function setup_3d()
{
for (var i = 0; i<800; i++) {
p[i] = new Particle();
p[i].o = random(1, random(1, width/p[i].n));
}
diagonal = Math.trunc(sqrt(width*width + height * height)*2.2);
}
function draw_3d()
{
if (!mouseIsPressed) {
background(0);
}
translate(width/2, height/2);
rotation-=0.002;
rotate(rotation);
for (var i = 0; i<p.length; i++) {
p[i].draw();
if (p[i].drawDist()>diagonal) {
p[i] = new Particle();
}
}
}
function Particle() {
this.n = random(1, width/2);
this.r= random(0, 2*PI);
this.o= random(1, random(1, width/this.n));
this.l= 1;
this.draw=function() {
this.l++;
push();
rotate(this.r);
translate(this.drawDist(), 0);
noStroke();
fill(255,255,0, min(this.l, 255));
star(0,0,width/this.o/20)
pop();
this.o-=0.07;
}
this.drawDist=function()
{
return atan(this.n/this.o)*width/PI;
}
}
“3d”模式
此模式实现了3d的星空的效果,鼠标拖拽、鼠标滚轮进行交互。
因为p5.js提供了3d画布,所以上面的3d星空效果实现起来就很简单了。黄色的球其实是在一个半径为130和一个半径为160的球体之间不断画点,在画点的过程中,画笔的粗细在0-50之间不断改变,并且使画布绕y轴旋转。后面的白色星空背景是一些随机生成的透明度不断改变的立方体。至于鼠标交互,p5.js提供了专门的3d鼠标交互的函数,只要在代码中添加orbitControl()这一句即可。
具体代码如下:
var objets=[];
var rx=0;
var ry=0;
var points=[];
function rotatesetup() {
createCanvas(720, 400,WEBGL);
for(var i=0;i<300;i++)
{
points[i]=new backpoint();
}
for(var a=0;a<400;a++){
objets.push(new objet(random(2*PI), random(2*PI), random(130,160)));
}
}
function rotatedraw() {
background(0);
for(var i=0;i<300;i++)
{
points[i].draw();
}
orbitControl();//鼠标控制视角
ry-=0.008;
var r=200;
rotateX(rx);
rotateY(ry);
for(var i=0;i<objets.length;i++)
{
objets[i].dessine();
}
}
function objet(_phi, _theta, _ray){
this.phi=_phi;
this.theta=_theta;
this.ray=_ray;
this.t=floor(random(50));
this.v=random(0.05,0.9);
this.dessine=function(){
this.t-=this.v;
if(this.t<0){this.t=50;}
strokeWeight(map(this.t,0,40,0.5, 8));
stroke(255,255,0,random(100,200));
var x=(sin(this.phi)*cos(this.theta))*this.ray;
var y=(sin(this.phi)*sin(this.theta))*this.ray;
var z=(cos(this.phi))*this.ray;
point(x,y,z);
}
}
//背景白块
function backpoint()
{
this.x=random(-720,720);
this.y=random(-400,400);
this.z=random(-400,400);
this.or_l=random(0,200)
this.draw=function()
{
noStroke();
var Millis = millis();
var Second = millis()/1000;
var randl = noise(Second)*50;
randl=(this.or_l+randl)%200;
fill(255,randl);
push();
translate(this.x,this.y,this.z);
box(2);
pop();
}
}
function drawpoints()
{
for(var i=0;i<200;i++)
{
points[i]=new backpoint();
points[i].draw();
}
}
系统绘画作品展示
整个绘画系统介绍完了,下面放几张用此绘画系统绘制的绘画作品。
系统与传统绘画系统的异同比较及绘画的可能性探讨
对于绘画这个动作,无非是从四个方面去讨论它——材料、交互方式、作品呈现和作画者。
显然,上述的“星”主题绘画系统在材料、作品呈现和作画者三个方面都与传统绘画具有较高的相似性,但作品的呈现又不像传统绘画那样只是简单地给观者以观感。而在交互方式这一个方面,此“星”主题绘画系统与传统绘画截然不同。
“绘画”一词,大概会让人不自觉联想到画笔、颜料、纸张……包括刷子、布、墙壁在内,这些被认为是最传统的绘画材料。而今天,数位板也已经是公认的绘画工具。百度词条对绘画的定义是这样的:绘画(Drawing 或Painting)在技术层面上,是一个以表面作为支撑面,再在其之上加上颜色的做法,那些表面可以是纸张或布,加颜色的工具可以通过画笔、也可以通过刷子、海绵或是布条等。如此看来,绘画的材料无非两类,一类是支撑面,一类是加颜色的工具。那么上述主题绘画系统与传统板绘其实是大同小异的,它们都以屏幕或者说以一个虚拟的平面作为支撑面,以数位板笔或者鼠标作为加颜色的工具来进行绘画。其实板绘的绘画材料与传统绘画的绘画材料还是差异非常明显的,但没有人会认为板绘不属于绘画,可见对于绘画而言,材料本身并不是关键。换句话说,绘画这一行为,丝毫不受材料的限制,这也给了绘画更多的可能性。
在交互方式这一方面,不管是传统的国画、水彩、油画等还是数位板+电脑作画,作画者手中的工具都可以直接接触画布,也就是作画者在作画过程中是有触感的,他们能够感受到自己下笔的轻重,并且通过点或线条的型态将这种触感表现出来。而上述的主题绘画系统,虽然有尽量模拟这种交互方式,但毕竟作画者手中的只是一个鼠标,它所有可能的交互方式无外乎点、按、拖、释及这几种动作的组合,而这些动作给予的反馈也只是通过屏幕上一个虚拟的光标。在这一方面,上述绘画系统的缺点还是比较明显的,它可能做到给作画者一些细腻而又丰富的交互体验,但若要做到如画者手中拿着一只笔一般细腻而又真实的反馈,还是比较困难的。
有人说,绘画的本质是将三次元变为二次元,也有人说绘画的本质是对美的追求。其实通过上述绘画系统的作品呈现方式就可以看出,绘画的呈现可以不拘束于二维,它可以使三维的,甚至是四维的。画家白安平曾经讲述过他对四维画的看法,他说,“既然是时间加空间,如果想把时间这一维表现在画面上真是难之又难。如果颜料能够一直移动,一幅画如同电子荧幕一样变来变去的话,或许可以认为画面上体现了时间这一维。但这样的绘画作品,目前还没有出现,或许未来会出现能流动的颜料,在画布上游来游去,形成各种不同的艺术画面展现在人们面前。果真那样,这样的画面只是颜料游动自然形成的艺术风格。”显然,这里白安平所说的绘画指的是传统文房四宝式的绘画,若要将绘画的概念拓展到上述主题绘画系统所表现的的形式上,呈现一幅四维的并且带有作画者情感的绘画并不是什么难事。说得稍微夸张一点,上述绘画系统的作品呈现,已经接近“神笔马良”的作品呈现——你画的所有星星都是可以闪烁的,你画的所有叶子都是可以随风飘落的,你可以动态改变月亮的大小胖瘦,你可以看到你笔下的花绽放的过程……当然,上述绘画系统并不成熟,但以此为思路,将时间维度加入绘画作品并非不可实现。
除维度方面,上述绘画系统对于观者来说也不只是局限于观看。你面前有一幅星空主题的绘画作品,你不仅可以静静地观赏它,你还可以尝试去跟画面中的星星互动,你可以摘星星、释放星星,甚至可以召唤流星雨,难道不是一件有意思的事情吗?有趣的交互体验一方面可以增加欣赏者对绘画作品的兴趣,增加他们的驻足时间。另一方面,若作画者设计得当,可以通过这种交互让欣赏者更直观地感受到作品的理念与思想。所以说,在绘画作品的呈现这一方面,绘画有着无限的可能性。
到了最后一个方面——作画者。在我看来,无论绘画的概念如何拓展,一幅可以被称之为绘画作品的画必须要有思想在里面,有作画者想要表达的东西在里面。引用吕澎的《论绘画》中的一句话:“既然材料本身并不是艺术的重点,那么仍然只有不断产生的思想能够承担起绘画的责任。”即使是机器作画,也必须要有一个人或者一种思想来引导机器,事实上机器永远不能成为作画者,而只有有思想的人才具备成为作画者的条件。这一点,无论是通常的国画、水彩、油画还是数位板+电脑作画,还是上述的“星”主题绘画系统都是一样的,正是因为作画者的情感与思想成就了绘画作品。
总而言之,从二维到三维,从静态到动态,从传统欣赏方式到交互式观赏,绘画具有无限的可能性。绘画永远是通过艺术家来审视自己的,绘画的可能性就在绘画自身。绘画可以借助任何手段与方法来呈现自己,并让自己保持不朽的韧性。
参考资料
【1】《论绘画》,吕澎;
【2】《对加拿大温哥华部分个展作品的解析》,白安平;
【3】https://www.openprocessing.org/sketch/182497
【4】https://www.openprocessing.org/sketch/240243
【5】https://www.openprocessing.org/sketch/496519
【6】https://www.openprocessing.org/sketch/498723
【7】https://www.openprocessing.org/sketch/492680