使用 Houdini 程序化优化:案例
Thepoly原创 41987 1
实名

通过了实名认证的内容创造者

发布于 2022-7-21 13:12:30

您需要 登录 才可以下载或查看,没有账号?注册

x
81206981b603f57a91c07389ac43df78.jpg



Hi,大家好!
今天给大家带来的是Houdini的HDA资产开发流程的案例解析
我是高鹏涛

       这个我们与Adolfo Reveron的对话的第二部分,今天,给大家介唠一下使用Houdini和UE4以程序方式去构建环境。Adolfo Reveron总结了一些在使用Houdini结合Unreal在程序上创建环境的问题,详细分解了一个案例,并分享了有关如何使用 VEX 语言、设置环境等不同方面的问题。当然,如果你感兴趣,你也可以阅读一下上一个部分,你可以了解到有关Houdini的重要性,以及如何将其用于不同开发任务的更多信息。



1介绍
为什么要在UE4中程序化地创建模块化关卡?
        生成环境是一项非常消耗的任务。它涉及创建一个有意义的关卡设计布局,并适当的进行艺术迭代。
        这在传统工作流程中是一个线性流程,逻辑上我们需要先搞定的是整个关卡的设计方案,然后,在当前基础上进行艺术方向的迭代,但是,这个工作流程同样也是一种僵硬而过时的工作方式。因为,在实际项目中,这些阶段其实是相互重叠的,任何布局的更改,都会导致重做某些环节或者重做所有环节。
        制作游戏或电影是需要随着它的进度而发展,这就是为什么Houdini引擎派上用场的原因:它可以在其他平台(如UE4,Unity,Maya,Max和Cinema4D)中运行任何Tool(又名HDA或Houdini数字资产),并实时迭代和评估。那么,如何在虚幻引擎中设计一个HAD带动态的模块化关卡呢?

0e1209930a4a15a413da6cbe24306d34.png





2构建HDA模块化Level



        你可以找到一些关于使用Houdini和虚幻引擎4创建模块化环境的项目,比如Simon Verstraete的这个优秀教程。
        经过一些研究,负责创建Tool的程序艺术家已经弄清楚了,墙模块大小和圆形的直径数值(1m,1.5m,2m等),这大大简化了后面的事情,因为,您可以在Houdini中根据这些尺寸来构建您的环境系统。但是,有一点需要注意,这意味着您需要根据您的输入的数值去调整Tool!如果您添加一个新的1.75m宽的墙壁模块这该咋弄呢?!

        关于这一点,我决定瞄准一种更强大的方法:设计Tool以适应不同的模块宽度,这将提高HDA的灵活性。此外,我还创建了完整的房间布局,而不仅仅是走廊/开放区域。
        通过这种方法,我可以非常轻松地更改这些参考测量值(不同项目的),以便该工具随机选择模块,对齐,最后缩放它们以完美地组装。


        Houdini支持三种编码语言:Hscript,Python, VEX。最后这个VEX与C++非常相似,我从学习Houdini的第一天起就接受了它。尽管一开始具有挑战性和痛苦,但我认为这是让胡迪尼屈服于我意志的关键,所以,我只是接受了它,我对它了解得越多,就越觉得它提供了真正的控制。如今,我尽可能多地使用它,与Python结合使用,并且无论项目的复杂性如何,没有它们解决不了的问题。如果你处于早期阶段,相信我:努力迟早能够学会,只要坚持下去,不断完善。



        关于模块化环境项目,VEX计算虽然不是太复杂,但是,在我找到解决项目内每个小问题的好方法之前,它变得有点具有挑战性和必要性。


3设立目标
        简而言之,我们的目标是设计一个Houdini的Tool(HDA),允许用户以最小的工作量和最大的灵活性创建一个完成度较高的环境。我发现了一个很好的工作习惯,你可以备注说明该工具将来要执行的操作。有时,在开发工具时,随着进度的进行,您也许比较容易焦虑失去方向,因为,项目中的每个环节的小问题,都会影响后面的设计方案和效果。有些时候就要时刻提醒自己,我需要保持初心。

        为了帮助我保持专注,我同时还比较喜欢写下基本的设计指南,就好像来自客户的一个Brief:
  • 依靠引擎内置曲线编辑器创建布局。
  • 能够创建精致的轮廓
  • 将资源从编辑器的内容浏览器动态调整。这意味着该工具需要更加有灵活性。
  • 模块化(在这个演示中,我使用了Megascan的资产)






