Обзор спецификации GLSL ES 2.0

Вместо вступления

Это странно, что в Сети так сложно найти перевод спецификации GLSL ES на русский язык. С момента ее выхода прошло ведь немало времени (опубликована еще в 2009 году). И хотя "не нашел" не значит - "нету", ждать пока этот перевод появится я не намерен.

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





Вступление

OpenGL ES Shading Language (также известен как GLSL ES или ESSL) основан на GLSL версии 1.20, поэтому его спецификация копирует соответвующие части разделов той спецификации. Также этот язык основан на С++ и в специальном разделе документ ссылается на спецификацию этого языка.

Конвейер GLSL ES обрабатывает информацию в два программируемых этапа: обработка вершин и фрагментов. Остальные фазы называются фиксированными функциями и приложение имеет лишь ограниченные возможности в работе с ними. Набор компилируемых элементов для каждого(из этих двух) программируемых этапов – называются шейдерами.

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

Кроме того, OpenGL ES 2.0 имеет дополнительные ограничения, описанные в приложении А этой спецификации.

Обзор процесса создания шейдеров в OpenGL ES

Вообще OpenGL ES представляет собой два тесно взаимосвязанных языка(для вершинного шейдера и для фрагментного). Эти языки используются для создания шейдеров. Шейдеры используются для программирования процессоров, составляющих конвейер обработки данных.

Вершинный процессор – это программируемое устройство, оперирующее с вершинами в виде входных данных. Исходный код, который после компиляции выполняется в этом процессоре, называется вершинным шейдером. Вершинный шейдер обрабатывает одну вершину за цикл. Вершинный процессор не заменяет графические операции, требующие использования нескольких вершин за одну операцию.

Фрашментный процессор – это программируемое устройство, которое оперирует входными данными, состоящими из значений фрагментов. Исходный код для него, соответственно называется фрагментный шейдер.
Фрагментный шейдер не может менять расположение фрагментов. Доступ к соседним фрагментам отсутствует. Значения, вычисляемые фрагментным шейдером используются исключительно  для обновления памяти фрейм-буфера или памяти, содержащей текстуру создаваемого фрагмента, обусловленного текущим состоянием OpenGL ES.

Основы

Набор используемых в исходном коде символов представляет собой подможество ASCII, которое включает в себя:
Буквы a-z, A-Z, символ подчеркивания ( _ ).
Цифры 0-9.
Символы точка, запятая, плюс, минус, деление, звездочка, процент, угловые скобки, квадратные скобки, обычные скобки, фигурные скобки, вставка(крыша), вертикальная черта, амперсанд, тильда, равно, восклицательный знак, вопросительный знак, двоеточие, точка с запятой.
Символ # используется препроцессором.
Отступы: пробел, горизонтальная и вертикальная табуляции, разрыв страницы, возврат каретки, и перевод строки.

Строки, предназначенные для препроцессора и отладки заканчиваются переводом строки или возвратом каретки. Если они используются вместе, то считаются одним символом завершения строки.
Обратный слеш (символ продолжения строки) не является частью языка. Язык чувствителен к регистру. Он не содержит типа данных символ или строка, так что никакие кавычки так же не используются.
Нет символа конца файла (EOF). Компилятор ориентируется по длине строки, а не по символу окончания.

Исходный код представляет собой массив строк.

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

Процесс компиляции такой же как в С++. Модули компиляции для вершинного и фрагментного процессоров обрабатываются компилятором каждый отдельно, а затем компонуются вместе на заключительном этапе компиляции. Логически процесс компиляции делится на следующие этапы:
1. Строки исходного кода объединяются
2. Исходный код преобразуется в последовательность знаков (токенов) для препроцессора. Эти токены представляют собой числа, идентификаторы и команды, обрабатываемые препроцессором. Каждый комментарий заменяется на символ пробела. Переносы строк сохраняются.
3. Работает препроцессор. Выполняются его директивы и макро-расширения.
4. Токены препроцессора преобразуются в токены.
5. Удаляются символы отступа и перевода строк.
6. Синтаксический анализ правильности кода.
7. Результат проверяется в соответствии с семантическим правилам языка.
8. Компоновка вместе вершинного и фрагментного шейдеров. Любые не используемые в шейдерах переменные могут быть удалены.
9. Формирование двоичного кода.

Обработка строки исходного кода препроцессором, является частью процесса компиляции. Ниже приведен список директив препроцессора:
#
#define
#undef

#if
#ifdef
#ifndef
#else
#elif
#endif

#error
#pragma

#extension
#version

#line

Также доступен оператор
defined

Перед каждым символом #, могут быть только символы пробела или горизонтальной табуляции.
Пробелы и отступы также допустимы после этого символа (перед именем директивы). Каждая директива заканчивается переводом строки. Препроцессор не меняет относительной нумерации строк исходного кода.
Одиночные символы # в строке игнорируются. Любые директивы, не входящие в указанный список, делают код ненадежным.

Функциональность #define и #undef такая же как в C++, для макроопределений как с, так и без макро параметров.

Список предопределенных макросов:
__LINE__
__FILE__
__VERSION__
GL_ES

__LINE__ преобразуется в десятичную константу, которая на 1 больше встреченных до этой строки символов перевода строки.

__FILE__ преобразуется в десятичную константу, которая указывает на номер обрабатываемой в данный момент строки исходного кода.

__VERSION__ преобразуется в десятичную константу, отображающую номер версии OpenGL ES Shader Language.

GL_ES будет определена и установлена равной 1. Для языков не OpenGL ES эта константа не определяется. Так что она может использоваться для проверки в момент компиляции: будет ли шейдер запускаться в ES системе.

Все макро имена, содержащие двойное подчеркивание или начинающиеся с GL_ – зарезервированы для будущих версий.

#if, #ifdef, #ifndef, #else, #elif, #endif – работают как в C++, за исключением следующих моментов:
1) Выражения, следующие за #if  и #elif состоят исключительно из десятичных констант либо идентификаторов, определенных оператором defined.
2) Идентификаторы, не определенные оператором defined не равны по умолчанию 0. Использование таких идентификаторов генерирует ошибку.
3) Символьные константы не поддерживаются.

Приоритет
Класс оператора
Операторы
Ассоциативность
1(наивысший)
группирующие скобки
( )
NA
2
унарный
defined  +  -  ~  !
справа-налево
3
мультипликативный
*  /  %
слева-направо
4
суммирующий
+  -
слева-направо
5
битовый сдвиг
<<  >>
слева-направо
6
отношение
<  >  <=  >=
слева-направо
7
равенство
==  !=
слева-направо
8
битовое И
&
слева-направо
9
битовое исключ. ИЛИ
^
слева-направо
10
битовое ИЛИ
|
слева-направо
11
логическое И
&&
слева-направо
12
логическое ИЛИ
||
слева-направо


Оператор defined может быть использован любым из следующих способов:
defined identifier
defined (identifier)

