本文最后更新于 2025-07-31,文章内容可能已经过时。

Houdini随笔

没有干货,没有什么很难的知识~(●'◡'●),只是一个备忘录,方便自己取用,毕竟我只有几秒钟的记忆( ̄y▽, ̄)╭

节点

点云曲线驱动粒子

curveu_v.png

通过点云查找曲线的属性在pop里面使用vop赋予速度属性进行粒子解算,节点POP Curve Force也可以使用曲线驱动粒子,但是不可控,很难控制。

Match Size物体回归原点

Match Size.png

在Matching中选择要回归原点的位置,随后物体将从当前位置移动到原点,勾选Stash Transform输出xform属性,再次使用Match Size节点时,勾选Restore Transform存在xform属性的物体将从原点移动到原位置。

Convert Line节点去除多余的线、面属性

convert line.png

打组删除会留下多余的线和面之类的属性,会对部分节点造成影响,可以使用Convert Line节点去除多余属性

measure 测量节点

可以测量距离曲率之类的数值,可以用来提取模型弧度较大的区域

PolyFrame 计算点属性

可以计算模型的法线切线之类的向量属性,其中切线常被用来制作速度方向

Orientation along Curve 计算曲线方向

可以计算曲线的各种信息,但和PolyFrame的区别是这个节点可以将模型视为曲线计算,且这个节点可以输出包含模型位移旋转缩放等的矩阵信息

Attribute Transform Extract 属性转换提取

这个SOP提供了一个接口,可以将输入点上的矩阵属性分解为新属性,提供了对新的旋转属性应用欧拉旋转过滤器的控件,以纠正旋转数据中的不连续。可以配合Orientation along Curve将计算的矩阵信息提取出位移旋转缩放等属性,并将其应用在其他节点上

Labs Straight Skeleton 3D 模型结构生成曲线

可以根据模型的结构生成线,类似Poly Wire的逆向,如有一棵树可以用来提取树的树干树杈变为曲线

Connectivity 赋予模型class属性

一般搭配For-Each使用,class基于3D连通性进For-Each。

IsoOffset 填充几何体

说填充几何体应该不太准确,会基于几何体生成一个类似density的东西填充几何体内部,可以用scatter撒点几何体内部用Voronoi

Voronoi Fracture 基于点切割几何体

破碎用

RBD Cluster 碎块成簇

posui.png

将一些小碎块粘在一起,使碎块有大有小有层次。可以结合For-Each使用

Connect Adjacent Pieces 模型之间创建线条

可用做刚体的约束线

RBD Configure 刚体打包

打包刚体碎块,也可以添加active等刚体属性

RBD Constraint Properties 刚体约束

posui2.png

基于约束线创建刚体约束

RBD Bullet solver 刚体解算器

houdini官方更新的一体式刚体解算器

RBD I/O 刚体缓存

适用于刚体一体解算器的缓存节点

Transform Pieces 点驱动刚体

通过刚体简模解算出来的点去驱动高模

Volume Deform 几何体驱动density

vol_def.png

通过Volume Deform使用曲线或几何体驱动火烟之类的体积,blendshapes制作曲线动画

Compress_Pyro 用于压缩烟雾的缓存节点

UV Coords 在材质中读取uv属性

可使用材质中的uv制作噪波,如线条拖尾,为线条设置width属性在材质使用uv制作噪波可以使用单根线实现带拉丝的拖尾,可以优化渲染速度

Volume Velocity 对density添加噪波速度

可以使用这个节点对发射源添加噪波以及修改density的速度,使发射源的速度以及density不规则对烟雾基于发射源添加细节

Sweep 通过曲线作为横截面生成几何体

和polywire功能非常接近的节点,区别在于sweep比polywire可控性更高,可以生成更多形状的几何体,可以通过sweep生成圆柱或者圆环用来制作贴图可使用的几何体,相比直接建立几何体uv更加可控,更加容易解决uv贴图断层的问题

