Houdini VEX 代码范例参考
基于 Houdini 21.0.671 本地文档 (
vex.zip) 及 SideFX 官方在线文档整理。参考链接:
目录
一、Wrangler VEX 语法基础
1.1 @ 属性绑定
在 Wrangle 节点中,使用 @ 前缀直接读写几何属性。VEX 会根据属性名称推断类型(如果之前已有定义)。
c
// 读取位置属性
v@myPos = @P;
// 设置颜色
@Cd = {1, 0, 0}; // 红色
// 设置法线
@N = {0, 1, 0};1.2 类型前缀
如果属性尚不存在,或需要明确指定类型,使用类型前缀:
| 前缀 | 类型 | 大小 | 示例 |
|---|---|---|---|
f@ | float | 1 | f@myFloat = 1.0; |
i@ | int | 1 | i@myInt = 5; |
s@ | string | 1 | s@myStr = "hello"; |
v@ | vector | 3 | v@myVec = {1, 2, 3}; |
p@ | vector4 ( quaternion ) | 4 | p@myQuat = {0, 0, 0, 1}; |
2@ | vector2 | 2 | 2@myVec2 = {1, 2}; |
3@ | vector3 | 3 | 3@myVec3 = {1, 2, 3}; |
4@ | vector4 | 4 | 4@myVec4 = {1, 2, 3, 4}; |
u@ | matrix2 | 2×2 | u@myMat2 = { {1,0},{0,1} }; |
d@ | matrix3 | 3×3 | d@myMat3 = ident(); |
3@ (matrix) | matrix | 4×4 | 见下方说明 |
注意:
3@既可以表示 vector3 也可以表示 matrix(4×4),VEX 会根据上下文自动判断。在大多数情况下,直接使用v@表示 vector,用4@表示 vector4 更清晰。
c
// 明确声明新属性
f@speed = 0.0;
v@velocity = {0, 0, 0};
i@id = @ptnum;
s@name = "point" + itoa(@ptnum);
// 矩阵
matrix myMatrix = ident(); // 单位矩阵
4@transform = myMatrix; // 写入属性1.3 隐式变量(内置变量)
Wrangle 节点中可直接使用的内置变量:
| 变量 | 类型 | 说明 |
|---|---|---|
@ptnum | int | 当前点编号 |
@primnum | int | 当前面(primitive)编号 |
@vtxnum | int | 当前顶点编号 |
@numpt | int | 几何体中的总点数 |
@numprim | int | 几何体中的总面数 |
@elemnum | int | 当前元素编号(随 Run Over 模式变化) |
@numelem | int | 当前元素总数 |
@Time | float | 当前时间(秒) |
@Frame | float | 当前帧号 |
@TimeInc | float | 时间步长(一帧的秒数) |
c
// 用 @ptnum 生成每点不同的随机值
@Cd = rand(@ptnum);
// 用 @Time 制作动画
@P.y += sin(@Time * 2 + @ptnum * 0.1) * 0.5;
// 用 @Frame 控制动画进度
float progress = @Frame / 120.0; // 120 帧为一个周期
// 用 @TimeInc 做帧率无关的累加
f@accum += f@speed * @TimeInc;1.4 Run Over 模式
Wrangle 节点的 Run Over 参数决定代码在哪些元素上执行:
| 模式 | 说明 | @elemnum 含义 |
|---|---|---|
| Points | 逐点执行 | 等同 @ptnum |
| Primitives | 逐面执行 | 等同 @primnum |
| Vertices | 逐顶点执行 | 等同 @vtxnum |
| Detail (only once) | 整个几何体执行一次 | 恒为 0 |
| Number of Min/Max | 指定次数执行 | 从 0 开始递增 |
c
// Run Over: Primitives 时,可以用 @primnum 给每个面不同颜色
@Cd = rand(@primnum);1.5 组(Group)语法
在属性名前加 group_ 前缀即可读写组信息:
c
// 检查当前点是否在某组中
if (@group_myGroup == 1) {
@Cd = {1, 0, 0}; // 组内点设为红色
}
// 将当前点加入组
@group_selected = 1;
// 将当前点移出组
@group_selected = 0;命名规则:如果组名包含特殊字符(如空格、连字符),VEX 会自动处理,但建议使用字母加下划线的命名方式。
1.6 跨输入引用
当 Wrangle 节点有多个输入时,可以使用 @opinput<n>_<name> 引用其他输入的属性:
c
// 引用第二个输入(input 1)的当前点位置
v@posFromInput1 = @opinput1_P;
// 引用第三个输入(input 2)的颜色
v@cdFromInput2 = @opinput2_Cd;
// 使用第三个输入的法线来计算反射方向
v@N_input2 = @opinput2_N;
v@reflect = reflect(normalize(@v), normalize(@opinput2_N));| 语法 | 含义 |
|---|---|
@opinput0_P | 第一个输入(input 0)的 P 属性 |
@opinput1_Cd | 第二个输入(input 1)的 Cd 属性 |
@opinput2_N | 第三个输入(input 2)的 N 属性 |
@opinput3_name | 第四个输入(input 3)的 name 属性 |
跨输入引用会自动匹配相同元素编号(如同一个
@ptnum),不做空间最近邻匹配。
1.7 几何创建与删除
在 Wrangle 中可以直接创建和删除几何体:
c
// --- 创建点 ---
int new_pt = addpoint(0, {0, 1, 0}); // 在指定位置创建点
int new_pt2 = addpoint(0, @P + {0, 1, 0}); // 在当前点上方创建点
// --- 创建面(primitive) ---
int pts[] = array(0, 1, 2, 3); // 点编号数组
int new_prim = addprim(0, "poly", pts); // 创建多边形面
// 创建线
int line_prim = addprim(0, "polyline", array(0, 1, 2));
// --- 创建顶点 ---
// addvertex(geohandle, primnum, ptnum) — 通常 addprim 已自动处理
int vtx = addvertex(0, new_prim, new_pt);
// --- 删除 ---
removepoint(0, @ptnum); // 删除当前点
removepoint(0, @ptnum, 1); // 删除点但不删除只属于该点的面顶点
removeprim(0, @primnum, 1); // 删除面(1 = 同时删除只属于该面的点)
removeprim(0, @primnum, 0); // 删除面但保留点(0 = 不删点)
// --- geohandle 参数 ---
// 0 = 当前几何体(写入端)
// 1 = 第一个输入的几何体(只读)
// 2 = 第二个输入的几何体(只读)1.8 字符串操作
c
// 字符串拼接
s@path = "/geo/" + s@name + "_" + itoa(@ptnum);
// 字符串格式化(VEX 支持 printf 风格)
s@label = sprintf("point_%04d", @ptnum);
// 字符串比较
if (s@name == "base") {
@Cd = {1, 1, 1};
}
// atoi / atof:字符串转数字
int id = atoi(s@id_string);
float val = atof(s@value_string);二、界面参数引用
Wrangle 节点界面上的参数(如 float、vector、string、ramp 等),通过 ch 系列函数在 VEX 中引用。
2.1 基本参数函数
| 函数 | 返回类型 | 对应参数类型 | 示例 |
|---|---|---|---|
ch("name") | float | Float | ch("speed") |
chf("name") | float | Float | chf("speed") |
chi("name") | int | Integer | chi("count") |
chs("name") | string | String | chs("label") |
chv("name") | vector | Vector | chv("direction") |
ch2("name") | vector2 | Vector 2 | ch2("uv_offset") |
ch3("name") | vector3 | Vector 3 | ch3("axis") |
ch4("name") | vector4 | Vector 4 | ch4("quat") |
chp("name") | vector4 | Quaternion | chp("rotation") |
chramp("name", pos) | float | Ramp | chramp("color_r", @Curve) |
c
// 假设界面上有以下参数:
// - "speed" (Float)
// - "direction" (Vector)
// - "count" (Integer)
// - "label" (String)
// - "color_ramp" (Ramp)
float speed = ch("speed");
v@v = chv("direction") * speed;
int count = chi("count");
s@label = chs("label");
// Ramp 参数:chramp("ramp_name", position)
// position 通常在 0-1 范围
float rampVal = chramp("color_ramp", @Curve);
@Cd = {rampVal, rampVal * 0.5, 1 - rampVal};2.2 带时间评估的参数函数
加 t 后缀表示在指定时间评估参数值(用于获取动画参数的历史值或未来值):
c
// 在当前时间评估
float currentVal = ch("speed");
// 在 2 秒前评估
float pastVal = cht("speed", @Time - 2);
// 在 1 秒后评估
float futureVal = cht("speed", @Time + 1);
// 向量参数的带时间评估
vector pastDir = chvt("direction", @Time - 0.5);| 函数 | 说明 |
|---|---|
cht("name", time) | float,指定时间 |
chit("name", time) | int,指定时间 |
chst("name", time) | string,指定时间 |
chvt("name", time) | vector,指定时间 |
ch3t("name", time) | vector3,指定时间 |
ch4t("name", time) | vector4,指定时间 |
2.3 参数路径引用
可以引用其他节点上的参数,使用完整路径:
c
// 引用场景中另一个节点的参数
float val = ch("/obj/geo1/transform1/rx");
// 引用父节点的参数
float scale = ch("../scale");
// 常见用法:引用参数面板上的参数路径
string paramPath = chs("param_path");
float val = ch(paramPath);2.4 Ramp 的完整用法
c
// 1. 基本用法:传入 0-1 的位置,返回 ramp 对应值
float r = chramp("myRamp", 0.5);
// 2. 用 ramp 控制颜色渐变
float pos = fit(@P.y, -1, 1, 0, 1); // 将高度映射到 0-1
float r = chramp("ramp_r", pos);
float g = chramp("ramp_g", pos);
float b = chramp("ramp_b", pos);
@Cd = set(r, g, b);
// 3. 用 ramp 控制粒子大小
@pscale = chramp("size_ramp", fit(@age, 0, 3, 0, 1));
// 4. 多段 ramp 的使用
float myWeight = chramp("weight", fit(@ptnum, 0, @numpt, 0, 1));2.5 参数引用的最佳实践
c
// ✅ 推荐在开头统一获取参数,便于管理
float speed = ch("speed");
float size = ch("size");
vector dir = chv("direction");
int mode = chi("mode");
// 然后在代码中使用局部变量
@P += dir * speed * @TimeInc;
@pscale = size;
// ❌ 不推荐:在表达式里反复调用 ch()
@P += chv("direction") * ch("speed") * @TimeInc;
@pscale = ch("size");三、常用计算写法
3.1 数学基础函数
c
// --- 基本运算 ---
abs(-5.0); // 5.0 绝对值
sqrt(9.0); // 3.0 平方根
pow(2.0, 3.0); // 8.0 幂运算
exp(1.0); // 2.718... e 的幂
// --- 取整 ---
floor(3.7); // 3.0 向下取整
ceil(3.2); // 4.0 向上取整
round(3.5); // 4.0 四舍五入
trunc(3.9); // 3.0 截断小数部分
frac(3.7); // 0.7 取小数部分
int(3.7); // 3 转为整数
// --- 最值 ---
max(3.0, 5.0); // 5.0
min(3.0, 5.0); // 3.0
max(1.0, 2.0, 3.0, 4.0); // 4.0 可变参数
clamp(value, 0.0, 1.0); // 限制在 0-1 范围
// --- 平均值与求和 ---
avg(1.0, 2.0, 3.0); // 2.0 可变参数的平均值
float arr[] = {1, 2, 3, 4};
sum(arr); // 10.03.2 三角函数与角度
c
// VEX 中所有三角函数使用弧度
sin(radians(90)); // 1.0 先将角度转为弧度
cos(radians(180)); // -1.0
tan(radians(45)); // 1.0
// 反三角函数返回弧度
degrees(asin(1.0)); // 90.0 将弧度转为角度
degrees(acos(0.5)); // 60.0
degrees(atan(1.0)); // 45.0
degrees(atan2(1.0, 1.0)); // 45.0 atan2(y, x)
// 弧度 / 角度互转
radians(180); // 3.14159...
degrees(PI); // 180.0
// 常量
PI; // 3.14159265...3.3 向量运算
c
// --- 向量创建 ---
vector a = set(1, 2, 3);
vector b = {1, 2, 3}; // 花括号语法,等价
// --- 基本运算 ---
vector c = a + b; // 逐分量相加
vector d = a - b; // 逐分量相减
vector e = a * 2.0; // 标量乘法
vector f = a * b; // 逐分量相乘(不是点积!)
// --- 分量访问 ---
a.x; a.y; a.z; // 通过 .x .y .z 访问
a[0]; a[1]; a[2]; // 通过索引访问
// 也可以用 .r .g .b 或 .x .y .z(取决于上下文)
// --- 向量函数 ---
length(a); // 向量长度
distance({0,0,0}, a); // 两点距离
normalize(a); // 单位化
dot(a, b); // 点积
cross(a, b); // 叉积(返回垂直于 a 和 b 的向量)
// --- 向量分量提取 ---
float x = getcomp(a, 0); // 提取第 0 个分量
setcomp(a, 5.0, 1); // 设置第 1 个分量为 5.03.4 映射与插值
c
// --- fit:将值从一个范围映射到另一个范围 ---
fit(0.5, 0, 1, 0, 100); // 50.0 将 0.5 从 [0,1] 映射到 [0,100]
fit(0.5, 0, 1, -1, 1); // 0.0
fit01(0.5, -1, 1); // 0.0 fit 的简写,输入范围为 [0,1]
fit10(0.5, -1, 1); // 0.0 输入范围为 [1,0](反向)
fit11(0.5, 0, 1); // 0.75 输入范围为 [-1,1]
// --- lerp:线性插值 ---
lerp(0.0, 10.0, 0.5); // 5.0 50% 处的值
lerp({0,0,0}, {1,1,1}, 0.5); // {0.5, 0.5, 0.5} 支持向量
// --- clamp:限制范围 ---
clamp(15, 0, 10); // 10
clamp(-5, 0, 10); // 0
clamp(5, 0, 10); // 5
// --- smooth:平滑插值(带 ease in/out)---
// smooth(value, min, max) 返回 0-1
smooth(0.5, 0, 1); // 0.5 但过渡更平滑
smooth(0, 0, 1); // 0
smooth(1, 0, 1); // 1
// --- spline:样条插值 ---
// spline(t, "linear", v0, v1, v2, ...)
vector result = spline(0.5, "catmullrom", {0,0,0}, {1,2,1}, {3,1,2}, {4,3,0});3.5 随机数与噪声
c
// --- rand:伪随机数 ---
// 基于种子返回 0-1 的值
rand(@ptnum); // 每点不同随机值
rand(@ptnum + 123); // 加偏移改变种子
rand(@ptnum * 0.5); // 乘系数改变种子
rand(@P); // 基于位置的随机(向量参数返回向量)
// 范围映射
float r = fit01(rand(@ptnum), -1, 1); // 映射到 -1 到 1
// --- noise:柏林噪声 ---
// 返回值大约在 -1 到 1 之间
float n = noise(@P); // 基于位置的噪声
float n2 = noise(@P * 2); // 缩放频率
float n3 = noise(@P + @Time); // 动画噪声
// 向量噪声
vector vn = vnoise(@P); // 每个分量独立的噪声
// --- 其他噪声函数 ---
float cn = curlnoise(@P).x; // curl 噪声(返回向量,适合无散度场)
float an = anoise(@P); // alligator 噪声
float sn = snoise(@P); // 简单噪声
// --- 使用噪声制作变形 ---
v@disp = normalize(@N) * noise(@P * ch("freq") + @Time) * ch("amp");
@P += @disp;3.6 矩阵与变换
c
// --- 创建矩阵 ---
matrix3 m3 = ident(); // 3×3 单位矩阵
matrix m4 = ident(); // 4×4 单位矩阵
// --- 旋转 ---
// 使用 dihedral:计算将向量 a 旋转到向量 b 的旋转矩阵
vector a = {0, 0, 1};
vector b = {1, 0, 0};
matrix3 rot = dihedral(a, b); // 返回 3×3 旋转矩阵
// 使用 lookat:计算从 from 看向 to 的旋转矩阵
matrix3 lookatMat = lookat({0,0,0}, {0,1,0}, {0,1,0});
// --- maketransform:构建完整变换矩阵 ---
// maketransform(int xform_type, int rotation_order, vector translate, vector rotate, vector scale, ...)
matrix xform = maketransform(0, 0, {1, 2, 3}, {30, 45, 60}, {1, 1, 1});
// --- cracktransform:从矩阵中提取分量 ---
// cracktransform(int xform_type, int rotation_order, int space, vector pivot, matrix xform)
vector t, r, s;
cracktransform(0, 0, 0, {0,0,0}, xform); // 提取平移
// 分别提取:
vector extractedTranslate = cracktransform(0, 0, 0, {0,0,0}, xform);
// 提取旋转(使用 mode = 1)
vector extractedRotate = cracktransform(0, 0, 1, {0,0,0}, xform);
// 提取缩放(使用 mode = 2)
vector extractedScale = cracktransform(0, 0, 2, {0,0,0}, xform);
// --- 四元数 ---
// 创建四元数(从欧拉角,弧度)
vector angles = radians(set(30, 45, 60));
p@orient = quaternion(angles);
// 从旋转轴和角度创建四元数
p@orient = quaternion(radians(45), normalize({0, 1, 0}));
// 四元数旋转
vector rotated = qrotate(p@orient, @P);
// 四元数乘法(组合旋转)
p@orient = qmultiply(p@orient, quaternion(radians(30), {0,1,0}));
// 球面线性插值(SLERP)
p@orient = slerp(q1, q2, 0.5); // 50% 插值
// --- 应用变换 ---
@P = @P * xform; // 应用 4×4 矩阵
@P *= rot; // 应用 3×3 矩阵(仅旋转)
// 矩阵运算
matrix3 invM = invert(m3); // 逆矩阵
matrix3 transM = transpose(m3); // 转置矩阵
float det = determinant(m3); // 行列式3.7 条件语句与流程控制
c
// --- if / else ---
if (@P.y > 0) {
@Cd = {1, 0, 0};
} else {
@Cd = {0, 0, 1};
}
// --- 三元运算符 ---
@Cd = (@P.y > 0) ? {1,0,0} : {0,0,1};
float val = (chi("mode") == 1) ? ch("val_a") : ch("val_b");
// --- for 循环 ---
for (int i = 0; i < 10; i++) {
// ...
}
// --- foreach 循环(遍历数组)---
int pts[] = {0, 1, 2, 3};
foreach (int pt; pts) {
// 处理每个点编号
}
foreach (int index; int pt; pts) {
// 同时获取索引和值
}
// --- while 循环 ---
int i = 0;
while (i < @numpt) {
// ...
i++;
}
// --- break / continue ---
for (int i = 0; i < 100; i++) {
if (i == 50) break; // 跳出循环
if (i % 2 == 0) continue; // 跳过偶数
}3.8 调试输出
c
// printf:输出到控制台
printf("Point %d position: %g\n", @ptnum, @P);
// 格式化字符串
string msg = sprintf("ptnum=%d, P=%v, Cd=%v", @ptnum, @P, @Cd);
printf("%s\n", msg);
// error:输出错误并停止执行
if (@ptnum < 0) {
error("Invalid point number: %d\n", @ptnum);
}
// assert:断言(需设置 HOUDINI_VEX_ASSERT 环境变量)
assert(@ptnum >= 0, "ptnum should be non-negative");printf 格式符:
| 格式符 | 类型 | 示例 |
|---|---|---|
%d | int | printf("%d", 42) |
%g | float | printf("%g", 3.14) |
%s | string | printf("%s", "hello") |
%v | vector | printf("%v", {1,2,3}) |
%4d | int(宽度) | printf("%4d", 42) → 42 |
%.2f | float(精度) | printf("%.2f", 3.14159) → 3.14 |
四、几何操作
4.1 读取属性
c
// --- point():读取指定点的属性 ---
// point(geometry, attribute_name, point_number)
vector pos = point(0, "P", 5); // 读取点 5 的位置
float pscale = point(0, "pscale", 5); // 读取点 5 的 pscale
// --- prim():读取指定面的属性 ---
int type = prim(0, "type", 3); // 读取面 3 的 type 属性
string name = prim(0, "name", 0); // 读取面 0 的 name 属性
// --- vertex():读取指定顶点的属性 ---
vector uv = vertex(0, "uv", 10); // 读取顶点 10 的 uv
// --- detail():读取 detail 属性(全局属性)---
int total = detail(0, "total_count"); // 读取全局计数
string info = detail(0, "info"); // 读取全局信息
// --- 跨输入读取 ---
vector pos = point(1, "P", @ptnum); // 从第二个输入读取4.2 设置属性
c
// --- setpointattrib():设置点属性 ---
// setpointattrib(geometry, name, ptnum, value, mode="set")
setpointattrib(0, "Cd", @ptnum, {1, 0, 0});
setpointattrib(0, "pscale", @ptnum, 2.0);
// mode 参数:
// "set" — 直接设置(默认)
// "add" — 累加
// "min" — 取最小
// "max" — 取最大
// "mult" — 乘法
// "toggle"— 切换
setpointattrib(0, "Cd", @ptnum, {0.1, 0, 0}, "add"); // 累加颜色
// --- setprimattrib():设置面属性 ---
setprimattrib(0, "Cd", @primnum, {1, 1, 0});
// --- setdetailattrib():设置全局属性 ---
setdetailattrib(0, "total", 1, "add"); // 累加(适合统计计数)
// --- setvertexattrib():设置顶点属性 ---
setvertexattrib(0, "uv", @vtxnum, {0.5, 0.5, 0});
// --- 设置组 ---
setpointgroup(0, "myGroup", @ptnum, 1); // 将点加入组
setpointgroup(0, "myGroup", @ptnum, 0); // 将点移出组
setprimgroup(0, "myPrimGroup", @primnum, 1);4.3 属性查询
c
// 检查属性是否存在
int hasP = hasattrib(0, "point", "P"); // 返回 1 或 0
int hasCd = hasattrib(0, "point", "Cd");
int hasName = hasattrib(0, "prim", "name");
// 获取属性类型
int type = attribtype(0, "point", "P"); // 返回类型 ID
int size = attribsize(0, "point", "P"); // 返回分量数(P=3)
// 获取点数 / 面数
int numPoints = npoints(0);
int numPrims = nprimitives(0);
// 获取面的顶点
int verts[] = primvertices(0, @primnum); // 返回顶点编号数组
int pts[] = primpoints(0, @primnum); // 返回点编号数组4.4 BBox 与空间查询
c
// --- 包围盒 ---
vector bboxMin, bboxMax;
getbbox(0, bboxMin, bboxMax); // 获取包围盒范围
vector bboxCenter = getbbox_center(0); // 包围盒中心
vector bboxSize = getbbox_size(0); // 包围盒尺寸
// relbbox:将位置映射到 0-1 的包围盒空间
vector relP = relbbox(0, @P); // 返回 {0-1, 0-1, 0-1}
// 用途:基于包围盒归一化位置做渐变
float gradient = relbbox(0, @P).y; // 0 = 底部, 1 = 顶部
@Cd = chramp("color_ramp", gradient);
// --- 体积查询 ---
float density = volume(0, "density", @P); // 读取体积密度
vector gradient = volumegradient(0, "density", @P); // 体积梯度(方向)五、邻近查询
5.1 最近点查询
c
// --- nearpoint():查找最近点 ---
// nearpoint(geometry, position)
// nearpoint(geometry, position, max_distance)
// nearpoint(geometry, position, max_distance, min_distance)
int nearest = nearpoint(1, @P); // 在输入 1 中找最近点
int nearest2 = nearpoint(1, @P, 5.0); // 最大距离 5
int nearest3 = nearpoint(1, @P, 5.0, 0.1); // 忽略 0.1 以内的点
// 注意:nearpoint 只返回一个点,且不包括当前点(如果查询同一个几何体)
// 要排除自身,可以用 min_distance
// --- nearpoints():查找多个最近点 ---
// nearpoints(geometry, position, max_distance, max_points)
int closePts[] = nearpoints(1, @P, 2.0, 10); // 半径 2 内最近的 10 个点5.2 距离与投影
c
// --- xyzdist():查找最近面并返回距离 ---
// xyzdist(geometry, position, &primnum, &uvw, maxdist)
int hitPrim;
vector hitUVW;
float dist = xyzdist(1, @P, hitPrim, hitUVW);
// hitPrim 存储最近面编号,hitUVW 存储面上的参数化坐标
// --- minpos():获取最近面位置 ---
// minpos(geometry, position)
// minpos(geometry, position, maxdist)
vector closestPos = minpos(1, @P); // 输入 1 上离 @P 最近的点位置
// 实际应用:投影到另一个几何体
@P = minpos(1, @P); // 将点吸附到最近的面5.3 邻接查询
c
// --- neighbour():查询相邻点 ---
// neighbour(geometry, ptnum, neighbour_index)
// 返回第 N 个相邻点的编号
int nextPt = neighbour(0, @ptnum, 0); // 第一个相邻点
// --- neighbours():获取所有相邻点 ---
int adjPts[] = neighbours(0, @ptnum); // 返回相邻点编号数组
// 遍历相邻点
foreach (int adjPt; adjPts) {
vector adjPos = point(0, "P", adjPt);
// 处理...
}5.4 射线投射
c
// --- intersect():射线与几何体求交 ---
// intersect(geometry, origin, direction, &position, &uvw, &primnum)
vector hitPos, hitUVW;
int hitPrim;
int hit = intersect(1, @P, {0, -1, 0}, hitPos, hitUVW, hitPrim);
if (hit >= 0) {
// hit >= 0 表示命中,hitPrim 是命中面编号
@P = hitPos; // 将点移到交点位置
@Cd = {1, 0, 0};
} else {
// 未命中
@Cd = {0, 1, 0};
}5.5 面上参数化查询
c
// --- primuv():根据 UVW 坐标在面上插值属性 ---
// primuv(geometry, attribute, primnum, uvw)
vector posOnPrim = primuv(0, "P", @primnum, {0.5, 0.5, 0}); // 面中心位置
vector colorOnPrim = primuv(0, "Cd", @primnum, @uv); // UV 处的颜色六、数组操作
6.1 数组声明与初始化
c
// --- 声明 ---
int myInts[];
float myFloats[];
vector myVecs[];
string myStrings[];
// --- 初始化 ---
int arr1[] = {1, 2, 3, 4, 5};
float arr2[] = {1.0, 2.5, 3.7};
vector arr3[] = { {1,0,0}, {0,1,0}, {0,0,1} };
// --- array() 函数创建 ---
int arr4[] = array(10, 20, 30);
float arr5[] = array(1.0, 2.0, 3.0);
// --- 从属性获取数组 ---
int pointGroup[] = expandpointgroup(0, "myGroup"); // 组内点编号数组6.2 数组基本操作
c
int arr[] = {10, 20, 30, 40, 50};
// --- 长度 ---
int n = len(arr); // 5
// --- 索引访问 ---
int first = arr[0]; // 10
int last = arr[len(arr)-1]; // 50
// --- 赋值 ---
arr[0] = 100; // arr = {100, 20, 30, 40, 50}
// --- 切片 ---
int sub[] = arr[1:3]; // {20, 30}(不包含索引 3)
int head[] = arr[:2]; // {100, 20}
int tail[] = arr[3:]; // {40, 50}
int all[] = arr[:]; // 完整副本
// --- 添加元素 ---
push(arr, 60); // 追加到末尾,arr = {..., 60}
push(arr, 70, 80); // 追加多个值
int newArr[] = append(arr, 90); // 返回新数组(不修改原数组)
// --- 查找 ---
int idx = find(arr, 30); // 返回 30 的索引(2),找不到返回 -1
// --- 排序 ---
sort(arr); // 原地排序(升序)
// 对于降序,先排序再反转
reverse(arr);6.3 数组遍历与综合示例
c
// --- foreach 遍历 ---
int pts[] = nearpoints(1, @P, 2.0, 10);
foreach (int pt; pts) {
vector ptPos = point(1, "P", pt);
float d = distance(@P, ptPos);
if (d < 0.5) {
@Cd = {1, 0, 0};
break;
}
}
// --- 带索引遍历 ---
foreach (int i; int pt; pts) {
printf("Neighbor %d: point %d\n", i, pt);
}
// --- 统计算例:计算邻居平均位置 ---
int neighbors[] = nearpoints(0, @P, 1.0, 20);
vector avgPos = {0, 0, 0};
int count = 0;
foreach (int pt; neighbors) {
if (pt != @ptnum) { // 排除自身
avgPos += point(0, "P", pt);
count++;
}
}
if (count > 0) {
avgPos /= count;
// 将点向平均位置轻微移动(平滑)
@P = lerp(@P, avgPos, ch("smoothness"));
}七、常见陷阱与注意事项
7.1 属性类型不匹配
c
// ❌ 错误:@P 是 vector,不能直接赋给 float
f@myFloat = @P; // 编译错误
// ✅ 正确:提取分量
f@myFloat = @P.y;
// ❌ 错误:@Cd 是 vector,不能赋给 int
i@myInt = @Cd; // 编译错误
// ✅ 正确:如果需要标量,手动转换
i@myInt = int(@Cd.x * 255);7.2 ch() 返回类型
c
// ❌ 错误:ch() 始终返回 float,不能赋给 vector
v@dir = ch("direction"); // 编译错误
// ✅ 正确:使用 chv()
v@dir = chv("direction");
// ❌ 错误:ch() 不能赋给 int
i@count = ch("count"); // ch 返回 float,会自动截断但可能出问题
// ✅ 正确:使用 chi()
i@count = chi("count");7.3 向量乘法不是点积
c
vector a = {1, 2, 3};
vector b = {4, 5, 6};
// ❌ 常见误解:* 不是点积
vector result = a * b; // 结果: {4, 10, 18}(逐分量相乘)
// ✅ 点积用 dot()
float dotProduct = dot(a, b); // 结果: 32
// ✅ 叉积用 cross()
vector crossProduct = cross(a, b); // 结果: {-3, 6, -3}7.4 @ 属性读写的作用域
c
// 在 Run Over: Points 模式下:
// @ptnum 是当前点编号
// 修改 @P 只影响当前点
// ❌ 错误:试图直接修改其他点的属性
@P = point(0, "P", 5); // 这只是将点 5 的位置读给当前点
// ✅ 正确:使用 setpointattrib 修改其他点
if (@ptnum == 0) {
setpointattrib(0, "Cd", 5, {1, 0, 0}); // 将点 5 设为红色
}
// ⚠️ 注意:setpointattrib 在 Run Over: Points 时可能不会立即生效
// 因为 VEX 是并行执行的,修改的属性会在下一轮迭代中可见
// 如果需要严格的顺序操作,考虑使用 Detail 模式 + 循环7.5 几何创建的注意事项
c
// ⚠️ 在 Run Over: Points 模式下用 addpoint 创建点会导致重复创建
// 因为每个点都会执行一次代码
// ❌ 错误:这样会为每个点创建一个新点(N 个点 → 2N 个点)
addpoint(0, @P + {0, 1, 0});
// ✅ 正确方案 1:用 Detail 模式(只执行一次)
// 将 Run Over 设为 Detail (only once)
for (int i = 0; i < chi("count"); i++) {
vector pos = set(i * 2.0, 0, 0);
addpoint(0, pos);
}
// ✅ 正确方案 2:用条件限制
if (@ptnum == 0) {
addpoint(0, @P + {0, 1, 0});
}7.6 Strict Variables 模式
VEX 默认开启严格变量模式(_strictvariables),未声明的变量会报错:
c
// ❌ 严格模式下报错:未声明的变量
myVar = 5.0; // Error: Undefined variable
// ✅ 正确:先声明类型
float myVar = 5.0;
// ✅ 使用 @ 绑定的属性不需要额外声明
@myAttr = 5.0; // OK,@ 自动处理
f@myAttr = 5.0; // 更明确7.7 弧度 vs 角度
c
// VEX 中所有三角函数使用弧度!
// ❌ 错误:直接传入角度值
float s = sin(90); // 返回 0.893...(90 弧度的 sin),不是 1.0
// ✅ 正确:先转换
float s = sin(radians(90)); // 返回 1.0
// ❌ quaternion 也使用弧度
p@orient = quaternion(90, {0, 1, 0}); // 90 弧度,约 5156 度!
// ✅ 正确
p@orient = quaternion(radians(90), {0, 1, 0});7.8 性能提示
c
// ✅ 在循环外获取参数值
float threshold = ch("threshold");
vector center = chv("center");
for (int i = 0; i < 100; i++) {
if (distance(@P, center) < threshold) {
// ...
}
}
// ❌ 不推荐:在循环内反复调用 ch()
for (int i = 0; i < 100; i++) {
if (distance(@P, chv("center")) < ch("threshold")) {
// ch() 每次迭代都会查找参数
}
}
// ✅ 使用 nearpoints() 代替遍历所有点
int closePts[] = nearpoints(0, @P, maxDist, maxCount);
// 这比遍历所有点再计算距离快得多
// ✅ 避免在 VEX 中使用过大的搜索半径
// nearpoints 的搜索半径越大,性能越差附录:完整示例
示例 1:波纹动画
c
// 在 Point Wrangle 中
float freq = ch("frequency");
float amp = ch("amplitude");
float speed = ch("speed");
float dist = length(@P.xz);
float wave = sin(dist * freq - @Time * speed) * amp;
@P.y += wave * exp(-dist * ch("decay"));
// 颜色渐变
@Cd = chramp("color", fit(wave, -amp, amp, 0, 1));示例 2:点云散射
c
// Run Over: Detail
// 在指定区域内随机生成点
int count = chi("point_count");
vector min = chv("bbox_min");
vector max = chv("bbox_max");
for (int i = 0; i < count; i++) {
vector pos = fit01(rand(i + 123), min, max);
int pt = addpoint(0, pos);
// 为每个点设置随机颜色
setpointattrib(0, "Cd", pt, rand(i + 456));
}示例 3:沿曲线流动
c
// 需要第二个输入连接一条曲线
// Run Over: Points
float u = fit01(rand(@ptnum), 0, 1);
// 沿曲线获取位置和切线
vector curvePos = primuv(1, "P", 0, set(u, 0, 0));
vector curveTangent = primuv(1, "tangentu", 0, set(u, 0, 0));
// 添加随机偏移
vector offset = curlnoise(@P * ch("noise_scale") + @ptnum) * ch("noise_amp");
@P = curvePos + offset;
@N = normalize(curveTangent);示例 4:邻居平滑
c
// 拉普拉斯平滑(将每个点向邻居平均位置移动)
float strength = ch("strength");
int neighbors[] = neighbours(0, @ptnum);
if (len(neighbors) > 0) {
vector avgPos = {0, 0, 0};
foreach (int npt; neighbors) {
avgPos += point(0, "P", npt);
}
avgPos /= len(neighbors);
@P = lerp(@P, avgPos, strength);
}示例 5:距离渐变着色
c
// 基于到目标点的距离设置颜色和大小
vector target = chv("target_position");
float maxDist = ch("max_distance");
float dist = distance(@P, target);
float weight = 1.0 - fit(dist, 0, maxDist, 0, 1);
weight = clamp(weight, 0, 1);
// 颜色:从蓝到红
@Cd = lerp({0, 0.5, 1}, {1, 0.2, 0}, weight);
// 大小:近处大,远处小
@pscale = fit(weight, 0, 1, 0.1, 2.0);
// 平滑过渡
weight = smooth(0.0, 1.0, weight);文档来源:
- Houdini 21.0.671 本地帮助文档 (
vex.zip)- SideFX 官方 VEX Snippets 文档
- SideFX 官方 VEX Functions 文档
最后更新:2025-06