Нет операторов работы со знаком числа(#, #@, ## и т.д.), также нет оператора sizeof.

Семантика применения операторов такая же как в C++ за исключением следующего:
- второй операнд в && оценивается только в случае, если первый не равен 0
- второй операнд в || оценивается только в случае, если первый равен 0
То есть в этих случаях даже если второй операнд не определен, то это не приведет к ошибке.

Выражения препроцессора будут вычислены на этапе компиляции.

#error вставляет сообщение об ошибке в логах объекта шейдера (см. API в документации по платформе, чтобы узнать как получить доступ к логам объекта  шейдера). Сообщением об ошибке будет текст, располагающийся после директивы #error до конца этой строки. В этом случае реализация кода должна рассматриваться как ненадежная(содержащая ошибки).

#pragma позволяет управлять компилятором в зависимости от реализации. Имя, которое следует за директивой #pragma не могут являться макрорасширениями препроцессора. #pragma с нераспознанными именами игнорируются. С директивой #pragma допустимы следующие имена:

#pragma STDGL
STDGL зарезервировано для использования в будущем

#pragma optimize(on)
#pragma optimize(off)
может быть использовано для включения/выключения оптимизаций как вспомогательных средств разработки и отладки шейдеров. Может использоваться только вне определения функций. По умолчанию опция включена для всех шейдеров.

#pragma debug(on)
#pragma debug(off)
Используется для включения отладочной информации в компилируемый код для использования в отладчике. Может использоваться только вне определений фкнкций. По умолчанию выключено.

Каждый элемент компиляции должен определяться версией языка, заданной директивой #version:
#version number
где number должен быть равен 100 для версии языка, описываемой данной спецификацией (исходя из описанной выше макро-константы __VERSION__). В каждом случае директива применяется без предупреждений и сообщениях об ошибке. Любой number, меньший 100 – сгенерирует ошибку. Любой number, больший, чем максимальная версия, поддерживаемая компилятором также вызовет ошибку. Для языка версии 100 эту директиву применять не обязательно, поскольку элементы языка вне этой директивы и так считаются этой версии.
В модуле компиляции директива #version должна быть самой первой. Перед ней могут быть только комментарии и пробелы.

По умолчанию компилятор этого языка должен выдавать сообщения о синтаксических, грамматических и семантических ошибках в элементах компиляции, не соответствующих данной спецификации. В случае наличия расширений, они сперва должны быть объявлены директивой #extension.
#extension extension_name:behavior
#extension all:behavior
где extension_name это имя расширения. Имена расширений не описаны в данной спецификации. Этот маркер означает, что поведение относится ко всем расширениям, поддерживаемым компилятором.
...

Директива #extension является простым низкоуровневым механизмом, который определяет поведение для каждого расширения. Она не устанавливает права, они должны быть установлены иным способом.
Приоритеты использования этой директивы следующие: директивы, описанные позже переопределяют те, что были описаны ранее. Все варианты установленного поведения расширений перекрывают все ранее определенные, но только для того, чтобы выдать предупреждение или отключить.
...

Комментарии

Комментарии задаются конструкциями /* */ либо //. Комментарии не могут быть вложенными. Комментарии синтаксически воспринимаются как одиночные пробелы и не считаются строками.

Маркеры (токены)

Язык является последовательностью следующих маркеров:
ключевые слова
идентификаторы
целые константы
константы с плавающей точкой
операторы

Ключевые слова

Зарезервированы следующие ключевые слова:
attribute
const
uniform varying
break continue do for while
if else
in out
inout
float int void
bool
true false
lowp mediump highp precision invariant
discard return
mat2 mat3 mat4
vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4
sampler2D
samplerCube
struct

Зарезервировано для будущего использования:
asm
class union
enum
typedef template this packed
goto switch default
inline
noinline
volatile public static extern external interface flat
long short double half fixed unsigned superp
input output
hvec2 hvec3 hvec4 dvec2 dvec3 dvec4 fvec2 fvec3 fvec4
sampler1D sampler3D
sampler1DShadow sampler2DShadow
sampler2DRect sampler3DRect sampler2DRectShadow
sizeof cast
namespace
using

Так же зарезервированы все константы, содержащие в имени двойное подчеркивание.

Идентификаторы

Идентификаторы используются для обозначения имен переменных, функций, структур, а также указателей полей(указатели полей указывают на компоненты векторов и матриц).
Идентификаторы могут иметь форму:
identifier
      nondigit
      identifier nondigit
      identifier digit
nondigit: _ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
digit: 0 1 2 3 4 5 6 7 8 9

Идентификатолы, начинающиеся с gl_ зарезервированы для использования в OpenGL ES. Не пользовательские идентификаторы могут начинаться с gl_.

Типы и переменные

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

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

Пользовательские типы могут быть заданы с помощью struct для создания списка имеющихся типов под одним именем. OpenGL ES нет преобразования типов.

Основные типы

Тип
Значение
void
для функций, не возвращающих значение либо для пустого списка параметров
bool
условный тип, принимает значение true и false
int
целое число со знаком
float
число с плавающей точкой, скалярная величина
vec2
вектор из двух float-величин
vec3
вектор из трех float-величин
vec4
вектор из четырех float-величин
bvec2
вектор из двух bool-величин
bvec3
вектор из трех bool-величин
bvec4
вектор из четырех bool-величин
ivec2
вектор из двух int-величин
ivec3
вектор из трех int-величин
ivec4
вектор из четырех int-величин
mat2
2x2 матрица из float-величин
mat3
3x3 матрица из float-величин
mat4
4x4 матрица из float-величин
sampler2D
указатель для доступа к двумерной текстуре
samplerCube
указатель для доступа к текстуре cube map

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

В этом языке нет типа указатель.

Void

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

Booleans

Этот тип не обязательно поддерживается аппаратно. Допустимо лишь два значения этого типа true и false. Ключевые слова true и false могут использоваться в качестве констант этого типа.  
Для осуществления условных переходов (if, for, ?:, while, do-while) в коде используется этот тип.

Integers

Это основной аппаратно поддерживаемый тип. Этот тип используется, например, для организации циклов, индексации массивов, доступа к элементам текстуры. Однако у языка нет жестких требований к аппарадной поддержке всего разнообразия операций с целыми числами. Главное чтобы была возможность поддержки всего множества целых чисел. В OpenGL ES целые числа могут быть преобразованы в формат с плавающей точкой.

Целочисленные константы могут быть представлены в десятичной, 8-чной или 16-чной форме. В целочисленных константах не может быть пробелов между разрядами. Предшествующий знак минуса интерпретируется не как часть константы, а как операция арифметического унарного отрицания.
Восьмеричные константы начинаются с 0. Шестнадцатиричные – с 0x(0X).

Буквенных суффиксов у констант нет.

Floats

Примеры констант с плавающей точкой:
0.2
.01
12.344e-3
-17E2

Минус перед константой как и в случае с целочисленной константой не является ее частью, а является унарной операцией отрицания.

Vectors

OpenGL ES поддерживает 2-х, 3-х и 4-х мерные векторы типов float, integer и bool. Float-векторы служат для многих целей в графике: позиция, цвет, нормаль, координаты текстуры и т.д. Boolean-векторы могут использоваться для комплексного сравнения векторов. Допустимо определение векторов как часть языка шейдеров для директ-маппинга векторных операций на графической аппаратуре, способной работать с векторными данными. В целом, приложениям при работе с графической аппаратурой лучше обрабатывать параллельно векторные данные, чем скалярные величины.

Инициализация векторов производится с помощью конструкторов, которые вскоре будут рассмотрены.

Matrices

Матрицы – другой полезный тип данных в компьютерной графике, и OpenGL ES поддерживает float-матрицы размеров 2x2, 3x3 и 4x4. Матрицы читаются и пишутся по столбцам. Инициализируются конструкторами.

Samplers

Тип данных sampler2D эффективно работает с текстурами. Он используется для доступа к текстуре и указывает к какой именно текстуре необходимо обращаться. Используется только в качестве параметра или uniform (см. далее). В отличие от параметров просмотра текстуры, индексации массива, выборки поля структуры или скобок, семплер не может быть частью выражения,

Structures

Типы, определяемые пользователем могут быть созданы путем объединения других, уже определенных типов в структуру с помощью ключевого слова struct. Например:
struct light {
float intensity;
vec3 position;
} lightVar;
В этом примере light – новый тип, а lightVar – переменная этого типа. Для объявления переменной типа структуры – просто используйте имя типа (без ключевого слова struct).
light lightVar2;

Структура должна включать по крайней мере одно поле. Битовые поля не поддерживаются. Типы членов структуры должны быть определены ранее. Члены структуры могут быть массивами. В этом случае размер массива должен быть задан и быть больше 0. Вложенное объявление структур не поддерживается.

Arrays

Массивы объявляются как обычные переменные с добавлением "[ ]" после имени, в скобках обязательно должен содержаться размер массива. Размер должен быть больше 0. Доступ к элементу массива, лежащему за пределами его размера недопустим, это может привести к непредсказуемому поведению(зависит от платформы). При объявлении массива в качестве параметра функции необходимо так же указывать его размер. Есть возможность работать только с одномерными массивами. Массив может состоять из элементов любого типа, в том числе структуры.
Не существует механизма инициализации массива во время его объявления внутри шейдера.

Области видимости

Переменные при объявлении имеют область видимости. Обычно область видимости ограничивается внутри фигурных скобок. Если переменная объявлена за пределами функций, то ее область видимости начинается с момента объявления и продолжается до конца модуля компиляции. При объявлении внутри функций, циклов, условных операторов – область видимости ограничивается фигурными скобками.
Переопределять переменные можно только если они были объявлены в более широкой области видимости. В этом случае нет возможности получить доступ к перекрытой переменной.

Глобальный доступ

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

Пространство имен

Внутри каждой области видимости имеется свое пространство имен. Каждое объявление новой переменной добавляет ее в пространство имен. Структуры так же при объявлении добавляют в пространство имен имя структуры и имя конструктора.
Все объявления переменных и структур скрывают объявления с такими же именами внешних областей видимости.

Квалификаторы

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

Квалификатор
Пояснение
<none:default>
локальная переменная или входной параметр функции
const
константа времени компиляции или параметр функции, доступный только для чтения
attribute
связь между вершинным шейдером и OpenGL ES для данных каждой вершины
uniform
значение не может меняться на протяжении обработки, связь между шейдером и приложением
varying
связь между вершинным и фрагментным шейдерами для интерполируемых данных

Локальные переменные и параметры функций могут содержать лишь квалификатор const.
Возвращаемые функцией параметры не могут использовать квалификаторы.
Нет связи между запусками шейдера(связи между вершинами или фрагментами). Это бы нарушило возможность параллельной обработки.
Глобальные объявления без квалификатора или только с квалификатором conts могут включать инициализаторы. Не инициализированные объявления будут равны undefined.
Attribute, uniform, varying не могут предварительно инициализироваться.

Квалификатор attribute применяется при объявлении переменных, передаваемых в вершинный шейдер из OpenGL ES для каждой вершины. Используется только для вершинных шейдеров. В процессе исполнения шейдера является значением только для чтения. Этот квалификатор может использоваться только со следующими типами: float, vec2, vec3, vec4, mat2, mat3, mat4. Attribute не может быть объявлен как массив или структура.

Квалификатор uniform используется в объявлениях глобальных переменных, которые используются в процессе обработки примитивов. Все переменные uniform являются read-only и инициализируются либо приложением через специальные API команды, либо непосредственно в OpenGL ES. Этот квалификатор может использоваться с любыми типами. Есть платформо-зависимые ограничения на количество uniform данных.
Когда вершинный и фрагментный шейдеры компонуются вместе, они формируют общее uniform  пространство имен. Следовательно типы и точность всех uniform-переменных с одинаковыми именами должны совпадать во всех шейдерах, компонуемых вместе.

 Varying-переменные служат интерфейсом между вершинным шейдером, фрагментным шейдером и взаимодействующими с ними фиксированными функциями. Вершинный шейдер вычисляет данные (такие как цвет, текстура, координаты и т.д.) и записывает их в переменные, объявленные с этим квалификатором. Вершинный шейдер также может читать из этих переменных записанные им значения. По определению, переменные varying устанавливаются для каждой вершины и интерполируются в соответствующей перспективе в процессе рендеринга примитивов. В однопроходном случае нтерполируются значения для центра пикселя. В многопроходном – интерполируемые значения могут быть в любом месте внутри пикселя, в том числе и для его центра. Фрагментный шейдер читает varying-переменные и интерполирует их для фрагмента. Фрагментный шейдер не может менять значения этих переменных.
Типы varying-переменных в шейдерах должны совпадать. Точность может не совпадать. Только используемые статически во фрагментном шейдере переменные должны быть объявлены в вершинном шейдере. В принципе допустимо объявление лишних переменных.
Ниже приведен свод правил соответствия для вершинного и фрагментного шейдеров:


Фрагментный шейдер
Нет ссылки
Объявлено;
не статическое использование
Объявлено и статически используется
Вершинный шейдер
Нет ссылки
Допустимо
Допустимо
Ошибка
Объявлено;
не статическое использование
Допустимо
Допустимо
Допустимо (значение не определено)
Объявлено и статически используется
Допустимо
Допустимо
Допустимо (значение возможно не определено)


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

Квалификатор varying может использоваться лишь со следующими типами: float, vec2, vec3, vec4, mat2, mat3, mat4 или массивами значений этих типов. Структуры использоваться не могут. Переменные с этим квалификатором должны быть в глобальной области видимости.

Квалификаторы параметров

Параметры могут иметь следующие квалификаторы:

Квалификатор
Пояснения
<none:default>
то же, что in
in
для входных параметров функции
out
для параметров, возращаемых из функции, но не инициализируемых на входе
inout
для параметров, используемых как входные и возвращаемые

Точность и квалификаторы точности

Диапазон и точность

Диапазон и точность используются для целочисленных переменных и переменных с плавающей точкой. С помощью квалификаторов точности устанавливается минимальная точность используемых значений. Вообще точность операций вычисления должна зависеть от точности оперируемых данных. Исключение составляют малые величины, требующие значительных вычислений, такие как atan(), который может вернуть величину с меньшей точностью.
Диапазон float-величин: (-262,262), а точность: 1/65536.
В вершинном шейдере точность integer-величин: 16 бит плюс бит знака. Это используемая, но не обязательная точность.
Во фрагментном шейдере диапазон float-величин: (-16384, 16384) и точность соответственно 1/1024.
Для целочисленных величин: 10 бит плюс знак.

Фактически используемые диапазоны и точность могут быть запрошены через соответствующие API-функции.

Квалификаторы точности

Любое целочисленное объявление либо с плавающей точкой может предваряться квалификатором точности:

Квалификатор
Пояснение
highp
Удовлетворяет минимальным требованиям вершинного языка. Опционально используется во фрагментном языке.
mediump
Удовлетворяет минимальным требованиям фрагментного языка. Среняя точность, может варьироваться от равной highp до равной lowp.
lowp
Диапазон и точность, которая может быть меньше чем mediump, но которую можно использовать для воспроизведения цветов.

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

Минимальные требования к диапазонам и точности:

Квалификатор
float-диапазон
float-диапазон величин
float-точность
integer-диапазон
highp
(-262,262)
(2-62,262)
Относительная: 2-16
(-216,216)
mediump
(-214,214)
(2-14,214)
Относительная: 2-10
(-210,210)
lowp
(-2,2)
(2-8,2)
Абсолютная: 2-8
(-28,28)

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

Вершинный язык имеет следующие предопределенные состояния в глобальной области видимости:

precision
highp
float;
precision
highp
int;
precision lowp sampler2D;
precision lowp samplerCube;

Фрагментный язык имеет следующие предопределенные состояния в глобальной области видимости:

precision
mediump
int;
precision lowp sampler2D;
precision lowp samplerCube;

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

Доступные квалификаторы точности

 Встроенный макрос GL_FRAGMENT_PRECISION_HIGH определяется в системах, поддерживающих highp точность во фрагментном языке

#define GL_FRAGMENT_PRECISION_HIGH 1

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

Квалификатор изменения и инвариантности (неизменности)

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

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

Квалификатор инвариантности (неизменности)

Чтобы гарантировать неизменность конкретной переменной, необходимо использовать квалификатор инвариантности.

invariant gl_Position; // делает имеющуюся gl_Position неизменной
varying mediump vec3 Color;
invariant Color; // делает имеющуюся Color неизменной

либо как часть объявления переменной:

invariant varying mediump vec3 Color;

Когда квалификатор комбинируется с объявлением переменной он должен стоять перед другими квалификаторами.

Только следующие переменные могут быть объявлены инвариантными:
- встроенные специальные переменные, возвращаемые вершинным шейдером
- varying-переменные, возвращаемые вершинным шейдером
- встроенные специальные переменные, поступающие на вход фрагментного шейдера
- varying-переменные, поступающие на вход фрагментного шейдера
- встроенные специальные переменные, возвращаемые фрагментным шейдером

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

Изначально по умолчанию все выходные переменные заданы как изменяемые. Для принудительной установки всех выходных переменных как инвариантные необходимо использовать перед всеми объявлениями в модуле компиляции:

#pragma STDGL invariant(all)

Если сделать это после какого-либо объявления переменной, то поведение выходных данных, которые должны быть инвариантны – неопределённо.
Как правило, инвариантность обеспечивается за счет гибкости при оптимизации, так что это может повлиять на производительность. Так что использование этой директивы предназначено скорее для помощи в отладке.

Инвариантность в шейдерах

Когда переменная хранит значение, обычно предполагается, что она будет оставаться постоянной, пока не будет изменена явным образом. Тем не менее, в процессе оптимизации возможно, что компилятор решит пересчитать значение, а не хранить его в регистре. Поскольку точность операций не всегда указана, может произойти так, что значение может отличаться от первоначального.
Значения могут быть изменяемыми в шейдере. Для предотвращения этого необходимо использовать директиву pragma либо спецификатор инвариантности. В шейдере не может быть инвариантности для значений, получаемых разными непостоянными выражениями, даже если эти выражения идентичны.

Например:

precision mediump;
vec4 col;
vec2 a = ...
...
col = texture2D(tex, a);// значение a1
...
col = texture2D(tex, a); // значение a2 где возможно a1 ≠ a2

Для принудительно инвариантности используйте:

#pragma STDGL invariant(all)

Пример 2:

vec2 m = ...;
vec2 n = ...;
vec2 a = m + n;
vec2 b = m + n;  // нет гарантий, что a и b абсолютно одинаковы

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

Инвариантность постоянных выражений

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

Для инвариантности выражений должны выполняться следующие условия:

- входные данные содержат одни и те же значения
- операции производятся те же самые и в том же порядке
- операции выполняются с той же точностью

Инвариантность и связь

Инвариантрость varying-переменных, объявленных как в вершинных шейдерах, так и во фрагментных, должны совпадать.
Встроенные специальные переменные gl_FragCoord могут быть объявлены инвариантными только тогда, если gl_Position объявлена инвариантной. Аналогично, gl_PointCoord может быть объявлена инвариантной только когда gl_PointSize тоже объявлена инвариантной. Ошибочно объявлять инвариантной gl_FrontFacing. Инвариантность gl_FrontFacing такая же как у gl_Positio.

Порядок следования квалификаторов

Когда при объявлении необходимо указать более одного квалификатора, они должны следовать в таком порядке:
invariant-qualifier storage-qualifier precision-qualifier
storage-qualifier parameter-qualifier precision-qualifier

Операторы и выражения

Операторы

Язык OpenGL ES содержит следующие операторы (помеченные как зарезервированные использовать нельзя):

Приоритет
Класс оператора
Операторы
Ассоциативность
1(наивысший)
группирующие скобки
( )
NA
2
индекс массива
вызов функции или конструктор структуры
селектор поля, swizzler
операторы пост инкремента/декремента
[ ]
( )
.
++ --
слева-направо
3
операторы предварительного инкремента/декремента
унврные (тильда зарезервирована)
++ --

+ - ~ !
справа-налево
4
мультипликативные (% зарезервирован)
* / %
слева-направо
5
аддитивные
+ -
слева-направо
6
битовый сдвиг (зарезервирован)
<< >>
слева-направо
7
операторы отношений
< > <= >=
слева-направо
8
равенства
== !=
слева-направо
9
битовое И (зарезервирован)
&
слева-направо
10
битовое исключающее И (зарезервирован)
^
слева-направо
11
битовое ИЛИ (зарезервирован)
|
слева-направо
12
логическое И
&&
слева-направо
13
логическое ИЛИ
^^
слева-направо
14
логическое исключающее ИЛИ
||
слева-направо
15
условный оператор (оператор выбора)
? :
справа-налево
16
оператор присваивания
арифметическое присваивание (остаток, сдвиг и битовые операции зарезервированы)
=
+= -=
*= /=
%= <<= >>=
&= ^= |=
справа-налево
17
последовательность
,
слева-направо

Не существует оператора-указателя или ссылки. Нет оператора приведения типов, .вместо этого используются конструкторы.

Индекс массива

Доступ к элементам массива осуществляется с помощью квадратных скобок. Это единственный оператор, работающий с массивами.
Пример:
diffuseColor += lightIntensity[3] * NdotL;

Вызовы функций

Если функция возвращает значение, то вызов этой функции может использоваться в выражениях.

Конструкторы

Конструкторы имеют синтаксис вызова функций, где в качестве имени функции находится имя структуры или ключевое слово базового типа, которым задаются значения требуемого типа для использования в инициализаторе или выражении.
Конструкторы могут служить для запроса на преобразование скалярных типов либо для создания больших типов из меньших, либо для усечения больших типов в малые.
Нет фиксированного списка конструкторов. Конструкторы не являются встроенными функциями. Синтаксически – все лексически правильные списки параметров верны. Семантически – число параметров должно быть достаточных размеров и верного типа для выполнения инициализации. Неиндексированные массивы и структуры, содержащие массивы, не допустимы в качестве параметров конструктора. Аргументы вычисляются слева-направо. Включать в конструктор аргументов больше, чем используется – является ошибкой.

Преобразование и скалярный конструктор

Преобразования между скалярными типами осуществляется так:

int(bool)    // converts a Boolean value to an int
int(float)   // converts a float value to an int
float(bool) // converts a Boolean value to a float
float(int)   // converts an integer value to a float
bool(float) // converts a float value to a Boolean
bool(int)    // converts an integer value to a Boolean

При преобразовании float в integer дробная часть отбрасывается.
При преобразовании float или integer в bool нулевые значения становятся равны false, ненулевые – true.
При преобразовании bool в integer или float: true → 1 (1.0) , false → 0 (0.0).
Идентичные конструкторы типа float(float) верны, но редко используемы.
Скалярные конструкторы с нескалярными параметрами испольуются для получения первого элемента нескалярной величины.

Конструкторы векторов и матриц

Конструкторы могут использоваться для создания векторов и матриц из набора скалярных величин, векторов или матриц. Здесь может быть использовано свойство сокращения векторов.
Если в качестве входного параметра в конструкторе вектора используется одна скалярная величина, то этой величиной заполняются все компоненты вектора. В случае с конструктором матрицы – этим скалярным значением заполняется диагональ матрицы, остальные компоненты равны 0.0.
Если вектор создается из набора скаляров, векторов или матриц, то в результирующий вектор заносятся компоненты входных параметров в том порядке, в каком они следуют на входе слева-направо. Тот же порядок при создании матрицы. Матрица строится по столбцам. При этом количество компонентов во входных параметрах должно быть столько, чтобы заполнить создаваемую переменную. Не должно быть лишних компонентов. При создании матрицы, если указывается матрица в качестве параметра, то создается матрица по аналогии расположения элементов. В этом случае дополнительных параметров быть не должно.
Если при создании объекта базовый тип его элементов не соответствует типам входных скалярных компонентов (bool, int или float), то входные параметры конвертируются по описанным выше правилам.

Несколько примеров:

vec3(float)                                               // initializes each component of with the float
vec4(ivec4)                                              // makes a vec4 with component-wise conversion
vec2(float, float)                          // initializes a vec2 with 2 floats
ivec3(int, int, int)             // initializes an ivec3 with 3 ints
bvec4(int, int, float, float)           // uses 4 Boolean conversions
vec2(vec3)                                               // drops the third component of a vec3
vec3(vec4)                                               // drops the fourth component of a vec4
vec3(vec2, float)                          // vec3.x = vec2.x, vec3.y = vec2.y, vec3.z = float
vec3(float, vec2)                          // vec3.x = float, vec3.y = vec2.x, vec3.z = vec2.y
vec4(vec3, float)
vec4(float, vec3)
vec4(vec2, vec2)

vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 rgba = vec4(1.0);                 // sets each component to 1.0
vec3 rgb = vec3(color);                // drop the 4th component

Для инициализации диагонали с установкой остальных элементов в ноль:

mat2(float)
mat3(float)
mat4(float)

Для инициализации матрицы с помощью векторов либо по всем 4, 9, 16 float-значениям:

mat2(vec2, vec2);
mat3(vec3, vec3, vec3);
mat4(vec4, vec4, vec4, vec4);
mat2(float, float,
      float, float);
mat3(float, float, float,
      float, float, float,
      float, float, float);
mat4(float, float, float, float,
      float, float, float, float,
      float, float, float, float,
      float, float, float, float);

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

Конструкторы структур

Как только структура определена и ее типу назначено имя, становится доступен конструктор с таким же именем для создания экземпляра этой структуры. Например:

struct light {
float intensity;
vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0));

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

