引言:一个差点让我翻车的曲面幕墙项目

去年秋天,我接了一个文化中心的外立面设计项目。建筑师的方案是一个由参数化曲面构成的连续幕墙,上面布满了渐变的百叶——每根百叶的角度、长度、间距都要沿着曲线精确变化。听起来很常规对吧?但当我真正开始建模时,才发现事情没那么简单。

我最初的想法很简单:用Rhino自带的ArrayAlongCurve命令,选一条曲线,选一个物体,输入数量,完事。但当我看到那个默认的阵列结果时,我愣住了——所有百叶都是平行的,间距均匀,方向一致。这看起来就像一堵死板的栅栏,完全没有建筑师想要的流动感和渐变。那一刻我意识到,我面对的是一场与Rhino底层逻辑的博弈。

Rhino 沿曲线阵列的生死局:一次曲面幕墙项目的技术复盘 - 配图1
Rhino 沿曲线阵列的生死局:一次曲面幕墙项目的技术复盘

第一章:需求分析与技术选型——为什么不能直接用ArrayAlongCurve?

在开始建模之前,我习惯先问自己三个问题:几何体要如何沿曲线分布?每个实例需要哪些独立属性?后续能否参数化调整?这些问题直接决定了技术路线。

对于这个项目,百叶需要:

  • 沿曲线均匀分布,但间距并非恒定,而是根据曲率变化——在弯曲大的地方更密,在平缓的地方更疏。
  • 每个百叶的朝向要跟随曲线的切线方向,同时绕自身轴线旋转一个随位置变化的角度,形成扭转的韵律。
  • 百叶的尺寸也要渐变,从底部的宽大过渡到顶部的纤细。

而Rhino自带的ArrayAlongCurve命令,本质上是一个线性变换的复制器:它只负责把物体沿曲线等距或按数量复制,每个实例的朝向固定为世界坐标,无法单独控制每个实例的旋转、缩放或变形。它的底层算法是基于曲线参数化的——将曲线按长度或参数等分,然后复制物体到每个分点,但仅此而已。

技巧提示:如果你只需要沿曲线均匀分布相同物体,且不要求朝向跟随曲线,ArrayAlongCurve足够用。但一旦涉及“每个实例独立变化”,就必须放弃这个命令,转向更底层的工具。

所以,我决定采用Grasshopper参数化设计方案。理由有三:

  • 可控性:每个实例的变换矩阵(平移、旋转、缩放)都可以通过数学表达式独立计算。
  • 可调性:参数化意味着我可以随时调整曲线、数量、角度函数,而不用重新建模。
  • 可扩展性:后续还可以对接Dynamo或Python脚本,实现更复杂的逻辑。

技术选型定下来了:Grasshopper + 曲面法线/切线计算 + 变换矩阵。

第二章:踩坑实录——那些让你怀疑人生的操作

理想很丰满,现实很骨感。我在Grasshopper里拖拽了半小时,就遇到了三个让我血压飙升的坑。

坑1:曲线方向不一致导致百叶翻转

我用了Curve电池拾取曲线,然后通过Perp Frames生成垂直于曲线的平面。结果发现,当曲线存在拐点时,平面的法线方向会发生翻转,导致百叶突然反转180度。这是因为Perp Frames基于曲线的曲率向量,而曲率在拐点处会改变方向。

解决:我改用Curve Frame电池(基于切线方向),并手动指定一个全局的参考向量来稳定法线方向。具体做法是:用Vector XYZ指定世界Z轴,然后与切线向量叉积,得到稳定的副法线,再叉积得到法线。这样无论曲线如何弯曲,法线始终朝外。

坑2:百叶间距不均匀,曲率大的地方挤成一团

我最初用Divide Curve按等弧长分割曲线,但曲率大的区域弧长相同但视觉上更密集,导致百叶重叠。我需要的是按曲率函数分布——曲率大的地方间距小。

解决:我引入了Evaluate Curvature电池,获取每一点的曲率值,然后通过Remap Numbers将曲率映射到间距因子,再通过Graph Mapper调整分布曲线。最后用SeriesCurve Point手动生成非均匀分布的点。

坑3:百叶的旋转角度无法逐根控制

我尝试用Rotate电池对每个百叶进行旋转,但角度是固定值。我需要角度随位置变化,比如从0°渐变到90°,形成扭转效果。

