news 2026/10/12 3:33:20

Processing + toxiclibs 纹理飘旗实战:Unikitty 旗帜的 Verlet 布料与贴图渲染

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Processing + toxiclibs 纹理飘旗实战:Unikitty 旗帜的 Verlet 布料与贴图渲染
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

本技术指南围绕 Coding Train 第 63 号挑战的第二部分展开:在基于 toxiclibs Verlet 物理引擎搭建的 3D 布料网格之上,把 Unikitty 图像作为纹理贴到一面迎风飘动的旗帜上。阅读本文后,你将掌握 Processing 中beginShape()/endShape()/texture()的完整贴图渲染流程、TRIANGLE_STRIP 三角形带与 UV 坐标映射原理,以及用柏林噪声驱动粒子受力实现布料飘动效果的完整可运行方案。

挑战背景:两集联动的纹理旗帜

本挑战(63.2)是对 63.1 的延续,两集共享同一仓库 CodingChallenges/CC_063_unikitty_flag:

  • 063.1-unikitty-flag.md:讲解如何用beginShape()、endShape()、texture()渲染带图像纹理的几何体,讨论TRIANGLE_STRIP、QUAD_STRIP等不同形状模式的选择;
  • 本文对应的 063.2:把 Unikitty 图片作为纹理贴到由 toxiclibs Verlet 物理创建的飘动旗帜上。

因此本文既包含 63.1 的贴图渲染核心概念,也完整展开 63.2 中"物理网格 + 纹理"的整合实现。仓库中的完整源码位于 CC_063_unikitty_flag.pde,配套的 Particle.pde 与 Spring.pde 分别定义了粒子与弹簧类。

第一步:用 toxiclibs 搭建 3D 布料网格

旗帜的本质是一张由粒子(VerletParticle3D)与弹簧(VerletSpring3D)交织而成的 40×40 网格。主程序开头引入 toxiclibs 的三维物理与几何包:

import toxi.physics3d.*; import toxi.physics3d.behaviors.*; import toxi.physics3d.constraints.*; import toxi.geom.*;

粒子网格初始化

int res = 1; int cols = 40 / res; // 列数 int rows = 40 / res; // 行数 Particle[][] particles = new Particle[cols][rows]; float w = 10 * res; // 相邻粒子的间距

网格以屏幕中心为原点对称铺开,每个粒子沿 x、y 方向间隔w:

float x = -cols*w/2 - 100; // x 起点左移 100,为旗杆留出空间 for (int i = 0; i < cols; i++) { float y = -rows*w/2; for (int j = 0; j < rows; j++) { Particle p = new Particle(x, y, 0); particles[i][j] = p; physics.addParticle(p); y = y + w; } x = x + w; }

Particle.pde 中的粒子类极简,直接继承 toxiclibs 的VerletParticle3D,仅在调试时用display()绘制 2 像素小圆点:

class Particle extends VerletParticle3D { Particle(float x, float y, float z) { super(x, y, z); } void display() { pushMatrix(); translate(x, y, z); noStroke(); fill(255); ellipse(0, 0, 2, 2); popMatrix(); } }

弹簧连接:右邻与下邻

遍历每个粒子,若存在右邻(i+1)或下邻(j+1),就创建弹簧并注册到物理世界:

for (int i = 0; i < cols; i++) { for (int j = 0; j < rows; j++) { Particle a = particles[i][j]; if (i != cols-1) { Particle b1 = particles[i+1][j]; Spring s1 = new Spring(a, b1); springs.add(s1); physics.addSpring(s1); } if (j != rows-1) { Particle b2 = particles[i][j+1]; Spring s2 = new Spring(a, b2); springs.add(s2); physics.addSpring(s2); } } }

Spring.pde 中,弹簧的静止长度直接取全局间距w,强度为 1(该值越大布料越"硬"):

class Spring extends VerletSpring3D { Spring(Particle a, Particle b) { super(a, b, w, 1); } void display() { stroke(255); strokeWeight(1); line(a.x, a.y, a.z, b.x, b.y, b.z); } }

横向对比:第 20 号挑战 CC_020_ClothSimulation 中的 2D 版本 P5/spring.js 使用同样的super(a, b, w, 1)构造方式,而 3D 版本 CC_020_Cloth3D.pde 通过particles[0][0].lock()与particles[cols-1][0].lock()锁定左上、右上两个角。本挑战则锁定整条左边缘(见下文)。

固定左边缘与重力

旗帜需要挂在旗杆上,因此锁定第一列全部粒子:

for(int i = 0; i < particles[0].length; i += 1){ particles[0][i].lock(); }

物理世界初始化时注册重力行为(Vec3D(0, 0.5, 0)表示 y 轴方向的轻微重力,数值比第 20 号挑战的Vec3D(0, 1, 0)更小,让旗帜更松弛):

physics = new VerletPhysics3D(); Vec3D gravity = new Vec3D(0, 0.5, 0); GravityBehavior3D gb = new GravityBehavior3D(gravity); physics.addBehavior(gb);