Компоненты вектора

Имена компонентов вектора обозначаются одной буквой. Для удобства записи несколько букв связаны с каждым из компонентов. Конкретный компонент вектора может быть выбран по имени, следующим за точкой.
Поддерживаемые имена компонентов:

{x,y,z,w}
используется для доступа к вектору, обозначающему точку или нормаль
{r,g,b,a}
используется для доступа к вектору, обозначающему цвет
{s,t,p,q}
используется для доступа к вектору, обозначающему текстурные координаты

Компоненты, к примеру, x, r и s являются синонимами для первого компонента вектора. Обратите внимание, что третий компонент вектора для текстур называется p, чтобы избежать путаницы с r(для красного цвета).

Нельзя обращаться к компонентам вектора, если его размерность меньше чем запрашивается этим именем.

vec2 pos;
pos.x                     // is legal
pos.z                     // is illegal

Можно выбирать одновременно группу компонентов в виде вектора. vec4 v4;

v4.rgba;     // is a vec4 and the same as just using v4,
v4.rgb;      // is a vec3,
v4.b;                     // is a float,
v4.xy;                   // is a vec2,
v4.xgba;    // is illegal - the component names do not come from the same set.

Нельзя выбрать больше 4 компонентов.

vec4 v4;
v4.xyzw;               // is a vec4
v4.xyzwxy;                      // is illegal since it has 6 components
(v4.xyzwxy).xy;   // is illegal since the intermediate value has 6 components
vec2 v2;
v2.xyxy;               // is legal. It evaluates to a vec4.