解决:我使用Evaluate Curve获取每一点的参数t,然后通过Construct PointEvaluate Length得到该点的长度比例,再用Expression电池计算角度:angle = baseAngle + amplitude * sin(parameter * frequency)。这样每个百叶的角度都是独立的,且可以通过调整参数实时变化。

注意:在Grasshopper中,Expression电池是处理逐实例变化的核心工具,它允许你基于任意输入(如曲线参数、距离、索引)计算数值。强烈建议掌握。

Rhino 沿曲线阵列的生死局:一次曲面幕墙项目的技术复盘 - 配图2
Rhino 沿曲线阵列的生死局:一次曲面幕墙项目的技术复盘

第三章:核心技术实操——从矩阵到几何的完整链路

解决了上述坑后,我搭建了完整的Grasshopper定义。下面逐步拆解核心逻辑。

第一步:曲线预处理

我首先用Curve电池拾取基础曲线,并用Rebuild Curve重建,确保曲线的控制点均匀,避免后续计算误差。然后我将曲线Offset到幕墙表面,得到实际的百叶安装线。

第二步:生成分布点

我利用Curve LengthSeries生成一系列长度参数,再通过Curve Point获取对应点。为了控制密度,我使用Graph Mapper将均匀的0-1区间映射到非均匀分布:

// 在Graph Mapper中使用Bezier曲线,控制点设为(0.2, 0.1)和(0.8, 0.9),使曲率大的区域点更密

第三步:计算每个点的坐标系

这是最关键的一步。我使用Evaluate Curve获取每个点的切线向量,然后用Vector Cross Product与全局Z轴交叉得到副法线,再与切线交叉得到法线。最后用Plane Normal构建平面,作为每个百叶的局部坐标系。

第四步:创建百叶几何体

我用Rectangle电池创建一个基础矩形,然后通过Orient电池将矩形从世界平面变换到每个点的局部平面。注意,Orient接受一个源平面和目标平面,会生成一个变换矩阵。

第五步:应用逐实例变化

现在,我通过Transform电池对每个矩形应用额外的变换:

  • 缩放:使用Scale电池,缩放因子由Expression计算,如1 - 0.5 * t(t为0到1的参数)。
  • 旋转:使用Rotate Axis电池,绕局部X轴旋转,角度由Expression计算,如45 * sin(t * 3.14)
  • 移动:沿法线方向偏移,偏移量也由表达式控制。

每个变换都输出一个变换矩阵,最后通过Transform Geometry一次性应用到矩形上。

第六步:生成最终实体

我将变换后的矩形Extrude成实体,然后Cap Planar Holes封盖,得到百叶。最后Bake到Rhino中,并进行渲染。

整个定义的电池组逻辑清晰,但要注意Data Matching——确保所有列表长度一致,否则会出现错位。

技巧提示:当列表长度不同时,Grasshopper默认使用“Longest List”匹配,这可能导致意想不到的结果。使用Trim TreeShortest List模式可以避免。

表格:ArrayAlongCurve vs Grasshopper方案对比

维度 ArrayAlongCurve Grasshopper参数化
分布方式 等弧长或数量 任意函数控制
朝向 固定世界坐标 跟随曲线切线/法线
逐实例变化 不支持 支持旋转/缩放/移动
可调性 需重新执行 实时更新
适用场景 简单重复 复杂渐变、参数化设计

第四章:行业案例与成果展示——从参数化到落地

最终,我通过这个Grasshopper定义生成了整个幕墙的百叶系统,并输出到Rhino。在渲染中,百叶的渐变扭转与曲面完美结合,光影流动,效果惊艳。甲方和建筑师都非常满意。

这个项目让我深刻体会到:

  • 参数化不是炫技,而是解决复杂重复几何的高效工具。
  • 理解底层原理(如矩阵变换、曲线参数化)比记住命令更重要。
  • Grasshopper的灵活性是Rhino原生命令无法比拟的,尤其在需要逐实例控制的场景。

后来,我又把这个定义应用到一个室内隔断墙项目,通过调整曲线和函数,快速生成了不同风格的渐变格栅,极大提升了设计迭代效率。如果你也面临类似的需求,不妨放下ArrayAlongCurve,拥抱Grasshopper。

希望这篇复盘能给你带来启发。记住,工具是死的,逻辑是活的。下次当你遇到“沿曲线阵列”时,先问自己:我需要的是复制,还是创造?

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。