4Overrides
   
        我们的想法是能够让用户通过简单的形状来创建房间,这些房间将与连接到楼层布局相关联(在 "楼层布局 "部分解释)。
        为此,我首先将来自编辑器的每一个形状作为“overrideinput”,这样,我就可以生成一个基于这个网格边界大小的楼层。
        我做的第一件事是为每个输入端口创建一个盒子,这很容易通过一个包含绑定节点的 " for each connectedpiece "循环来完成。这将确保创建一个简单的盒子,无论用户用什么方式来键入工具(你可以拖动任何现有的资产作为输入,甚至是一个T-Rex,目前,这个方案可能会导致工具的不稳定)。






        然后,删除所有的面,除了一个面,根据选定的尺寸,使用 " bricker "选项将其分割。最后,使用" snap "节点挪到网格上。
        同样,我前段时间习惯的一个很好的做法是,尝试通过VEX尽可能多地解决问题,比如在这种情况下删除重叠的面。我发现这种简单的“intersect”功能,使用节省了我大量时间并可以防止了拓扑问题。
        尽可能多地使用VEX功能非常提高效率,因此,我时不时的去逛一下vex参考网站,去寻找我从未听说过的功能,然后,尝试一下。起初,看起来你是在浪费时间,但我发现这总是能及时得到回报。
        但是,您越有经验,就越能更好地平衡代码部分的工作量。有时过于强迫症,你可能会花一整晚的时间去解决一些事情,然后找到一个节点解决了这个问题,但是,我似乎也失去了时间。
        但总的来说,我做的越多,我就越认为这是一个好的决定,因为,这迫使我越来越精通这种脚本语言。







5楼层布局
        我开始复制一个盒子,将尺寸拟定为变量的框(这将允许以后调整走廊的宽度)。曲线的重新取样点为间隔1米。然而,这并不能保证这些点能正确地与网格对齐。(这个点也许在P=(1.25, 2.01, 35.6)的位置上,不管它与邻居点的距离如何)






        为了解决这个问题,我倾向于依靠这个四舍五入的技巧,它根据 " grid "变量来吸附点。这是一个便宜的、相当强大的移动点的方法,顺便说一下,在玩阈值的时候你会得到意想不到的点的位置。

//Ground curve
@P.y=0;
//
floatgrid=chf("Floor_grid_unit_size");
@P.x=rint(@P.x*grid)/grid;
@P.z=rint(@P.z*grid)/grid;

        然后我删除了重叠部分,指定一个基本法线,最后,用这段代码在每个primitive的中间创建一个点。尽管我发现,不存在primitive的这种"@P "中间点,但当使用VEX函数时,它实际上是可以作为primitive的中心点来工作(当把某个元素固定在网格上,有许多不同的方法,但是我发现这种方法很好用)。我稍后使用这些点来创建出网格,我最终将此网格与前一个“重叠”部分的网格合并起来。


6墙体
        这是该工具的核心。以下是该过程的直观概述。

        我首先用组节点和一些辅助的primitive从角点中分离出" in-between "的点。如果可能的话,我倾向于使用较少的primitive,并将这个形状设置为金字塔,因为它是最简单的封闭形状,但是,我也不确定用球形作为primitive用来当作分组点是否更为合适。





        一旦我对这条线进行了重新采样,我就告诉每个点:“这个点将被分配到一个X米宽的距离”。通过一些VEX,我向系统报告可能性(红色箭头位置)并随机分配此“width_pick”。PS:我忘了将其更改为变量,以便根据用户输入动态数值进行组装。