Порядок компонентов может быть перемешан и компоненты могут повторяться.

vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
vec4 swiz= pos.wzyx; // swiz = (4.0, 3.0, 2.0, 1.0)
vec4 dup = pos.xxyy; // dup = (1.0, 1.0, 2.0, 2.0)

Примеры записи данных в компоненты вектора:

vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
pos.xw = vec2(5.0, 6.0);              // pos = (5.0, 2.0, 3.0, 6.0)
pos.wx = vec2(7.0, 8.0);              // pos = (8.0, 2.0, 3.0, 7.0)
pos.xx = vec2(3.0, 4.0);               // illegal - 'x' used twice
pos.xy = vec3(1.0, 2.0, 3.0);        // illegal - mismatch between vec2 and vec3

Обращаться к компонентам вектора можно и как к элеменам массива. Для объявленного вектора
vec4 pos;
pos[2] ссылается на элемент pos.z. Индексация вектора начинается с нуля.

Элементы матрицы

Обращаться к элементам матриц можно как к массивам. Индексация с нуля.

mat4 m;
m[1] = vec4(2.0); // sets the second column to all 2.0
m[0][0] = 1.0; // sets the upper left element to 1.0
m[2][3] = 2.0; // sets the 4th element of the third column to 2.0

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

Структуры и поля

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

