跳转到内容

vex language reference

本页内容* 上下文 * 语句 * 内置函数 * 用户定义函数 + 注意事项 * 主(上下文)函数 + 用户界面编译指示 * 运算符 + 点运算符 + 比较运算 + 优先级表 + 运算符类型交互 * 数据类型 * 结构体 + 结构体函数 * Mantra专用类型 * 类型转换 + 变量类型转换 + 函数类型转换 * 注释 * 保留关键字

上下文

VEX程序是为特定上下文编写的。例如,控制对象表面颜色的着色器是为surface上下文编写的。确定光源照明的着色器是为light上下文编写的。创建或过滤通道数据的VEX程序是为chop上下文编写的。

上下文决定了哪些函数、语句和全局变量可用。

参见VEX上下文了解使用VEX的不同方式概述。

如果您正在为着色上下文(表面、置换、光源等)编写代码,还应阅读着色上下文特定信息

语句

VEX支持类似C语言的常用语句。它还支持特定于着色的语句,如仅在特定上下文中可用的illuminancegather循环。

内置函数

VEX包含一个大型的内置函数库。某些函数仅在特定上下文中可用。

参见VEX函数

用户定义函数

函数的定义方式与C类似:指定返回类型、函数名称、带括号的参数列表,然后是代码块。

相同类型的参数可以在逗号分隔的列表中声明,无需重新声明类型。其他参数必须用分号分隔。

int test(int a, b; string c) {
if (a > b) {
printf(c);
}
}

您可以重载具有相同名称但不同参数签名和/或返回类型的函数。

可以使用可选的function关键字引入函数定义以避免类型歧义。

function int test(int a, b; string c) {
if (a > b) {
printf(c);
}
}
void print(basis b) {
printf("basis: { i: %s, j: %s, k: %s }\n", b.i, b.j, b.k);
}
void print(matrix m) {
printf("matrix: %s\n", m);
}
void print(bases b) {
printf("bases <%s> {\n", b.description);
printf(" "); print(b.m);
printf(" "); print(b.n);
printf(" "); print(b.o);
printf("}\n");
}
basis rotate(basis b; vector axis; float amount) {
matrix m = 1;
rotate(m, amount, axis);
basis result = b;
result.i *= m;
result.j *= m;
result.k *= m;
return result;
}
void rotate(basis b; vector axis; float amount) {
b = rotate(b, axis, amount);
}

notes

  • 用户函数必须在被引用之前声明。
  • 函数由编译器自动内联,因此递归将不起作用。要编写递归算法,应改用着色器调用
  • 与RenderMan着色语言一样,用户函数的参数总是通过引用传递,因此在用户函数中的修改会影响调用该函数的变量。可以通过在参数前添加const关键字来强制着色参数为只读。为确保用户函数写入输出参数,可在其前添加export关键字。
  • 用户函数的数量没有限制。
  • 一个函数中可以有多个return语句。
  • 可以直接访问全局变量(与RenderMan着色语言不同,不需要用extern声明它们)。但是,建议避免访问全局变量,因为这会将函数限制在仅在一个上下文中工作(存在这些全局变量的地方)。相反,应将全局变量作为参数传递给函数。
  • 函数可以在函数内部定义(嵌套函数)。

主(上下文)函数

VEX程序必须包含一个返回类型为上下文名称的函数。这是程序的主函数,由mantra调用。编译器期望每个文件有一个上下文函数。

此函数应通过调用内置和/或用户定义的函数来完成计算所需信息和修改全局变量的工作。您不使用return语句从上下文函数返回值。请参阅特定上下文页面了解每个上下文中可用的全局变量。

上下文函数的参数(如果有)成为程序的用户界面,例如引用VEX程序的着色节点的参数。

如果存在与上下文函数参数同名的几何属性,则该属性会覆盖参数的值。这允许您将属性绘制到几何体上以控制VEX代码。

surface
noise_surf(vector clr = {1,1,1}; float frequency = 1;
export vector nml = {0,0,0})
{
Cf = clr * (float(noise(frequency * P)) + 0.5) * diffuse(normalize(N));
nml = normalize(N)*0.5 + 0.5;
}

