手绘与码绘的比较—模拟风吹树动
一、内容介绍
本文主要介绍用手绘和码绘两种方式模仿一种自然现象–风吹树动的过程,以及在实现过程对这两种方式的比较和思考。之所以选择风吹树动,是因为每次速写画一些场景时(尤其是室外场景)都难免要画树,而我自己在画树时,经常不知道怎么下手,再加上这学期上了图像处理,互动媒体的课,因此想要用代码来实现这种效果。
二、作品展示
手绘
手绘作品图1-1
手绘作品图1-2
动态效果
三、手绘与码绘的比较
下面将从以下几个角度来分析比较两种方式:技法,工具,理念,创作体验,呈现效果,载体,局限性,应用。
A.技法:
让我们从手绘的整个过程开始,绘画可分为选择工具,构思画面,动手,后期加工这几个步骤,首先,我们可以根据自己构思的画面选择合适的笔和纸(如钢笔,水笔或者铅笔等),画面构思需要考虑整个画面的大小比例、物体的结构线、阴影透视、物体遮挡前后关系,不重要细节的取舍等等,我在手绘树时,会构思如何对树干的走向,结构线,树叶的遮挡进行处理,后期加工处理有很多种方式,我们可以借用印刷技术,将我们的草图扫描到电脑里面,进行一些加工,然后打印在像衣服,杯子等物品上。
然后我们来谈谈码绘,也可以把它分成选择工具,构思,实现,后期加工这几步,码绘的工具当然是编程语言和平台,C++,Java等都可以实现,还有一种比较灵活方便的是Shader Labguage,目前三大主流Shader Labguage是基于OpenGL的GLSL语言,基于DirectX的HLSL语言,以及NVIDIA公司的Cg语言,这些语言相应的平台也有很多,如Processing,Visual Studio,Unity,openframeworks等,我是在processing上实现的,processing的语法和Java很类似,然后我想着重讲一下码绘树的构思和实现环节, 这个环节可分成两个部分:树的生成和树在风力作用下运动,我用基于L-x系统的算法生成树,然后使用perlin噪声产生随机风场,根据每一个节块受到的力使树干旋转相应角度,这个过程中我发现这种方法生成树与手绘有很多共同之处,首先,两种方式生成的树都是由一个个小的单元不断组合形成,这里我想用一组图来解释:手绘的树由一些小的结构线不断重复所构成,不同种类的树的结构线不一样,如灌木和棕榈的结构线要硬直些,而乔木的要圆润些(图2-1),让我们再来看码绘的,图2-2b的大树是以图2-2a的树干为基本单元不断迭代生成的,仔细观察可以看到这棵大树的每个部分都有相同的结构,让我们再比较图2-3b和图2-2b中的树,明显看到组成这两棵树的基本单元树干也是不同的(实现代码在第四部分)。
图2-1
左图为2-2a 右图为2-2b
左图为2-3a 右图为2-3b
大多数时候用手绘实现动态效果大多利用一些视错觉,现在有一种有趣的玩法是讲一些经典绘画作品串在一起形成一些有趣的动图,具体可以点击经典名画动图,而码绘实现可以用一些随机函数如perlin噪声和循环实现,至于算法的实现过程以及生成原理请参考《基于 L- 系统和 Perlin噪声函数的风吹树动模拟》
B.理念
借用百度词条对理念的解释:一是“看法、思想。思维活动的结果”,二是“理论,观念(希腊文idea)。通常指思想。有时亦指表象或客观事物在人脑里留下的概括的形象。” 。从这个角度来看,人们对手绘的看法有:创作者表达自我情感的一个载体,闲暇时会进行的爱好,观察世界的方式,赚钱的工具等等。对身边学艺术的同学以及一些设计大师观察,发现手绘对他们来说是一个帮助自己抓住灵感,理清思路,表达自己想法且能够和他人沟通的一种非常好的工具。
而在现实生活中,我们对码绘的想法也大致相同,除了不会用代码画画来抓住灵感,因为码绘的起点的是会编程,然后还要在写完代码调试成功后确定灵感还在。我个人对两者的看法是,手绘是一个有生活气息,有趣味的过程,而码绘相对要理智,酷炫些。
C.创作体验
在创作体验上手绘和码绘的差别还是挺大的,一方面,我们完成一幅作品依靠我们的各种感觉器官以及手对笔的掌控能力,而码绘就主要靠脑力以及耐心了;另一方面,我们在任何环境下都可以进行绘画(只要有笔和纸),我们会喜欢一边听歌一边画,而在码绘时,我们希望自己是在一个安静的环境里敲着代码同时自己也是清醒的状态。
D.呈现效果
手绘与码绘的作品请看上面第二部分的作品展示。手绘作品与码绘作品呈现出来的效果是很不一样的,手绘作品会让人觉得很有趣,即使作品中有很多不精细或者走形的地方也会让人觉得很有趣,可爱,相较之下,码绘会给人一种距离感,让人感觉很冰冷,但也会有一些高级感。
E.局限性及应用
手绘作品在表现空间立体感,制造动态效果是无法与码绘相比的,结合手绘作品的呈现效果,手绘适合一些用于产品设计,产品包装,海报等一些需要人情味的设计中,而码绘可以通过精细的计算设计制作出逼真的动态以及立体效果,因此适合在地质模拟,电影特效上这种对仿真度要求高的工作中使用。
四、主要代码
//生成构建树的字符串
class Grammar
{
string rules[];
int ruleNum=0;
int times;
chArr result;
char axioms;
Grammar(int t, string R[], char a)
{
this.ruleNum=R.length;
this.times=t;
result=new chArr();
rules=new string[R.length];
for (int k=0; k<R.length; k++)
{
//rules[k]=new string();
rules[k]=R[k];
}
axioms=a;
this.Iteration();
}
void Iteration()
{
result.addCharacters(this.axioms);
for (int i=0; i<this.times; i++)
{
chArr tmpstr=new chArr();
for (int j=0; j<result.size; j++)
{
tmpstr.addCharacters(search(result.get(j)));
}
result .set(tmpstr);
}
}
chArr getResult()
{
return result;
}
chArr search(char target)
{
//string generations;
chArr tmp=new chArr();
tmp.addCharacters(target);
//generations=new string(target);
for (int i=0; i<rules.length; i++)
{
if (rules[i].first==target)
{
int index;
index=(int)(random(1)*rules[i].size);
return rules[i].str.get(index);
}
}
return tmp;
}
}
//构建树的节点
void getVertices()
{
chArr result=this.grammar.getResult();
int i=0;
int count =0;
float ang=90;
IntList outbrake=new IntList();
FloatList angleSign=new FloatList();
FloatList radiusSign=new FloatList();
FloatList lengthSign=new FloatList();
float radius=treeRadius, len=treeLen;
State curS;
ArrayList<State> node1=new ArrayList<State>();
curS=firstState;
for (int loop=0; loop<result.size; loop++)
{
char ch=result.get(loop);
switch(ch) {
//move forward
case 'F':
{
PVector p=new PVector();
State cur=new State();
if (i==0)
{
p.set(400, 150);
cur.setStartPos(p);
cur.setAngle(90);
cur.Length=treeLen;
cur.radius=treeRadius;
} else
{
cur.Length=len*lengthFactor;
cur.radius=radius*radiusFactor;
radius=cur.radius;
len=cur.Length;
}
cur.setAngle(ang);
curS=cur;
stack.push(cur);
break;
}
case 'f':
{
PVector p1=new PVector();
State cur=new State();
if (i==0)
{
p1.set(400, 150);
cur.setStartPos(p1);
cur.setAngle(90);
cur.Length=treeLen;
cur.radius=treeRadius;
} else
{
cur.Length=len*lengthFactor;
cur.radius=radius*radiusFactor;
radius=cur.radius;
len=cur.Length;
}
cur.setAngle(ang);
curS=cur;
stack.push(cur);
break;
}
case '+':
ang=ang-(this.increment.y);
break;
case '-':
ang=ang+(this.increment.y);
break;
case '[':
{
angleSign.append(curS.angle);
lengthSign.append(curS.Length);
radiusSign.append(curS.radius);
outbrake.append(0);
node1.add(curS);
count++;
break;
}
case ']':
{
radius=radiusSign.get(count-1);
len=lengthSign.get(count-1);
ang=angleSign.get(count-1);
angleSign.remove(angleSign.size()-1);
lengthSign.remove(lengthSign.size()-1);
radiusSign.remove(radiusSign.size()-1);
outbrake.append(1);
curS.level=1;
curS=node1.get(count-1);
node1.remove(count-1);
count--;
break;
}
default:
break;
}
i++;
}
}
五.参考资料
1.了解基于L-系统算法
[1]: 基于L-系统和 Perlin 噪声函数的风吹树动模拟-文档
[2]:基于L-系统和 Perlin 噪声函数的风吹树动模拟–原论文
2.了解perlin噪声
[1]: Perlin在线讲座
3.代码画画
[1]:用代码画画——搞艺术的学编程有啥用?
[2]:开始第一幅“码绘”——以编程作画的基本方法
3.processing学习
[1]: processing学习文档
[2]: processing三维用法