селектор поля структуры
.
равенство
== !=
присваивание
=


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

Struct S {int x;};
S a;
{
      struct S {int x;};
      S b;
      a = b; // error: type mismatch
}

Оператор присваивания

Присваивание значения производится с помощью оператора равно (=). Оператор присваивания сохраняет значение справа в переменную слева и возвращает в качестве результата значение справа с точностью и типом переменной слева. Выражения справа и слева должны быть одного типа. Все преобразования типов должны быть указаны исключительно через конструкторы. Переменные встроенных типов, все структуры, поля структур, левосторонние значения с селекторами полей(.) используются для выборки компонентов без повторения полей, левосторонние значения в скобках и без, разыменованные с помощью оператора индексации массива ([]) могут находиться слева от оператора присваивания. Другие унарные, двойные и тройные выражения не могут находиться слева. Это относится к объявлениям, именам функций, смешанной выборке полей с повторяющимися компонентами и константам.
Выражение слева от оператора присваивания оценивается раньше чем то, что справа.

Другие операторы присваивания:
- арифметические (+=, -=, *=, /=)
- зарезервированы для будущего использования (%=, <<=, >>=, |=, ^=)

      Чтение переменных то присваивания им значений допустимо, однако они неопределенны.

Выражения

В шейдерном языке выражения строятся из следующих элементов:
- константы типов bool, int, float, все типы векторов и матриц
- конструкторы всех типов
- имена переменных всех типов, исключая имена массивов
- имена массивов могут использоваться лишь как параметры функций либо с квадратными скобками для выбора элемента
- имена функций, возвращающих значения
- селекторы полей и элементы массивов
- выражения в скобках. Любое выражение может быть взято в скобки. Скобки используются для группирования операций. Приоритет обработки выражений в скобках - наивысший
- арифметические операции (+, -, *, /) выполняются над выражениями типов int и float(в том числе векторы и матрицы). Оба операнда должны быть одного типа (либо комбинация скалярных и векторных/матричных операндов одного типа). Деление на ноль не вызывает ошибки, но возвращает не выбранное значение. Умножение векторов и матриц (*) производит попокомпонентное умножение. Умножение матрицы на вектор дает линейное алгебраическое преобразование. Для получения скалярного, векторного и покомпонентного умножения используйте встроенные функции dot, cross и matrixCompMult.
- оператор остатка (%)зарезервирован
- операторы унарного отрицания, инкремента и декремента
- операторы отношений (< > <= >=) применяемые только со скалярными величинами (возвращают скалярную boolean-величину). Для покомпонентного сравнения используйте встроенные функции lessThan, lessThanEqual, greaterThan, greaterThanEqual
- операторы равенства (== !=) применяемые только со скалярными величинами. Для векторов, матриц и структур используйте встроенные equal и notEqual
- логические операторы (&&, ||, ^^) используются лишь с bool-выражениями
- оператор логического отрицания (!)используются только с bool-выражениями
- оператор последовательности (,) оперирует выражениями, возвращающими список правосторонних выражений, разделенных запятой. Все выражения оцениваются слева-направо
- оператор выбора (exp1?exp2:exp3). Первое выражение должно быть boolean-типа. Второе и третее выражения должны совпадать по типу
- зарезервированы операторы &, |, ^, ~, >>, <<

Если операнды разного типа, то они должны удовлетворять одному из следующих правил:
- один из операндав – скалярная величина, которая перед использованием преобразуется к вектору/матрице
- левый аргумент – float-вектор, а правый – матрица соответствующей размерности. В этом случае оператор * производит построчное умножение вектора на матрицу
- левый аргумент – матрица, а правый – float-вектор соответствующей размерности. В этом случае оператор * производит умножение вектора на матрицу по столбцам

Постоянные выражения

К постоянным выражениям относятся:
- литеральные значения (например 5 или true)
- глобальные или локальные переменные, объявленные с квалификатором const (кроме имен функций)
- выражения, формируемые операторами и операндами, состоящими из постоянных выражений. В том числе постоянные векторы, матрицы или элементы постоянных структур
- конструкторы, у которых в качестве всех параметров выступают постоянные выражения
- встроенные функции, у которых в качестве параметров выступают постоянные выражения за исключением функций, производящих выборку из текстур

В качестве постоянных выражения не могут использоваться:
- пользовательские функции
- uniforms, атрибуты и обычные переменные

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

Операции с векторами и матрицами

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

vec3 v, u;
mat3 m;
u = v * m;
эквивалентно
u.x = dot(v, m[0]); // m[0] is the left column of m
u.y = dot(v, m[1]); // dot(a,b) is the inner (dot) product of a and b
u.z = dot(v, m[2]);