注意 VEX对上下文函数的参数有特殊处理。可以使用与变量同名的几何属性覆盖参数的值。除了这种特殊情况外,参数在着色器范围内应被视为”const”。这意味着修改参数值是非法的。如果发生这种情况,编译器将生成错误。

您可以使用export关键字标记您希望修改原始几何体的参数。

user-interface-pragmas Houdini从此程序生成的用户界面将是最小的,基本上只是变量名和基于数据类型的通用文本字段。例如,您可能希望指定frequency应该是一个具有特定范围的滑块,而clr应该被视为颜色(给它一个颜色选择器UI)。您可以使用用户界面编译器编译指示来实现这一点。

#pragma opname noise_surf
#pragma oplabel "噪波表面"
#pragma label clr "颜色"
#pragma label frequency "频率"
#pragma hint clr color
#pragma range frequency 0.1 10
surface noise_surf(vector clr = {1,1,1}; float frequency = 1;
export vector nml = {0,0,0})
{
Cf = clr * (float(noise(frequency * P)) + 0.5) * diffuse(normalize(N));
nml = normalize(N)*0.5 + 0.5;
}

运算符

VEX具有标准C运算符和C优先级,但有以下差异。

乘法定义在两个向量或点之间。乘法执行逐元素乘法(而不是点积或叉积;参见crossdot)。

许多运算符为非标量数据类型定义(即向量乘以矩阵将用矩阵变换向量)。

在将两种不同类型与运算符组合的模糊情况下,结果具有第二个(右侧)值的类型,例如

int + vector = vector

dot-operator 您可以使用点运算符(.)引用向量、矩阵或结构体的各个组件。

对于向量,组件名称是固定的。

  • .x.u引用vector2的第一个元素。
  • .x.r引用vectorvector4的第一个元素。
  • .y.v引用vector2的第二个元素。
  • .y.g引用vectorvector4的第二个元素。
  • .z.b引用vectorvector4的第三个元素。
  • .w.a引用vector4的第四个元素。

选择u,v/x,y,z/r,g,b字母是任意的;即使向量不包含点或颜色,也适用相同的字母。

对于矩阵,您可以使用一对字母:

  • .xx引用[0][0]元素
  • .zz引用[2][2]元素
  • .ax引用[3][0]元素

此外,点运算符可用于”混合”向量的组件。例如

  • v.zyx等同于set(v.z, v.y, v.x)
  • v4.bgab等同于set(v4.b, v4.g, v4.a, v4.b)

注意 您不能赋值给混合向量,只能从中读取。因此不能执行v.zyx = b,而必须执行v = b.zyx

comparisons 比较运算符(==, !=, <, <=, >, >=)在运算符左侧与右侧类型相同时定义,仅适用于字符串、浮点和整数类型。运算结果为整数类型。

字符串匹配运算符(~=)仅在运算符两侧都是字符串时定义,等同于调用match函数。

逻辑(&&, ||, !)和位运算(& |, ^, ~)运算符仅对整数定义。

precedence 表中位置越高的运算符优先级越高。

顺序运算符结合性描述
15()从左到右函数调用、表达式分组、结构成员。
13!从左到右逻辑非
13~从左到右按位取反
13+从左到右一元加(例如+5)
13-从左到右一元减(例如-5)
13++从左到右递增(例如x++)
13--从左到右递减(例如x--)
13(type)从左到右类型转换(例如(int)x)。
12*从左到右乘法
12/从左到右除法
12%从左到右取模
11+从左到右加法
11-从左到右减法
10<从左到右小于
10>从左到右大于
10<=从左到右小于等于
10>=从左到右大于等于
9==从左到右等于
9!=从左到右不等于
9~=从左到右字符串匹配
8&从左到右按位与
7^从左到右按位异或
6``从左到右
5&&从左到右逻辑与
4``
3?:从左到右三元条件(例如x ? "true" : "false")
2= += -= *= /= %= &= ```vex=“^= ```从右到左
1,从左到右参数分隔符

operator-type-interactions

  • 当对floatint类型执行运算时,结果类型取决于运算符左侧的类型。即float * int = float,而int * float = int
  • 当向量与标量值(intfloat)进行加减乘除运算时,VEX会返回相同维度的向量,并按分量逐个运算。例如:
{1.0, 2.0, 3.0} * 2.0 == {2.0, 4.0, 6.0}
  • 对不同维度的向量进行加减乘除运算时,VEX会返回较大维度的向量。运算按分量逐个执行。 重要:较小维度向量中”缺失”的分量会以{0.0, 0.0, 0.0, 1.0}填充
{1.0, 2.0, 3.0} * {2.0, 3.0, 4.0, 5.0} == {2.0, 6.0, 12.0, 5.0}

