- •Предисловие
- •Лабораторная работа № 1. Знакомство с OpenGl с использованием кроссплатформенной библиотеки sdl
- •Теоретические сведения
- •Двумерная и трехмерная графика с помощью OpenGl
- •Подключение и инициализация OpenGl.
- •Расширенная работа с OpenGl-контекстом
- •Постановка задачи
- •Контрольные вопросы
- •Лабораторная работа № 2. Примитивы OpenGl, построение двумерных объектов, язык шейдеров.
- •Теоретические сведения
- •Линии: одиночные, ломаные, замкнутые ломаные.
- •Вывод примитивов на экран
- •Задание к лабораторной работе
- •Дополнительное задание
- •Лабораторная работа № 3. Аффинные преобразования. Трехмерные построения. Параллельная и перспективная проекции. Квадрик-объекты.
- •Лабораторная работа № 4. Камера. Освещение. Текстуры.
- •Контрольные вопросы.
- •Лабораторная работа № 5. Загрузка готовых моделей. Геометрические шейдеры. Анимация.
- •Библиографический список.
- •Оглавление
Лабораторная работа № 4. Камера. Освещение. Текстуры.
Цель работы - знакомство с возможностями библиотеки OpenGL для задания параметров освещения сцены; анализ различных вариантов освещения; установка и использование камеры; исследование и сравнительный анализ результатов, полученных при перемещении камеры и при перемещении объекта.
Дать представление о возможностях наложения текстур на поверхности объектов средствами OpenGL.
Порядок выполнения лабораторной работы.
Перед выполнением лабораторной работы следует ознакомиться с необходимыми теоретическими сведениями, относящимися к возможностям трехмерного моделирования с использованием библиотеки OpenGL. Рекомендуется внимательно изучить теоретический материал, относящиеся к цветовой модели RGB ([1] – лекция 4), изучить теоретический материал, связанный с различными уровнями визуализации ([1] – лекция 15), с учетом свойств материала объектов при их изображении; различия методов Гуро и Фонга ([1] – лекция 16).
Задания лабораторной работы следует выполнять в указанном порядке, используя данные, указанные преподавателем.
Необходимые теоретические сведения.
Текстура – одномерное, двухмерное или трехмерное изображение, которое имеет множество ассоциированных с ним параметров, определяющих, каким образом будет производиться наложение изображения на поверхность. Проще говоря, текстура – это изображение, накладываемое на поверхность. С физической точки зрения текстура – массив данных, например цветовых, световых или цветовых и альфа-компонентов. Каждый элемент этого массива называется текселем. Текстурные координаты – координаты текселя, назначаемого вершине.
Координаты текстур могут представлять собой одно скалярное значение в случае одномерной текстуры (один ряд текселей, которые могут представлять собой градиент, прозрачность или вес), два скалярных значения в случае двухмерных
текстур, или три скалярных значения в случае трехмерных текстур или даже четыре скалярных значения в случае кубических (проецируемых) текстур. В рамках данной лабораторной работы будет говориться только о применении двухмерных текстур, то есть будут использоваться всего два скалярных значения в текстурных координатах.
Правильное вычисление текстурных координат для вершины может оказаться весьма сложным процессом, и это, как правило, работа художников по трехмерному моделированию. Поэтому, для создания текстурных координат можно воспользоваться специализированным программным обеспечением, такими как 3D Studio Max [2], Maya [3] или распространяемый под лицензией GPL Blender [4].
Координаты текстуры предназначены для определения того, какой тексель необходимо выбрать из текстуры. При работе с двухмерными текстурами ось вдоль ширины текстуры (горизонтальная), как правило, называют U-осью (или S-координатой), а ось по высоте (вертикальная) – V-осью (или T-координатой). По этой причине наложение текстур также часто называют "UV Mapping".
Текстурные координаты находятся в пределах [0;1] по каждой из осей. Это удобно с точки зрения того, что не всегда заранее известен размер используемой текстуры. На рисунке 1 показаны координаты текстур и соответствующие им оси, которые используются для обозначения текселей.
Рисунок 1 – Текстурные координаты и соответствующие им оси
Создание текстур.
Существует достаточно большое количество различных растровых форматов хранения графической информации, но перед началом использования их все надо первоначально преобразовать в формат представления в OpenGL. В рамках лабораторной работы будет показан пример работы с растровым форматом хранения графической информации "PNG" как наиболее удобным форматом для хранения графической информации без потерь. Для удобства работы с данным форматом будет использоваться свободно распространяемая библиотека libpng [5].
Для загрузки изображения с помощью библиотеки libpng можно воспользоваться следующим кодом:
GLuint textureName; //ID текстуры
const char *imagePath; //абсолютный / относительный путь до изображения
………………………
png_image image = { 0 };
image.version = PNG_IMAGE_VERSION;
assert(png_image_begin_read_from_file(&image, imagePath));
image.format = PNG_FORMAT_RGBA;
auto buffer = new png_byte[PNG_IMAGE_SIZE(image)];
assert(png_image_finish_read(&image, 0, buffer, 0, nullptr));
//Создаем новый ID для текстуры
glGenTextures(1, &textureName);
//Делаем текстуру активной
glBindTexture(GL_TEXTURE_2D, textureName);
//Ассоциируем изображение с текущей активной текстурой (Загружаем в графическую память)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
//Освобождаем ресурсы
delete [] buffer;
buffer = nullptr;
………………………
Переменная textureName теперь связана с уникальным идентификатором текстуры в графической памяти и может быть использована для применения текстуры к моделям.
Свойства текстур.
При различии размеров текстуры и размеров объекта, на который накладывается текстура, могут понадобиться различные свойства, применяемые к текстуре для более естественной передачи.
Свойства текстуры задаются с помощью семейства функций glTexParametr, но прежде чем можно будет изменить свойства текстуры необходимо сделать текстуру активной. Для этого можно воспользоваться следующим методом:
void glBindTexture (GLenum target, GLuint texture);
Первый параметр определяет тип текстуры, может быть одним из следующих констант:
– GL_TEXTURE_1D – одномерная текстура;
– GL_TEXTURE_2D – двухмерная текстура;
– GL_TEXTURE_3D – трехмерная текстура.
Второй параметр определяет ID текстуры, которую необходимо сделать активной. Чтобы освободить текстуру, в качестве параметра можно передать 0.
Текстуры имеют множество свойств, которые могут быть изменены при помощи следующего семейства функций:
void glTexParameter[f|i]{v}(GLenum target, GLenum pname, [GLfloat|GLint]{*} param);
Первый параметр определяет тип текстуры, может быть одним из следующих констант:
– GL_TEXTURE_1D – одномерная текстура;
– GL_TEXTURE_2D – двухмерная текстура;
– GL_TEXTURE_3D – трехмерная текстура.
Второй параметр определяет символическое имя для параметра текстуры, может быть одним из следующих значений:
– GL_TEXTURE_MIN_FILTER – позволяет указать способ, который будет использоваться для выбранной текстуры, когда несколько текселей из текстуры вписывается в один пиксель экрана (или пиксель фрагмента);
– GL_TEXTURE_MAG_FILTER – позволяет указать способ, который будет использоваться для выбранной текстуры, когда один тексел вписывается в несколько пикселей экрана (или пиксель фрагмента);
– GL_TEXTURE_WRAP_S – позволяет определить, как правильно интерпретировать значения, если S-координата находится вне диапазона [0;1]. По умолчанию установлено значение GL_REPEAT;
– GL_TEXTURE_WRAP_T – позволяет определить, как правильно интерпретировать значения, если T-координата находится вне диапазона [0;1]. По умолчанию установлено значение GL_REPEAT;
– GL_TEXTURE_WRAP_R – позволяет определить, как правильно интерпретировать значения, если R-координата находится вне диапазона [0;1]. По умолчанию установлено значение GL_REPEAT.
Третий параметр определяет само значение, применяемое для параметра текстуры. Может принимать, в зависимости от параметра текстуры, следующие значения:
– Для GL_TEXTURE_[MIN|MAG]_FILTER:
– GL_NEAREST – возвращает тексел, который находится ближе к центру визуализируемого пикселя;
– GL_LINEAR – возвращает средневзвешенное значение четырех текстурных элементов, которые находятся ближе к центру визуализируемого пикселя.
– Для GL_TEXTURE_WRAP_[S|T|R]:
– GL_REPEAT – игнорируется целая часть и используется только дробная для определения необходимого текселя, тем самым создавая повторяющиеся узор.;
– GL_CLAMP_TO_EDGE – в независимости от изначального диапазона координаты текстуры будут представляться в диапазоне [0;1], тем самым создавая эффект растяжения.
Текстурные блоки (Мультитекстурирование)
Для получения ряда визуальных эффектов (карты освещенности, туман, микрофактурные текстуры и т.п.) часто возникает необходимость наложения на грань не одной текстуры, а сразу нескольких. Для этого, в OpenGL была предусмотрена следующая функция:
void glActiveTexture(GLenum texture);
Параметр определяет какой текстурный блок необходимо активировать, для этого используется символьная константа GL_TEXTUREi, где i диапазон от 0 до 32 (максимальное число текстурных блоков).
Работа с текстурами в шейдере.
Для возможности работы с текстурами в шейдере необходимо использовать специальный тип данных – sampler. Для этого необходимо объявить юниформ-переменную во фрагментном шейдере (для двухмерной текстуры – юниформ-переменная типа sampler2D) и передать туда номер используемого текстурного блока (не ID текстуры). Пример наложения нескольких текстур на модель во фрагментом шейдере представлен ниже:
layout(location = 0) out vec4 outputColor;
uniform sampler2D texture0;
uniform sampler2D texture1;
smooth vec2 textureCoordinate0;
smooth vec2 textureCoordinate1;
void main() {
outputColor = vec4(1.0,1.0,1.0,1.0);
//Наложение текстур (Смешивание двух текстур)
outputColor *= mix(texture(texture0, textureCoordinate0), texture(texture1, textureCoordinate1), 0.5);
}
После того, как была создана текстура и ассоциирована с ней некоторое растровое изображение, необходимо связать полученную текстуру с шейдерной программой, используя код, аналогичный тому, что представлен ниже:
GLuint programID; //ID шейдерной программы
GLuint texture0, texture1; //ID текстур
//Привязываем первую текстуру к первому текстурному блоку
glActiveTexture( GL_TEXTURE0 );
glBindTexture( GL_TEXTURE_2D, texture0);
//Привязываем вторую текстуру ко второму текстурному блоку
glActiveTexture( GL_TEXTURE1 );
glBindTexture( GL_TEXTURE_2D, texture1);
//Получаем позицию юниформ-переменной в шейдере
auto textureLocation0 = glGetUniformLocation(programID, "texture0");
auto textureLocation1 = glGetUniformLocation(programID, "texture1");
//Привязываем юниформ-переменную к соответствующему текстурному блоку
glUniform1i(textureLocation0, 0);
glUniform1i(textureLocation1, 1);
Базовая модель освещения.
OpenGL рассчитывает освещение так, как будто свет может быть разделен на красный, зеленый и синий компоненты. Таким образом, источник света характеризуется количеством красного, зеленого и синего света, которое он излучает, а материал поверхности характеризуется долями красного, зеленого и синего компонентов, которые он отражает в различных направлениях.
В модели освещения OpenGL свет исходит от нескольких источников, которые могут включаться и выключаться индивидуально. Часть света обычно исходит из какого-либо определенного направления или позиции, часть распределена по всей сцене. Например, если включить лампочку в комнате, большая часть света будет исходить от нее, но часть света падает на поверхности предметов в комнате после того, как он отразился от одной, двух, трех или более стен. Считается, что этот многократно отраженный свет (называемый фоновым светом) распределен настолько сильно, что не существует никакого способа определить его исходное направление, однако он исчезает при выключении определенного источника света.
Наконец, в сцене может также присутствовать общий фоновый свет, у которого нет никакого конкретного источника, как будто он был отражен столько раз и распределен так сильно, что его оригинальный источник установить невозможно.
С OpenGL 2.0 и введения PP все расчеты по освещению должны производиться либо в вершинном шейдере (или при любой другой стадии обработки вершин) для каждой вершины или во фрагментном шейдере (пиксельном) для каждого фрагмента (пикселя).
В базовой модели освещения OpenGL предполагается, что освещение может быть разделено на несколько компонентов: фоновое (ambient), диффузное (diffuse), отраженное (specular) и собственное (эмиссионное – emissive). Все компоненты освещения рассчитываются независимо друг от друга и только затем суммируются. Следовательно, окончательный цвет фрагмента (пикселя) будет рассчитываться следующим образом:
Окончательный цвет берется в диапазоне [0;1] для каждого красного, зеленого, синего и альфа-компонентов, прежде чем будет произведена запись в буфер цвета.
Рассмотрим каждый из компонентов по отдельности:
Компонент собственное свечение.
В модели освещения OpenGL собственное свечение поверхности добавляет объекту интенсивности (яркости), но на него не влияют никакие источники света, и он не производит дополнительного света для сцены в целом. Однако, существует техника для моделирования переноса эмиссионного излучения называемая "Indirect Illumination" (Вторичное освещение), но она выходит за рамки настоящей лабораторной работы.
Собственное свечение может быть рассчитано следующим образом:
где Materiale – собственное свечение материала.
Компонент фоновое излучение.
Фоновое излучение – это свет, который настолько распределен средой (предметами, стенами и так далее), что его направление определить невозможно – кажется, что он исходит отовсюду. Лампа дневного света имеет большой фоновый компонент, поскольку большая часть света, достигающего глаза, сначала отражается от множества поверхностей. Уличный фонарь имеет маленький фоновый компонент: большая часть его света идет в одном направлении, кроме того, поскольку он находится на улице, очень небольшая часть света попадает в глаз после того, как отразится от других объектов. Когда фоновый свет падает на поверхность, он одинаково распределяется во всех направлениях.
Фоновое излучение может быть рассчитано следующим образом:
где Globala – глобальное фоновое излучение, Materiala – способность материала воспринимать фоновое излучение.
Взаимодействие с источниками освещения.
Каждый активный источник освещения будет участвовать в расчете финального цвета фрагмента (пикселя). Общий вклад от всех источников освещения является суммой всех активных источников освещения. Следовательно, формула для расчета общего вклада от всех источников освещения может быть записана следующим образом:
Рассмотрим каждый компонент подробно:
Коэффициент затухания.
Коэффициент светового затухания определяет, как будет происходить ослабление интенсивности света в зависимости от расположения источника света относительно текущего фрагмента (пикселя).
где d – скалярное расстояние от источника света до текущего фрагмента (пикселя), kc – постоянная составляющая ослабления источника света, kl – линейная составляющая ослабления источника света, kq – квадратичная составляющая ослабления источника света.
Стоит отметить, что коэффициент затухания учитывается только когда имеем дело с позиционными источниками света: точечным (point) или прожектором (spot). Если источник света определяется как направленный (directional), то затухание не должно происходить. Это связано с тем, что вычисление коэффициента затухания зависит от расстояния от источника света до текущей вершины. Направленный источник освещения в основном не имеет положения и можно считать его бесконечно далеким, а это означает, что интенсивность света будет постоянно ослаблена, то есть свет не достигнет точки. Чтобы этого избежать, предполагается, что свет от направленного источника затухать не будет. Для этого можно установить коэффициент затухания в 1.0f, путем установки постоянной составляющей в 1.0f, а линейной и квадратичной в 0.0f.
Фоновое излучение от источника света.
где Lighta – фоновое излучение от источника света, Materiala – способность материала воспринимать фоновое излучение.
Диффузное излучение от источника света.
Диффузное излучение – это свет, идущий из одного направления, таким образом, он выглядит ярче, если падает на поверхность под прямым углом, и выглядит тусклым, если касается её всего лишь вскользь. Однако, когда он падает на поверхность, он распределяется одинаково во всех направлениях, то есть его яркость одинакова вне зависимости от того, с какой стороны производится наблюдение на поверхность. Вероятно, любой свет, исходящий из определенного направления или позиции, имеет диффузный компонент.
Диффузное излучение может быть рассчитано следующим образом:
где p – позиция текущего фрагмента (пикселя), N – вектор нормали текущего фрагмента (пикселя), Lp – позиция источника света, Ld – диффузная составляющая источника света, L – вектор направления от текущего фрагмента (пикселя) к источнику света, kd – способность материала воспринимать диффузное излучение.
Выражение max(L×N, 0) гарантирует, что для поверхностей, которые загорожены от источника света (когда L×N < 0), фрагмент (пиксель) не будет иметь никакого диффузного освещения.
Зеркальное излучение от источника света.
Зеркальное излучение исходит из определенного направления и отражается от поверхности в определенном направлении. При отражении хорошо сфокусированного лазерного луча от качественного зеркала происходит почти 100 процентное зеркальное отражение. Блестящий метал или пластик имеет высокий зеркальный компонент, а кусок ковра или плюшевая игрушка – нет. В OpenGL можно считать о зеркальности как о том, насколько блестящим выглядит материал.
Используя модель освещения по Фонгу, зеркальное излучение может быть рассчитано следующим образом:
где p – позиция текущего фрагмента (пикселя), N – вектор нормали текущего фрагмента (пикселя), Lp – позиция источника света, eyep – позиция камеры наблюдения, V – вектор направления от текущего фрагмента (пикселя) к камере наблюдения, L – вектор направления от текущего фрагмента (пикселя) к источнику света, R – вектор отражения от источника света относительно вектора нормали к поверхности, Ls – зеркальная составляющая источника света, ks – способность материала воспринимать зеркальное излучение, α – коэффициент блеска материала, чем выше коэффициент блеска, тем меньше световой эффект от материала.
Рисунок 2 наглядно показывает интенсивность изменения отраженного освещения при различных значениях α.
Рисунок 2 – интенсивность отраженного освещения
Свойства материала.
В модели OpenGL эффект от источника света присутствует только если есть поверхности поглощающие или отражающие свет. Считается, что каждая поверхность состоит из материала с несколькими свойствами. Материал может излучать свой собственный свет (например, фара автомобиля), он может распределять некоторое количество входящего света во всех направлениях, также он может отражать часть света в определенном направлении (например, зеркало или другая блестящая поверхность).
Модель освещения OpenGL делает допущение о том, что цвет материала зависит от долей падающего красного, зеленого и синего света, которые он отражает. Например, максимально красный мяч отражает весь красный свет, который на него падает и поглощает весь зеленый и синий. Если посмотреть на такой мяч под белым светом (состоящим из одинакового количества красного, зеленого и синего), весь красный свет отразится, и можно будет увидеть красный мяч. Если смотреть на мяч при красном свете, он также будет выглядеть красным. Однако, если посмотреть на него под зеленым светом, он будет выглядеть черным (весь зеленый свет поглотится, а красного нет, то есть никакой свет отражен не будет).
Также как и свет, материалы имеют разные фоновый (Ambient), диффузный (Diffuse) и отраженный (Specular) цвета, которые задают реакцию материала на фоновый, диффузный и отраженный компоненты света. Фоновый цвет материала комбинируется с фоновым компонентом всех источников света, диффузный цвет с диффузным компонентом, а отраженный с отраженным. Фоновый и диффузный цвета задают видимый цвет материала, они обычно близки, если не эквивалентны. Отраженный цвет обычно белый или серый. Он задет цвет блика на объекте (то есть он может совпадать с отраженным компонентом источника света).
Моделирование камеры.
В OpenGL точка обзора по умолчанию располагается в центре координат. Направление зрения – вдоль оси Z противоположно её направлению (то есть все, что рисуется, видно из центра координат).
Для моделирования камеры, которая направлена в произвольную точку пространства и расположена в заданном месте, удобно использовать функцию библиотеки vmath – createLookAt, например:
static Matrix4<T> createLookAt(Vector3<T>& eyePos, Vector3<T>& centerPos, Vector3<T>& upDir)
Функция имеет 3 параметра: первый параметр определяет координаты местоположения камеры; второй параметр задает координаты точки, в которую нацелена камера; последний параметр – направление вектора «вверх».
Направление вектора «вверх» может быть любым, но не должно быть параллельно вектору нормали к плоскости наблюдения. Удобным является выбор вектора «вверх» параллельным оси Y, т.е. Vector3f(0.0f, 1.0f, 0.0f).
Задание к лабораторной работе.
Для повышенного уровня сложности, реализовать модель теней другой модель расчета теней (По возможности реализовать переключение между моделями расчета теней).
Все задания должны быть выполнены в 2 вариантах: с наложением текстуры и без нее. Для двух вариантов должны быть написаны две разные шейдерные программы; предполагается, что во всех заданиях должна быть предоставлена возможность изменения положения камеры наблюдения и возможности вывода примитивов в каркасном и вершинном виде.
1. Установить диффузный источник света, освещающий белую неблестящую сферу. По нажатию клавиши на клавиатуре лампа должна плавно изменять свой цвет от красного до фиолетового в порядке следования цветов в спектре.
2. Установить диффузный источник света, освещающий белую неблестящую сферу. При нажатии клавиш курсора лампа должна передвигаться в указанном направлении.
3. Построить пирамиду, освещаемую двумя источниками света. Фиолетовым и Желтым. При нажатии клавиши "P" или "Y" интенсивность соответственно фиолетового или желтого источника света увеличивается на 5%. При достижении интенсивности в 100%, она должна автоматически падать до 10%.
4. Построить куб, освещенный вращающимся вокруг него источником света. При нажатии клавиши "вправо" источник света начинает двигаться с большей скоростью. При нажатии клавиши "влево", скорость движения лампы должна уменьшаться вплоть до изменения направления своего движения на противоположный.
5. Подготовиться к защите лабораторной работы.
Дополнительное задание.
1. Разработать класс(ы) для работы с текстурами, со следующими необходимыми действиями (использование библиотек по работе с несколькими растровыми форматами одновременно – запрещено):
– загрузка растрового изображения из файла;
– привязка текстуры к определенному текстурному блоку;
– применение различных свойств к текстуре;
– активация / деактивация текстуры.
