Справка Houdini на русском VEX

Справка по языку VEX

Сведения о синтаксисе VEX, типах данных и т.д.

On this page

Контексты

VEX программы пишутся для конкретных контекстов. Например, шейдер, который управляет цветом поверхности объекта, должен быть написан для контекста поверхности (surface). Шейдер, определяющий освещенность светом, написан для контекста света (light). VEX программа, которая создает или фильтрует данные канала, записывается для контекста анимационных каналов (CHOP).

Контекст влияет на то, какие функции, операторы и глобальные переменные доступны.

Смотрите контексты VEX для понимания того, как вы можете использовать VEX.

Если вы пишете для шейдинг контекста (surface, displacement, light и т.д.), вам также необходимо ознакомиться с информацией о шейдинг контексте.

Операторы

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); 
} 

Заметки

  • Пользовательские функции должны быть объявлены до первой ссылки на них.

  • Эти функции автоматически компилируются компилятором, поэтому рекурсия не будет работать. Чтобы написать рекурсивный алгоритм, вы должны использовать шейдерные вызовы.

  • Как и в языке RenderMan Shading Language, параметры для пользовательских функций всегда передаются по ссылке, поэтому изменения в пользовательской функции влияют на переменную, с которой была вызвана функция. Вы можете принудительно сделать параметр шейдера доступным только для чтения, используя перед ним ключевое слово const. Чтобы гарантировать, что пользовательская функция записывается в выходной параметр, используйте перед ним ключевое слово export.

  • Количество пользовательских функций не ограничено

  • Вы можете иметь более одного оператора возврата return в функции.

  • Вы можете напрямую обращаться к глобальным переменным (в отличие от RenderMan Shading Language, вам не нужно объявлять их с помощью extern). Однако мы рекомендуем избегать доступа к глобальным переменным, поскольку это ограничивает вашу функцию работой только в одном контексте (там, где существуют эти глобальные переменные). Вместо этого передайте глобальные переменные функции в качестве параметров.

  • Функции могут быть определены внутри функции (вложенные функции).

Основная (контекстная) функция

VEX программа должна содержать одну функцию, тип возврата которой является именем контекста. Это основная функция программы, которая вызывается Мантрой. Компилятор ожидает одну контекстную функцию для каждого файла.

Эта функция должна выполнять работу (вызывая встроенные и/или пользовательские функции) вычисления любой требуемой информации и измененять глобальные переменные. Вы не используете оператор возврата 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;
}

Note

Параметры контекстных функций обрабатываются VEX особым образом. Можно переопределить значение параметра, используя атрибут геометрии с тем же именем, что и переменная. Помимо этого специального случая, параметры следует рассматривать как "константные" в области видимости шейдера. Это означает, что изменять значение параметра запрещено. Компилятор будет возвращать ошибки, если это произойдет.

Ключевое слово export можно использовать для определения параметров, которые вы хотите изменить в исходной геометрии.

Псевдокомментарии пользовательского интерфейса (Pragmas)

Пользовательский интерфейс, созданный из этой программы Houdini, будет минимальным, в основном просто имя переменной и общее текстовое поле, основанное на типе данных. Например, вы можете указать, что frequency (частота) должна быть слайдером с определенным диапазоном, и что clr следует рассматривать в качестве цвета (предоставляя ему интерфейс выбора цвета). Вы можете сделать это с помощью псевдокомментариев пользовательского интерфейса компилятора.

#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 с тем же старшинством, но со следующими отличиями.

Умножение определено между двумя векторами или точками. Умножение выполняется поэлементно, а не скалярное или векторное произведение, см. dot и cross.

Много операций определено для нескалярных типов данных (т.е. вектор умноженный на матрицу, трансформирует вектор по матрице).

В неоднозначных ситуациях, когда вы объединяете два разных типа с операцией, результат имеет тип второго (правая сторона) значения, например

int + vector = vector

Операция точка

Вы можете использовать операцию точка (.) для получения доступа к отдельным компонентам вектора, матрицы или структуры (struct).

Для векторов имена компонентов фиксированы.

  • .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] элемент

В дополнение, операция точка может использоваться для перестановки (swizzle) компонентов вектора. Например:

  • v.zyx эквивалентно set(v.z, v.y, v.x)

  • v4.bgab эквивалентно set(v4.b, v4.g, v4.a, v4.b)

Сравнения

Операции сравнения (==, !=, <, <=, >, >=) определяются, когда левая сторона того же типа, что и правая сторона, только для строковых, вещественных и целочисленных типов. Результатом операции является целочисленное значение.

Логические (&&, ||, и !) и побитовые (& |, ^, и ~) операции определены только для целых чисел.

Таблица старшинства (или приоритетов)

Операции расположенные выше в таблице имеют более высокий приоритет.

Порядок

Операция

Воздействие

Описание

15

()

LtR

Вызов функции, группировка выражений, структурный член.

13

!

LtR

Логическое отрицание

13

~

LtR

Обратный код

13

+

LtR

Унарный плюс (например, +5)

13

-

LtR

Унарный минус (например, -5)

13

++

LtR

Инкремент (например, x++)

13

--

LtR

Декремент (например, x--)

13

(type)

LtR

Приведение типов (например, (int)x).

12

*

LtR

Умножение

12

/

LtR

Деление

12

%

LtR

Деление со взятием остатка

11

+

LtR

Сложение

11

-

LtR

Вычитание

10

<

LtR

Меньше чем

10

>

LtR

Больше чем

10

<=

LtR

Меньше или равно

10

>=

LtR

Больше или равно

9

==

LtR

Равно

9

!=

