简介

上一篇博文我主要写了用向量来实现笛卡尔之心的形态变化。这一次我们以此为基础,把这种向量变化作为“颜料”来制作一个非常有趣的交互式绘画系统。
另外,这次我还使用了一种新的充满艺术美感的数学图案——玫瑰曲线。这种曲线是如此适合用于图形变换,以至于能创造出我意想不到的艺术效果。
综上,本文将详细介绍这个系统的设计思路、代码结构设计以及从多个角度进行的总结。

设计思路

嗯,多说无益,先看看最终效果图吧!
展览模式
此模式用于欣赏(或者说观察)各种参数对图形变化的影响,这些参数将在代码章节具体介绍。

绘画模式
调整各种参数绘制动态画面
拖动绘画

点击绘画

GUI界面

这里是设计思路
一般来讲,绘画有四种要素:材料、作画者、交互方式和作品。
传统的绘画是艺术家使用笔、颜料、画笔等材料,在画布上直接画出图案。
更加宽泛的来讲,只要满足绘画的四要素,创作出可以表达情感的作品,都可以被称之为绘画。比如用延时摄影的技术画“灯影”,在白纸上用裁纸刀“画”线等。

本文的绘画系统即是对“绘画”概念的扩展:
1.在材料上,本系统和传统绘画有很大的不同。传统绘画的颜料是静止的,画完怎么样就是怎么样;本文系统的颜料是向量组成的图形,能够动态周期性变化。
2.在作画者角度上,两者相似。
3.在交互方式上,抽象的来看,鼠标和屏幕,或者数位板和屏幕与传统绘画的画笔与画布没有本质性的不同。打个比方,我用数位板控制机械手在画布上画画和用数位板在屏幕上画画在交互方式上有任何不同吗,这只有“感觉”上的不同。
4.在作品上,传统绘画是静止的,本文的系统是运动的,为的是表现出一种充满活力的动感还有酷炫的变化效果。
总的来讲,就是在材料和展现效果上的对传统绘画进行扩展。

以下是新花样——玫瑰曲线的介绍。
在上一篇博文中我实现了笛卡尔之心的变换,而这一次我找到了另一个有趣的玩意——玫瑰曲线(Rose Curve)。这是一种可以生成类似花瓣样图形的函数。

其极坐标方程如下:

其参数方程如下:

k这个参数决定了玫瑰图案的花瓣数:
花瓣数为2k如果k是偶数
花瓣数为k如果k是奇数

以下是不同参数下的玫瑰图案。这张图中的玫瑰曲线是由极坐标方程定义的,参数k=n/d。
仔细看,里面有笛卡尔之心的样子。遗憾的是,在系统中的参数k只能取整数,所以看不到那个熟悉的身影。

关于玫瑰曲线的资料和图片均来自维基百科。
维基百科——玫瑰曲线介绍

代码

和上次一样,本次的开发平台依然是好用的Processing(3.4版本)。开发语言是java。

封装的类

通过封装核心代码,实现简化代码编写的目的。同时,这种方法也增强了程序的可扩展性和可读性。以下是实现核心功能的两个类:Morph_Color和Morph_Brush
Morph_Color
这个类是用于初始化图形的向量位置的,跟上一次相比新增了一个初始化玫瑰曲线的方法。我已经在上一篇博文中详细介绍了用向量构建图形的原理,这里就不赘述了。

