欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Processing 案例 | 扑面而来的满天繁星

发布时间:2024/1/8 编程问答 68 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Processing 案例 | 扑面而来的满天繁星 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

文章目录

  • 引言
  • 效果展现
  • 原理分析
  • 代码实现
    • 确定代码执行流程
    • 星星绘制在屏幕上
    • 让星星动起来
    • 点击鼠标,星星的速度发生改变
    • 视角改变
  • 完整代码
  • 结语

  

引言

清明时节雨纷纷,路上行人欲断魂”,每到清明,你总能从那如丝如雨缕的春雨绵绵中感到心上缭绕的那点忧愁。

很少有这样一个节日,像清明这样的矛盾。当你放歌踏青时,它是一个轻盈的日子,是节气;当你慎终追远的时候,它是一个悲怆的日子,是节日。

在每一个清明冰凉的夜,你是否也曾仰望满天的繁星,追忆那已故的人?他们就像那满天的繁星,在你生命的漫漫长夜中匆匆划过,照亮了一段时光。

现在就让我们一起用 Processing 来模拟那片星域,让记忆中的人和事翻上心头,细细评味。

效果展现


  

原理分析

首先通过观看视频,我们可以发现在鼠标所在的点附近的星星是最小的,离该点越远,星星的面积越大,移动的速度越快。就好像星星都从这个点里出来一样,我们暂且把这个点叫做消失点。并且观察可得,消失点把屏幕分成了四个区域A,B,C,D(如图一所示),同时把星星也分成了四类。其中A中的星星向左上方移动,B中点向右上方移动,C中的点向左下方移动,D中的点向右下方移动。

接下来我们来看一下代码实现的大概思路。

我们在脑海里假想一个长方体,以这个长方体的一个顶点为原点,建立世界坐标系。然后选取一个面作为Processing 程序的窗口,建立屏幕坐标系。然后在屏幕上任选一个点,作为消失点,该点在屏幕坐标系下的坐标为(x_endpoint,y_endpoint )。

接着在这个长方体里随机生成一系列的星星,每个星星由一个三维向量(x_world,y_world,z_world)表示,这是它在世界坐标系下的坐标。

接下来我们要做的就是把每一个星星的在世界坐标系的坐标,转换成在processing窗口里面的坐标(x_screen,y_screen),这样我们就可以在屏幕上把星星绘制出来。

首先需要计算星星在世界坐标系下相对于消失点(endpoint)x,y方向的偏移量,接下来将这个偏移量根据星星的z_world进行放缩,最后再将放缩完成的偏移量加回消失点的坐标,得到星星在屏幕上的坐标。
公式如下:

x s c r e e n = ( x w o r l d − x e n d p o i n t ) / z w o r l d ∗ s c a l e + x e n d p o i n t x_{screen}=(x_{world}-x_{endpoint})/z_{world} *scale+x_{endpoint} xscreen=(xworldxendpoint)/zworldscale+xendpoint
y s c r e e n = ( y w o r l d − y e n d p o i n t ) / z w o r l d ∗ s c a l e + y e n d p o i n t y_{screen}=(y_{world}-y_{endpoint})/z_{world} *scale+y_{endpoint} yscreen=(yworldyendpoint)/zworldscale+yendpoint

其中scale是放缩比例,用来控制控制星域的范围。

分析公式可以发现z_world越大对应的 x_screen、y_screen 越大,也就是说离消失点越远。也就说我们只要将z_world 初始设为一个比较大的值,然后在不断减小它,这样就会出现星星离消失点越来越远,离屏幕越来越近的效果。同时我们根据 z_world 的大小设置星星的直径 diam 设置,z_world 越大,diam 越小,这样就符合近大远小的透视规律。
  

代码实现

首先我们来确定代码的结构,创建三个 Processing 文件,分别为 main.pde、StarField.pde、Star.pde。其中main.pde 里面是程序执行的主流程。StarField.pde 里面主要定义了 StarField类、Star.pde 主要定义了 Star 类。