如果不注意这点可能会得到意外结果,例如:

// 向量2的第三个元素被视为0,
// 但第四个元素被视为1.0
{1.0, 2.0} + {1.0, 2.0, 3.0, 4.0} == {2.0, 4.0, 3.0, 5.0}

当组合不同维度的向量时,建议手动拆分分量进行操作,以避免意外结果。

数据类型

警告 默认情况下,VEX使用32位整数。如果使用AttribCast SOP将几何属性转换为64位,在VEX代码中操作该属性时,VEX会静默丢弃多余的位。

VEX引擎运行在32位或64位模式下。在32位模式下,所有浮点数、向量和整数都是32位的。在64位模式下,它们都是64位的。没有doublelong类型来支持混合精度计算。

可以使用下划线分隔长数字。

类型定义示例
int整数值21, -3, 0x31, 0b1001, 0212, 1_000_000
float浮点标量值21.3, -3.2, 1.0, 0.000_000_1
vector2两个浮点值。可用于表示纹理坐标(虽然Houdini通常使用向量)或复数{0,0}, {0.3,0.5}
vector三个浮点值。可用于表示位置、方向、法线或颜色(RGB或HSV){0,0,0}, {0.3,0.5,-0.5}
vector4四个浮点值。可用于表示齐次坐标位置,或带alpha通道的颜色(RGBA)。常用于表示四元数。VEX中的四元数按x/y/z/w顺序排列,而非w/x/y/z。这适用于四元数和齐次坐标位置。{0,0,0,1}, {0.3,0.5,-0.5,0.2}
array值列表。详见数组{ 1, 2, 3, 4, 5, 6, 7, 8 }
struct固定命名的值集合。详见结构体
matrix2表示2D旋转矩阵的四个浮点值{ {1,0}, {0,1} }
matrix3表示3D旋转矩阵或2D变换矩阵的九个浮点值{ {1,0,0}, {0,1,0}, {0,0,1} }
matrix表示3D变换矩阵的十六个浮点值{ {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} }
string字符串。详见字符串"hello world"
dictstring映射到其他VEX数据类型的字典。详见字典
bsdf双向散射分布函数。关于BSDF的信息请参阅编写PBR着色器

结构体

从Houdini 12开始,可以使用struct关键字定义新的结构化类型。

成员数据可以在结构体定义中分配默认值,类似于C++11的成员初始化。

每个结构体会隐式创建两个构造函数。第一个按结构体中声明的顺序接受初始化参数,第二个不接受参数但将所有成员设置为其默认值。

#include <math.h>
struct basis {
vector i, j, k;
}
struct bases {
basis m, n, o;
string description;
}
struct values {
int uninitialized; // 未初始化的成员数据
int ival = 3;
float fval = 3.14;
float aval[] = { 1, 2, 3, 4.5 };
}
basis rotate(basis b; vector axis; float amount) {
matrix m = 1;
rotate(m, amount, axis);
basis result = b;
result.i *= m;
result.j *= m;
result.k *= m;
return result;
}
// 声明结构体变量
basis b0; // 使用默认值初始化(本例中为0)
basis b1 = basis({1,0,0}, {0,1,0}, {0,0,1}); // 使用构造函数初始化
basis b2 = { {1,0,0}, {0,1,0}, {0,0,1} }; // 显式结构体初始化
// 可以使用M_PI或PI
b1 = rotate(b1, {0,0,1}, M_PI/6);

注意 必须在源文件中先定义结构体才能使用。

methods 可以在结构体中定义函数来组织代码,并允许有限形式的面向对象编程。

  • 在结构体函数内部,可以使用this引用结构体实例。
  • 在结构体函数内部,可以通过名称引用结构体字段,就像它们是变量一样(例如,basisthis.basis的简写)。
  • 可以使用->箭头运算符在结构体实例上调用结构体函数,例如sampler->sample()。 注意在结构体函数内部,可以使用this->method()调用结构体上的其他方法。
