Skip to content

Houdini VEX 代码范例参考

基于 Houdini 21.0.671 本地文档 (vex.zip) 及 SideFX 官方在线文档整理。

参考链接:


目录

  1. Wrangler VEX 语法基础
  2. 界面参数引用
  3. 常用计算写法
  4. 几何操作
  5. 邻近查询
  6. 数组操作
  7. 常见陷阱与注意事项

一、Wrangler VEX 语法基础

1.1 @ 属性绑定

在 Wrangle 节点中,使用 @ 前缀直接读写几何属性。VEX 会根据属性名称推断类型(如果之前已有定义)。

c
// 读取位置属性
v@myPos = @P;

// 设置颜色
@Cd = {1, 0, 0};  // 红色

// 设置法线
@N = {0, 1, 0};

1.2 类型前缀

如果属性尚不存在,或需要明确指定类型,使用类型前缀:

前缀类型大小示例
f@float1f@myFloat = 1.0;
i@int1i@myInt = 5;
s@string1s@myStr = "hello";
v@vector3v@myVec = {1, 2, 3};
p@vector4 ( quaternion )4p@myQuat = {0, 0, 0, 1};
2@vector222@myVec2 = {1, 2};
3@vector333@myVec3 = {1, 2, 3};
4@vector444@myVec4 = {1, 2, 3, 4};
u@matrix22×2u@myMat2 = { {1,0},{0,1} };
d@matrix33×3d@myMat3 = ident();
3@ (matrix)matrix4×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 节点中可直接使用的内置变量:

变量类型说明
@ptnumint当前点编号
@primnumint当前面(primitive)编号
@vtxnumint当前顶点编号
@numptint几何体中的总点数
@numprimint几何体中的总面数
@elemnumint当前元素编号(随 Run Over 模式变化)
@numelemint当前元素总数
@Timefloat当前时间(秒)
@Framefloat当前帧号
@TimeIncfloat时间步长(一帧的秒数)
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")floatFloatch("speed")
chf("name")floatFloatchf("speed")
chi("name")intIntegerchi("count")
chs("name")stringStringchs("label")
chv("name")vectorVectorchv("direction")
ch2("name")vector2Vector 2ch2("uv_offset")
ch3("name")vector3Vector 3ch3("axis")
ch4("name")vector4Vector 4ch4("quat")
chp("name")vector4Quaternionchp("rotation")
chramp("name", pos)floatRampchramp("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.0

3.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.0

3.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 格式符:

格式符类型示例
%dintprintf("%d", 42)
%gfloatprintf("%g", 3.14)
%sstringprintf("%s", "hello")
%vvectorprintf("%v", {1,2,3})
%4dint(宽度)printf("%4d", 42) 42
%.2ffloat(精度)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);

文档来源

最后更新:2025-06

Released under the MIT License.