确定代码执行流程

在 main.pde 中输入如下代码:

/*这个是一个由星星构成的粒子系统 拥有星星的所有状态和行为*/ StarField sf; void setup(){ size(400, 400); sf = new StarField(); } void draw(){ background(0); /*运行星域系统 更新星星的状态和绘制星星*/ sf.run(); } /*鼠标按下时调用的函数 用来改变星域的速度*/ void mousePressed(){ } /*鼠标移动的时候调用的函数 用来改变视角*/ void mouseMoved(){ }

星星绘制在屏幕上


首先在StarField.pde中输入如下代码:

class StarField{ //常量声明 //变量声明 //构造函数 //成员函数 }

以上代码我们确定了该类的四大组成部分,接下来我们在将构造函数替换为如下代码:

StarField(){ /*将初始消失点设置在鼠标最开始的位置*/ endpoint = new PVector(mouseX, mouseY); /*初始化所有的星星*/ stars = new ArrayList(); for(int i = 0; i < STAR_COUNT; i++){ stars.add(new Star()); } }

接着在StarField类的成员函数中定义run函数:

void run(){ for(Star s : stars){ /*对星星进行坐标变换 获得星星在屏幕坐标系的坐标 用于之后的星星的渲染*/ s.transform(endpoint); /*对屏幕外的星星进行裁剪 同时生成一个新的星星 使得星星可以源源不断的出现*/ s.checkEdge(); /*依据屏幕坐标系渲染星星, 使得星星在屏幕上出现*/ s.display(); } }

接下来我们来定义出现在 StarField 的构造函数中和 run 函数中的变量和常量。

/*该粒子系统包含的星星的个数 星星越多画面越密*/ final int STAR_COUNT = width / 2; /*数据类型为Star object的动态数组 存储该星域中所有的星星*/ ArrayList<Star> stars; /*消失点 用来控制视角*/ PVector endpoint;

之后我们来定义Star对象,输入如下代码:

class Star{ //常量声明 //变量声明 //构造函数 //成员函数 }

将 Star 类的构造函数替换为:

Star(){ /*在一个长方体区域内随机生成一个点 返回它在世界坐标系下的坐标*/ worldPosition = new PVector(random(0, width), random(0, height), random(0, MAX_DEPTH)); }

接下来在Star类成员函数部分加入 transfrom 函数,这个函数是本作品最关键的地方,希望大家能好好体会一下。

void transform(PVector endpoint){ /*将星星的坐标从世界世界坐标系变换到消失点坐标系 以下代码等同于: viewPosition.x = (worldPosition.x - endpoint.x) / worldPosition.z * SCALE; viewPosition.y = (worldPosition.y - endpoint.y) / worldPosition.z * SCALE; */ viewPosition = PVector.sub(worldPosition, endpoint).div(worldPosition.z).mult(SCALE); /*将星星的坐标从消失点坐标系变换到屏幕坐标系 以下代码等同与: screenPosition.x = endpoint.x + viewPosition.x; screenPosition.y = endpoint.y + viewPosition.y; */ screenPosition = PVector.add(endpoint, viewPosition); /*根据世界坐标z的大小来确定星星的直径 z越小,说明越靠近屏幕,所以星星越大*/ diam = map(worldPosition.z, 0, MAX_DEPTH, MAX_DIAM, 0); }