// this class is used to initiative the graph's Vectors
class Morph_Color{// save the vectors of a morph profileArrayList<PVector> morphList;Morph_Color(){morphList = new ArrayList<PVector>();}// init rectvoid addPolygonVectors(int vectorNum, int pWidth, float part){// Top of squarefor (int x = -pWidth/2; x < pWidth/2; x += pWidth/(vectorNum/4)) {morphList.add(new PVector(x, -(int)pWidth*part));}// Right sidefor (int y = -pWidth/2; y < pWidth/2; y += pWidth/(vectorNum/4)) {morphList.add(new PVector((int)pWidth*part, y));}// Bottomfor (int x = pWidth/2; x > -pWidth/2; x -= pWidth/(vectorNum/4)) {morphList.add(new PVector(x, (int)pWidth*part));}// Left sidefor (int y = pWidth/2; y > -pWidth/2; y -= pWidth/(vectorNum/4)) {morphList.add(new PVector(-(int)pWidth*part, y));}}// init heartvoid addHeartVectors(int vectorNum, int r){float x,y,a;float crossOver;crossOver = 1.6;x=y=0;a=r;pushMatrix();translate(width/2,height/2);int count = 0;for(float t = -PI;t < PI;t += 2*PI/vectorNum){if(count == vectorNum){break;}x = a*(crossOver*cos(t)-cos(2*t));y = a*(crossOver*sin(t)-sin(2*t));morphList.add(new PVector(x,y));count++;}print("heart"+count);popMatrix();}// init circlevoid addCircleVectors(int vectorNum, int radius){float angleSector = 2*PI/vectorNum;int count = 0;for (float angle = -PI; angle < PI; angle += angleSector) {if(count == vectorNum){break;}PVector v = PVector.fromAngle(angle);v.mult(radius);morphList.add(v);count++;}}// init rose curvevoid addRoseCurveVectors(int vectorNum, int a, int k){float x,y;x=y=0;pushMatrix();translate(width/2,height/2);int count = 0;for(float t = -PI;t < PI;t += 2*PI/vectorNum){if(count == vectorNum){break;}x = a*sin(k*t)*cos(t);y = a*sin(k*t)*sin(t);morphList.add(new PVector(x,y));count++;}popMatrix();}}

Morph_Brush
这个类是用于实现形态切换的。切换是靠向量的线性插值实现的。原理详见上篇博文,链接在参考文献中。

// this class is used to draw the process of the
// graph's morph change
class Morph_Brush{// the two state of one graphArrayList<PVector> morphList1;ArrayList<PVector> morphList2;boolean state = false;boolean state2 = true;int delayValue = 0;ArrayList<PVector> morph = new ArrayList<PVector>();Morph_Brush(ArrayList<PVector> ml1){morphList1 = ml1;for (int i = 0; i < morphList1.size(); i++) {morph.add(new PVector());}}Morph_Brush(ArrayList<PVector> ml1, ArrayList<PVector> ml2){morphList1 = ml1;morphList2 = ml2;for (int i = 0; i < morphList1.size(); i++) {morph.add(new PVector());}}// draw only one statevoid drawOneMorph(){pushMatrix();translate(width/2, height/2);rotate(-PI/2);strokeWeight(4);beginShape();noFill();for (PVector v : morphList1) {vertex(v.x, v.y);}endShape(CLOSE);popMatrix();}// draw the process of the changevoid drawTwoMorph(float mx, float my, int rgbValue){delay(delayValue);float totalDistance = 0;// intermediate statefor (int i = 0; i < morphList1.size(); i++) {   PVector v1;if (state) {v1 =morphList1.get(i);}else {v1 = morphList2.get(i);}PVector v2 = morph.get(i);//v2.lerp(v1, random(0,0.1));v2.lerp(v1, 0.1);totalDistance += PVector.dist(v1, v2);}//if get close to other graphif (totalDistance < 0.1) {state = !state;}// start drawingpushMatrix();translate(mx, my);rotate(-PI/2);strokeWeight(2);beginShape();noFill();stroke(rgbValue);for (PVector v : morph) {vertex(v.x, v.y);}endShape(CLOSE);popMatrix();}// a method to init the intermidiate state morphvoid addMorph(int vectorNum){for (int i = 0; i < vectorNum; i++) {morph.add(new PVector());}}}

GUI设计

GUI设计使用了Processing官网中一大佬制作的GUI包,叫controlP5。好看又好用,强烈推荐!
这里是下载链接。
controlP5下载地址

界面设计

导入controlP5包,然后自己调用各类方法,生成下拉列表、滑条、按钮、开关等各类GUI样式。另外还要调整位置以及设置数值范围。