u = m * v;
эквивалентно
u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z;
u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z;
u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z;

mat m, n, r;
r = m * n;
эквивалентно
r[0].x = m[0].x * n[0].x + m[1].x * n[0].y + m[2].x * n[0].z;
r[1].x = m[0].x * n[1].x + m[1].x * n[1].y + m[2].x * n[1].z;
r[2].x = m[0].x * n[2].x + m[1].x * n[2].y + m[2].x * n[2].z;
r[0].y = m[0].y * n[0].x + m[1].y * n[0].y + m[2].y * n[0].z;
r[1].y = m[0].y * n[1].x + m[1].y * n[1].y + m[2].y * n[1].z;
r[2].y = m[0].y * n[2].x + m[1].y * n[2].y + m[2].y * n[2].z;
r[0].z = m[0].z * n[0].x + m[1].z * n[0].y + m[2].z * n[0].z;
r[1].z = m[0].z * n[1].x + m[1].z * n[1].y + m[2].z * n[1].z;
r[2].z = m[0].z * n[2].x + m[1].z * n[2].y + m[2].z * n[2].z;

Унарные операции работают с операндами покомпонентно. Битовые операции с операндами одинакового типа тоже производятся покомпонентно, возвращаемый результат – тоже такого типа.

Точность операций

Точность операций зависит от реализации платформы.

Вычисление выражений

В требованиях стандарта C++ выражения вычисляются в порядке приоритетов операций и может быть изменен только если это не изменит результат или если результат undefined.  Никакие иные преобразования не могут быть применены. OpenGL ES ослабляет эти требования следующими условиями:
- сложение и умножение считается ассоциативным (операнды могут меняться местами при компиляции)
- умножение может быть заменено повторяющимся сложением (в целях оптимизации)
- деление может быть заменено на умножение перевернутой дроби (1/x)
- значения могут быть представлены в более высокой точности, чем указано. С учетом инвариантности (где она используется), точность может варьироваться
- целые величины могут быть заменены на float. Соответственно операции над этими значениями могут быть выполнены как с float-значениями

Определения и структуры

Базовыми элементами языка OpenGL ES являются:
- определения и объявления
- объявления функций
- выбор (if-else)
- итерации (for, while и do-while)
- переходы (discars, return, break и continue)

Общая структура модуля компиляции следующая:
translation-unit:
      global-declaration
      translation-unit global-declaration
global-declaration:
      function-definition
      declaration
Т.е. модуль компиляции содержит последовательность объявлений и тел функций.

Тело функции определяется так:
function-definition:
      function-prototype { statement-list }
statement-list:
      statement
      statement-list statement
statement:
      compound-statement
      simple-statement

Фигурные скобки служат для объединения операторов в составной оператор.
compound-statement:
      { statement-list }
simple-statement:
      declaration-statement
      expression-statement
      selection-statement
      iteration-statement
      jump-statement

Простое объявление, выражение или переход заканчиваются точкой с запятой.

Объявление функций

Шейдер представляет собой последовательность глобальных объявлений и определений функций. Функция объявляется следующим образом:

// prototype
returnType functionName (type0 arg0, type1 arg1, ..., typen argn);

// definition
returnType functionName (type0 arg0, type1 arg1, ..., typen argn)
{
// do some computation
return returnValue;
}

где returnType обязателен и должен описывать тип. Каждый typeN должен содержать тип и может также содержать квалификаторы (const и точность).

Вызов функции осуществляется по её имени, за которым следует список параметров в круглых скобках.

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

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

Для каждой функции должен быть задан прототип либо описано тело функции до того, как она будет впервые вызвана.

Возвращаемое функцией значение может быть типа void. Если функция имеет тип не void, то она обязательно должна возвращать значение указанного типа. В качестве пустого списка входных параметров не обязательно указывать void. Идиома (void) оставлена для совместимости.

имена функций могут быть перегружены. Это означает использования одного и того же имени функции для реализаций с разными входными параметрами. Перегруженные функции часто встречаются среди встроенных функций. При объявлении перегруженных функций необходимо точно указывать список параметров, в том числе размеры входных массивов для каждой реализации. Не обязательно типы возвращаемых значений перегруженных функций должны совпадать. Они рассматриваются как разные функции.
Пользовательские функции могут быть объявлены многократно, но определены лишь однажды. Функция main используется в шейдере как точка входа. И вершинный и фрагментный шейдер должны содержать функцию main. Эта функция не принимает на вход никакие параметры и ничего не возвращает:
void main()
{
...
}

В функции main может быть использован оператор return без параметра. Любые иные объявления функции main являются ошибочными.

Соглашения для вызовов функций

Функции вызываются по возвращаемому значению. Это подразумевает, что входные аргументы копируются в функцию во время вызова, а выходные параметры копируются обратно в вызывающую сторону перед выходом из функции. Т.к. функция работает с локальной копией входных параметров, не возникает вопросов перезаписи переменных внутри функции.
Для контроля за тем, какие параметры должны копироваться в/из функции указываются следующие ключевые слова:
- in – параметр копируется в функцию, но не копируется обратно
- out – параметр не копируется в функцию, но копируется обратно
- inout – параметр копируется в обоих случаях
- если ключевое слово не указано, то подразумевается in

Все параметры проверяются при вызове слева-направо. При возврате из функции порядок копирования возвращаемых параметров – не определен.

Допустимы функции, в которых параметры только in. Такие параметры могут предваряться квалификатором const.

Void-функция возвращает значение undefined. Из квалификаторов для возвращаемых функцией значений может быть лишь квалификатор точности.

прототип функции:
квалификатор_точности тип имя_функции(квалификатор_const квалификаатор_параметра квалификатор_точности тип имя спецификатор_массива, ... )

тип:
любой базовый тип, имя структуры или задание структуры
квалификатор_const:
empty
const

квалификатор_параметра:
empty
in
out
inout

имя:
empty
identifier

спецификатор_массива:
empty
[integral-constant-expression]

Условный оператор

Управление с помощью условий в языке шейдера осуществляется с помощью if или if-else:

if (bool-expression)
true-statement

или

if (bool-expression)
true-statement
else
false-statement

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

Циклы

for (for-init-statement; condition(opt); expression)
statement-no-new-scope

while (condition)
statement-no-new-scope

do
statement
while (expression)

Циклы могут быть вложенными. Возможны бесконечные циклы. Последствия очень длинных или бесконечных циклов зависят от платформы.

Переходы

continue;
break;
return;
return expression;
discard; // in the fragment shader language only

Нет операторов безусловного перехода типа "goto".

Continue используется только в циклах. Используется чтобы пропустить остаток текущей итерации цикла и перейти к началу следующей.

Break прерывает текущий цикл и выходит из него.

Discard используется только во фрагментном шейдере.  Прекращает обработку текущего фрагмента, обновление никаких буферов не производится. Обычно употребляется внутри условного оператора:
if (intensity < 0.0) discard;
В этой проверке фрагментный шейдер проверяет альфа-значение фрагмента и отменяет его обработку, основываясь на результате. Тем не менее стоит отметить, что проверка может производиться после того, как шейдер уже некоторое время работал и мог изменить альфа-значение.

Return прекращает работу текущей функции. Если этот оператор содержит параметр, то он служит возвращаемым параметром функции.

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

Встроенные переменные

Некоторые операции OpenGL ES выполняются в виде фиксированной функциональности между вершинным и фрагментным процессорами. Другие операции выполняются после фрагментного процессора. Шейдеры взаимодействуют с помощью фиксированной функциональности OpenGL ES используя встроенные переменные.

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

Специальные переменные вершинного шейдера

