前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VEX 语言参考

VEX 语言参考

作者头像
南郭先生
发布2022-04-25 13:50:09
1.3K0
发布2022-04-25 13:50:09
举报
文章被收录于专栏:Google DartGoogle Dart

上下文

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

上下文决定哪些函数、语句和全局变量是否可用。有关使用 VEX 的方式的概述,请参阅 VEX 上下文。如果您正在编写着色上下文(表面、置换、光照等),您还应该阅读着色上下文的特定信息。

声明

VEX 支持 C 中熟悉的常用语句。它还支持特定于着色的语句,例如仅在某些上下文中可用的照度illuminance和聚集gather循环。

内置函数

VEX 包含一个大型的内置函数库。 某些功能仅在某些情况下可用。请参阅 VEX 函数

用户自定义函数

函数的定义与 C 类似:指定返回类型、函数名称和带括号的参数列表,然后是代码块。可以在逗号分隔的列表中声明相同类型的参数,而无需重新声明类型。 其它参数必须用分号分隔。

代码语言:javascript
复制
int test(int a, b; string c) {
    if (a > b) {
        printf(c);
    }
}

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

代码语言:javascript
复制
function int test(int a, b; string c) {
    if (a > b) {
        printf(c);
    }
}

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

代码语言:javascript
复制
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); 
} 

要点:

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

Main(上下文)函数

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

这个函数应该完成计算任何所需信息和修改全局变量的工作(通过调用内置和/或用户定义的函数)。您不使用 return 语句从上下文函数返回值。有关每个上下文中可用的全局变量,请参见特定 上下文页面。

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

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

代码语言:javascript
复制
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)。您可以使用用户界面编译器编译指示执行此操作。

代码语言:javascript
复制
#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 运算符,但有以下区别。乘法是在两个向量或点之间定义的。

乘法执行逐个元素的乘法(而不是点或叉积;请参阅叉和点)。

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

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

代码语言:javascript
复制
int + vector = vector

点运算符

您可以使用点运算符 (.) 来引用向量、矩阵或结构的各个组件。 对于向量,组件名称是固定的。

  • .x 或 .u 引用 vector2 的第一个元素。
  • .x 或 .r 引用 vector 和 vector4 的第一个元素。
  • .y 或 .v 引用 vector2 的第二个元素。
  • .y 或 .g 引用 vector 和 vector4 的第二个元素。
  • .z 或 .b 引用 vector 和 vector4 的第三个元素
  • .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)

注: 您不能分配给 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 成员初始化。

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

代码语言:javascript
复制
#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 函数内部,您可以使用 this 来引用 struct 实例。
  • 在 struct 函数中,您可以按名称引用 struct 字段,就好像它们是变量一样(例如,basis 是 this.basis 的快捷方式)。
  • 您可以使用 -> 箭头运算符在结构实例上调用结构函数,例如 sampler->sample()。 请注意,在结构函数内部,您可以使用 this->method() 调用结构上的其它方法。
代码语言:javascript
复制
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特定类型

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)。

这有时是必要的,例如当您有以下情况时:

代码语言:javascript
复制
int a, b;
float c;
c = a / b;

在此示例中,编译器将进行整数除法(请参阅类型解析)。 如果您想改为进行浮点除法,则需要将 a 和 b 显式转换为浮点数:

代码语言:javascript
复制
int a, b;
float c;
c = (float)a / (float)b;

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

函数铸造

VEX 不仅基于参数的类型(如 C++ 或 Java)调度函数,还基于返回类型。 要消除对具有相同参数类型但返回类型不同的函数的调用的歧义,您可以强制转换函数。

例如,噪声函数可以采用不同的参数类型,但也可以返回不同的类型:噪声可以返回浮点数或向量。 在代码中:

代码语言:javascript
复制
float n;
n = noise(noise(P));

…VEX 可以分派到float noise(vector)或vector noise(vector)。要强制转换函数调用,请使用 typename( ... ) 将其括起来,如下所示:

代码语言:javascript
复制
n = noise( vector( noise(P) ) );

虽然这看起来像一个函数调用,但它只是消除了内部函数调用的歧义,并且没有性能开销。当您将函数调用直接分配给指定类型的变量时,隐含了函数转换。 所以下面的表达式是等价的,为了更简洁的代码,可以省略函数转换:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/03/21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上下文
  • 声明
  • 内置函数
  • 用户自定义函数
  • Main(上下文)函数
  • 用户界面编译指示
  • 操作符
    • 点运算符
      • 比较
        • 优先表
          • 操作符类型交互
          • 数据类型
          • 结构
            • 结构函数
            • Mantra特定类型
            • 类型铸造
              • 变量铸造
                • 函数铸造
                • 注释
                • 保留关键字
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档