//Goal: oofset points within a prim accordingto possible offsets
//(aka modules_widths)
//1. Find first and last point of prim
//2. Move all prim points to first pointposition
//3. Find the prim axis
//4. For each point, offset it randomlyaccording to
//the axis+suitable offsets
//5. Do this for all points
//6. Measure both original and current prim(offsetted)
//7. Remove points with measure higher thanoriginal prim

floatwall_widths[]={1.5,2,4};
floatseed,pick;
seed=chf("Seed");

//input possible offsets
int pts[]=primpoints(0,@primnum);
intprimpts=len(pts)-1;
intpt0=vertexpoint(0,primvertex(0,@primnum,0));
intpt1=vertexpoint(0,primvertex(0,@primnum,primpts-1));
vectorprim_axis=normalize(point(0,"P",pt1)-point(0,"P",pt0));
float perimeter=primintrinsic(0,"measuredperimeter",@primnum);
vector tempZeroPos,newPos;
floattempWidth;
foreach(int pt;pts)
{
tempZeroPos=point(0,"P",pt0);
setpointattrib(0,"P",pt,tempZeroPos,"set");
pick=int(rint(fit01(rand(seed+pt+@primnum),0,len(wall_widths)-1)));

tempWidth=wall_widths[pick];

setpointattrib(0,"width_pick",pt,tempWidth,"set");
setpointattrib(0,"prim_axis",pt,prim_axis,"set");
setpointattrib(0,"perimeter",pt,perimeter,"set");
}
//f@pick_width=wall_widths[pick]
//v@prim_axis=prim_axis;
i@pt0=pt0;
i@pt1=pt1;
i@primpts=primpts;
f[]@wall_widths=wall_widths;


        接下来要做的是偏移,它涉及到抓取之前的点,指定一个宽度挤出(堆叠)。因此,这些点相当于是在原来的位置,进行偏移后的线。




        对比现在的结果,扩展的prim和原始的未扩展的y我们就可以判断哪些点超出了原始边界。在这种情况下,我尝试使用xyzdist()VEX函数。它非常方便,也是我最喜欢的功能之一,因为它为您提供了大量信息:从一个点到最近同步点的距离、prim数及parametric UVs。




intpts=len(primpoints(0,@primnum))-1;
vectorcen=v@P;

floatwidths[]=f[]@widths;
floattempWidth;
floatoffset=0;
intpt_counter=0;
int startPt=i@pt0;//first point of each prim
vector startPos=point(0,"P",i@pt0); ////Grab position of each first point
vectorprevPos=startPos;
vectornewPos={0};

foreach(floatw;widths)
{
offset=widths[pt_counter-1];
newPos=prevPos+v@prim_axis*(offset);
setpointattrib(0,"P",startPt+pt_counter,newPos,"set");
prevPos=newPos;
pt_counter++;
}

f@new_perimeter=offset;
i@pts=pts;
f[]@widths=widths;
f@new_perimeter=offset;

        在删除passingpoints后,我还需要调整最后一点,这需要比预期更多的 VEX 脚本。我将primitive与其“crippled”版本(删除传递点)进行了比较,将间隙测量值与可用的模块宽度进行对比,产生了四种不同的可能性,需要采取不同的解决方案:

//possibilities:
//1. Gap inferior to min width
//2. Gap equals min width
//3. Gap major to min width
//4. Gap inferior than last pt widht_pick

intpts[]=primpoints(0,@primnum);
int last_pt=vertexpoint(0,primvertex(0,@primnum,len(pts)-1));

floatgap=f@gap;
floatmin_width=min(f[]@wall_widths);
floatwidths[]=f[]@wall_widths;

//1. Gap minor to min width
if(gap<min_width){ removepoint(0,last_pt);
floatwidth_pick=point(0,"width_pick",last_pt-1);
floatk=(gap+width_pick)/width_pick; vector scale=set(k,1,1);
setpointattrib(0,"scale",last_pt-1,scale,"set");}

//2. Gapequals min width
if(gap==min_width)
{
setpointattrib(0,"width_pick",last_pt,min_width,"set");
}

//3. Gap major to min width