Переменная gl_Position доступна только вершинному языку и предназначена для представления однородной позиции вершины. Все запускаемые вершинные шейдеры должны заполнять эту переменную. Она может быть заполнена в любое время в процессе выполнения вершинного шейдера. Также в процессе выполнения шейдера эта переменная может быть прочитана после записи. Это значение будет использоваться функциями пост обработки (primitive assembly, clipping, culling и другихфиксированных функций, которые обрабатывают данные после выполнения вершинного шейдера). Компилятор может генерировать диагностическое сообщение, если обнаружит, что gl_Position не заполнена либо читается до заполнения, но не все подобные случаи могут быть обнаружены.  Значение gl_Position не определено, если вершинный шейдер в процессе выполнения ничего в нее не записывает.

Переменная gl_PointSize доступна только в вершиненом шейдере и предназначена для записи в нее вершинным шейдером размера точки для растеризации. Измеряется в пикселях.

Эти встроенные переменные вершинного шейдера для связи с фиксированной функциональностью объявлены следующим образом:

highp vec4 gl_Position; // should be written to
mediump float gl_PointSize; // may be written to

Попытка прочесть эти переменные до записи в них значений вернет undefined. Если в эти переменные производилась запись несколько раз, то хранится, естественно, последнее значение.
Эти переменные имеют глобальную область видимости.

Специальные переменные фрагментного шейдера

То, что поступает на выход фрагментного шейдера, обрабатывается фиксированными функциями OenGL ES конвейера. Выходные значения фрагментного шейдера используют переменные FragColor и gl_FragData кроме случаев срабатывания discard.
Фрагментный шейдер не обязательно должен заполнять переменные gl_FragColor и gl_FragData. существует множество алгоритмов, таких как оттенение, которые включают обработку данных, где цвет не сохраняется.

Эти переменные могут перезаписываться фрагментным шейдером многократно. В этом случае они хранят последние записанные значения. Они также могут быть прочитаны во фрагментном шейдере после записи. Чтение до изменения вернет undefined.

Запись в  gl_FragColor описывает цвет фрагмента, который будет использован в дальнейшем фиксированными функциями конвейера. Если эти функции используют цвет фрагмента, а значение во фрагментном шейдере не было записано, то цвет равен undefined.

Переменная gl_FragData является массивом. Запись в gl_FragData[n] определяет данные фрагмента, которые будут использованы в последующей обработке конвейером для данных n. Если данные не записаны, то передается undefined. Если шейдер статически задает значение gl_FragColor, то нет необходимости определять каждый элемент gl_FragData. Если шейдер статически определяет gl_FragData, то нет необходимости заполнять gl_FragColor. То есть, шейдер может задать одну из этих переменных, не обязательно обе.

Если шейдер выполняет discard, то переменные gl_FragColor и gl_FragData остаются не использованными.

Переменная gl_fragCoord доступна только для чтения внутри фрагментного шейдера и содержит оконно-зависимые координаты x, y, z и 1/w для фрагмента. Это значение является результатом постобработки примитивов после выполнения вершинного шейдера и хранит интерполированное значение. Значение z-компонента храних глубину, которая используется для задания глубины фрагмента.

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

Переменная gl_PointCoord доступна фрагментному шейдеру только для чтения. Значения в этой переменной представляют собой двумерные координаты, показывающие где располагается текущий фрагмент. Диапазн значений координат от 0.0 до 1.0. Более подробно об этом можно прочесть в спецификации OpenGL 2.0 раздел 3.3.1 Основы растеризации точки, где обсуждаются спрайты точек. Если текущий примитив не является точкой, то значение этой переменной undefined.

Доступные фрагментному шейдеру переменные объявлены таким образом:

mediump   vec4 gl_FragCoord;
                  bool gl_FrontFacing;
mediump   vec4 gl_FragColor;
mediump   vec4 gl_FragData[gl_MaxDrawBuffers];
mediump   vec2 gl_PointCoord;

Эти переменные имеют глобальную область видимости.

Встроенные атрибуты вершинного шейдера

В OpenGL ES нет встроенных атрибутов.

Встроенные константы

Для вершинного и фрагментного шейдеров доступны следующие константы:

//
// константы зависят от платформы
// приведены минимально допустимые значения
//
const mediump int gl_MaxVertexAttribs = 8;
const mediump int gl_MaxVertexUniformVectors = 128;
const mediump int gl_MaxVaryingVectors = 8;
const mediump int gl_MaxVertexTextureImageUnits = 0;
const mediump int gl_MaxCombinedTextureImageUnits = 8;
const mediump int gl_MaxTextureImageUnits = 8;
const mediump int gl_MaxFragmentUniformVectors = 16;
const mediump int gl_MaxDrawBuffers = 1;

Встроенные uniform state

Для помощи в доступе к состоянию процесса обработки OpenGL имеются следующие uniform-переменные:

// диапазон глубины в оконных координатах
struct gl_DepthRangeParameters
{
highp float near; // n
highp float far; // f
highp float diff; // f - n
};
uniform gl_DepthRangeParameters gl_DepthRange;

Если фрагментный шейдер не поддерживает точность highp, то используется mediump.

Встроенные фукнкции

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

В целом встроенные функции делятся на 3 категории:
- эти функции представляют аппаратные возможности в удобном виде (например доступ к текстурной карте). В языке нет никаких средств для эмуляции этих функций
- представляют собой простые операции (clamp, mix и т.д.), которые просты в написании пользователем, но слишком распространены и могут иметь аппаратную поддержку. Для компилятора это довольно серьезная задача – представить сложное выражение в виде инструкций ассемблера
- представляют собой аппаратные графические операции для ускорения некоторых моментов. К этой категории относятся, например, тригонометрические функции

Некоторые функции названы аналогично таким же функциям в библиотеках языка C, но они поддерживают векторные входные данные помимо привычных скалярных величин.

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

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

Если в описании ниже встроенных функций не указана размерность векторов или матриц, это означает, что поддерживаются все (2-, 3- и 4-) размерности.

Квалификаторы точности функций не указаны. Для функций, работающих с текстурами, точность соответствует точности типа sampler.

uniform lowp sampler2D sampler;
highp vec2 coord;
...
lowp vec4 col = texture2D (sampler, coord); // texture2D returns lowp

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

Угловые и тригонометрические функции

Синтаксис
Описание
genType radians(genType degrees)
преобразует градусы в радианы
genType degrees (genType radians)
преобразует радианы в градусы
genType sin (genType angle)
синус
genType cos (genType angle)
косинус
genType tan (genType angle)
тангенс
genType asin (genType x)
арксинус. При |x|>1 результат undefined
genType acos (genType x)
арккосинус. При |x|>1 результат undefined
genType atan (genType y, genType x)
арктангенс. При x=y=0 результат undefined. Диапазон возвращаемых значений: [-π,π]

genType atan (genType y_over_x)
арктангенс. При x=y=0 результат undefined. Диапазон возвращаемых значений: [-π/2,π/2]

Экспоненциальные функции

Все операции – покомпонентные. Описание – для компонента.

Синтаксис
Описание
genType pow(genType x, genType y)
xy
при x<0 результат undefined
при x=0 и y<=0 результат undefined
genType exp(genType x)
ex
genType log(genType x)
ln(x)
при x<=0 результат undefined

genType exp2(genType x)
2x
genType log2(genType x)
log2(x)
при x<=0 результат undefined
genType sqrt(genType x)
корень квадратный из x
при x<0 результат undefined
genType inversesqrt(genType x)
1 / корень квадратный из x
при x<=0 результат undefined

Функции общего назначения

Синтаксис
Описание
genType abs(genType x)
|x|
genType sign(genType x)
знак операнда (1.0 или -1.0)
genType floor(genType x)
округление в меньшую сторону
genType ceil(genType x)
округление в большую сторону
genType fract(genType x)
дробная часть
genType mod(genType x, float y)
Деление по модулю (modulo). Взвращает x-y*floor(x/y)
genType mod(genType x, genType y)
Деление по модулю (modulo). Взвращает x-y*floor(x/y)
genType min(genType x, genType y)
genType min(genType x, float y)
меньшее значение
genType max(genType x, genType y)
genType max(genType x, float y)
большее значение
genType clamp(genType x, genType minVal, genType maxVal)
genType clamp(genType x, float minVal, float maxVal)
min(max(x, minVal), maxVal)
результат undefined, если minVal > maxVal

