зачем нужны шейдеры в opengl

Что такое шейдеры в OpenGL и для чего они нам нужны?

Я не носитель английского языка, и когда я пытаюсь пройти через OpenGL wiki и учебники по www.learnopengl.com, это никогда не заканчивается понятным интуицией, как работает вся концепция. Может кто-нибудь может объяснить мне более абстрактно, как это работает? Что такое вершинный шейдер и шейдер фрагментов и для чего мы их используем?

2 ответов

на OpenGL wiki дает хорошее определение:

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

в прошлом графические карты были не программируемыми кусками кремния, которые выполняли набор фиксированных алгоритмов: точки / цвета / огни входили, а 2D-изображение выходило с использованием фиксированного алгоритма (обычно вместе https://en.wikipedia.org/wiki/Phong_reflection_model).

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

так как технология продвинулась, поставщики GPU начали позволять некоторым частям конвейера рендеринга быть запрограммированными языками программирования, такими как GLSL.

эти языки затем преобразуются в полу-недокументированные наборы инструкций это работает на небольших «процессорах», встроенных в эти новые GPU.

до недавнего времени, шейдерные языки не было даже Тьюринга полным.

термин GPU общего назначения (GPGPU) относится к этой повышенной программируемости современных графических процессоров.

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

зачем нужны шейдеры в opengl. 4f1eaea5bdf72e75c757cc713ed02530. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-4f1eaea5bdf72e75c757cc713ed02530. картинка зачем нужны шейдеры в opengl. картинка 4f1eaea5bdf72e75c757cc713ed02530.

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

два самых важных из них:

TODO может ли OpenGL быть эффективно реализован только с OpenCL, т. е. сделать все этапы программируемыми? Конечно, должен быть компромисс производительности / гибкости.

первые графические процессоры с шейдерами использовали различное специализированное оборудование для затенения вершин и фрагментов, поскольку они имеют совершенно разные рабочие нагрузки. Однако в текущих архитектурах используется один тип аппаратное обеспечение (в основном небольшие процессоры) для всех шейдеров, что экономит аппаратное дублирование. Это понятие известно как:https://en.wikipedia.org/wiki/Unified_shader_model

зачем нужны шейдеры в opengl. 77804644c4dd9b0ca50034573223aac0. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-77804644c4dd9b0ca50034573223aac0. картинка зачем нужны шейдеры в opengl. картинка 77804644c4dd9b0ca50034573223aac0.

чтобы по-настоящему понять шейдеры и все, что они могут сделать, вам нужно посмотреть на многие примеры и изучить API. https://github.com/JoeyDeVries/LearnOpenGL например хороший источник.

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

зачем нужны шейдеры в opengl. f2939774823bf934741f4cbd8aa28089. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-f2939774823bf934741f4cbd8aa28089. картинка зачем нужны шейдеры в opengl. картинка f2939774823bf934741f4cbd8aa28089.

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

вершинный шейдер преобразует 3D-позицию каждой вершины в виртуальном пространстве (вашу 3d-модель) в 2D-координату, в которой она появляется на экране.

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

Источник

Записки программиста

Продолжаем изучение OpenGL: VBO, VAO и шейдеры

В прошлый раз мы сделали заготовку кроссплатформенного проекта на C++, в котором используется OpenGL. Мы определились с тем, как будем управлять зависимостями, какую систему сборки и IDE будем использовать. Мы даже научились рисовать окошки при помощи GLFW и перемножать матрички, используя GLM. Как оказалось, в этом нет большой науки. Сегодня же мы поговорим о самом OpenGL, так как, напомню, в предыдущем примере мы использовали устаревшие процедуры glBegin, glEnd и подобные, и в наши дни так делать не нужно.

Для начала нам потребуется подключить к проекту еще одну библиотеку, GLXW. Делается это аналогично тому, как мы это делали для GLFW и GLM — через сабмодули Git. GLXW является лоадером OpenGL. Дело в том, что доступ к функциям OpenGL осуществляется через подгрузку динамических библиотек. На разных платформах эти динамические библиотеки загружаются по-разному, да и находятся в разных местах. Собственно, лоадеры предназначены для того, чтобы скрыть эти различия от программиста. Таких лоадеров написано очень много. Например, во многих туториалах используется GLEW. Мной был выбран GLXW, так как он использовался в примерах, по которым я начинал разбираться. Также на FreeNode зависает автор GLXW (ник exDM69), а также человек (ник dav1d), который хостит статическую версию GLXW по адресу http://glxw.dav1d.de/glxw.git. GLXW на GitHub использует Python для генерации заголовочных файлов. Статическая версия от dav1d делает это по cron, и потому не требует установки Python. В общем и целом, хотя бы понятно, куда обращаться за помощью в случае чего. Наконец, с GLXW пока не было никаких проблем. Раз он просто работает, почему бы на нем и не остановиться?

if ( glxwInit ( ) ) <
std :: cerr «Failed to init GLXW» std :: endl ;
glfwDestroyWindow ( window ) ;
glfwTerminate ( ) ;
return 1 ;
>

Ранее многократно отмечалось, что glBegin, glEnd и прочие функции нынче признаны устаревшими (так называемый immediate mode). Но как же прикажете что-то без них рисовать? Чтобы ответить на этот вопрос, придется для начала понять, что такое VBO и VAO. Противоположность immediate mode, о которой далее пойдет речь, называется retained mode.

Vertex Buffer Object (VBO) — это такое средство OpenGL, позволяющее загружать определенные данные в память GPU. Например, если вы хотите сообщить GPU координаты вершин, цвета или нормали, нужно создать VBO и положить эти данные в него.

Пример загрузки координат треугольника:

Процедура glGenBuffers возвращает заданное количество неиспользуемых в настоящее время айдишников VBO. В терминологии OpenGL идентификаторы принято называть именами, поэтому далее будем стараться вместо «айдишники» говорить «имена». Заметьте, что никакого создания буферов здесь пока не происходит.

Далее процедурой glBindBuffer мы связываем (bind) полученное на предыдущем шаге имя с определенной точкой связывания (binding point). Мы хотим сохранить в буфере координаты вершин, и должны использовать для этого точку связывания GL_ARRAY_BUFFER. Обратите внимание, здесь снова ни о каком создании буферов речи пока не идет. Мне лично проще всего думать о glBindBuffer, как о команде «ок, сейчас мы будем что-то делать вот с этим VBO».

Наконец, при вызове glBufferData происходит выделение памяти и загрузка в нее наших данных. Последний аргумент процедуры называется usage и задает предполагаемый паттерн использования буфера. Часть STATIC говорит о том, что мы не собираемся модифицировать данные, а часть DRAW — о том, что данные будут использованы для отрисовки чего-то. Этот аргумент не приводит к каким-то действительным ограничениям на использование буфера. Но он является подсказкой для реализации OpenGL, и может существенно влиять на производительность приложения. Так что, лучше указывать в нем что-то правдоподобное. Заметьте, что первым аргументом указывается binding point, а не конкретный VBO. То есть, выбор самого VBO был сделан на предыдущем шаге.

Когда VBO больше не нужен, его можно удалить следующим образом:

Итак, мы сохранили данные в буфере. Зачем, спрашивается, нужны еще какие-то VAO?

Vertex Arrays Object (VAO) — это такая штука, которая говорит OpenGL, какую часть VBO следует использовать в последующих командах. Чтобы было понятнее, представьте, что VAO представляет собой массив, в элементах которого хранится информация о том, какую часть некого VBO использовать, и как эти данные нужно интерпретировать. Таким образом, один VAO по разным индексам может хранить координаты вершин, их цвета, нормали и прочие данные. Переключившись на нужный VAO мы можем эффективно обращаться к данным, на которые он «указывает», используя только индексы.

Насколько я смог разобраться, то, что лежит в VAO по каким-то индексам, правильно называется «vertex attribute array». Название часто сокращают до «vertex attributes» или просто «attributes». Соответственно, отдельный элемент массива называется атрибутом (attribute, без s на конце). При этом каждый атрибут состоит из компонетов (components). Например, координаты вершины (один атрибут) задаются тремя координатами (три компонента типа float). Чтобы нарисовать треугольник, нужно задать три вершины, поэтому нужен массив из трех атрибутов. Усложняется ситуация тем, что авторы туториалов могут использоваться свою собственную терминологию, вроде attribute list или подобную. Еще мне встречались просто опечатки, когда в тексте написано attribute, когда по контексту явно должно быть attributes. Стоит ли говорить, что такой бардак несколько усложняет изучение OpenGL? 🙂

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

Создание и удаление VAO происходит по аналогии с VBO:

Используются VAO примерно следующим образом:

glBindVertexArray ( vao ) ;
glBindBuffer ( GL_ARRAY_BUFFER, vbo ) ;

glBindBuffer ( GL_ARRAY_BUFFER, 0 ) ; // unbind VBO
glBindVertexArray ( 0 ) ; // unbind VAO

C bind и unbind, надеюсь, все понятно.

Процедура glVertexAttribPointer говорит, откуда брать данные для массива атрибутов, а также в каком формате эти данные находятся. Номер массива атрибутов, переданный первым аргументом этой процедуре, еще понадобится нам чуть ниже, а также когда мы дойдем до работы с шейдерами. Второй аргумент задает размер компонента. В данном случае — три float’а на одну вершину. Третий аргумент задает тип компонента. Оставшиеся аргументы называются normalized, stride и pointer. Сейчас они нам не очень интересны.

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

while ( glfwWindowShouldClose ( window ) == GL_FALSE ) <
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;

glUseProgram ( programId ) ; // на это пока не обращайте внимания

glfwSwapBuffers ( window ) ;
glfwPollEvents ( ) ;
>

Как видите, в нем выбирается интересующий нас VAO при помощи уже знакомой нам процедуры glBindVertexArray.

Вызываемая далее процедура glEnableVertexAttribArray «включает» указанный vertex attribute array. На предыдущем шаге мы решили, что у него будет номер ноль, поэтому здесь используем тот же номер. Как можно без труда догадаться, процедура glDisableVertexAttribArray совершает обратное действие. Нужно это включение и выключение, как мне объяснили в IRC, для оптимизации. Отключая ненужные атрибуты, мы не передаем лишних данных шейдерам (о них ниже). По умолчанию все атрибуты находятся в выключенном состоянии.

Наконец, glDrawArrays отвечает за рисование примитивов. Первым аргументом передается тип примитива. В данном случае рисуется треугольник. Второй аргумент определяет, с вершины под каким номером нужно начать. Последний аргумент задает количество вершин, которое следует использовать.

Вот так замысловато происходит передача координат вершин в OpenGL! Однако остается открытым вопрос, что же делает glUseProgram?

Шейдеры

Шейдер — это программа, предназначенная для выполнения на определенном этапе rendering pipeline. Шейдеры пишутся на специальном языке GLSL (OpenGL Shading Language). Как у всех программ, у шейдеров есть какой-то вход и какой-то выход. Какие именно — зависит от типа шейдера. Существует много типов шейдеров, но в рамках данной заметки нам потребуется только два. Это vertex shader и fragment shader.

Vertex shader работает с отдельными вершинами. На вход данный шейдер получает координаты вершин и их же он должен отдавать на выход. Наш vertex shader на языке GLSL будет выглядеть так:

layout ( location = 0 ) in vec3 vertexPos ;

В части location = 0 используется тот самый номер, который мы передавали первым аргументом при вызове glVertexAttribPointer. То есть, это номер массива атрибутов, в котором мы решили хранить вершины примитива. Шейдер говорит, что входную вершину он будет брать оттуда. Соответственно, атрибуты должны быть включены вызовом glEnableVertexAttribArray. Выход же мы должны записать в переменную gl_Position. Тут есть небольшое неудобство, связанное с тем, что в программе мы использовали координаты xyz, а в шейдере на выход должны отдавать уже xyzw. Поэтому выходному w присваиваем значение 1 вручную.

Fragment shader работает с фрагментами. С ним все немного сложнее. Пока нам достаточно знать, что, помимо прочего, на выход он должен отдавать цвета пикселей. В самом простом варианте fragment shader выглядит так:

Здесь просто для любого входа возвращается один и тот же цвет.

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

Поскольку шейдеры предназначены для различных этапов rendering pipeline, они также объединяются в program object. Делается это как-то так:

GLuint prepareProgram ( bool * errorFlagPtr ) <
* errorFlagPtr = false ;

GLuint programId = glCreateProgram ( ) ;
glAttachShader ( programId, vertexShaderId ) ;
glAttachShader ( programId, fragmentShaderId ) ;
glLinkProgram ( programId ) ;

* errorFlagPtr = checkProgramLinkStatus ( programId ) ;
if ( * errorFlagPtr ) return 0 ;

glDeleteShader ( vertexShaderId ) ;
glDeleteShader ( fragmentShaderId ) ;

Удалять шейдеры после их присоединения к программе совершенно легально. Затем, когда в основном цикле мы говорим:

… подцепляются соответствующие шейдеры. Vertex shader никак не меняет переданные ему координаты, а fragment shader окрашивает примитив в заданный цвет:

зачем нужны шейдеры в opengl. opengl vbo vao shaders. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-opengl vbo vao shaders. картинка зачем нужны шейдеры в opengl. картинка opengl vbo vao shaders.

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

В IRC мне настоятельно советовали проверять, работает ли код с GPU от NVidia. Но, к сожалению, графических карт этого производителя у меня под рукой не имеется. Думается, на таком простом коде проблем возникнуть не должно.

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

Источник

Применение шейдеров OpenGL в QML

Этот пост участвует в конкурсе „Умные телефоны за умные посты“

Данный пост посвящен применению шейдеров OpenGL вместе с элементами декларативного языка QML. Тема, на мой взгляд, является актуальной, так как в будущей версии QML 2.0 планируется широко использовать OpenGL, как backend для отрисовки графических элементов интерфейса. Написание шейдеров — тема непростая и целью данного поста является то, чтобы в первую очередь человек, прочитав её, мог сразу же что-то попробовать сделать интересное для себя и поэкспериментировать, получив, например, вот такие примеры:

зачем нужны шейдеры в opengl. image loader. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-image loader. картинка зачем нужны шейдеры в opengl. картинка image loader.

В конце я приведу полезные ссылки, где Вы сможете посмотреть материал для дальнейшего, более глубокого изучения данной темы, если она конечно Вас заинтересует, и реализовать еще более интересные шейдеры, применив их вместе с элементами языка QML. Работу с шейдерами можно рассмотреть на примере различных элементов QML: ShaderEffectItem, множества классов Qt3D, так же использующих OpenGL и т.д. В данном посте я продемонстрирую несколько примеров, используя элемент ShaderEffectItem вместе с ShaderEffectSource.

Далее следует план данной статьи в целом:
Установка элемента ShaderEffectItem и ShaderEffectSource
Немного теории шейдеров
Связывание элементов QML с шейдерами
Пример 1. Реализация градиента с помощью шейдеров
Пример 2.1 Простейшая анимация
Пример 2.2 Создание меню с анимацией
Пример 3. Выделяем некоторую область текстуры в зависимости от указателя мыши
Пример 4. Смешивание двух изображений
Заключение
Полезные ссылки

Начнём c его установки необходимых элементов.

Установка необходимых плагинов
Немного теории шейдеров

Знакомые с понятием шейдеров могут пропустить эту небольшую главу. В ней я сделаю краткий обзор этой темы. Зачем же нужны шейдеры? Говоря простым языком, шейдеры позволяют «вмешиваться» программисту в процесс отрисовки примитивов, т.е. вносить изменения в этапы работы конвейера (о котором будет сказано чуть ниже), посредством написания собственно кода. Для написания шейдеров существует язык GLSL (OpenGL Shading Language), созданный комитетом OpenGL. Его синтаксис базируется на языке программирования C. GLSL был разработан специально для того, чтобы программисты имели контроль над программируемыми точками конвейера OpenGL, представляющего собой последовательность стадий, через которые проходят команды OpenGL). Как один и вариантов конвейера показан на рисунке ниже:

зачем нужны шейдеры в opengl. image loader. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-image loader. картинка зачем нужны шейдеры в opengl. картинка image loader.

Связывание элементов QML с шейдерами

Обязательным требованием для работы шейдеров с QML элементами является установка OpenGL для отрисовки, в объекте класса QDeclarativeView:

ShaderEffectSource необходим для указания QML компонента, который будет доступен в шейдере. У него в основном будут использоваться свойства sourceItem и hideSource. Первое указывает на конкретный элемент QML (его идентификатор), который будет подвергаться «воздействию» шейдеров, а hideSource «говорит», что исходный элемент будет скрыт, когда будет применён эффект шейдеров.

В QML есть возможность определить свои свойства для элемента (с помощью property ), и они также будут доступны как переменные в программах-шейдерах. Это происходит автоматически, если имена их совпадают и переменная в шейдере объявлена с уже упоминавшимся спецификатором uniform — так называемое связывание. Когда дойдём до примеров, сразу будет ясен этот момент.

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

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

Пример 1. Реализация градиента с помощью шейдеров
Пример 2.1 Простейшая анимация

Применим шейдеры для работы с изображением планеты:
зачем нужны шейдеры в opengl. 9f12be8e. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-9f12be8e. картинка зачем нужны шейдеры в opengl. картинка 9f12be8e.

