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 |
Унарный плюс (например, |
13 |
|
LtR |
Унарный минус (например, |
13 |
|
LtR |
Инкремент (например, |
13 |
|
LtR |
Декремент (например, |
13 |
|
LtR |
Приведение типов (например, |
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 |
Тернарное условие (например, |
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 коде.
Вы можете использовать символы подчеркивания для лучшей читабельности длинных чисел.
Тип |
Определение |
Пример |
---|---|---|
|
Целочисленные значения |
|
|
Скалярные значения с плавающей запятой |
|
|
Два значения с плавающей запятой. Вы можете использовать этот тип для представления текстурных координат (хотя обычно Houdini использует векторы) или комплексных чисел |
|
|
Три значения с плавающей запятой. Вы можете использовать этот тип для представления позиций, направлений, нормалей или цветов (RGB или HSV) |
|
|
Четыре значения с плавающей запятой. Вы можете использовать этот тип для представления позиций в однородных координатах или цвета с альфа каналом (RGBA) |
|
|
Список значений. Смотрите массивы для получения дополнительной информации. |
|
|
Фиксированный набор именованных значений. Смотрите structs для получения дополнительной информации. |
|
|
Четыре значения с плавающей запятой, представляющие матрицу 2D вращения |
|
|
Девять значений с плавающей запятой, представляющие матрицу 3D поворота или матрицу 2D трансорфмации |
|
|
Шестнадцать значений с плавающей запятой, представляющие матрицу 3D трансформации |
|
|
Строка символов. Смотрите строки для получения дополнительной информации. |
|
|
Функция распределения двунаправленного рассеяния. Смотрите написание 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 имеет некоторые предопределенные типы структур, которые используются в специальных шейдерных функциях.
|
Определен только в контексте шейдинга мантры. Это структура, представляющая handle источника света. Структура имеет методы:
В IFD вы можете увидеть строки Эти операторы определяют шейдер, вызываемый при вызове метода |
|
Определен только в контексте шейдинга мантры. Это непрозрачная структура, представляющая материал, назначенный объекту. |
Приведение типов
Приведение переменных
Это похоже на приведение типов в языке 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