Edge Transport 沿着曲线或多边形边缘计算属性值

可以计算一个点到一条线上各个点的距离输出一个属性,可用于线解算

gas 微解算器详解

包含其他人的成果,暂不公开展示

VEX

detail 用来提取某个节点的属性

detail("../foreach_begin1_metadata3","iteration",0)+@Frame/20

可以在For-Each Number里面配合其他节点实现随机大小、旋转、线的长短等~

lerp 在两个值之间进行线性插值

nearpoint做速度

int zfza = nearpoint(1,v@P);
vector bb = point(1,"P",zfza);
@N = @P-bb;
@P += @N*chf("line_sca");

基于物体查找最近点,设置不同的速度

addpoint addprim加点连线

// 在输入1的几何体中查找距离当前点位置(v@P)最近的点编号
int npt = nearpoint(1, v@P);

// 从输入1的几何体中获取最近点(npt)的位置属性"P"
vector npos = point(1, "P", npt);

// 在当前几何体(输入0)中创建新点,位置为获取到的npos坐标
int newpt = addpoint(0, npos);

// 在当前几何体中创建折线图元(polyline),连接新创建的点(newpt)和当前处理点(@ptnum)
// @ptnum 表示当前正在处理的源点的编号
int newprim = addprim(0, "polyline", newpt, @ptnum);

setpointattrib 获取最近点的属性

// 在输入1的几何体中查找距离当前点位置(v@P)最近的点编号
int npt = nearpoint(1, v@P);

// 获取输入1中最近点的"name"字符串属性
string nname = point(1, "name", npt);

// 将name属性写入新点的"name"属性
setpointattrib(0, "name", newpt, nname);

为点添加随机数值属性

// 声明参数(需在节点面板创建对应参数)
float seed = chf("seedm");
float speed_min = chf("speed_min");
float speed_max = chf("speed_max");

// 计算速度值
float local_speed = fit01(rand(@primnum + seed), speed_min, speed_max);

// 属性写入(其余代码保持不变)
int pts[] = primpoints(0, @primnum);
foreach (int pt; pts) {
    setpointattrib(0, "local_speed", pt, local_speed, "set");
}

周期循环的数值

float speed = chf("speed");

// 计算周期性时间变量,周期为1.2秒
float t = (@Time * speed) % 1.2;

// 将curveu属性映射到动态衰减区间,生成基础能量值
float energy = fit(f@curveu, t - 0.25, t, 1.0, 0.0);

// 通过Ramp控制能量曲线形态(非线性调整)
energy = chramp("energy_shape", energy);

// 将最终能量值写入属性
f@energy = energy;

取余数做循环

@Time = 0.0t = 0.0

@Time = 1.2t = 0.0(重置循环)

@Time = 1.5t = 0.3

point采样几何体属性

vector pos = point(1,'P',0);
v@v.x = (@P - pos).x;
v@v.z = (@P - pos).z;
v@v.y = abs((@P - pos)).y;

简单的面向一个点的速度

copy切割的刚体后重定义name属性

string name = @name;
int copynum = @copynum;
int copynum1 = @copynum1;
string copynum_str = itoa(copynum);
string copynum_str1 = itoa(copynum1);
s@name = name + "_" + copynum_str + "_" + copynum_str1;

为了解决copy已经切割的刚体后name属性相同的问题,可以勾选copy节点底下的输出copynum属性,多个copy节点则注意修改输出的copynum属性名称不然会被覆盖掉,然后根据这个属性利用Attribute Wrangle选择Primitives然后重定义name属性

noise 手写噪波

float scale = chf('scale');//缩放,默认值1
vector freq = ch('freq');//频率
vector offset = chv('offset');//偏移
int turb = chi('turb');//噪波层数
float rough = ch('rough');//粗造度
float atten = ch('atten');//衰减