//4. Gap inferior than widht_pick floatwidth_pick=point(0,"width_pick",last_pt); if(width_pick>gap)
{
//Check if existing matching w value for thatgap first
intsolved=0;
foreach(floatw;widths)
{
if(w==gap)
{
setpointattrib(0,"width_pick",last_pt,w,"set");
solved=1;
break;
}
else
{
}
}

if(solved==0)
{
int index=0;
floatclosest_width;
floatmin_difference=9999999;
floatdifference;
foreach(floatw;widths)
{
difference=abs(w-gap);
if(difference<min_difference)
{
min_difference=difference;
closest_width=widths[index];
}
index++;
}

setpointattrib(0,"width_pick",last_pt,closest_width,"set");
floatk=gap/closest_width;
vectorscale=set(k,1,1);
setpointattrib(0,"scale",last_pt,scale,"set");

}

}


        剩下的唯一事情就是根据随机宽度来分配拆分点,并适当的将其与unreal_instance属性配对。

7Dressing
       道具实例化是最简单的部分。我使用网格布局(及其周长)作为起点,将走廊和房间组合在一起,按重要性顺序放置元素(柱子,桌子,椅子,长凳,装饰道具等)。就像下面的例子一样。
    去掉每个引子的第一点和最后一点(tips)。
int pts=len(primpoints(0,@primnum));
int last_pt=vertexpoint(0,primvertex(0,@primnum,pts-1));
int first_pt=vertexpoint(0,primvertex(0,@primnum,0));

setpointgroup(0,"last_pt",last_pt,1,"set");
setpointgroup(0,"first_pt",first_pt,1,"set");
Removingpoints too close to the corridor:
int neirs[]=nearpoints(1,v@P,chf("Search_radius"));
if(len(neirs)>1) removepoint(0,@ptnum);

//Iconsider a gap between pts of at least 1m

        每当面对任务时,我都会尝试尽可能多地改变我的方法,以迫使自己使用不同的VEX功能和方案。对于根据与另一个几何体的距离来移除或分组的点,我通常使用xyzdist()和neighpoints()。

8结论、问题和限制
问:它是否适用于Unity等其他引擎?
        Houdini引擎插件很好地与Unreal和Unity连接(但我在某处读到SideFX还提供了Houdini API来构建自己的自定义插件)。我已经在虚幻引擎中开发并测试了这个工具,所以我将我的答案集中在Unity上:是的,它可能会起作用,但我需要关心两个问题:界面相关和marshaling数据相关。
        例如,虚幻引擎和Houdini之间有一些特殊的属性略有不同,比如“unreal_instance”。在Unity的情况下,它被称为“unity_instance”。


        关于界面,你需要了解其他平台显示数据的方式,这将影响你定制工具的方式。比如说。3dsMax不能使用ramp的图形表示,而Maya不支持输入低于0.001的数值。
        根据我的经验,将Houdini制作出来的Tool用于支持的所有平台,这是一种不好的做法,这会导致功能失调。


问:如果从头开始创建该工具,我会更改哪些内容?
        我创建的HDA越多,我就越意识到创作工具就像爬山一样:只有当你到达山顶,并意识到其他合适的解决方案和规避危险的时候,你才能获得大局。因此,我很可能以不同的方式解决问题。很多时候,我忍不住想重做一些零件,甚至从头开始,但时间有限,所以也会经常问我自己:该工具是否符合设计要求,?如果答案是肯定的,则工作已完成。


- End -

公众号地址:https://mp.weixin.qq.com/s/X-fy4N_mSgwJ6dTECcmuyQ
喜欢Thepoly的可以通过三种方式与我们建立联系。分别是公众号、微信群以及QQ群。公众号是我们最为官方的窗口,更多内容都必须关注公众号后才能获取。另外现已开通网站:www.thepoly.cn更多精彩请关注我们。         

评分

参与人数 3元素币 +36 活跃度 +14 展开 理由
拷给你 + 11 + 2 为了元素币,拼了!
blue0904 + 20 + 6 感谢
欠钱不想还... + 5 + 6 这...

查看全部评分

本帖被以下画板推荐:

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
逆水寒  发表于 2022-7-21 19:56:32  
2#
回复 收起回复
使用道具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表