  // GUI libraryimport controlP5.*;ControlP5 cp5;// GUI System// Use the ControlP5 librarycp5 = new ControlP5( this );//color wheel cp5.addColorWheel("exRGBValue" , 1000 , 100 , 200 ).setRGB(color(128,0,255));noStroke();// drop down listList l = Arrays.asList("rect_heart","rect_circle","circle_heart","roseCurve_rect","roseCurve_circle","roseCurve_heart");cp5.addScrollableList("dropdown").setPosition(1000, 350).setSize(200, 100).setBarHeight(20).setItemHeight(20).addItems(l);// slider // get the vectors of each graphcp5.addSlider("VectorsNum").setPosition(1000,450).setSize(200,40).setRange(12,36).setValue(36).setNumberOfTickMarks(3);// change the color of backgroundcp5.addSlider("backgroundColor").setPosition(1000,510).setSize(200,40).setRange(0,255).setValue(150);// set the maximum of the graph on the canvas at the same timecp5.addSlider("maxGraph").setPosition(1000,570).setSize(200,40).setRange(16,3000).setValue(300);// get the k value of the rose curvecp5.addSlider("getKValue").setPosition(1000,630).setSize(200,40).setRange(1,39).setValue(3);// get the radius value of the rose curvecp5.addSlider("roseCurveRadius").setPosition(1000,700).setSize(200,40).setRange(10,100).setValue(80);// get the radius value of the circlecp5.addSlider("circleRadius").setPosition(1000,770).setSize(200,40).setRange(10,100).setValue(10);// get the radius value of the heartcp5.addSlider("heartRadius").setPosition(1000,840).setSize(200,40).setRange(10,100).setValue(50);// toggle// choose whether see the exhibition or draw on the canvascp5.addToggle("drawOrViewToggle").setPosition(1000,40).setSize(60,30).setValue(true).setMode(ControlP5.SWITCH);// button// click to clear all graphs on the canvas when drawingcp5.addButton("ClearCanvas").setValue(0).setPosition(1122,38).setSize(60,40);
响应函数

通过这些函数,我可以实时获取改变的参数。

// GUI callback funcs // func to get the index of the dropdown list
void dropdown(int n) {indexValue = n;
}// func to get the value of the VectorsNum slider
void VectorsNum(int VecNum) {vectorsNum = VecNum;
}// func to get the value of the backgroundColor slider
void backgroundColor(int bgc) {backgroundColor = bgc;
}// func to get the value of the maxGraph slider
void maxGraph(int mg) {maxGraph = mg;
}// func to get the value of the getKValue slider
void getKValue(int temp) {k = temp;println(k);
}void roseCurveRadius(int temp) {roseCurveRadius = temp;println(k);
}void circleRadius(int temp) {circleRadius = temp;println(k);
}void heartRadius(int temp) {heartRadius = temp;println(k);
}
// func to get the value of the toggle
void drawOrViewToggle(boolean theFlag) {drawOrView = theFlag;
}// function ClearCanvas will receive changes from button with name ClearCanvas
boolean need2Clear = true;
void ClearCanvas() {//println(theValue);need2Clear=!need2Clear;println("nwc" + need2Clear);if(need2Clear == true){drawBrushes = new ArrayList<Morph_Brush>();indexPosition = new ArrayList<PVector>();index=-1;need2Clear = false;}println(drawBrushes.size());println(indexPosition.size());println(index);
}
GUI各个功能介绍

没错,这就是系统的使用说明书

模式切换开关,点击实现展览模式和绘画模式的切换。


清屏按钮,在绘画模式中可以清楚屏幕上所有的图形。
特别强调,点击模式切换开关后一定要点击一下这个按钮再画画!不然会报错。

颜色盘,鼠标拖动选择需要的颜色。

点击选择当前应该展示或者绘制的图形。
一共有六中形态变化图形,分别为:矩形-心,矩形-圆,圆-心,玫瑰曲线-矩形,玫瑰曲线-圆,玫瑰曲线-心。


每个图案的总向量数,向量越多图形越光滑,越少越尖锐。
一共有三个数字选择:12、24、36。


同屏最多出现的图形数,我默认设置为1500,不过实际上画一两百个我电脑就开始卡了……
如果电脑配置好,可以试试开最大值3000,画3000个图形去变化形态,想想那画面真是太美了。

背景颜色调整,从0-255,画面从纯黑到纯白。

玫瑰曲线的k值。
如果仔细观察k值对应的图形,你会发现它的周期性变化规律。
另外,之所以画出来的效果不圆滑,是因为有些玫瑰曲线图案太复杂而图形的向量组成数又太少。


这三个滑条分别代表玫瑰曲线、圆、笛卡尔之心的大小。滑动可以调节它们。

具体实现

下面是主程序说明。

极简实现模块

我的编程习惯是,在具体运行的部分越简单越好,所有复杂的,重复的代码全部封装成类或者函数。这样子的好处就是逻辑清楚,可读性非常高。
这样一来,对于开发者而言,调试时很容易就能够发现是哪个逻辑模块出错;对于学习的人来说,他能够一下子就明白整个程序的运行流程,然后再一个个看每个模块实现的函数,事半功倍。
我的主程序运行逻辑是:
在setup()中,初始化一大堆需要的玩意。
在draw()中,以模式切换开关的状态来选择模式函数。
具体代码实现和说明将于实现模块说明中写到。

// init the canvas and GUI system
void setup()
{// init canvassize(1400, 900);background(backgroundColor);// init initInSetup();// GUI SystemGUISystem();
}// have fun!
void draw()
{if(drawOrView == true){    // exhibitiondisplayExhibition();}else{// draw clearCanvas(backgroundColor);  // refresh the canvas for oncedrawing();}
}
实现模块说明

全局设置

// import java lib
import java.util.*;// GUI library
import controlP5.*;
ControlP5 cp5;// init colors
Morph_Color rect;
Morph_Color circle;
Morph_Color DescartesHeart;
Morph_Color roseCurve;// morph graphs list
ArrayList<Morph_Brush> displayBrushes;
ArrayList<Morph_Brush> drawBrushes;// global arguments of the system
int exRGBValue = color(100);  // color of the exhibition graph
int[] drawRGBValue = new int[3000];  // color of the drawing graph
int indexValue;    // the value of the dropdown list index
int vectorsNum = 36;    // the value of the vectorsNum of the morph graph
boolean drawOrView;  // whether draw or view the defalut displayExhibition on the canvas
int backgroundColor = 150;    // the color value of the background
int maxGraph = 1500;  // the maximum of the morph graphs on the canvas
int k = 3;    // the k value of the roseCurve
int roseCurveRadius = 80;  // the rose curve radius value of the rose curve
int circleRadius = 10;  // the circle radius value of the rose curve
int heartRadius = 50;  // the heart radius value of the rose curve

初始化图形函数 initInSetup() 的内容

void initInSetup()
{// the grapgs to display firstly// rectrect = new Morph_Color();rect.addPolygonVectors(36, roseCurveRadius, 0.5);// circlecircle = new Morph_Color();circle.addCircleVectors(36, circleRadius);// heartDescartesHeart = new Morph_Color();DescartesHeart.addHeartVectors(36, heartRadius);// rose curveroseCurve = new Morph_Color();roseCurve.addRoseCurveVectors(36, roseCurveRadius, 3);// Brush list // brushes for displaydisplayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(rect.morphList,DescartesHeart.morphList));}// brushes for drawingdrawBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {drawBrushes.add(new Morph_Brush(rect.morphList,DescartesHeart.morphList));}
}

GUI设计略
……………………

展示模式函数 displayExhibition() 说明
此函数将实时获取图形改变的各种参数,并立刻在展示模式下的画布上绘制更新后的图案。

// these values are needed to update the graphs' arguments
int tempVNValue = 36;    // Vectors num
int tempIndexValue = 0;    // the index of the dropdown list
int tempMaxGraph = 300;  // maximum of the graphs which you can draw on the canvas
int tempKValue = 3;    // the k value of the rose curve
int tempRCRadius = 100;    // the rose curve radius value of the rose curve
int tempCRvalue = 10;    // the circle radius value of the rose curve
int tempHRvalue = 50;    // the heart radius value of the rose curve
boolean hasChanged = false;    // judge whether the arguments are changed// func to display the exhibition mode
void displayExhibition()
{// if something is changedif(vectorsNum != tempVNValue || indexValue != tempIndexValue || k != tempKValue || roseCurveRadius != tempRCRadius || circleRadius != tempCRvalue || heartRadius != tempHRvalue){hasChanged = true;tempVNValue = vectorsNum;tempIndexValue = indexValue;tempKValue = k;tempRCRadius = roseCurveRadius; tempCRvalue = circleRadius;tempHRvalue = heartRadius;}// update the dataif(hasChanged == true){// get the new argumentsrect = new Morph_Color();rect.addPolygonVectors(vectorsNum, 36, 0.5);circle = new Morph_Color();circle.addCircleVectors(vectorsNum, circleRadius);DescartesHeart = new Morph_Color();DescartesHeart.addHeartVectors(vectorsNum, heartRadius);roseCurve = new Morph_Color();roseCurve.addRoseCurveVectors(vectorsNum, roseCurveRadius, k);//listswitch(indexValue){case(0):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(rect.morphList,DescartesHeart.morphList));}break;case(1):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(rect.morphList,circle.morphList));}break;case(2):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(circle.morphList,DescartesHeart.morphList));}break; case(3):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(rect.morphList,roseCurve.morphList));}break;  case(4):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(circle.morphList,roseCurve.morphList));}break;    case(5):displayBrushes = new ArrayList<Morph_Brush>();for (int i = 0; i < maxGraph; i++) {displayBrushes.add(new Morph_Brush(DescartesHeart.morphList,roseCurve.morphList));}break;              }  hasChanged = false;}// regular matrixbackground(backgroundColor);for(int i = 0; i < 4; i++){for(int j = 0; j < 4; j++){Morph_Brush mb = displayBrushes.get(i*4+j);mb.drawTwoMorph(i*200+150, j*200+150, exRGBValue);}}
}

绘画模式函数 drawing() 说明
事实上,这个函数会在后台改变作为颜料绘制的图案的各类属性。在鼠标响应函数中这种变化才会体现出来。

// func to update the arguments of the drawing system
void drawing()
{// if something is changedif(vectorsNum != tempVNValue || indexValue != tempIndexValue|| k != tempKValue || roseCurveRadius != tempRCRadius || circleRadius != tempCRvalue || heartRadius != tempHRvalue){hasChanged = true;tempVNValue = vectorsNum;tempIndexValue = indexValue;tempKValue = k;tempRCRadius = roseCurveRadius; tempCRvalue = circleRadius;tempHRvalue = heartRadius;}// update the dataif(hasChanged == true){// get the new argumentsrect = new Morph_Color();rect.addPolygonVectors(vectorsNum, 36, 0.5);circle = new Morph_Color();circle.addCircleVectors(vectorsNum, circleRadius);DescartesHeart = new Morph_Color();DescartesHeart.addHeartVectors(vectorsNum, heartRadius);roseCurve = new Morph_Color();roseCurve.addRoseCurveVectors(vectorsNum, roseCurveRadius, k);hasChanged = false;}background(backgroundColor);// draw it!if(index!=-1){for(int i = 0; i < index + 1; i++){Morph_Brush mb = drawBrushes.get(i);mb.drawTwoMorph(indexPosition.get(i).x, indexPosition.get(i).y, drawRGBValue[i]);}}
}

鼠标响应函数
这是实现绘画的核心部分
一共有两种交互模式,一种是鼠标拖动,一种是鼠标点击。

// mouse
// mouse position
float mx,my;
int index = -1;  // the index of the morph painted
ArrayList<PVector> indexPosition = new ArrayList<PVector>();    // the position of the graphvoid mouseDragged()
{if(mouseX<950 && drawOrView == false){   println(indexValue);mx = mouseX;my = mouseY;index++;indexPosition.add(new PVector(mx, my));drawRGBValue[index] = exRGBValue;if(indexValue == 0){drawBrushes.add(new Morph_Brush(rect.morphList,DescartesHeart.morphList));}else if(indexValue == 1){drawBrushes.add(new Morph_Brush(rect.morphList,circle.morphList));}else if(indexValue == 2){drawBrushes.add(new Morph_Brush(circle.morphList,DescartesHeart.morphList));}else if(indexValue == 3){drawBrushes.add(new Morph_Brush(roseCurve.morphList,rect.morphList));}else if(indexValue == 4){drawBrushes.add(new Morph_Brush(roseCurve.morphList,circle.morphList));}else if(indexValue == 5){drawBrushes.add(new Morph_Brush(roseCurve.morphList,DescartesHeart.morphList));}    }println("index:" + index);
}void mousePressed()
{if(mouseX<950 && drawOrView == false){   println(indexValue);mx = mouseX;my = mouseY;index++;indexPosition.add(new PVector(mx, my));drawRGBValue[index] = exRGBValue;if(indexValue == 0){drawBrushes.add(new Morph_Brush(rect.morphList,DescartesHeart.morphList));}else if(indexValue == 1){drawBrushes.add(new Morph_Brush(rect.morphList,circle.morphList));}else if(indexValue == 2){drawBrushes.add(new Morph_Brush(circle.morphList,DescartesHeart.morphList));}else if(indexValue == 3){drawBrushes.add(new Morph_Brush(roseCurve.morphList,rect.morphList));}   else if(indexValue == 4){drawBrushes.add(new Morph_Brush(roseCurve.morphList,circle.morphList));}else if(indexValue == 5){drawBrushes.add(new Morph_Brush(roseCurve.morphList,DescartesHeart.morphList));}      }println("index:" + index);
}

总结

对比传统的绘画,我们将在载体、技法和理念、创作理念、呈现效果、局限性和应用上进行探讨。

载体

本系统的“颜料”和传统绘画区别很大,前者是动态的,后者是静态的;前者是由两类向量组成的数据,后者是现实中的实体。

技法和理念

绘画绘画,单纯就是“画”出能够“看”的作品吗?我认为并不是,正如其他艺术作品一样,绘画的重要意义在于传达情感,引起共鸣或者思考。格调低一点说,就是能让人有新奇的体验。
传统绘画对创作者的要求很高,不仅要有技术和自己的风格,还需要不断追寻灵感来创作绘画作品。
对本系统来说,技术啊风格啊都已经定好了,用户也不需要什么灵感,纯粹的来享受画画的过程就好了。各种各样的参数调节提供了灵活的选择。这样说起来,相比于传统绘画,本文的绘画系统更加追求让创作者和欣赏者获得乐趣。

创作体验

我是抱着好玩、有趣、酷炫的想法来开发这个绘画系统的,所以在绘画模式中用户一定会有“特别爽”的感觉——数位板最爽,亲身体会——尤其是玫瑰曲线的变化,我第一次画的时候都惊呆了。画一条曲线,立刻就得到了像是龙张开鳞片的效果。那种动态变化的流畅性带来的感觉真是难以用语言形容。这也是传统绘画不能带给我的体验。

呈现效果

更多效果图来袭!


所有演示的画面都是用数位板画的,因为鼠标拖拽效果太过密集而缺少美感,而且用鼠标画画的感觉不够“酣畅淋漓”。

局限性

本系统的图形只有四个,变化模式有点少。而且因为计算量问题,一旦在绘画模式下画的图形过多,电脑必卡。这也是我为什么不去设置更多向量数的原因之一,因为我的老电脑实在是带不动啊带不动。

应用

也许可以给小孩子当玩具玩。
除了这一点外,这个系统可以让人更加深刻的体会到数学之美,因为不仅所有的复杂图形都是用函数生成的,而且图形间的变化也是依靠向量的线性插值实现。
另外,有数位板的同学有福了,这个系统用数位板画画特别流畅特别爽,绝对有舒畅心情的作用。

结语

这是本学期手绘与码绘系列博文的最后一期了,接下来就聊聊我的创作初衷吧。
上篇博文说了,我是看了3Blue1Brown才开始真正认识到数学之美的,由此,我就希望能够自己去追寻,去开发,去分享,让更多的人能够通过图案,视频或者软件来认识到数学的无尽魅力。
我不知道为什么人类创造的数学体系能够如此完美的贴合自然,再现自然,甚至是给人带来美的感受;我所知道的是,数学不应该被看做是“豺狼虎豹”、“洪水猛兽”,它作为人类追寻真理的最重要的工具,应当被人所广泛的接受和欣赏。

参考链接

什么是绘画?——以抽象思维理解绘画
3Blue1Brown B站系列视频
Processing Examples
手绘与码绘————静态对比
手绘与码绘————让画面动起来!

手绘与码绘————趣味交互式绘画系统的实现相关推荐

  1. 手绘与码绘的比较---模拟风吹树动

    手绘与码绘的比较-模拟风吹树动 一.内容介绍 本文主要介绍用手绘和码绘两种方式模仿一种自然现象–风吹树动的过程,以及在实现过程对这两种方式的比较和思考.之所以选择风吹树动,是因为每次速写画一些场景时( ...

  2. 手绘vs码绘1——Q版小人

    手绘与码绘静态对比 前言 在互动媒体的课程上,我了解了P5.js这项技术,并且学会了运用它进行简单的网页绘图.可能有很多人认为,绘画是一种艺术形式,必须要拿起画笔才能产生好的作品,而代码听起来和绘画毫 ...

  3. 手绘vs码绘 动态对比

    用手绘和码绘两种方式创作"运动"主题的作品,并作出对比 手绘图: 码绘图: 比较 技法:不考虑美观的话手绘比较方便,可以怎么想怎么做,而码绘却比较复杂,需要转换为代码. 工具:手绘 ...

  4. 手绘VS码绘——动态篇(视觉错觉)

    前言 在介绍这次的实验前,先让我们看看一组图片: 相信大家看完这组图片已经有点难受的想点一下右上角的X了,其实不止你们,我在写这篇文章的时候也想关掉它-- 接下来我就按照正常的介绍流程告诉大家,这些图 ...

  5. 手绘与码绘对比(一)

    最近在老师的带领深入学习研究了手绘与码绘啊. 话不多说,直接上图. enmmm.....我知道这图不怎么样,有点沙地奥,但我们的主要目的是感受手绘与码绘的不同创作体验啊! 码绘我这次用的是p5.js ...

  6. 手绘与码绘的比较——实战之梵高《星空》

    手绘与码绘的比较--实战之梵高<星空> 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近博主的老师在交互媒体设计课上布置了一个作业,是关于"运动"主题的作品创 ...

  7. 手绘与码绘————动态对比

    本项目通过Python的turtle包的 简单练笔项目对手绘和码绘进行动态对比 思维异同 手绘:人类自古以来的传统绘画方式,千百年来无论是艺术家还是普通人都可以通过绘画来记录景色,传达情感,记录思考. ...

  8. 手绘vs码绘2——动态弓箭

    手绘与码绘动态对比 前言 在上一次博客中,我们已经成功用代码重现了手绘的Q版小人,对手绘和码绘进行了几个方面的对比分析.不得不承认,码绘由于形状和编写时间的限制,在绘制静态图像的方面和手绘还是有很大差 ...

  9. 手绘和码绘的对比(2)——动态比较

    码绘 手绘 代码实现过程 var numBalls = 30; var spring = 0.05; var gravity = 0.01; var friction = -0.9; var ball ...

最新文章

  1. 大学生学单片机怎么入门?
  2. 刘满强:干扰和恢复下农田土壤线虫群落研究及热点问题初探
  3. 建立可扩展的silverlight应用框架 step-7 final
  4. Oracle RAC在思科UCS上的应用
  5. shutil模块、json和pickle模块
  6. Java 多线程 —— ReentrantLock 与 Condition
  7. 网络性能测试工具iperf编译记录
  8. python实现MD5加密
  9. C#基于RabbitMQ实现客户端之间消息通讯实战演练
  10. Atitit 容器化技术之道 attilax著 1. 概念 1 1.1. 容器是应用服务器中位于组件和平台之间的接口集合。 1 1.2. 有时候也指集合的概念,里面可以存放不同对象 2 1.3. 、新
  11. 在云测平台对手机进行兼容性测试
  12. Pyramidal Feature Shrinking for Salient Object Detection阅读笔记
  13. apple账号被锁定且密码无法重设
  14. pikachu SQL 注入(皮卡丘漏洞平台通关系列)
  15. 用matlab实现任意点图片的旋转_(实验二) --- 图像旋转变换---matlab实现
  16. 手机vnc远程桌面,手机vnc远程桌面的配置说明
  17. MySQL 去重方法之一
  18. 搭建STC89C51工程模板
  19. 2021年11月15日到2021年11月21日笔记
  20. Spring核心编程思想

热门文章

  1. 页面出现错误提示状态码所代表的含义
  2. 美容院如何设计管理会员卡?
  3. python anaconda和pycharm_python anaconda与pycharm的特点
  4. 在外包搞了7年,废了.....
  5. JDBC 在IDEA中配置mysql8驱动过程详解
  6. Quark-Renderer----第十一篇
  7. Tomcat部署项目修改浏览器上猫咪头像
  8. 【程序员觉醒】提高效率,增加输出
  9. laxtex 输出度数符号
  10. Intel(R) Software Guard Extensions Developer Guide 参考中译文,阅读