vector noise = onoise(@P*scale+freq+offset, turb, rough, atten);//x- s- o- a- curl- 噪波函数前缀
noise *= ch('amp');//振幅

int invert = chi('invert');//反转噪波的值
if (invert == 1)
{
    noise *= -1;
}

int noisetype = chi('cd____p____mountains');//选择噪波的应用场景
if (noisetype == 0)
{
    @Cd = fit(noise[chi('color_channel')], -0.5, 0.5, 0, 1);//噪波颜色,但是灰度图
}
if (noisetype == 1)
{
    @P += noise;//噪波P
}
if (noisetype == 2)
{
    @P.y += abs(noise[1]);//模拟mountain效果
}

保留若干点

//保留若干点
if( rand(@ptnum+ch('seed')) > (ch('keep_count')/@numpt) )//keep_count指定需要保留的元素数量,注意这个数值必须是浮点。且最终的保留数量不一定是该数值,但一定在该数值附近。
{
	removepoint(0,@ptnum);
}

//保留若干prim(注意运行在prim层级)
if( rand(@primnum+ch('seed')) > (ch('keep_count')/@numprim) )
{
	removeprim(0,@primnum,1);
}

//保留若干点的极简形式
removepoint(0, rand(@ptnum+ch('seed')) > (ch('keep_count')/@numpt)?@ptnum:-1);

//保留若干prim的极简形式
removeprim(0, rand(@primnum+ch('seed')) > (ch('keep_count')/@numprim)?@primnum:-1, 1);

按比例删除点

//按百分比删除点
if( rand(@ptnum+ch('seed')) < ch('persents') )
{
    removepoint(0, @ptnum);
}

//按百分比删除prim(注意运行在prim层级)
if( rand(@primnum+ch('seed')) < ch('persents') )
{
	removeprim(0, @primnum, 1);
}

//按比例删除点的极简形式
removepoint(0, rand(@ptnum+ch("seed")) < ch("persents")?@ptnum:-1);

//按比例删除prim的极简形式
removeprim(0, rand(@primnum+ch("seed")) < ch("persents")?@primnum:-1, 1);

材质

制作输出用于nuke制作动态模糊的速度属性

nuke_v.png

在材质里输出vel属性后记得在渲染节点里输出vel层

三渲二材质

uv线拖尾

工具

提取几何体位移旋转缩放矩阵信息

node = hou.selectedNodes()[-1]
orinet_node = node.createOutputNode("orientalongcurve","My_orient")
extract_node = orinet_node.createOutputNode("kinefx::attribtransformextract","My_extract")
Out_node = extract_node.createOutputNode("null","Out_Extract")

orinet_node.setParms({"outputyaxis":0,"outputzaxis":0,"outputtransform4":1})
extract_node.setParms({"usepieceattrib":0,"decompmethod":1,"usetrnattrib":1,"userotattrib":1,"usescaleattrib":1})

parent = node.parent()
parent_pos = parent.position()
offset = hou.Vector2(3,0)
obj = hou.node("/obj")
null = obj.createNode("null","Transfrom_"+parent.name())
null.setPosition(parent_pos+offset)
color = hou.Color((1,0.46,1))
null.setColor(color)

s_path = Out_node.path()

transExp = "point("+"'"+s_path+"'" +",0,'transform_trn',"
rotExp = "point("+"'"+s_path+"'" +",0,'transform_rot',"
scaleExp = "point("+"'"+s_path+"'" +",0,'transform_scale',"

translate = ["tx","ty","tz"]
rot = ["rx","ry","rz"]
scale = ["sx","sy","sz"]

for trans in range(3):
  null.setParmExpressions({translate[trans]: transExp+str(trans)+")", rot[trans]: rotExp+str(trans)+")",scale[trans]: scaleExp+str(trans)+")"})

护盾拉丝粒子


import sys
import toolutils

outputitem = None
inputindex = -1
inputitem = None
outputindex = -1

