从连 surf() 为什么要转置都搞不明白,到用贝塞尔曲面建模出豌豆射手、向日葵和僵尸的完整动画场景——这篇文章记录了整个学习过程。如果你也在学MATLAB的表面建模,希望这里面的踩坑记录能帮到你。
260314
不知道写什么了,写几个字凑凑数吧,不然显得太敷衍了
260315
先看看matlab能干什么吧,问了下grok,叫他生成一个篮球,就生成下面这个了:

我觉得这种体现不了画图的过程,就让他画一个有网格的篮球,我不需要上色,下面这是效果:

ai写的代码我没看,不过感觉大部分逻辑都是比较简单的,所以代码就会很长,不过用ai来建一些简单的物体还是可行的。
让ai之间互相评估打架,在这个项目里面应该会有很大的作用,我是这样感觉的哈。
260316
用matlab来画图最大的感受是:他是用细小间隔的分段点来近似一个连续的区间的,你分段分的越小,自然图像就看起来越连续,就看不出那些网格了。
1 | clear;clc;close all; |
我有一个很大的问题,就是surf()里面的Sx,Sy,Sz我发现转置和不转置画出来的图像是没有什么区别的,那我为什么还要进行转置啊???
其实就是没有什么区别(有区别就怪了)
1 | 假设我们有下面三个 3 × 3 矩阵(为了简单,用很小的网格): |

还有,为什么surf(u,w,Sx’,‘edgecolor’,‘k’,‘facealpha’,0.5); xlabel(‘u’);ylabel(‘w’)里面Sx非得转置不可,不转置就会报错呢?
因为surf指令有一个原则:
- X和Y是向量,Z是矩阵,要满足:
-
length(X) == size(Z, 2) (X 的长度 = Z 的列数)
-
length(Y) == size(Z, 1) (Y 的长度 = Z 的行数)
至于为什么是这样的,哎,其实我不懂啊…
10X1 7X1 7X10是对的
10X1 7X1 10X7是错的
-
surf(X, Y, Z) 三个都是矩阵,且尺寸相同
解释见上面(嘿嘿偷个懒偷个懒):
1 | **Bezier曲面的Matlab实现** |
看不动了看不动了,先贴着,留着明天进步吧。
260319
之所以感觉有点难搞是因为你没有练习,可以试着建立一下小鱼的简单表面建模,采用三次贝塞尔曲面进行拼接练习一下
260324
书接上回260319的曲面绘制代码,我们来一行一行细说
1 | clear; clc; close all; |
这“三件套”必不可少,因为我们要清除之前的变量赋值、清空命令窗口,关闭打开的图形窗口,养成这个习惯只有好处没有坏处
1 | m = 3;n = 3; |
干什么:设置了贝塞尔曲面的阶数为3(有个问题啊,mn是可以不同的吗?)
数学意义:说明有(m+1)x(n+1)个控制点,也就是(3+1)x(3+1)=16个控制点,阶数越高,曲面越灵活但计算量越大
启发:如果发现曲面很复杂无法通过三次贝塞尔曲面来进行绘制,我们可以采用四阶甚至五阶贝塞尔曲面来绘制
1 | pxx = [0 2 4 6]; |


干什么:pxx、pyy定义 x 方向和 y 方向控制点的坐标序列,使用meshgrid生成一个4X4矩阵,现在 px(i,j) 和 py(i,j) 就是第 i 行、第 j 列控制点的 (x,y) 坐标了,就是下面这十六个点:

有什么用:这是生成参数曲面控制点网格的标准写法,以后要自己建模时是会用到的
思考:标准写法?我一定是0246,不能是0123吗?
1 | pz = [0 1 1 0; |
干什么:手动定义 4×4 的 z 坐标矩阵,给每个控制点一个高度

有什么用:其实我们已经可以推测出大概的形状了,就是16个控制点的坐标已经确定了,Bézier 曲面的 z 值由控制点决定,这就是你“设计”曲面形状的地方
思考:那这样的话控制点的xy都是固定的了,但是我们实际的大作业控制点肯定不能是xy固定的啊,所以我是不是可以理解为:我们的pxpy都是要手动去输入的?
1 | px(4,1)=3;py(4,1)=3;pz(4,1)=0; |
干什么:这一步很好的回答了你的问题,这就是更改控制点的坐标的,所以,你可以自己任意去定义控制点啦,想想就有无限的可能性

有什么用:用处大了去了,我们可以调整边界点,对控制点可以说是随便定义了
思考:大作业里应该会经常这么干!两个不同的曲面进行拼接,就一定少不了控制点的重定义,因为要让控制点进行重合!所以在做项目之前就应该先想好控制点的位置,后续再微调,一旦项目成形了改点很难啊
1 | figure(1) |
干什么:打开第 1 个图形窗口(后面所有图都画在这里)
1 | plot3(reshape(px,[numel(px),1]),reshape(py,[numel(py),1]),reshape(pz,[numel(pz),1]),'rs','markerfacecolor','r'); |
干什么:把 4×4 矩阵展平成 16×1 的列向量,用 plot3 画出 16 个红色实心方块(控制点),‘rs’ = red square,‘markerfacecolor’,‘r’ 让方块内部也填充红色
px、py被扁成这样了


有什么用:把控制点的坐标显示出来了,可视化设计,可以显得自己的设计更加装逼一点,这是真的哈哈哈哈
思考:控制点的颜色代码都是什么?常见颜色的代码是啥样的?

还有,我想用圆形来表示控制点呢?六边形呢?
为什么要进行这个操作呢?不扁可以吗?
1 | hold on; |
干什么:告诉 MATLAB“不要清空画布,继续在上面叠加图形”
1 | mesh(px,py,pz,'edgecolor','b','facealpha',0); |
干什么:用蓝色线条画出控制点之间的连线网格(像一张“网”)
注意:‘facealpha’,0 让网格面完全透明,只显示边线,把他改成0.5,你会发现一个新宇宙哈哈哈
1 | [Sx,Sy,Sz] = PSBezierMxN(px, py, pz, m, n); |
干什么:调用你自己写的函数 PSBezierMxN,计算 Bézier 曲面上的所有点坐标
输入参数:
-
px, py, pz:4×4 的控制点坐标矩阵(你前面已经手动改过四个角了)
-
m=3, n=3:u 方向和 w 方向的阶数(都是三次)
输出:
- Sx, Sy, Sz:计算好的曲面点坐标,每个是 200×200 的矩阵(因为函数里 Rm=Rn=200)
对大作业的意义:把“计算曲面”这部分封装成函数后,后面你想画多少个不同的 Bézier 曲面,只要改控制点再调用一次这个函数就行,非常干净
函数如下:
1 | function [Sx,Sy,Sz,Rm,Rn]=PSBezierMxN(px,py,pz,m,n) |
1 | surf(Sx, Sy, Sz, 'linestyle', 'none'); |
干什么:用 surf 命令把刚才计算出的 200×200 个点画成一个曲面,‘linestyle’,‘none’:表示不画网格线,只画曲面本身。这样曲面没有线条干扰(适合做大作业展示)
思考:如果想看到曲面上的网格线,可以把 ‘none’ 改成 ‘-’ 或 ‘:’
1 | colormap(jet); |
干什么:给曲面设置颜色映射方案
思考:我想要一种色系的颜色映射方案呢?每一种方案的效果图有没有呢?
1 | light('Position',[-1 0 0], 'Style', 'local'); |
干什么:在三维空间中添加一盏“灯”,让曲面产生明暗效果(立体感)
思考:似乎可以通过光源的不断转换,来让大作业更有质感,模拟光照
1 | box off; |
干什么:把图形四周的“盒子”边框去掉(只剩下坐标轴)
思考:其实这一步我都不是很理解,去不去掉好像没啥区别,先放在这里吧
来吧,看看ai生成的好东西:
啤酒瓶:

山:

降落伞:

机翼:

钱包:

(这个钱包我是真的没绷住啊哈哈哈)
260326
今天学习一下曲面的拼接哈😄,基本思想如下:

一个简单的曲面拼接实验,说实话其实并不会很难

有点晚了,好困啊,先睡了,明天再搞吧,感觉真的要搞豌豆射手的话,嘴那一部分可以使用旋转命令进行,身子那一部分可以使用扫掠命令去搞,叶片底部那里可以用三次贝塞尔曲面/孔斯曲面进行绘制,大概的思路就是这样的啦,再加一个向日葵🌻,射手可以搞寒冰射手、火焰射手,再搞个僵尸🧟♀️我觉得就可以了
260330
眼睛干涩的厉害,不想看屏幕…
第一个问题,就是我怎么把每一片面片连接在一起,难不成要把一大堆面片都放在一个脚本里面?还是每一个部分分开建面片,最后进行一个聚合呢?
看了一下,一个模型基本上都是在一个.m文件里面去实现的
第二个问题,我怎么去找我想要的控制点坐标呢?
第三个问题,我怎么去知道自己需要的面片是什么类型呢?贝塞尔三次曲面?孔斯曲面?扫掠曲面?旋转曲面?
我看基本上采用的应该是贝塞尔三次曲面
哇去,学姐他们写的代码真的是,长,实在是长,有很多函数,而且主函数也很长,九百多行…

调用我自己编写的函数,只要放在同一个文件夹就可以
搞懂了一部分:
豌豆射手上半头

豌豆射手下半头(更改z值为负 对称得到的)

茎秆(不过怎么看怎么不对劲)其实挺对的,因为y的一格是0.005,其实很小,茎秆是很细的

不过我想着代码里面应该会有对称的部分,但是今天暂时没有找到,好奇怪啊,这茎秆不是只画了一半吗哈哈哈,明天看看,说不定就有想法了
这里贴一张建模的效果图哈

260331
有几个问题,第一个,就是为什么上下豌豆的头会出现没有完全连上的情况
这一步,看好了,关键就在这一步,主要是如果不这样调整的话就会出现这个问题,缝合的曲面不光顺了,说明需要不断地调整才能做好


第二个,后脑勺面片如何去缝合的,形成了不仅仅是一个矩形面片而是一个椭圆形面片(有点超乎我的想象了)
好吧其实不是你想不出来,而是本来就这样,其实,本质还是一个椭圆形面片,只不过叠加起来你看不太出来这是两块罢了哈哈哈

第三个,怪异的茎秆,感觉从代码来看完全就不是这个效果啊…
感觉可以粗一点,不然杆子实在是太细了


ctrl+r注释
茎秆部分发现有点太长了,缩短一点


发现叶子太贴近了,可以改的分散一点

效果还是不错的,茎秆有粗度的变化,叶子显得分散


创建了三个函数,分别是绕xyz轴旋转的函数,这样我们后续使用就可以直接调用了

这两个名字一定是要一样的,不然识别不了函数的


笑死了,做出了一个舞娘豌豆射手,很风骚有没有

由于坐标倒了,所以我们使用这个指令 flipud(B17_x(:,4)) 进行矩阵的翻转,问题得到解决,侧面面片成功获得(原来是扭在一起的)


260401
发现者这一部分曲面存在一定的干涉,后续可以优化一下

发现把划横线地方的flipud删除掉就好了,为啥呢?

今天把后面的叶片耳朵给做了,还贴了一些好看的贴图,把茎秆优化了一下,感觉效果很不错的,很厉害!

260402
今天打算做好豌豆射出来的球,并且把小僵尸的建模思考搞清楚
这里发现豌豆的嘴巴可以加厚一圈,这样豌豆射手会更加真实一点

发现豌豆怎么这么大,居然比射手的嘴巴还大,笑死我了

感觉效果还可以哈

今天问了下grok,项目是这样实现的
| 文件名 | 作用 | 详细说明 |
|---|---|---|
| main.m | 整个程序的主入口(最重要) | 负责读取所有 .obj 模型、转换贝塞尔曲面、设置动画逻辑、调用 visualizePart 绘制僵尸和豌豆射手、控制动画循环 |
| Peashooter_dynamic.m | 豌豆射手动态摇头动画 | 用你之前写的 Bézier 曲面控制点生成豌豆射手头部 + 叶子 + 茎,并实现左右摇头动画 |
| Peashooter_static.m | 豌豆射手静态展示 | 只显示静态的豌豆射手(不摇头) |
| convertQuadToBezier.m | 四边形转 Bézier 曲面 | 把僵尸模型的四边形面片转换为 4×4 控制点的 Bézier 曲面片(核心曲面重建函数) |
| convertTriToBezier.m | 三角形转 Bézier 曲面 | 把僵尸模型的三角形面片转换为 Bézier 三角形曲面(10个控制点) |
| Bezier.m | 计算 Bézier 曲面上的点 | 给定 4×4 控制点,用矩阵方式计算出实际曲面上的插值点(21×21网格) |
| discretizeBezier.m | Bézier 曲面离散化(生成网格) | 把 Bézier 控制点离散成可以画的顶点和四边形面片 |
| discretizeBezierTri.m | 三角形 Bézier 离散化 | 处理三角形 Bézier 曲面的离散 |
| genEdgePoints.m / genBezierEdgePoints.m | 生成边上的控制点 | 为相邻面片的共享边生成中间控制点,保证拼接处 G¹ 连续 |
| genInnerPoints.m | 生成曲面内部控制点 | 根据四个角点生成内部 2×2 控制点 |
| readQuadObj.m | 读取 .obj 文件 |
读取僵尸的顶点、面片(支持四边形和三角形) |
| RotateX.m / RotateY.m / RotateZ.m | 旋转矩阵函数 | 提供绕 X/Y/Z 轴旋转的功能 |
| Symmetry.m | 轴对称函数 | 提供关于 X/Y/Z 轴的镜像翻转 |
| create_and_move_ball.m | 生成并移动豌豆球 | 画一个绿色球并让它沿 X 轴移动(发射动画) |
| hat_move.m / move_hat.m | 帽子移动函数 | 用于帽子掉落或移动的动画(未在主程序中使用) |
| draw_all_zombies.m | 批量绘制僵尸(未使用) | 尝试用 batch 方式渲染多个僵尸(目前未启用) |
| 身体.obj / 左手.obj / 右手.obj / 左腿.obj / 右腿.obj / 头.obj | 僵尸模型文件 | Blender 导出的僵尸各部位几何模型 |
发现僵尸的表面建模很是困难,所以选择改变方式来进行建模,探索到的一个方式是使用obj文件导入matlab,进行一系列的函数编写,读取僵尸的顶点、面片等,并把面片更改为贝塞尔曲面即可
当程序读到 f 10//1 1//4 7//3 8//2 这行时:
它把这行拆分成 4 个部分,提取出每个部分的第一个数字(顶点索引),得到 [10, 1, 7, 8],然后存入 quad_faces 矩阵,表示这是一个由第10、1、7、8号顶点组成的四边形。
这篇文章我觉得应该是有帮助的

写了这个代码,可以提取obj里面的顶点数据和面片数据

260405
不想搞表面建模项目,随便写一点东西吧那就,今天是清明节,感觉人的心也随着节日飘走了
这样问ai
1 | 我想要把一大堆的obj文件读取并在一个matlab图给出来,并且可以自己手动去调整模型的空间位置,如何去实现 |

但是僵尸是倒着的,所以我们用symmetry函数给他正过来
1 | [vx, vy, vz] = Symmetry(vertices(:,1), vertices(:,2), vertices(:,3), 'xoy'); |
260407
完成了向日葵的脸部完整建模、并且贴上了好看的贴图,好啊,好啊!

260408
今天把向日葵、豌豆射手的茎秆部分后半部分空缺填上了,还给豌豆射手增加了嘴部面片的加厚设计。除此之外,还把文件夹整理了一下,非常不错的


怎么样,是不是挺清爽的啦,之后的工作就是运动部分的啦!加油加油!

260409
会出现以下报错,如何解决
1 | 错误使用 matlab.graphics.primitive.Surface/set |
ai给出的解决方法是:把所有surface创建语句全部移动到 while true 循环外面(只执行一次)。但我寻思我本来就是在外面创建的surface啊
把豌豆射手的动态效果做出来了,很惊艳的效果!
260410
优化了一下豌豆射手的茎秆摆动,让看起来更加同步了一些
向日葵的花瓣更加密集,不会那么空虚啦

添加了向日葵的运动
260413
同时,因为循环一直在跑,也无法正常进行句柄移动操作
把b p h xr一些变量重命名了一下,加上了前缀pea、sun,这样的话就可以在同一个面板下面画图了,这是做的最正确的一件事
250415
实在是锯齿看得我生理不适了

似乎面片会更加光滑,而且更有立体感

具体更改如下,在向日葵、豌豆射手后面僵尸绘制前面加入了这段代码,具体功能是为了去除网格线,并实现平滑着色
1 | plant_surfaces = findobj(gca, 'Type', 'surface'); |
修复了向日葵茎秆异常移动的情况
250416
移动了僵尸的位置到稍远处
增加了僵尸被击杀以后自动消失的逻辑
优化了僵尸的颜色
锯齿问题解决(通过僵尸面片的消失)
经验提炼
从MATLAB零基础到做出完整的植物大战僵尸动画场景,最大的收获不是技术本身,而是学会了「怎么学一门新技术」:
-
先跑起来,再理解为什么。 一开始不理解
surf()为什么要转置,不理解贝塞尔曲面的数学原理——没关系,先把代码跑起来看看效果,有了直观感受之后再回头理解原理,效率高得多。 -
“感觉难是因为你没练。” 看教程觉得简单,自己动手全是bug。表面建模没有捷径,就是反复调控制点、反复试参数、反复看效果。
-
变量命名前缀是我做的最正确的事。
pea_、sun_、zombie_这种命名空间隔离,让多个角色能在同一个figure里独立动画。这不只是MATLAB技巧,是通用的软件工程思维。 -
审美驱动技术学习。 “锯齿看得我生理不适”——正是这种对粗糙画面的不能忍受,驱动我去学了Gouraud光照、interp着色这些渲染优化技术。有时候,不舒服是最好的老师。
-
跨格式整合能力很重要。 OBJ文件导入、面片解析、贝塞尔转换——最终项目不只是MATLAB原生建模,还整合了外部3D模型。学会处理不同格式的数据,比单纯会画曲面更有价值。
-
允许自己有低谷。 “今天不想搞表面建模项目了,随便写点”——这种状态很正常。重要的是低谷之后还能回来继续干。
-
让AI互相评审。 “让AI们互评互掐”——用多个AI工具交叉验证代码和方案,比只信一个AI的输出靠谱得多。