  接着在Star类的成员函数部分加入checkEdge函数的定义

void checkEdge(){ if(screenPosition.x <= 0 || screenPosition.x >= width || screenPosition.y <=0 || screenPosition.y >= height){ /*如果这个点已经在屏幕外了,那么将其裁剪, 同时在相同的长方体区域随机生成一个新的点*/ worldPosition.set(random(0, width), random(0, height), MAX_DEPTH); } }

然后继续在Star类的成员函数部分Star类的display函数。

void display(){ /*在屏幕上绘制该星星 每一个星星是一个白色的、没有边的圆*/ fill(255); noStroke(); ellipse(screenPosition.x, screenPosition.y, diam, diam); }

在第二步的最后我们把Star类需要的一些成员变量和常量加上,把它们添加到Star类的常量和变量部分。

/*星星在屏幕坐标系直径的最大值 用于控制星星在屏幕上的整体大小*/ final float MAX_DIAM = 16; /*星星里屏幕最远的距离 用来控制星域的立体感*/ final float MAX_DEPTH = width / 2; /*进行坐标变换时候的 用来控制星星在屏幕上的分布范围*/ final float SCALE = MAX_DEPTH; /*星星在三个坐标系下的坐标 记录它们在不同参考系下的位置*/ PVector worldPosition, screenPosition, viewPosition; /*星星在屏幕坐标系下的直径 确定星星在屏幕上的大小*/ float diam;

让星星动起来


首先我们在 StarField 的 run 方法中,给每一个星星添加如下的行为:

/*更新在世界坐标系的坐标 让星星飞向观察者*/ s.move(speed);

接着在StarField的构造方法中初始化 speed 这个值。

/*初始化星星的移动速度 让移动速度不用太快和太慢*/ speed = (MAX_SPEED + MIN_SPEED) / 2;

然后在 StarField 的变量和常量部分,定义新增加和星星速度有关的常量和变量。

/*分别代表星星移动的最大速度,最小速度, 用来控制星星移动速度的范围*/ final int MAX_SPEED = 11, MIN_SPEED = 1; /*速度改变的步长 每一次增加或者减小速度的时速度改变的最小值*/ final int SPEED_STEP = 1; /*星星移动的速度 控制星星移动快慢*/ int speed;

这之后我们在 Star 的成员函数部分加入如下代码,然后第三阶段就到此结束。

void move(float speed){ worldPosition.z -= speed; /*限制星星世界坐标系的位置 防止出现星星移动到屏幕外的情况*/ worldPosition.z = constrain(worldPosition.z, 0, MAX_DEPTH); }

点击鼠标,星星的速度发生改变



直接在 main.pde 的 mousePressed 中加入如下代码:

if(mouseButton == LEFT){ /*如果是鼠标左键按下了 提高星域的速度*/ sf.speedUP(); }else if(mouseButton == RIGHT){ /*如果是鼠标右键按下了 降低星域的速度*/ sf.speedDown(); }

然后在 StarField 的成员函数中定义 speedUP 和 speedDown 函数:

void speedUP(){ speed += SPEED_STEP; /*限制speed在最大和最小值之间 防止速度太快*/ speed = constrain(speed, MIN_SPEED, MAX_SPEED); } void speedDown(){ speed -= SPEED_STEP; /*限制speed在最大和最小值之间 防止速度小于零*/ speed = constrain(speed, MIN_SPEED, MAX_SPEED); }

&enmsp;

视角改变


首先在 mouseMoved 函数里面加入如下代码:

/*根据鼠标的位置来更新消失点的位置 达到改变视角的目的*/ sf.updateEndpoint(mouseX, mouseY); 然后再StarField的成员函数部分对上面调用的updateEndpoint函数进行定义:void updateEndpoint(float x, float y){ endpoint.x = x; endpoint.y = y; }

到了现在我们就完成了代码的所有编写。
  

完整代码

我的github
我的openprocessing

结语

我们有很多可以扩展的地方,比如改变 Star 类里的 MAX_DEPTH 来改变星域的立体感,改变 StarField 类的 STAR_COUNT,Star 类 SCALE 的来改变星域的密度和范围。

目前为止对于每一星星我们只是简单的画了一个白色的小圆,其实这里有很大的发挥空间。比如用 Noise 函数根据星星在屏幕坐标系的位置计算星星的颜色,或者用你思念的人的名字或者其中的字母来替代圆圈,创造出那一片独一无二,只属于你的灿烂的星域!

最后,祝大家清明节快乐!

总结

以上是生活随笔为你收集整理的Processing 案例 | 扑面而来的满天繁星的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。