num_args = 1
h_extra_args = ''
pane = toolutils.activePane(kwargs)
if not isinstance(pane, hou.NetworkEditor):
    pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
    if pane is None:
       hou.ui.displayMessage(
               'Cannot create node: cannot find any network pane')
       sys.exit(0)
else: # We're creating this tool from the TAB menu inside a network editor
    pane_node = pane.pwd()
    if "outputnodename" in kwargs and "inputindex" in kwargs:
        outputitem = pane_node.item(kwargs["outputnodename"])
        inputindex = kwargs["inputindex"]
        h_extra_args += 'set arg4 = "' + kwargs["outputnodename"] + '"\n'
        h_extra_args += 'set arg5 = "' + str(inputindex) + '"\n'
        num_args = 6
    if "inputnodename" in kwargs and "outputindex" in kwargs:
        inputitem = pane_node.item(kwargs["inputnodename"])
        outputindex = kwargs["outputindex"]
        h_extra_args += 'set arg6 = "' + kwargs["inputnodename"] + '"\n'
        h_extra_args += 'set arg9 = "' + str(outputindex) + '"\n'
        num_args = 9
    if "autoplace" in kwargs:
        autoplace = kwargs["autoplace"]
    else:
        autoplace = False
    # If shift-clicked we want to auto append to the current
    # node
    if "shiftclick" in kwargs and kwargs["shiftclick"]:
        if inputitem is None:
            inputitem = pane.currentNode()
            outputindex = 0
    if "nodepositionx" in kwargs and             "nodepositiony" in kwargs:
        try:
            pos = [ float( kwargs["nodepositionx"] ),
                    float( kwargs["nodepositiony"] )]
        except:
            pos = None
    else:
        pos = None

    if not autoplace and not pane.listMode():
        if pos is not None:
            pass
        elif outputitem is None:
            pos = pane.selectPosition(inputitem, outputindex, None, -1)
        else:
            pos = pane.selectPosition(inputitem, outputindex,
                                      outputitem, inputindex)

    if pos is not None:
        if "node_bbox" in kwargs:
            size = kwargs["node_bbox"]
            pos[0] -= size[0] / 2
            pos[1] -= size[1] / 2
        else:
            pos[0] -= 0.573625
            pos[1] -= 0.220625
        h_extra_args += 'set arg2 = "' + str(pos[0]) + '"\n'
        h_extra_args += 'set arg3 = "' + str(pos[1]) + '"\n'
h_extra_args += 'set argc = "' + str(num_args) + '"\n'

pane_node = pane.pwd()
child_type = pane_node.childTypeCategory().nodeTypes()

if 'liangtl_Pointnoise' not in child_type:
   hou.ui.displayMessage(
           'Cannot create node: incompatible pane network type')
   sys.exit(0)

# First clear the node selection
pane_node.setSelected(False, True)

