VEX 程序是为特定的上下文编写的。 例如,控制对象表面颜色的着色器是为表面surface上下文编写的。 为灯光light上下文编写了用于确定灯光照度的着色器。 创建或过滤通道数据的 VEX 程序是为斩波chop上下文编写的。
上下文决定哪些函数、语句和全局变量是否可用。有关使用 VEX 的方式的概述,请参阅 VEX 上下文。如果您正在编写着色上下文(表面、置换、光照等),您还应该阅读着色上下文的特定信息。
VEX 支持 C 中熟悉的常用语句。它还支持特定于着色的语句,例如仅在某些上下文中可用的照度illuminance和聚集gather循环。
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);
}
要点:
VEX 程序必须包含一个返回类型为上下文名称的函数。这是被mantra调用的程序的main函数。编译器期望每个文件有一个上下文函数。
这个函数应该完成计算任何所需信息和修改全局变量的工作(通过调用内置和/或用户定义的函数)。您不使用 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 以一种特殊的方式处理上下文函数的参数。可以使用与变量同名的几何属性覆盖参数的值。除了这种特殊情况,参数应该被认为是着色器范围内的“常量”。这意味着修改参数值是非法的。 如果发生这种情况,编译器将生成错误。
您可以使用 export 关键字来标记您希望在原始几何上修改的参数。
Houdini 从这个程序生成的用户界面将是最小的,基本上只有变量名和基于数据类型的通用文本字段。例如,您可能希望指定频率frequency应该是具有特定范围的滑块,并且应该将 clr 视为一种颜色(给它一个颜色选择器 UI)。您可以使用用户界面编译器编译指示执行此操作。
#pragma opname noise_surf
#pragma oplabel "Noisy Surface"
#pragma label clr "Color"
#pragma label frequency "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 运算符,但有以下区别。乘法是在两个向量或点之间定义的。
乘法执行逐个元素的乘法(而不是点或叉积;请参阅叉和点)。
许多运算符是为非标量数据类型定义的(即向量乘以矩阵将通过矩阵变换向量)。
例如,在将两种不同类型与运算符组合在一起的模棱两可的情况下,结果具有第二个(右侧)值的类型
int + vector = vector
您可以使用点运算符 (.) 来引用向量、矩阵或结构的各个组件。 对于向量,组件名称是固定的。
字母 u,v/x,y,z/r,g,b 的选择是任意的; 即使向量不包含点或颜色,也适用相同的字母。对于矩阵,您可以使用一对字母:
此外,点运算符可用于“混合”向量的分量。 例如
注: 您不能分配给 swizzled 向量,只能从它们中读取。 所以你不能做 v.zyx = b,而是必须做 v = b.zyx。
比较运算符(==、!=、<、<=、>、>=)在运算符的左侧与右侧的类型相同时定义,仅适用于字符串、浮点和整数类型。 这些操作产生整数类型。
字符串匹配运算符(~=)仅在运算符两边都有字符串时才定义,相当于用这两个值调用匹配函数。
逻辑(&&、|| 和 !)和按位(& |、^ 和 ~)运算符仅针对整数定义。
表中排序越靠前的运算符具有越高的优先级。
当您对浮点数和整数应用运算时,结果是运算符左侧的类型。 也就是说,float * int = float,而 int * float = int。
如果用标量值(int 或 float)对向量进行加法、乘法、除法或减法,VEX 将返回一个大小相同的向量,并按分量应用运算。 例如:
如果对不同大小的向量进行加、乘、除或减,VEX 会返回一个更大的向量。 该操作是按组件应用的。重要提示:较小向量上的“缺失”组件填充为 {0.0, 0.0, 0.0, 1.0}
警告 默认情况下,VEX 使用 32 位整数。 如果您使用 AttribCast SOP 将几何属性转换为 64 位,如果您在 VEX 代码中操作该属性,VEX 将默默地丢弃额外的位。
VEX 引擎以 32 位或 64 位模式运行。 在 32 位模式下,所有浮点数、向量和整数都是 32 位的。 在 64 位模式下,它们是 64 位的。 没有允许混合精度数学的 double 或 long 类型。您可以使用下划线来拆分长数字。
类型 | 定义 | 例子 |
---|---|---|
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 | 表示二维旋转矩阵的四个浮点值 | { {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" |
dict | 将字符串映射到其他 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; // Uninitialized member data
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;
}
// Declare struct variables
basis b0; // Initialize using default values (i.e. 0 in this case)
basis b1 = basis({1,0,0}, {0,1,0}, {0,0,1}); // Initialize using constructor
basis b2 = { {1,0,0}, {0,1,0}, {0,0,1} }; // Initialize as explicit struct
b1 = rotate(b1, {0,0,1}, M_PI/6);
笔记 在源文件中使用它们之前,您必须定义结构。
您可以在结构中定义函数来组织代码并允许有限形式的面向对象编程。
struct randsampler {
// Fields
int seed;
// Methods
float sample()
{
// Struct functions can refer to fields by name
return random(seed++);
}
}
cvex shader()
{
randsampler sampler = randsampler(11);
for (int i = 0; i < 10; i++)
{
// Use -> to call methods on struct instances
printf("%f\n", sampler->sample());
}
}
Mantra 有一些预定义的结构类型,用于特定于着色的函数。
light | 仅在Mantra着色上下文中定义。 这是一个表示光源句柄的结构。 该结构具有方法: Illuminate(…) 调用绑定到光源的 vm_illumshader 属性的 VEX 表面着色器。 在 IFD 中,您可能会看到像 ray_property light illumshaderdiffuselighting 或 ray_property light illumshader mislighting misbias 1.000000 这样的线条。 这些语句定义了在光照对象上调用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/number_of_samples)。 int getid() - 返回分配给 lpeaccumulator 的整数 id。 lpeaccumulator getlpeaccumulator(int id) - 根据 id 返回 lpeaccumulator。 与 getid() 一起使用以跨越着色器边界传递 lpeaccumulator。 |
这类似于 C++ 或 Java 中的类型转换:将一种类型的值转换为另一种类型(例如,将 int 转换为 float)。
这有时是必要的,例如当您有以下情况时:
int a, b;
float c;
c = a / b;
在此示例中,编译器将进行整数除法(请参阅类型解析)。 如果您想改为进行浮点除法,则需要将 a 和 b 显式转换为浮点数:
int a, b;
float c;
c = (float)a / (float)b;
这会生成额外的指令来执行强制转换。 这可能是代码中性能敏感部分的问题。
VEX 不仅基于参数的类型(如 C++ 或 Java)调度函数,还基于返回类型。 要消除对具有相同参数类型但返回类型不同的函数的调用的歧义,您可以强制转换函数。
例如,噪声函数可以采用不同的参数类型,但也可以返回不同的类型:噪声可以返回浮点数或向量。 在代码中:
float n;
n = noise(noise(P));
…VEX 可以分派到float noise(vector)或vector noise(vector)。要强制转换函数调用,请使用 typename( ... ) 将其括起来,如下所示:
n = noise( vector( noise(P) ) );
虽然这看起来像一个函数调用,但它只是消除了内部函数调用的歧义,并且没有性能开销。当您将函数调用直接分配给指定类型的变量时,隐含了函数转换。 所以下面的表达式是等价的,为了更简洁的代码,可以省略函数转换:
vector n = vector( noise(P) ); // Unnecessary function cast
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