Исходный код доступен здесь

Пример 2.2 Создание меню с анимацией

Ну и сам файл menu.qml

Хочу обратить внимание на то, что в событии onExited необходимо обязательно сбрасывать свойство угла angle элемента effect в 0.0, иначе угол, подставляемый в вычисление соседнего пикселя будет начинаться расчитываться не с 0, а с последнего значения и получится не совсем то, что мы ожидаем. В итоге получается вот такой эффект:

Исходный код доступен здесь

Пример 3. Выделяем некоторую область текстуры в зависимости от указателя мыши

Стоит отметить, что здесь мы применяем условные операторы (точно такие же как в языке C), которые доступны в язык GLSL.
Исходный код доступен здесь

Пример 4. Смешивание двух изображений (текстур)

В результирующий вектор gl_FrontColor будет записываться уже знакомый нам (в ходе создания градиента) результат линейной интерполяции двух векторов, содержащих параметры цвета пикселя. Причём каждый канал цвета в текстуре s2 (кофейные зёрна, будет умножен на 0.6, так как он нам необходим как фон). В итоге мы имеем вот такой результат выполнения:

зачем нужны шейдеры в opengl. image loader. зачем нужны шейдеры в opengl фото. зачем нужны шейдеры в opengl-image loader. картинка зачем нужны шейдеры в opengl. картинка image loader.

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

Исходный код доступен здесь

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

Заключение

Подводя итоги, можно сказать, что благодаря возможности написания программ-шейдеров, мы получаем очень гибкие механизмы для работы с наиболее важными этапами обработки графики OpenGL при отрисовки элементов QML. Стоит так же отметить, что язык GLSL, как уже упоминалось, очень похож на C, но как сказано в официальной спецификации отличия есть. Например, отсутствуют указатели (данные в функцию передаются по значению), нельзя никаким образом использовать рекурсию и т.д. Следует помнить, что плохо или неправильно написанная программа-шейдер может очень сильно сказаться на производительности. Работа данных плагинов протестирована на платформах: Symbian^3, Maemo 5, Mac OS X, Windows 7 и Ubuntu. Сами по себе требования к платформе представляют собой версию Qt SDK 4.7.x и поддержку QtOpenGL. Будущая версия QML — QML2 в своём Scene Graph будет поддерживать API комбинирующий GL/GLES шейдеры с QML кодом. Можно рассмотреть в Qt 5.0 элемент ShaderEffect. Если я правильно понял это и есть некое подобие того, о чём я писал выше.

Источник

Добавить комментарий

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