h_path = pane_node.path()
h_preamble = 'set arg1 = "' + h_path + '"\n'
h_cmd = r'''
if ($argc < 2 || "$arg2" == "") then
   set arg2 = 0
endif
if ($argc < 3 || "$arg3" == "") then
   set arg3 = 0
endif
# Automatically generated script
# $arg1 - the path to add this node
# $arg2 - x position of the tile
# $arg3 - y position of the tile
# $arg4 - input node to wire to
# $arg5 - which input to wire to
# $arg6 - output node to wire to
# $arg7 - the type of this node
# $arg8 - the node is an indirect input
# $arg9 - index of output from $arg6

\set noalias = 1
set saved_path = `execute("oppwf")`
opcf $arg1

# Node $_obj_Set_hudun_liangtl_Pointnoise1 (Sop/liangtl_Pointnoise)
set _obj_Set_hudun_liangtl_Pointnoise1 = `run("opadd -e -n -v liangtl_Pointnoise liangtl_Pointnoise1")`
oplocate -x `$arg2 + 0` -y `$arg3 + 0` $_obj_Set_hudun_liangtl_Pointnoise1
chblockbegin
chadd -t 0 0 $_obj_Set_hudun_liangtl_Pointnoise1 offset3
chkey -t 0 -v 0 -m 0 -a 0 -A 0 -T a  -F '$F*0.035' $_obj_Set_hudun_liangtl_Pointnoise1/offset3
chblockend
opparm $_obj_Set_hudun_liangtl_Pointnoise1 length_in ( 25 ) freq ( 0.5 0.5 0.5 ) offset ( 0 0 offset3 ) bias ( 0.66000000000000003 )
opset -d off -r off -h off -f off -y off -t off -l off -s off -u off -F on -c on -e on -b off $_obj_Set_hudun_liangtl_Pointnoise1
opexprlanguage -s hscript $_obj_Set_hudun_liangtl_Pointnoise1
opuserdata -n '___Version___' -v '' $_obj_Set_hudun_liangtl_Pointnoise1
opset -p on $_obj_Set_hudun_liangtl_Pointnoise1

opcf $arg1
opwire -n $_obj_Set_hudun_scatter1 -0 $_obj_Set_hudun_liangtl_Pointnoise1
opwire -n $_obj_Set_hudun_blast1 -1 $_obj_Set_hudun_liangtl_Pointnoise1

set oidx = 0
if ($argc >= 9 && "$arg9" != "") then
    set oidx = $arg9
endif

if ($argc >= 5 && "$arg4" != "") then
    set output = $_obj_Set_hudun_liangtl_Pointnoise1
    opwire -n $output -$arg5 $arg4
endif
if ($argc >= 6 && "$arg6" != "") then
    set input = $_obj_Set_hudun_liangtl_Pointnoise1
    if ($arg8) then
        opwire -n -i $arg6 -0 $input
    else
        opwire -n -o $oidx $arg6 -0 $input
    endif
endif
opcf $saved_path
'''
hou.hscript(h_preamble + h_extra_args + h_cmd)

歪门邪道

攻击爆炸激发

攻击爆炸激活.png

使用碰撞检测节点PoP Collision Behavior将已经碰撞的粒子添加组aa,勾选Preserve Group防止粒子脱离当前组,勾选Move to Hit使粒子跟随被碰撞物体,选择stop使粒子碰撞后停止运动

攻击爆炸激活.png

@MF = @Frame;

根据粒子保留发射帧数据的特性,将粒子发射的当前帧保存为自定义的MF属性

if(i@group_aa == 1 )
@EX += 1;

根据碰撞检测节点的组aa,使粒子在进入组后添加属性EX并使该属性每帧加一

攻击爆炸激活.png

将每个粒子进入循环,time shift获取前面自定义的MF属性,并使用$F减去MF使得粒子出生时time shift值为零,逻辑为MF是粒子碰撞后的当前帧,$F减去MF后为零然后随着时间增长,当爆炸从第零帧开始解算则粒子击中碰撞体时time shift为零爆炸开始,即每个粒子集中碰撞体后爆炸发生。

if(i@EX > 0 )
    removepoint(0,@ptnum);

快速运动物体

快速运动物体.png

vector pos1 = point(0,"P",0);
vector pos2 = point(1,"P",0);

vector dir = pos2-pos1;

dir *= chf("dir");//调这个

@P+=dir;

0号端口连接动画,1号端口连接动画定帧,读取两个端口的位置信息做减法使物体位移幅度降低,transform节点translate栏使用-ch("px") * ch("dir"),中心点减去当前centroid位置信息,类推也可实现相同结果

vector pos1 = point(0,"P",0);
vector pos2 = point(1,"P",0);

vector dir = pos2-pos1;

dir *= chf("dir");//调这个

@P+=dir;

0号端口连接处理过的粒子模型之类的,1号端口连接动画定帧,2号端口连接定帧前的物体

XPU渲染

线解算

刚体解算

流体解算

微解算器