LtR

Не равно

8

&

LtR

Побитовое И

7

^

LtR

Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ

6

|

LtR

Побитовое ИЛИ

5

&&

LtR

Логическое И

4

||

LtR

Логическое ИЛИ

3

?:

LtR

Тернарное условие (например, x ? "true" : "false")

2

= += -= *= /= %= &= |= ^=

RtL

Присваивание переменной значения

1

,

LtR

Разделитель аргументов

Взаимодействие типов в операциях

  • Когда вы применяете операцию к float и int, результатом является тип слева от оператора. То есть, float * int = float, тогда как int * float = int.

  • Если вы добавляете, делите или вычитаете скалярное значение из вектора (int или float), 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}
    
    // Третий элемент vector2 рассматривается как 0,
    // но четвертый элемент рассматривается как 1.0
    {1.0, 2.0} + {1.0, 2.0, 3.0, 4.0} == {2.0, 4.0, 3.0, 5.0}
    

Типы данных

Warning

VEX использует 32 битные целые. Если вы используете AttribCast SOP для приведения атрибута геометрии в 64 бита, VEX будет автоматически отбрасывать лишние биты, если вы манипулируете атрибутом в VEX коде.

Вы можете использовать символы подчеркивания для лучшей читабельности длинных чисел.

Тип

Определение

Пример

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

Четыре значения с плавающей запятой. Вы можете использовать этот тип для представления позиций в однородных координатах или цвета с альфа каналом (RGBA)

{0,0,0,1}, {0.3,0.5,-0.5,0.2}

array

Список значений. Смотрите массивы для получения дополнительной информации.

{ 1, 2, 3, 4, 5, 6, 7, 8 }

struct

Фиксированный набор именованных значений. Смотрите structs для получения дополнительной информации.

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"

bsdf

Функция распределения двунаправленного рассеяния. Смотрите написание PBR шейдеров для получения информации о BSDFs.

Структуры

Начиная с 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} };         // Инициализация как явной структуры
b1 = rotate(b1, {0,0,1}, M_PI/6);

Note

Вы должны определить структуры прежде, чем использовать их в исходном файле.

Структурные функции (методы)

Вы можете определять функции внутри структур, чтобы организовать свой код и реализовать ограниченную форму объектно-ориентированного программирования.

  • Вы можете использовать this внутри структурной функции, чтобы ссылаться на экземпляр структуры.

  • Вы можете ссылаться на поля структуры внутри структурных функций по имени, как если бы они были переменными (например, basis - это аналог this.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 имеет некоторые предопределенные типы структур, которые используются в специальных шейдерных функциях.

light

Определен только в контексте шейдинга мантры. Это структура, представляющая handle источника света. Структура имеет методы:

  • illuminate(…) Вызывает VEX шейдер поверхности связанный со свойством vm_illumshader источника света.

В IFD вы можете увидеть строки ray_property light illumshader diffuselighting или ray_property light illumshader mislighting misbias 1.000000.

Эти операторы определяют шейдер, вызываемый при вызове метода illuminate() на объект освещения.

material

Определен только в контексте шейдинга мантры. Это непрозрачная структура, представляющая материал, назначенный объекту.

Приведение типов

Приведение переменных

Это похоже на приведение типов в языке C++ или Java: вы преобразовываете значение одного типа в значение другого типа (например, int в float).

В некоторых случаях это необходимо, например, когда у вас следующая ситуация:

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

В данном примере компилятор будет выполнять целочисленное деление (смотрите разрешение типов). Если вы хотите сделать деление с плавающей запятой, вам необходимо явно преобразовать a и b в числа с плавающей запятой (float):

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

Это создает дополнительные инструкции для выполнения приведений, что может быть проблемой в разделах, чувствительных к производительности вашего кода.

Приведение функций

VEX выполняет функции основываясь не только на типах аргументов (как C++ или Java), но и на возвращаемом типе. Для устранения неоднозначности вызовов функций с теми же типами аргументов, но с разными типами возвращаемого значения, вы можете использовать приведение функций.

Например, функция шума может принимать разные типы параметров, но также может возвращать разные типы: шум может возвращать либо float, либо vector.

В коде:

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

…VEX может выполнить либо float noise(vector), либо vector noise(vector).

Для приведения вызова функции используйте следующую конструкцию имя типа( ... ), как в:

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

Хоть это и выглядит как вызов ещё одной функции, данная конструкция ничего не делает, кроме устранения неоднозначности вызова функции внутри и не вызывает потери в производительности.

Приведение функций подразумевается при назначении вызова функции непосредственно переменной указанного типа. Таким образом, следующие выражения эквивалентны и приведение может быть опущено для краткости кода:

vector n = vector( noise(P) );        // Ненужное приведение функции
vector n = noise(P);

Note

Если VEX не сможет определить, какую функцию вы пытаетесь вызвать, случится ошибка неоднозначности и опишет функции-кандидаты. Вам необходимо выбрать соответствующее возвращамое значение и добавить приведение функции, чтобы выбрать её.

Поскольку приведение функций не генерирует никаких преобразований типов (оно просто выбирает функцию для вызова), её использование не сказывается на производительности. Может стать хорошим правилом, использование приведения функций там, где это возможно, и использовать приведение переменных только в тех случаях, когда требуется явное преобразование.

Комментарии

VEX использует комментирование в стиле языка C++:

  • Однострочным комментариям предшествует //

  • Многострочные комментарии начинаются с /* и заканчиваются */

Зарезервированные ключевые слова

break, bsdf, char, color, const, continue, do, 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

VEX

Язык

Следующие шаги

Справочная информация