您需要 登录 才可以下载或查看,没有账号?注册
x
Introduction
介绍
Hello! I am Fabien Burger, an Environment and Technical Artist currently open to opportunities. After playing and observing Elden Ring’s environments full of ruins, I wondered if I could find a procedural way to create ruins and started working on it. The goal was to find a solution that would not rely on simulation to allow fast iterations and would be optimized for games.
你好!我是 Fabien Burger,一位环境和技术艺术家,目前对机会持开放态度。在玩并观察了 Elden Ring 充满废墟的环境之后,我想知道是否可以找到一种创建废墟的程序方法并开始研究它。目标是找到一种不依赖模拟以实现快速迭代并针对游戏进行优化的解决方案。
In my research, I came across Ekaterina Surikova’s HDA breakdown, some of the techniques described below are heavily inspired by hers.
在我的研究中,我遇到了Ekaterina Surikova 的 HDA 故障,下面描述的一些技术深受她的启发。
Bricks Placement
砖放置
Following Simon Verstraete’s tutorial, I prepared three simple bricks having the same size but different bevel seeds. UVs are unwrapped using Labs Auto UV and Labs Texel Density, so I don’t need to do it later on hundreds of bricks, which would slow the process.
按照Simon Verstraete 的教程,我准备了三块大小相同但斜角种子不同的简单砖块。UV 使用 Labs Auto UV 和 Labs Texel Density 展开,因此我不需要稍后在数百块砖上进行此操作,这会减慢处理速度。
Then, the walls. I wanted the tool to be used with either one, two, three, or four walls. It was one of the hardest things to handle, as each scenario would sometimes need its own solution. In this case, I would use a Switch node and apply a different solution for each. The process below corresponds to the mode using four walls.
然后,墙壁。我希望该工具可用于一堵、两堵、三堵或四堵墙。这是最难处理的事情之一,因为每个场景有时都需要自己的解决方案。在这种情况下,我将使用 Switch 节点并为每个节点应用不同的解决方案。下面的过程对应于使用四壁的模式。
The idea is to start making walls completely full of bricks and then only keep the ones on the corner and those affected by the damage. This is done by creating lines containing points, where every point will then be replaced by a random brick out of the three.
这个想法是开始让墙壁完全充满砖块,然后只保留角落的砖块和受损坏影响的砖块。这是通过创建包含点的线来完成的,其中每个点将被三个中的一个随机砖块替换。
From a given length value for the wall, I need to round this value up or down depending on the number of bricks it can contain. That way, I can avoid unwanted spacing or penetration between the bricks.
根据墙的给定长度值,我需要根据它可以包含的砖块数量向上或向下舍入该值。这样,我可以避免砖之间不必要的间距或穿透。
The round expression will round a number to the nearest integer.
round 表达式会将数字四舍五入到最接近的整数。
From a given length of 5.5 meters, the value is clamped down to 5.4 meters to precisely match the bricks' size of 0.3 meters and avoid spacing. In an Attribute Wrangle, I delete the last point to end up with the 18 points needed.
从给定的 5.5 米长度,该值被限制为 5.4 米,以精确匹配 0.3 米的砖块尺寸并避免间距。在 Attribute Wrangle 中,我删除了最后一个点以得到所需的 18 个点。
I now have one line with the right size and number of points, pointing on the X-axis. In order to get the bricks oriented in the right direction when copied to the line points, I set the points normal in an Attribute Wrangle with "@N = set(0,0,1);".
我现在有一条大小和点数正确的线,指向 X 轴。为了让砖块在复制到线点时朝向正确的方向,我使用“@N = set(0,0,1);”在 Attribute Wrangle 中设置点法线。
For the wall height, I make another line pointing on the Y-axis (up and down in Houdini). The same logic from above is applied but with the brick height this time. The last point is deleted in an Attribute Wrangle. To get a brick pattern, I use a group range to group one point out of two and offset them to half the length of a brick on the X-axis. Then, I copy the line pointing on the X-axis to points from the line pointing up.
对于墙的高度,我在 Y 轴上画了另一条线(在 Houdini 中上下)。应用了与上面相同的逻辑,但这次是砖的高度。最后一点在 Attribute Wrangle 中被删除。为了获得砖块图案,我使用组范围将两个点中的一个点分组,并将它们偏移到 X 轴上砖块长度的一半。然后,我将指向 X 轴的线复制到指向线上的点。
All these lines correspond to only one surface out of the eight I need. To get the second surface, I duplicate them and move the copy on the Z-axis to the value of the wall width. The same process is then repeated to create the other lines needed, with the normals oriented in the appropriate direction. For the interior surfaces, I subtract two times the wall thickness value to the wall length.
所有这些线只对应于我需要的八个表面中的一个。为了获得第二个表面,我复制它们并将 Z 轴上的副本移动到墙壁宽度的值。然后重复相同的过程以创建所需的其他线,法线朝向适当的方向。对于内表面,我将壁厚值减去壁长的两倍。
When everything is ready, I use an Add node with "Delete Geometry But Keep the Points" enabled, then randomly copy my bricks to the points.
一切准备就绪后,我使用启用了“删除几何但保留点”的添加节点,然后随机将我的砖块复制到点。
Using two Bound nodes, one on the exterior lines and one on the interior then subtracting each other with a boolean, I get the base geometry needed for the walls. I will use it later.
使用两个 Bound 节点,一个在外线上,一个在内部,然后用布尔值相互减去,我得到了墙壁所需的基本几何图形。我稍后会使用它。
Damaging the Walls
破坏墙壁
To damage the walls, I create geometry that will help group the bricks I need to delete and those I need to keep. I use two kinds of shapes for this.
为了破坏墙壁,我创建了几何图形来帮助对需要删除的砖块和需要保留的砖块进行分组。我为此使用了两种形状。
The first one is used to create an open gap on the wall. It starts with a cube of the same height as the wall. I make different group selections that will be scaled later with Transform nodes to modify the shape.
第一个用于在墙上创建一个开放的间隙。它从与墙壁高度相同的立方体开始。我做了不同的组选择,稍后将使用变换节点缩放以修改形状。
These shapes are then duplicated and placed at the center of each of the four walls. With a Boolean Node, they are used as a subtract to my geometry walls to preview the damage.
然后将这些形状复制并放置在四堵墙的中心。使用布尔节点,它们被用作我的几何墙的减法以预览损坏。
The second kind of shape used for the damage is length and width based. Starting from a cube, I randomly offset the bottom points on the Y-axis, group the bottom right and bottom left points, and offset them with a Soft Transform, affecting the whole geometry. The top points are flattened up, I don’t want them affected by the Soft Transform.
用于损坏的第二种形状是基于长度和宽度的。从一个立方体开始,我在Y轴上随机偏移底部点,将右下角和左下角点分组,并用Soft Transform偏移它们,影响整个几何体。顶点被压平,我不希望它们受到软变换的影响。
Repeating the process three times, and subtracting these shapes to my walls, I have everything needed to get a slope damage effect.
重复该过程三遍,并将这些形状减去我的墙壁,我拥有获得斜坡损坏效果所需的一切。
Removing Bricks
搬砖
I now want to use my created shapes as a bounding object to group the bricks affected. I use a Group Expand node with a negative value to only keep the bricks that were fully contained by the shapes, and I delete them. With another Group Expand with a positive value this time, I select the bricks on the edges, the ones I want to keep.
我现在想使用我创建的形状作为边界对象来对受影响的砖块进行分组。我使用具有负值的 Group Expand 节点来仅保留完全包含在形状中的砖块,然后删除它们。这次使用另一个具有正值的 Group Expand,我选择边缘的砖块,即我想要保留的砖块。
I want the affected bricks on the edge to be rotated to make them look more interesting, but their rotation pivot needs to be appropriate whether they are on the left or the right side of the gap. This is one of the trickiest parts of the process to handle, as bricks from the wall length, width, exterior, and inside need to be treated differently. You may notice that I handle the “gap” and “slope” shapes separately for this, the edge bricks affected by the slope shapes are simply rotated based on their center.
我希望边缘上受影响的砖块被旋转以使它们看起来更有趣,但它们的旋转枢轴需要适当,无论它们是在间隙的左侧还是右侧。这是处理过程中最棘手的部分之一,因为墙壁长度、宽度、外部和内部的砖块需要区别对待。你可能会注意到我分别处理了“间隙”和“坡度”形状,受坡度形状影响的边缘砖只是根据它们的中心旋转。
I use half of the bounding box of my gap shapes to split the bricks between left and right. For more precision, I offset and inflate (Peak node) the bricks and use it as a bounding object to get the bricks on the very edge. I repeat the process for the eight groups needed.
我使用间隙形状的边界框的一半在左右之间分割砖块。为了更精确,我对砖块进行偏移和膨胀(峰值节点)并将其用作边界对象以使砖块位于最边缘。我对需要的八组重复这个过程。
When every group is ready, I randomly rotate every brick between 0 and 40 degrees based on their respective appropriate pivots using bbox min or max expressions. I do it one group at a time using different Transform nodes in a For-Each Connected Piece using Metadata.
当每个组都准备好后,我使用 bbox min 或 max 表达式根据它们各自适当的枢轴在 0 到 40 度之间随机旋转每块砖。我使用元数据在 For-Each Connected Piece 中使用不同的 Transform 节点一次做一组。
To keep the bricks in the corners, I use the base geometry walls, delete all but the bottom primitives and copy thin boxes to their points. This new geometry is a bounding object for the bricks.
为了让砖块保持在角落里,我使用了基础几何墙,删除了除底部图元之外的所有图元,并将薄框复制到它们的点。这个新的几何图形是砖块的边界对象。
I randomly keep around 10 percent of the unaffected bricks so the flat parts of the wall look more interesting later.
我随机保留了大约 10% 的未受影响的砖块,这样墙壁的平坦部分以后看起来会更有趣。
To summarize, there are four different kinds of bricks kept: the ones affected by the gap shapes, the slope shapes, the corners, and a few random ones.
总而言之,保留了四种不同的砖块:受间隙形状影响的砖块、斜坡形状、角落和一些随机的砖块。
With a For-Each Connected Piece using Metadata, I randomly offset and rotate all of the bricks with a small value to add variation.
对于使用元数据的 For-Each Connected Piece,我随机偏移并旋转所有具有小值的砖块以增加变化。
Wall Geometry
墙体几何
I handle the sides separately from the top and thickness of the walls. To damage the flat textured walls, I use the deleted bricks, which are more precise than the shapes. The bricks are inflated with a Peak node, voxelized and recomputed as geometry with a Remesh To Grid node, and serve as subtracting geometry to my walls with a boolean. In this case, using a UV Project for each wall is the cleanest way to do the UVs.
我将侧面与墙壁的顶部和厚度分开处理。为了破坏平坦的纹理墙壁,我使用删除的砖块,它们比形状更精确。砖块使用 Peak 节点膨胀,体素化并使用 Remesh To Grid 节点重新计算为几何图形,并使用布尔值作为几何图形减去我的墙壁。在这种情况下,对每面墙使用 UV 项目是进行 UV 的最干净的方法。
For the thickness of the wall, I retrieve and isolate the affected geometry from the boolean, remesh it, smooth it, select the unshared edges with a Group node and convert this group to points. I use this point group as a mask for the two mountain nodes I put on top of each other, one large and one smaller to make it look more organic. I remesh again to a lesser polygon density and unwrap the UVs at the right texel density. To add some nice details, I expand the point group, and color them black to act as a mask for the scatter from which I copy random bricks to.
对于墙的厚度,我从布尔值中检索并隔离受影响的几何图形,重新划分网格,平滑它,使用组节点选择未共享的边并将该组转换为点。我使用这个点组作为我放在彼此顶部的两个山节点的蒙版,一个大一个小,使它看起来更有机。我再次重新网格化为较小的多边形密度,并以正确的纹素密度展开 UV。为了添加一些漂亮的细节,我扩展了点组,并将它们涂成黑色,作为我复制随机砖块的散布的遮罩。
With all these different elements merged together, I end up with the final geometry for the tool!
将所有这些不同的元素合并在一起,我最终得到了该工具的最终几何图形!
Houdini Controller
胡迪尼控制器
To avoid going back and forth between the hundreds of nodes contained in the graph, I put a Null node and use the Edit Parameter Interface window to create parameters. All the interesting parameters in the graph are linked to this controller.
为了避免在图中包含的数百个节点之间来回切换,我放置了一个 Null 节点并使用 Edit Parameter Interface 窗口来创建参数。图中所有有趣的参数都链接到这个控制器。
Waiting for Houdini to cook the final mesh between every parameter change is not convenient for the tool user, so I put a switch node at the end of the graph with both the preview and final meshes plugged in. I link it to an integer parameter in my controller, which is by default set to the preview mode. You may also notice that I applied colors to the final mesh, they will act as a Vertex Color mask in Unreal to avoid using multiple materials.
等待 Houdini 在每次参数更改之间烹制最终网格对工具用户来说并不方便,因此我在图表的末尾放置了一个切换节点,同时插入了预览和最终网格。我将它链接到一个整数参数我的控制器,默认设置为预览模式。您可能还注意到我将颜色应用于最终网格,它们将充当 Unreal 中的顶点颜色蒙版,以避免使用多种材质。
Creating the HDA
创建 HDA
In order to convert our Houdini graph into a tool and use it inside Unreal, I need to create a Houdini Digital Asset.
为了将我们的 Houdini 图转换为工具并在 Unreal 中使用它,我需要创建一个 Houdini 数字资产。
I select all of the graphs, press Shift + C to create a subnet, then right-click to select “Create Digital Asset”. I make sure to remember where I save the file because this is the one that will be imported later in Unreal. This is non-destructive, as we can still work on the graph after creating an HDA.
我选择所有图表,按 Shift + C 创建子网,然后右键单击选择“创建数字资产”。我一定要记住我保存文件的位置,因为这是稍后将在 Unreal 中导入的文件。这是非破坏性的,因为我们仍然可以在创建 HDA 后处理图表。
Now I need to create parameters for our HDA, these are the ones the tool user will have access to. Right-click on the HDA node and “Type Properties…” to access the Parameters tab. Instead of recreating all the parameters from my controller by hand, I can copy them to our HDA Parameters.
现在我需要为我们的 HDA 创建参数,这些是工具用户可以访问的参数。右键单击 HDA 节点和“类型属性...”以访问“参数”选项卡。我可以将它们复制到我们的 HDA 参数中,而不是手动从我的控制器中重新创建所有参数。
Integration to Unreal Engine
与虚幻引擎集成
The HDA created in Houdini can be used inside Unreal Engine using the plug-in Houdini Engine. What this plug-in does is read and cook the nodes inside the HDA and then deliver the result to Unreal. Make sure to have it enabled then create a Houdini Session inside Unreal.
在 Houdini 中创建的 HDA 可以通过插件Houdini Engine在虚幻引擎中使用。 这个插件所做的就是读取和处理 HDA 中的节点,然后将结果传递给 Unreal。确保启用它,然后在 Unreal 中创建一个 Houdini 会话。
The HDA file can be imported inside Unreal Engine’s Content Browser like any other file.
HDA 文件可以像任何其他文件一样导入到虚幻引擎的内容浏览器中。
When dropped into the scene, the HDA loads and we find our HDA Parameters in the Details panel. We cook the final result when we are satisfied with the shape.
当放入场景中时,HDA 会加载,我们会在 Details 面板中找到我们的 HDA 参数。当我们对形状感到满意时,我们会烹饪最终的结果。
Creating the Collisions for Unreal
为虚幻创建碰撞
In order to have accurate procedural collisions, I need to split the geometry into different pieces for Unreal to generate multiple colliders.
为了获得准确的程序碰撞,我需要将几何体分割成不同的部分,以便 Unreal 生成多个碰撞器。
I start by creating a shape that separates my four walls. I use a Convert Line Node, group the side edges, and Dissolve them. Polypath then I set the normals oriented away from the center of each piece in order for the Peak to work, then finally PolyExtrude it.
我首先创建一个分隔我的四面墙的形状。我使用转换线节点,对侧边进行分组,然后将它们溶解。Polypath 然后我将法线设置为远离每个部分的中心以使 Peak 工作,然后最后 PolyExtrude 它。
Creating thin boxes at one and two-thirds of the height of my wall and unioned with the corner shapes above, I split the collision geometry into different pieces.
在墙高的三分之二处创建薄盒子,并与上面的角形状结合,我将碰撞几何体分割成不同的部分。
Using SideFX’s documentation about Unreal collisions, we learn that we need to group our geometry and name our group right by respecting the naming convention depending on the type of collider we want Unreal to generate. In Houdini, I make sure to delete the prim groups that are not needed anymore. Using a Connectivity node set to Primitive with the Attribute “name”, giving a number for each piece, then a Partition node with “collision_geo_ucx_`@name+1`” as Rule, one group per piece is created with the right name.
使用SideFX 关于 Unreal 碰撞的文档,我们了解到我们需要对几何体进行分组,并根据我们希望 Unreal 生成的碰撞器类型遵守命名约定来正确命名我们的组。在 Houdini 中,我确保删除不再需要的 prim 组。使用属性“name”设置为 Primitive 的 Connectivity 节点,为每个片段指定一个编号,然后使用“collision_geo_ucx_`@name+1`”作为规则的 Partition 节点,使用正确的名称为每个片段创建一个组。
In Houdini, this collision geometry is merged with our final result at the very end of the network, but in Unreal it is recognized and turned invisible to help generate the colliders.
在 Houdini 中,这种碰撞几何体在网络的最末端与我们的最终结果合并,但在 Unreal 中,它被识别并变为不可见以帮助生成碰撞器。
Here is an example between one and multiple colliders given to Unreal:
下面是给 Unreal 的一个和多个碰撞器之间的示例:
Houdini Tips
胡迪尼小贴士
There are Houdini expressions that I use all the time to avoid relying on manually typed values and keep the workflow procedural, like bbox(0, D_XSIZE), bbox(0, D_XMAX), bbox(0, D_XMIN), and centroid(0, D_X). 0 means it is reading the bounding box and centroid values from the current node. Sometimes it is useful to obtain these pieces of information from another node, so you replace 0 by “../NodeName”, don’t forget the quote marks! These expressions are extremely useful in procedural modeling to scale and move objects or create groups.
我一直使用Houdini 表达式来避免依赖手动输入的值并保持工作流程的程序化,例如 bbox(0, D_XSIZE)、bbox(0, D_XMAX)、bbox(0, D_XMIN) 和 centroid(0, D_X)。0 表示它正在从当前节点读取边界框和质心值。有时从另一个节点获取这些信息很有用,因此将 0 替换为“../NodeName”,不要忘记引号!这些表达式在用于缩放和移动对象或创建组的程序建模中非常有用。
Pick and apply a color pattern to your nodes (green for UVs, purple for groups…), and stick with it. That will help you read your network when de-zoomed, especially if like me, you like to keep the entire tool in one geometry network. Also, keep your network clean by using Object Merge referencing Null nodes.
选择颜色模式并将其应用到您的节点(UV 为绿色,组为紫色……),并坚持下去。这将帮助您在取消缩放时阅读您的网络,特别是如果您像我一样喜欢将整个工具保留在一个几何网络中。此外,通过使用引用 Null 节点的对象合并来保持网络清洁。
My advice to keep learning Houdini at an intermediate level is to be curious and watch tutorials even on topics that do not seem useful to you at the moment. It will open your field of solutions and possible workflows for future problems.
我建议在中级水平继续学习 Houdini 是保持好奇并观看教程,即使是关于目前对您来说似乎没有用的主题。它将打开您的解决方案领域和未来问题的可能工作流程。
Conclusion
结论
I had a lot of fun working on this tool and there is room for improvement in the future, like adding debris on the floor or letting the user input custom bricks. I find the combination of Houdini and Unreal extremely powerful and I love seeing what amazing things artists do with it, especially for games. Huge thanks to 80 Level for the opportunity to share my work and thank you for reading. I hope you find this breakdown instructive and feel free to DM me if you have any questions!
我在使用这个工具时获得了很多乐趣,并且未来还有改进的空间,比如在地板上添加碎片或让用户输入自定义积木。我发现 Houdini 和 Unreal 的组合非常强大,我喜欢看到艺术家们用它做的令人惊奇的事情,尤其是在游戏方面。非常感谢 80 Level 有机会分享我的工作并感谢您的阅读。我希望你觉得这个分解很有启发性,如果你有任何问题,请随时 DM 我!
|