struct randsampler {
// 字段
int seed;
// 方法
float sample()
{
// 结构体函数可以通过名称引用字段
return random(seed++);
}
}
cvex shader()
{
randsampler sampler = randsampler(11);
for (int i = 0; i < 10; i++)
{
// 使用->在结构体实例上调用方法
printf("%f\n", sampler->sample());
}
}

Mantra特定类型

Mantra有一些预定义的结构体类型,用于特定着色函数。

light仅在Mantra着色上下文中定义。表示光源句柄的结构体。该结构体有方法:* illuminate(…) 调用绑定到光源vm_illumshader属性的VEX表面着色器。 在IFD中,可能会看到类似ray_property light illumshader diffuselightingray_property light illumshader mislighting misbias 1.000000的行。这些语句定义了在light对象上调用illuminate()方法时调用的着色器。
material仅在Mantra着色上下文中定义。表示分配给对象材质的不透明结构体。
lpeaccumulator仅在Mantra着色上下文中定义。表示光路表达式累加器的结构体。该结构体有方法:* begin() - 构造并初始化累加器。 * end() - 完成并销毁。 * move(string eventtype; string scattertype; string tag, string bsdflabel) - 根据当前事件修改内部状态。如果传入空字符串,则假定为’any’。 * pushstate() - 将内部状态压入堆栈。 * popstate() - 从堆栈弹出内部状态。用于pushstate()来”撤销”move()。 * int matches() - 如果当前内部状态匹配用户定义的任何光路表达式,则返回非零值。 * accum(vector color, …) - 将输入颜色累加到中间缓冲区。还接受可选前缀字符串与LPE图像平面声明的前缀进行比较。所有前缀必须匹配才能累加。 * flush(vector multiplier) - 将中间缓冲区乘以乘数并添加到图像平面。中间缓冲区用于允许方差抗锯齿(即乘数为1/样本数)。 * int getid() - 返回分配给lpeaccumulator的整数id。 * lpeaccumulator getlpeaccumulator(int id) - 根据id返回lpeaccumulator。与getid()一起用于跨着色器边界传递lpeaccumulator。

类型转换

variable-casting 这与C++或Java中的类型转换类似:将一种类型的值转换为另一种类型(例如,将int转换为float)。

有时这是必要的,例如:

int a, b;
float c;
c = a / b;

在这个例子中,编译器会执行整数除法(参见类型解析)。如果想执行浮点除法,需要显式将ab转换为float:

int a, b;
float c;
c = (float)a / (float)b;

这会生成额外的指令来执行转换。在性能敏感的代码部分可能会成为问题。

function-casting VEX不仅根据参数类型(如C++或Java)分发函数,还根据返回类型分发函数。为了消除具有相同参数类型但不同返回类型的函数调用的歧义,可以转换函数

例如,noise函数可以接受不同的参数类型,也可以返回不同的类型:noise可以返回float或vector。

在代码中:

float n;
n = noise(noise(P));

…VEX可以分派到float noise(vector)vector noise(vector)

要转换函数调用,用typename( ... )包围它,如:

n = noise( vector( noise(P) ) );

虽然这看起来像函数调用,但它只是消除内部函数调用的歧义,没有性能开销。

当直接将函数调用赋值给指定类型的变量时,函数转换是隐含的。因此以下表达式是等价的,可以省略函数转换以使代码更简洁:

vector n = vector( noise(P) ); // 不必要的函数转换
vector n = noise(P);

注意 如果VEX无法确定要调用哪个函数签名,将触发歧义错误并打印候选函数。然后应选择适当的返回值并添加函数转换来选择它。

由于函数转换不会生成任何类型转换(它只是选择要调用的函数),因此使用它没有性能损失。一个好的经验法则是尽可能使用函数转换,只有在需要显式类型转换时才使用变量转换。

注释

VEX使用C++风格的注释:

  • 单行注释以//开头
  • 自由格式注释以/*开头,以*/结尾

保留关键字

break, bsdf, char, color, const, continue, do, dict, else, export, false, float, for, forpoints, foreach, gather, hpoint, if, illuminance, import, int, integer, matrix, matrix2, matrix3, normal, point, return, string, struct, true, typedef, union, vector, vector2, vector4, void, while