简介:面向MATLAB初学者与需要处理3D可视化问题的科研及工程人员,这是一份讲解基础表面绘制技巧的英文PDF教程。资源包仅含1个PDF文件,大小约88KB,内容精炼,便于随时查阅。目前已有148人学习下载,教程从生成x、y向量和调用meshgrid建立网格开始,逐步演示如何定义二元函数、利用surf绘制曲面,并给出axis控制坐标范围、colormap选择颜色方案、rotate3d旋转视角等常用设置;示例选用z=x^2-2xy+3y+2这一具体函数,代码简洁直观,读者可直接运行体会操作效果。除3D曲面外,还简要介绍等高线图、梯度图以及surfl、mesh、meshc等变体命令,有助于初学者在短时间内搭建起MATLAB三维绘图的知识框架,并为进一步探索contour、quiver等高级可视化工具打下基础。 MatLab的曲面绘图(Surface Plotting)能力,是我在过去几年处理数据和做项目展示时,几乎绕不开的一项基本功。很多人学到surf函数就以为大功告成,结果画出来的图要么像一块皱巴巴的塑料布,要么颜色刺眼得完全看不出数据梯度,投论文或放组会汇报时总被问“这图到底想表达什么”。这篇文章不是帮你背命令,而是把曲面绘图的底层逻辑、工具选型和调参经验一次讲透,适合刚接触 MatLab 可视化的学生,也适合那些已经会用surf但始终觉得出图不够专业的研究生和工程师。
1. 为什么曲面图总是一团糊:数据网格与坐标映射的底层逻辑
先说一个我在帮人调代码时反复遇到的场景:明明数据是一块光滑的曲面,画出来却像一堆乱插的牙签,或者颜色过渡生硬得能看见一格一格的方块。问题的根源,绝大多数不在surf本身,而在于数据是怎么组织的。
1.1 meshgrid:先有网格,才有曲面
曲面图在数学上其实是一个二元函数 z = f(x, y) 的可视化,但在 MatLab 里,计算机并不认识“函数”,它只认识离散的矩阵。你需要先把 x 和 y 各自展开成二维网格,然后计算这个网格上每个点的 z 值。
x = linspace(-3, 3, 100); y = linspace(-3, 3, 100); [X, Y] = meshgrid(x, y); Z = sin(X) .* cos(Y);很多人不理解meshgrid到底干了什么,我常打的一个比方是:它相当于在一张坐标纸上画满方格线,每条横线和竖线的交叉点都有一个坐标。X矩阵存的是每个交叉点的横坐标,Y矩阵存的是纵坐标。你后面要对网格上的每个点算高度,就必须先有这张“坐标纸”。
这里有一个新手几乎必踩的坑:如果你直接用x和y的向量算sin(x) .* cos(y),MatLab 会报维度不匹配,或者算出个奇怪的结果。因为x是 1×100,y是 1×100,点乘以后还是 1×100,压根构不成曲面。只有通过meshgrid展开成 100×100 的矩阵,Z才有和网格一一对应的 100×100 个值。
1.2 Z矩阵的维度陷阱与“隐式扩展”的误导
说到维度,我见过一个很隐蔽的错误:当你的 x 和 y 长度恰好相等时,有人会写成Z = sin(x)' * cos(y),利用外积生成一个方阵,看起来也能画出图。但一旦 x 的个数是 50、y 的个数是 80,这种做法立刻崩掉,或者方向错位。我自己写过太多类似的代码,最终得出的结论是:统一走 meshgrid 流程,不要搞微操。它会让你的数据组织方式固定下来,出错的概率降低一大截。
另外,MatLab 从 R2016b 开始支持隐式扩展,X和Y即使一个是行向量一个是列向量,也能直接参与运算。这确实方便,但也会掩盖数据结构的问题。如果你画的图出现了“方向本身反了”“曲面被拉伸”这类问题,先用size(Z)对比一下size(X)和size(Y),确保三个矩阵尺寸完全一致,再去调图。
1.3 网格密度:太大糊成一团,太小全是棱角
网格密度直接决定曲面的视觉质量。取 20×20 的网格,surf出来的图能明显看到多边形棱角;取 500×500,曲面平滑了,但旋转视角或加光照时 MatLab 会明显卡顿,导出的矢量图文件体积也会暴涨。
我自己的经验是:做探索性分析时用 100×100 左右足够;做最终出图,如果数据本身是仿真结果,按实际物理分辨率来;如果是由函数生成的演示曲面,150×150 是一个视觉和性能都比较舒服的平衡点。有一次我用 1000×1000 的网格画一个多峰函数,MatLab 直接卡了十几秒,加了lighting gouraud之后旋转视角更是灾难。不要盲目追求“光滑”,数据的真实采样密度才应该是你选择的依据。
2. surf、mesh、contour 三兄弟:不同场景下到底该用谁
MatLab 的曲面绘图命令看着多,真正核心的就那么几个。我通常把它们分成两类:三维结构展示类(surf系)和二维数值投影类(contour系)。很多人一上来就surf,其实是不分青红皂白的。
2.1 surf:带颜色填充的实体曲面
surf(X, Y, Z)是曲面绘图的绝对主力。它把每个网格单元铺上一层“有色面砖”,颜色默认映射的是 Z 值。这个命令最符合人对“山体模型”的直觉,适合展示数据的整体走势、高峰低谷和大尺度结构。
figure; surf(X, Y, Z); xlabel('X'); ylabel('Y'); zlabel('Z'); title(' surf 默认效果');但注意,surf默认的着色方式是faceted,也就是每个网格面块之间会有黑色细线边界。网格稍微密一点,图上看起来全是黑线,非常脏。我的习惯是:只要网格数超过 50×50,就立刻加一句shading interp,把面块的颜色插值成连续渐变,去掉黑线,图面马上干净起来。
2.2 mesh:只看“铁丝网”,不关心表面材质
mesh(X, Y, Z)只画出网格线,不填充面。它适合两类场景:一是网格本身有信息含量,比如有限元分析的网格划分结果,你需要看单元的分布密度和形状;二是数据叠加展示时,mesh可以作为底层参照,上面再铺一层surf或散点。
figure; mesh(X, Y, Z); colormap(gray);我经常用mesh做“骨架检查”。画完一个复杂的曲面之前,先用稀疏网格mesh快速跑一遍,确认数据的长宽方向、扭曲方向都对,再加密网格上surf。这相当于写代码时的“先跑通再优化”,能省下不少来回调试的时间。
2.3 contour 和 surfc:把三维压成二维,专治“看不清”
三维曲面有个天然缺陷——你只能从某一个视角看它,背面的结构永远被挡住。这时候contour(X, Y, Z)(等高线图)就派上用场了。它把 Z 值切成若干层,用二维平面上的闭合曲线表示高度,像地图上的等高线一样。
figure; contour(X, Y, Z, 20); % 画 20 条等高线 colorbar;而surfc(X, Y, Z)则是在曲面图下方额外附一张等高线投影图,相当于给三维曲面配了一张“阴影地图”。我在地形数据或电磁场分布的处理里,最喜欢用surfc,因为它能同时回答两个问题:形状大概什么样(上半部分)?具体数值边缘在哪里(下半部分)?
为了让你一眼看懂区别,我把常用命令整理成一个对照表:
| 命令 | 显示方式 | 适合场景 | 缺点 |
|---|---|---|---|
surf | 填充色面 | 展示整体走势、高峰低谷 | 网格密时发黑,需配合shading |
mesh | 网格线 | 展示网格结构、有限元结果 | 看不出表面明暗变化 |
contour | 等高线 | 看数值梯度、边缘位置 | 没有立体感,需要想象 |
surfc | 曲面+投影 | 兼顾立体与数值信息 | 图面偏拥挤,布局需微调 |
meshz | 网格+垂帘 | 强调曲面在底面范围 | 视觉噪声大,用途较窄 |
3. 颜色、光照与视角:把图从“能看”调成“能发”
我常说,曲面绘图有三层境界:第一层是把数据画出来,第二层是画清楚,第三层是画得让看图的人一眼抓住重点。绝大多数人停在第一层,就是因为忽略了颜色、光照和视角这三板斧。
3.1 colormap 选择:色带即叙事
MatLab 默认的parula色带(新版默认)在连续数据上表现尚可,但如果你想要更强烈的“冷到热”对比,或者更符合学术期刊审美的渐变,我强烈推荐turbo和jet之外的几个选项。
先说jet。这是个老牌色带,人手一份。但它有个臭名昭著的缺点:颜色过渡不均匀,某些区域会产生视觉上的“伪梯度”,让人误以为数据有剧烈变化,实际上根本没有。如果审稿人恰好懂可视化,这个色带会被直接点名。相比之下parula和turbo的感知均匀性更好,适合绝大多数场量分布图。
figure; surf(X, Y, Z, 'EdgeColor', 'none'); shading interp; colormap(turbo); colorbar;我自己在展示温度场和应力场时,更喜欢用colormap(hot)或colormap(jet)做一些特定主题的图,但只限私下讨论,正式出图一律上感知均匀色带。记住一个原则:颜色的作用是分层,不是好看。
3.2 shading 与光照:决定立体感和质感
shading有三个值:faceted、flat、interp。三者的差别在于每个网格面块的颜色取法:faceted是面块内单色且带黑边,flat是单色无黑边,interp是四个顶点颜色插值渐变。interp出来的图最顺滑,也是我默认的选择。
真正让曲面“立起来”的是光照。默认的surf没有开光照,整个表面是平涂的,看不出沟壑。加上light和lighting gouraud之后,曲面会根据法线方向计算明暗,立体感立刻拉满。这一步对于展示有明显坡度变化的曲面(比如地形、曲面误差分布)几乎是质的飞跃。
surf(X, Y, Z, 'EdgeColor', 'none'); shading interp; colormap(parula); light('Position', [-5, 5, 20], 'Style', 'local'); lighting gouraud; material dull;注意light的位置要放在曲面的斜上方,Style可以选local或infinite。前者是点光源,光随距离衰减,适合展示局部细节;后者是平行光,整体均匀,适合展示大尺度趋势。material dull能让表面不反光,避免高光区白成一片。
3.3 view 视角:三维图的第一观感在“角度”
很多人画完图忘记调视角,MatLab 默认的仰角大概是 30 度,方位角大概在 -37.5 度。这组默认值对大部分曲面还算友好,但当你遇到一个特别平缓或者特别陡峭的曲面时,默认视角往往会把关键结构“压扁”。
view(az, el)函数接收两个参数:az是方位角(绕 z 轴的旋转角),el是仰角(视线与 xy 平面的夹角)。我的调试顺序是:先用鼠标拖拽旋转到一个看起来最“正”的角度,然后用view读取当前角度值,再手动微调,最后固定下来。
view(45, 25); % 方位角 45 度、仰角 25 度还有一个容易忽略的点:axis equal和axis square。如果你的 x、y、z 三个轴的量纲不一致(比如 x 是长度、z 是压强),不设置轴比例会导致曲面沿某个方向被拉长,视觉上严重失真。设置axis equal后,各轴的单位长度一致,曲面形状才有几何上的可信度。但注意,如果 z 轴数据范围远小于 x/y 范围,axis equal会把 z 方向压得几乎看不见,这时候需要手动调整zlim,在“比例真实”和“结构可见”之间做取舍。
4. 一个完整实例:从函数到论文级出图的全流程
讲了这么多理论,不如走一遍全流程。我拿一个工程里常见的二元高斯叠加峰做例子,把从数据生成到最终输出的每个环节都过一遍,代码可以直接复制运行。
4.1 生成数据和基础曲面
% 定义网格 x = linspace(-4, 4, 150); y = linspace(-4, 4, 150); [X, Y] = meshgrid(x, y); % 两个高斯峰的叠加 Z = 2 * exp(-((X - 1).^2 / 2 + (Y - 0.5).^2 / 0.8)) ... + 1.5 * exp(-((X + 1.5).^2 / 1.2 + (Y + 1).^2 / 1.5)); % 基础出图 figure; surf(X, Y, Z); xlabel('x (mm)'); ylabel('y (mm)'); zlabel('Intensity (a.u.)'); title('Double Gaussian Peaks');到这里,你会得到一张未经修饰的曲面图。面块之间黑线密集,颜色用的默认parula,没有光照,视角也是默认值。接下来我们一步一步升级。
4.2 分步优化:着色、色带、光照、视角
figure; surf(X, Y, Z, 'EdgeColor', 'none'); shading interp; colormap(turbo); colorbar; clim([0, 2.5]); % 锁定颜色映射范围,避免 outlier 拉伸色带 hold on; % 在峰值位置标注位置 plot3(1, 0.5, 2, 'k.', 'MarkerSize', 30); plot3(-1.5, -1, 1.5, 'k.', 'MarkerSize', 30); hold off; light('Position', [-5, 5, 20], 'Style', 'local'); lighting gouraud; material dull; view(40, 25); axis tight; set(gca, 'FontSize', 12, 'LineWidth', 1.2);这里面有几个细节值得说:
clim([0, 2.5])(老版本是caxis)非常有用。如果不设置,MatLab 会把颜色映射范围自动设置为 Z 的最小到最大值。但当你数据里有一个特别突出的尖峰时,其他区域的色差会被压缩到完全看不出梯度。手动锁定范围,可以强制颜色分布集中在有效数据区间。
plot3加标记点是我的习惯。曲面图看的是趋势,但看图的人往往想知道“峰到底在哪、峰高多少”。用 Marker 标出来,信息传递效率会高很多。
4.3 输出与导出:矢量图优先
出图最后一步是导出。很多人直接截图,或者用工具栏的“Save As”,这样导出的图分辨率固定,放进论文里放大就发虚。
我推荐用exportgraphics(R2020a 之后可用),直接控制分辨率。
exportgraphics(gcf, 'double_gaussian_surface.png', 'Resolution', 300);如果是投期刊,优先导出矢量格式,比如 PDF,文字和线条在任何缩放级别下都保持清晰:
exportgraphics(gcf, 'double_gaussian_surface.pdf', 'ContentType', 'vector');4.4 进阶一步:用 subplot 组合多视角
当你需要在一张图里同时展示曲面和等高线时,可以并排放置。
figure; subplot(1, 2, 1); surf(X, Y, Z, 'EdgeColor', 'none'); shading interp; colormap turbo; lighting gouraud; light('Position', [-5, 5, 20]); title('3D Surface'); subplot(1, 2, 2); contourf(X, Y, Z, 15); colorbar; axis equal; title('Contour Map');contourf是填充式等高线图,比contour好看不少,适合做俯视对比。但注意两个 subplot 共用一个colormap,且色带范围可能因为坐标范围不同而对不上。要严格对齐,可以在这两个 axes 上分别设置clim,或者在画contourf时手动指定等高线层级,让数值区间一致。
5. 画了三年曲面图,我踩过最深的几个坑
最后一部分,分享一些画曲面图时常见的报错、反直觉行为和省时技巧。这些内容在官方文档里往往只有一句话,但实际踩坑时排查起来是真的费劲。
5.1 维度不匹配的报错与排查链
surf最常见的报错就是“Z must be a matrix, not a scalar or vector”。这句话直译是“Z 必须是矩阵”,但新手往往看不出自己哪里错了。按我的排查顺序走一遍:
- 先
size(X)、size(Y)、size(Z),确认三者尺寸完全一致。不一致就是网格生成环节出了问题。 - 如果 X 和 Y 是向量,Z 是矩阵,那是你没有用
meshgrid展开。这是最典型的错误。 - 如果 X、Y、Z 都是 n×n 的方阵,但数据趋势明显对不上,检查是不是 x 和 y 弄反了。画二维图不容易发现这个问题,一上三维,方向错位会非常显眼。
- 还有一种隐藏情况:Z 里含有
NaN。某个数据点缺失时,MatLab 会在这个位置留一个空洞。这个空洞在mesh下表现不明显,但在surf下可能引起大面积的伪影。排查时可以先sum(isnan(Z(:)))看看 NaN 的数量和位置,再用fillmissing或插值补全。
我记得有次处理一个温度场数据,某几个传感器没采集到信号,导致曲面上出现几个突兀的洞。我当时没检查NaN,以为数据本身有个巨大的凹陷,白白调了两天参数。
5.2 坐标轴的“隐没”:z轴范围过小或过大的视觉欺骗
画二维图时,axis auto不是什么大事,但三维曲面图不行。当 z 的变化范围只有 0.01 级别,而 x、y 范围是 1 到 100 时,你不设置zlim,曲面会被压成一块几乎看不到起伏的平板。反过来,z 范围特别大时,曲面又会“刺破”坐标轴框,视觉上非常突兀。
我的习惯是:每次画完surf之后,立刻手动看一眼zlim和xlim/ylim,然后按需固定。
zlim([min(Z(:)) - 0.1 * range(Z(:)), max(Z(:)) + 0.1 * range(Z(:))]);这串代码给 z 方向上下各留了 10% 的余量,有效避免曲面贴顶或贴底。配合view调视角,基本能保证曲面在坐标盒子里既饱满又不溢出。
5.3 颜色和坐标轴比例冲突时的“伪直观”
axis equal并不是永远安全的。当 z 轴的数据量纲和 x、y 完全不在一个数量级时,axis equal会让 z 方向压缩得只剩一层纸厚,曲面本身的起伏完全消失。
这种场景下我不会用axis equal,而是改成手动设置daspect(数据宽高比):
daspect([1, 1, 0.3]);意思是在 x、y 方向按 1:1 比例,z 方向按 0.3 倍压缩。这样既保住了曲面的起伏可见性,又不至于让坐标轴比例完全失真。到底压成 0.3 还是 0.5,取决于你的数据范围差多大,需要反复试,直到视觉上“既立体又不失实”。
5.4 一个操作习惯:善用 MATLAB 的 interact 工具
MatLab 图形窗口左上角有“旋转”、“缩放”、“数据提示”等工具,很多人只用它来看图,其实它还能辅助你定位问题。比如开启“数据提示”(Data Tips)后,鼠标悬停在曲面上可以直接显示该点的 X、Y、Z 坐标值,排查异常峰、异常凹陷比写代码快得多。
还有一个小技巧:在旋转视角时,如果发现曲面某些区域出现奇怪的黑边或闪烁,那往往不是数据问题,而是 OpenGL 渲染问题。这时候可以试试在命令窗口执行opengl software再重新画,或者直接导出成图片看是否正常。硬件加速有时会引入不明伪影,但只影响显示,不影响底层数据。
6. 我的调试顺序与出图习惯
每个人都有自己的工作流,我分享一个我自己这几年固定下来的画图顺序,供你参考:
- 先跑数据,再谈图:先把 Z 的基本统计量算出来,看看范围、有没有 NaN、有没有异常极值。数据本身不对,画出来的图再怎么调颜色都是错的。
- 稀疏网格快览:用
mesh或低分辨率surf先快速扫一眼整体结构,确认方向、范围、大致形态。 - 加密网格正式出图:确认无误后,切换到目标分辨率,设置
shading interp。 - 加光照和视角:这一步放在着色之后,因为你先要看清结构,才能决定从哪个角度观察。
- 锁定色带范围、轴范围和坐标比例:这一步费时,但决定了图的“专业性”。
- 标注、图例、坐标标题:记得单位一定要带上,这是我的导师当年反复提醒我的——没有单位的数值图,等于没有说清楚物理量。
- 导出矢量图:投论文用 PDF 矢量格式,做 PPT 用 300dpi 的 PNG 就行。
曲面绘图这门手艺,看着命令少,真正做出能直接放进论文里的图,需要你对“数据到底想表达什么”有清晰的认识。配色、光照、视角都是服务于这个核心的。希望这篇分享能帮你少走一些我当年走过的弯路。最后再提醒一句:每画完一张图,回头看一眼 colorbar 上的数值范围,很多低级错误都会在这个过程中被你自己一眼揪出来。
本文还有配套的精品资源,点击获取