第二步:柏林噪声驱动的三维风力

布料的"飘动"并非真实流体模拟,而是每一帧对每个粒子施加一个由noise()计算的风力向量。核心技巧是给噪声输入叠加不同的偏移量(+5000、+3000),让 x、y、z 三个方向的风力互不相关:

float xoff = 0; for (int i = 0; i < cols; i++) { float yoff = 0; for (int j = 0; j < rows; j++) { float windx = map(noise(xoff, yoff, zoff), 0, 1, 0, 3); // 主导风向,向右 float windy = map(noise(xoff+5000, yoff+5000, zoff), 0, 1, -0.5, 0); // 轻微向上 float windz = map(noise(xoff+3000, yoff+3000, zoff), 0, 1, -1, 1); // 前后摆动 Vec3D wind = new Vec3D(windx, windy, windz); particles[i][j].addForce(wind); yoff += 0.1; } xoff += 0.1; } zoff += 0.1; // 每帧推进第三个噪声维度,形成随时间变化的流动

参数要点:

  • map(noise(...), 0, 1, 0, 3)将噪声输出从 [0,1] 映射到风力区间;x 方向取 0~3 是主风力,y 方向取 -0.5~0 让旗帜略微上飘,z 方向取 -1~1 提供纵深摆动;
  • xoff/yoff每次递增 0.1 控制风力在网格上的空间变化频率,值越小风越"平滑";
  • zoff每帧递增 0.1,是风随时间演化(周期性飘动)的关键。

随后调用physics.update()让 Verlet 积分器推进所有粒子位置。主循环还预留了rotateY(a)的旋转演示代码(默认注释),可取消注释观察旗帜的 3D 形态。

第三步:TRIANGLE_STRIP 与 UV 贴图渲染

这是本挑战(也是 63.1)的核心:如何把 Unikitty 图像贴到随粒子位置变形的网格上。

加载纹理

PImage unikitty; // ...setup 末尾... unikitty = loadImage("unikitty.jpg");

纹理图像存放在仓库的 CodingChallenges/CC_063_unikitty_flag/Processing/CC_063_unikitty_flag/data/unikitty.jpg(2448×2448 的方形图片),Processing 会从 sketch 的data目录加载它。

渲染循环:三角形带 + 纹理坐标

noFill(); noStroke(); textureMode(NORMAL); // 纹理坐标使用归一化 [0,1] for (int j = 0; j < rows-1; j++) { beginShape(TRIANGLE_STRIP); texture(unikitty); for (int i = 0; i < cols; i++) { // 上一行顶点 float x1 = particles[i][j].x; float y1 = particles[i][j].y; float z1 = particles[i][j].z; float u = map(i, 0, cols-1, 0, 1); float v1 = map(j, 0, rows-1, 0, 1); vertex(x1, y1, z1, u, v1); // 下一行顶点 float x2 = particles[i][j+1].x; float y2 = particles[i][j+1].y; float z2 = particles[i][j+1].z; float v2 = map(j+1, 0, rows-1, 0, 1); vertex(x2, y2, z2, u, v2); } endShape(); }

实现要点逐条拆解:

  1. textureMode(NORMAL):声明后续vertex(x, y, z, u, v)中的纹理坐标是 0~1 的归一化值,而非像素值;
  2. TRIANGLE_STRIP模式:每行两两顶点(第 j 行与第 j+1 行)交替写入,构成一条连续的三角形带。这种方式相比逐个绘制独立三角形,顶点数量几乎减半,是 GPU 渲染布料/地形的标准做法。63.1 中讨论的QUAD_STRIP是另一种选择,但三角形带在纹理插值上更通用;
  3. UV 坐标映射:u = map(i, 0, cols-1, 0, 1)、v = map(j, 0, rows-1, 0, 1)把网格行列索引线性映射到纹理坐标。由于顶点坐标取自粒子的实时物理位置,而 UV 坐标由固定行列索引算出,旗帜飘动时图像就会随之扭曲——这正是"飘动的 Unikitty 旗帜"的视觉效果来源;
  4. noFill()与noStroke():渲染纯纹理表面,避免出现线框干扰。

最后绘制旗杆,把旗帜左边缘与一条竖线对齐:

stroke(255); strokeWeight(4); line(-cols*w/2 - 100, -rows*w/2, -cols*w/2 - 100, height);

调试时可取消注释//for (Spring s : springs) s.display();与粒子display(),直接看到物理网格结构。

完整可运行代码

将 CC_063_unikitty_flag.pde、Particle.pde、Spring.pde 三个文件放入同一 sketch 目录,并把unikitty.jpg放入data子目录,即可在 Processing(需安装 toxiclibs 库)中运行。渲染窗口为size(800, 600, P3D),启用 3D 渲染器。

p5.js 移植参考

仓库中同一系列的 2D 布料(CC_020_ClothSimulation/P5)提供了 p5.js 移植的完整参照:页面按顺序引入 p5.js、toxiclibs.js 与 toxichelper.js(见 index.html),JS 侧同样遵循"粒子入物理世界 → 弹簧连接 → 锁定锚点 → 每帧 update"的结构。若要把本挑战移植到 p5.js,仅需将三维类替换为VerletPhysics3D对应接口、loadImage改为异步加载,并保持beginShape(TRIANGLE_STRIP)+texture()+ 归一化 UV 的渲染管线不变。

参数调节指南

参数位置效果与建议
cols/rows(40)主程序顶部网格密度。增大可让旗帜更细腻,但粒子与弹簧数量平方级增长,注意性能
w(10)主程序顶部粒子间距,同时决定弹簧静止长度与旗帜整体尺寸
弹簧强度(super(a, b, w, 1)的第三参)Spring.pde越大布料越硬挺,越小越柔软、摆动幅度越大
重力Vec3D(0, 0.5, 0)setup增大使旗帜下垂更明显,减小则更轻盈
风力区间0~3/-0.5~0/-1~1draw控制三个方向的风力强度,主导飘动幅度与方向
xoff/yoff步长 0.1draw风力在网格上的空间变化频率
zoff步长 0.1draw风随时间演化的速度,越大飘动越急促

常见问题

  • 纹理未显示:确认调用了textureMode(NORMAL)、beginShape()后立即texture(unikitty),且vertex()传入 5 个参数(x, y, z, u, v);纹理坐标必须落在 [0,1] 区间(归一化模式下)。
  • 旗帜完全不飘:检查physics.update()是否在每帧draw()中调用,以及是否在draw()中为每个粒子执行了addForce(wind)。
  • 旗帜脱离旗杆:确认左边缘粒子已通过particles[0][i].lock()锁定;若只锁两端,边缘中部会下垂。
  • 画面只有线框:确认渲染循环中已设置noFill(); noStroke();,或检查是否误把调试用的弹簧/粒子display()保留在纹理绘制之后。

延伸阅读

  • 本挑战第一部分:063.1-unikitty-flag.md,深入讲解beginShape()/endShape()/texture()与TRIANGLE_STRIP、QUAD_STRIP的取舍;
  • 布料物理基础:020-cloth3d.md 与 CC_020_ClothSimulation,展示无纹理的 2D/3D 布料网格搭建;
  • 自定义形状:9.22-custom-shapes.md 与 18.8-custom-shapes-in-webgl-with-p5js.md,介绍 p5.js 中beginShape()、vertex()的通用用法。
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

相关推荐

上一篇:跨越Android固件格式鸿沟:一站式智能提取工具的完整指南
下一篇:Android固件提取终极解决方案:一站式工具集Firmware_extractor深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 3:32:26

LSI3008 RAID卡驱动与固件匹配实战指南

简介&#xff1a;本资源为LSI 3008 SAS RAID阵列卡官方兼容驱动合集&#xff0c;面向服务器运维工程师、系统集成人员及企业级存储管理员&#xff0c;解决Windows/Linux等主流平台下RAID控制器识别异常、性能受限或功能缺失等关键问题。压缩包共97个文件&#xff0c;涵盖28个核…

作者头像 李华
网站建设 2026/10/12 3:32:09

缠论程序化实战:从K线合并到买卖点信号的全链路实现

简介&#xff1a;这是一份面向股票量化与缠论研究者的Python程序化实践项目&#xff0c;以《缠中说禅博客》中的交易方法为蓝本&#xff0c;实现了K线包含处理、顶底分型识别、画笔划分等核心流程&#xff0c;并在TODO中规划线段划分、均线选股、均线轮动与板块强弱指标、每日走…

作者头像 李华
网站建设 2026/10/12 3:30:28

React 搜索框闪烁问题全解析:竞态条件、防抖与请求生命周期管理

2026年了&#xff0c;React 的数据获取链路早就被各种方案武装到了牙齿&#xff1a;路由级有加载态编排&#xff0c;服务端有流式渲染&#xff0c;请求库有缓存和重试&#xff0c;并发特性连渲染优先级都帮你排好了队。可真到了生产环境&#xff0c;用户对一个系统最直接的一句…

作者头像 李华
网站建设 2026/10/12 3:29:59

AI快速进步但不会通用超级智能:技术约束与工程实践判断

1. 为什么这个判断值得认真对待1.1 一个反直觉但越来越主流的观点AI 会快速进步&#xff0c;但不会走向通用超级智能——这个判断乍一听有点矛盾。既然进步快&#xff0c;为什么不会走到那一步&#xff1f;但如果你真的在一线做模型训练、做产品落地、做推理优化&#xff0c;你…

作者头像 李华
网站建设 2026/10/12 3:29:44

Hive性能优化与执行原理深度解析

1. 这不是背题手册&#xff0c;而是一份Hive生产环境“踩坑实录”你打开这份文档时&#xff0c;大概率正面临两类场景&#xff1a;要么是明天就要进面试间&#xff0c;手心冒汗地翻着零散笔记&#xff0c;对着“Hive和传统数据库区别”这种题反复默念&#xff1b;要么是刚在数仓…

作者头像 李华