genType mix(genType x, genType y, genType a)
genType mix(genType x, genType y, float a)
линейное смешивание
x*(1-a)+y*a
genType step(genType edge, genType x)
genType step(float edge, genType x)
возвращает 0.0, если x<edge, иначе возвращает 1.0
genType smoothstep(genType x)
возвращает 0.0, если x<=edge0,
возвращает 1.0, если x>=edge1,
в случае если edge0<x<edge1 возвращается интерполяция Эрмита
эквивалентно:
genType t;
t = clamp ((x – edge0) / (edge1 – edge0), 0, 1);
return t * t * (3 – 2 * t);
Если edge0 >= edge1, то результат undefined

Геометрические функции

Синтаксис
Описание
float length(genType x)
длина вектора
float distance(genType p0, genType p1)
расстояние между точками
float dot(genType x, genType y)
скалярное произведение
x[0]y[0]+x[1]y[1]+...
vec3 cross(vec3 x, vec3 y)
векторное произведение
genType normalize(genType x)
нормализация вектора
genType faceforward(genType N, genType I, genType Nref)
если скалярное произведение I и Nref < 0, то возвращает N, иначе возвращает -N
genType reflect(genType I, genType N)
для вектора (I) и ориентации поверхности (N) возвращает направление отражения:
I-2*dot(N, I)*N
для правильного результата N должно быть нормализованным
genType refract(genType I, genType N, float eta)
для вектора (I), ориентации поверхности (N) и коэффициента преломления (eta) возвращает вектор преломления:
k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I))
if (k < 0.0) return genType(0.0)
else return eta * I- (eta * dot(N, I) + sqrt(k)) * N
I и Т должны быть нормализованы для правильного результата

Операции над матрицами

Синтаксис
Описание
mat matrixCompMult(mat x, mat y)
покомпонентное произведение матриц
каждое значение result[i][j] является скалярным произведением x[i][j] и y[i][j]
Примечание: для получения линейного алгебраического произведения матриц используйте оператор умножения (*).

Vector Relational Functions

Операторы отношения и сравнения определены (или зарезервированы) для возвращения скалярного результата. Для получения векторного результата используйте встроенные функции. Размерность входных параметров и возвращаемых функцией – одинаковы.

Синтаксис
Описание
bvec lessThan(vec x, vec y)
bvec lessThan(ivec x, ivec y)
возвращает покомпонентное сравнение векторов x < y
bvec lessThanEqual(vec x, vec y)
bvec lessThanEqual (ivec x, ivec y)
возвращает покомпонентное сравнение векторов x <= y
bvec greaterThan(vec x, vec y)
bvec greaterThan(ivec x, ivec y)
возвращает покомпонентное сравнение векторов x < y
bvec greaterThanEqual(vec x, vec y)
bvec greaterThanEqual (ivec x, ivec y)
возвращает покомпонентное сравнение векторов x >= y
bvec equal(vec x, vec y)
bvec equal (ivec x, ivec y)
bvec equal (bvec x, bvec y)
bvec notEqual(vec x, vec y)
bvec notEqual (ivec x, ivec y)
bvec notEqual (bvec x, bvec y)
возвращает покомпонентное сравнение векторов x == y


возвращает покомпонентное сравнение векторов x != y

bvec any(bvec x)
true, если хотя бы 1 компонент true
bvec all(bvec x)
true, если все компоненты true
bvec not(bvec x)
покомпонентное отрицание логтческого вектора

Функции работы с текстурами

Функции работы с текстурами доступны в обоих типах шейдеров. Однако уровень детализации не определяется фиксированной функциональностью для вершинных шейдеров, так что могут быть некоторые отличия в работе. Доступ к текстурам осуществляется с помощью семплеров, настроенных с помощью OpenGL ES API. Свойства текстуры. такие как размер, формат пикселей, число измерений, метод фильтрации, число уровней MIP-карты, глубина сравнения и т.д., так же задаются с помощью вызовов OpenGL ES API. Все эти свойства учитываются при обращении к текстурам с помощью описанных ниже функций.

Функции, содержащие параметры сдвига (bias), доступны только фрагментному шейдеру. Если параметр сдвига указан, то он будет учтен в расчетах при операции доступа к текстуре. Если он не указан, то выбирается следующий уровень детализации:
- для текстур, не являющихся mip-map, текстура используется непосредственно
- если это mip-map и выполнение происходит во фрагментном шейдере, то уровень детализации (LOD) используется для поиска в текстуре. В вершинном шейдере для mip-map используется основная текстура. Встроенные функции с суффиксом "Lod" допустимы только в вершинных шейдерах. В lod-функциях уровень детализации используется непосредственно.

Синтаксис
Описание
vec4 texture2D(sampler2D sampler, vec2 coord)
vec4 texture2D(sampler2D sampler, vec2 coord, float bias)
vec4 texture2DProj(sampler2D sampler, vec3 coord, float bias)
vec4 texture2DProj(sampler2D sampler, vec3 coord, float bias)
vec4 texture2DProj(sampler2D sampler, vec4 coord)
vec4 texture2DProj(sampler2D sampler, vec4 coord, float bias)
vec4 texture2DLod(sampler2D sampler, vec3 coord, float lod)
vec4 texture2DLod(sampler2D sampler, vec4 coord, float lod)
используется для получения значения в указанных координатах текстуры, назначенной семплеру. Для проекционной версии координаты текстуры (coord.s, coord.t) делятся на последний компонент coord. Третий компонент coord игнорируется для варианта vec4.
vec4 textureCube(samplerCube sampler, vec3 coord)
vec4 textureCube(samplerCube sampler, vec3 coord, float bias)
vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod)

используется для получения значения в указанных координатах 3-мерной текстуры, назначенной семплеру. Направление вектора coord используется для выбора какая поверхность 2D-текстуры будет отображена

Грамматика языка шейдеров

Грамматика происходит из выходного лексического анализа. Маркеры, возвращаемые лексическим анализатором:
ATTRIBUTE CONST BOOL FLOAT INT
BREAK CONTINUE DO ELSE FOR IF DISCARD RETURN
BVEC2 BVEC3 BVEC4 IVEC2 IVEC3 IVEC4 VEC2 VEC3 VEC4
MAT2 MAT3 MAT4 IN OUT INOUT UNIFORM VARYING
SAMPLER2D SAMPLERCUBE
STRUCT VOID WHILE
IDENTIFIER TYPE_NAME FLOATCONSTANT INTCONSTANT BOOLCONSTANT
FIELD_SELECTION
LEFT_OP RIGHT_OP
INC_OP DEC_OP LE_OP GE_OP EQ_OP NE_OP
AND_OP OR_OP XOR_OP MUL_ASSIGN DIV_ASSIGN ADD_ASSIGN
MOD_ASSIGN LEFT_ASSIGN RIGHT_ASSIGN AND_ASSIGN XOR_ASSIGN OR_ASSIGN
SUB_ASSIGN
LEFT_PAREN RIGHT_PAREN LEFT_BRACKET RIGHT_BRACKET LEFT_BRACE RIGHT_BRACE DOT
COMMA COLON EQUAL SEMICOLON BANG DASH TILDE PLUS STAR SLASH PERCENT
LEFT_ANGLE RIGHT_ANGLE VERTICAL_BAR CARET AMPERSAND QUESTION
INVARIANT
HIGH_PRECISION MEDIUM_PRECISION LOW_PRECISION PRECISION

FAQ

Точность вершинного шейдера
Вопрос: какая минимальная точность должна быть?
Ответ: минимальная точность – float24. Многие платформы оперируют float32.

Точность фрагментного шейдера
Вопрос: какая минимальная точность должна быть?
Ответ: требуемая для большинства случаев доступа к текстурам – float24. Float24 считается дорогим удовольствием для low-end аппаратуры. Даже float16 является дорогим вариантом, когда речь идет о простом смешивании цветов. Доступно более низкая точность. Реализовано 3 типа точности: float32, float16 и int10. По умолчанию фрагментный шейдер использует максимальную точность.









2 комментария: