Как сделать фон в opengl

Добавил пользователь Дмитрий К.
Обновлено: 04.10.2024

Примечания к исследованию OpenGL: цвет (цвет RGBA, режим цветового индекса)

Поддержка OpenGLДва цветовых режима: ОдинRGBA, Один цветИндексный режим。

Независимо от цветового режима компьютер должен сохранять некоторые данные для каждого пикселя.

Разница в том, что в режиме RGBA данные непосредственно представляют цвет; в режиме индекса цвета данные представляют индекс. Чтобы получить реальный цвет, вы также должны проверить таблицу индексов.

1. Цвет RGBA
В режиме RGBA каждый пиксель сохраняет следующие данные: значение R (красный компонент), значение G (зеленый компонент), значение B (синий компонент) и значение A (альфа-компонент). Среди них можно комбинировать три цвета: красный, зеленый и синий, чтобы получить различные цвета, которые нам нужны, и альфа не влияет напрямую на цвет, она будет представлена ​​позже.
Выбрать цвета в режиме RGBA очень просто, и это можно сделать с помощью только одной функции. Чтобы
Для установки цвета можно использовать серию функций glColor *, версия трех параметров может указывать значения R, G, B, а значение A является значением по умолчанию;
Четыре версии параметров могут указывать значения R, G, B и A соответственно.

(3f означает, что есть три параметра с плавающей запятой ~ см. Описание функции glVertex * во втором уроке.)
В качестве параметра возьмите число с плавающей запятой, где 0,0 означает, что цвет не используется, а 1,0 означает, что цвет используется чаще всего.

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

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

OpenGL не предоставляет напрямую метод для настройки таблицы цветов, поэтому для настройки таблицы цветов требуется поддержка операционной системы. Windows, которую мы используем, и большинство других графических операционных систем имеют эту функцию, но используются другие функции. Так же, как я не описал, как написать код для создания окна под Windows, я не буду описывать, как установить цветовую таблицу под Windows. Инструментарий GLUT предоставляет функцию glutSetColor для установки таблицы цветов, но у моего теста всегда есть проблемы. Теперь, чтобы позволить каждому испытать индексированные цвета, я представлю еще один инструментарий OpenGL: aux. Этот набор инструментов поставляется с Visual Studio, и его не нужно устанавливать отдельно, но он устарел. Это просто опыт, поэтому вам не нужно вдаваться в подробности.

Каждый может игнорировать другие части, просто посмотрите на функцию myDisplay.
Сначала используйте auxSetOneColor, чтобы установить сетку в таблице цветов. Восемь сеток можно настроить восьмикратным циклом. glShadeModel расскажет об этом позже, здесь не упоминается. Чтобы
Затем используйте glVertex, чтобы установить вершину в цикле, и используйте glIndexi, чтобы изменить цвет вершины. Чтобы
Конечный результат - восемь треугольников одинаковой формы и разных цветов. Хотя об индексном цвете говорить не приходится.

4. Укажите модель затенения.

OpenGL позволяет указывать разные цвета для разных вершин одного и того же многоугольника. Например:

Как использовать glShadeModel:

резюме:
В этом уроке рассказывается, как устанавливать цвета. Среди них метод цвета RGB является распространенным методом на ПК. Чтобы
Вы можете установить цвет, оставшийся на экране после очистки glClear. Чтобы

Вы можете установить способ заливки цвета: плавный или монохромный.

Рабочая среда:

НижеприведенныйИсходный код:

Скомпилируйте и запустите:

График результатов:



Код и содержание взяты из " Вводное руководство по OpenGL (отлично) 》, Если есть какие-либо нарушения, пожалуйста, свяжитесь для удаления.

Это мой исходный код из серии уроков, которые я изучаю в отношении opengl 3+.

В предыдущих исследованиях opengl (2.x) с переизбытком, которые я делал пару лет назад, у меня не было подобных проблем, может кто-нибудь объяснить, что здесь не так?

Решение

Я получаю такое же поведение (даже запускаю выпуск exe за пределами IDE) на ATI FirePro V5700. Если вы действительно обеспокоены этим, скачать исходный код GLFW и измените строку 764 из carbon_window.c, строку 1210 из win32_window.c и строку 962 из x11_window.c.

.\ Lib \ углерод \ carbon_window.c

.\ Lib \ win32 \ win32_window.c

.\ Lib \ x11 \ x11_window.c

Другие решения

Что произойдет, если вы сделаете следующее?

Редактировать: Мой графический процессор — Nvidia GTX 580 (способный по крайней мере OpenGL 4.3).

Цвет фона окна настраивается путем вызова функции openGL glClearColor () и обновился с glClear () функция.

Задержка в изменении заголовка окна, возможно, может быть связана с тем, что для создания openGL 3.x необходимо создать стандартный контекст OpenGL (версия 1.x или 2.x), а затем получить ваше приложение должно использовать контекст OpenGL 3.x. Задержка 1-2 секунды, хотя кажется большой.

Ну, как кажется, это связано с IDE, если вы запустите реальный .exe-файл, он работает так, как задумано, и без каких-либо задержек.

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

Цвета в мире графики

Цвет объекта, который мы видим в реальной жизни — это не тот цвет, который он действительно имеет. На самом деле, мы видим луч конкретного цвета, отраженный от объекта. Лучи различных цветов, которые не поглотились объектом, создадут именно тот цвет объекта, который мы и увидим. Например, свет солнца воспринимается нами как белый свет, но на самом деле он является совокупностью многих лучей разных цветов. Если бы мы посветили этим белым светом на синюю игрушку, то она поглотила бы все лучи, составляющие белый свет, кроме синего. А поскольку луч синего цвета игрушка не поглотила, то он отражается от нее. Отраженный луч попадает в наш глаз, и в результате мы воспринимаем нашу игрушку в виде предмета синего цвета. На следующем рисунке этот процесс показан для игрушки кораллового цвета; она отражает несколько лучей различных цветов с различной интенсивностью:


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

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

В последней заметке, посвященной OpenGL, мы нарисовали вращающийся разноцветный куб, а также научились управлять камерой при помощи мыши и клавиатуры. Сегодня же мы наконец-то научимся работать с текстурами. Теоретическая часть касательно текстур ранее уже объяснялась в заметке Учимся работать с текстурами в Haskell’евом OpenGL. Поэтому в рамках этой заметки предполагается, что с теорией читатель уже знаком, и основной акцент будет сделан на практике.

Мультисэмплинг

Хотелось бы отметить одно изменение в проекте, не связанное с текстурами. А именно, включенный мультисэмплинг:

glfwWindowHint ( GLFW_SAMPLES, 4 ) ;

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

Создание и удаление текстур в OpenGL происходит по аналогии с созданием и удалением других сущностей:

// создаем три текстуры
GLuint textureArray [ 3 ] ;
int texturesNum = sizeof ( textureArray ) / sizeof ( textureArray [ 0 ] ) ;
glGenTextures ( texturesNum, textureArray ) ;

// не забываем освободить их при выходе из скоупа
defer ( glDeleteTextures ( texturesNum, textureArray ) ) ;

Тут для нас нет ничего нового, поэтому двигаемся дальше.

Сам по себе OpenGL ничего не знает ни о каких JPG, PNG, TGA или BMP. Все, с чем он умеет работать — это массивы цветов в определенном формате, скажем, RGBA. А значит, декодирование текстур из JPG или иного формата ложится на наши с вами плечи. К счастью, готовых библиотек для решения этой задачи имеется в избытке. Например, есть SOIL (давно не обновлялся и у меня лично он крэшился), SOIL2, DevIL, FreeImage, да и старых-добрый ImageMagick в конце концов. Я лично на данный момент сделал выбор в пользу библиотеки под названием STB. STB находится в public domain (является общественным достоянием, то есть, на использование кода нет вообще никаких ограничений), написана на Си, что позволяет использовать библиотеку в проектах как на Си, так и на C++, поддерживает все распространенные форматы, а также живет на GitHub, что позволяет очень удобно подключать либу к проекту при помощи сабмодулей Git. Наконец, STB имеет очень простой интерфейс и просто работает на всех популярных платформах. Чего еще можно желать?

Пример загрузки текстуры:

int width, height, n ; // ширина, высота, байт на пиксель

// читаем файл, при необходимости конвертируем в 3 байта на пиксель
unsigned char * textureData = stbi_load ( fname, & width, & height, & n, 3 ) ;
if ( textureData == nullptr ) <
std :: cout "loadTexture failed, fname sy1"> fname std :: endl ;
return false ;
>

// не забываем освободить память
defer ( stbi_image_free ( textureData ) ) ;

Имея на руках декодированную текстуру, можно передать ее OpenGL следующим образом:

glBindTexture ( GL_TEXTURE_2D, textureArray [ 0 ] ) ;
glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGB, width, height, 0 , GL_RGB,
GL_UNSIGNED_BYTE, textureData ) ;

С подробным описанием процедуры glTexImage2D можно ознакомиться здесь.

UV-координаты

По аналогии с тем, как раньше мы задавали цвет для каждой вершины, при работе с текстурами для каждой вершины мы должны задать соответствующие UV-кординаты на текстуре. В OpenGL координате с U = 0 и V = 0 соответствует нижний левый угол текстуры, а с U = 1 и V = 1 — верхний правый угол. Но есть нюанс.

static const GLfloat globBoxVertexData [ ] = <
// X Y Z U V
// front
1.0f , 1.0f , 1.0f , U ( 1.0f ) , V ( 1.0f ) ,
- 1.0f , 1.0f , 1.0f , U ( 0.0f ) , V ( 1.0f ) ,
1.0f , - 1.0f , 1.0f , U ( 1.0f ) , V ( 0.0f ) ,

Интересно, что координаты U и V могут выходит за диапазон [0, 1].

OpenGL предлагает 4 способа обработки этой ситуации:

  • GL_CLAMP_TO_BORDER — при выходе за [0, 1] используется заданный цвет;
  • GL_CLAMP_TO_EDGE — при значении > 1 используется 1, а в случае // в константах вместо букв U и V используются S и T
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ) ;
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ) ;

Если не очень понятно, то в этом туториале есть наглядные картинки.

Filtering и mipmapping

Представьте, что в неком примитиве используется текстура размером, скажем, 128 x128 текселей. Нам нужно каким-то образом определить цвета пикселей на экране по цвету текселей. Проблема в том, что если камера находится очень далеко от примитива, на один пиксель может приходится сразу очень много текселей. Камера может находится так далеко, что примитив вообще сожмется в один пиксель. И наоборот, при приближении камеры один и тот же тексель может попасть во множество пикселей. Для того, чтобы картинка лучше выглядела при таких вот сценариях, и придумали так называемый filtering.

Допустим, для некого пикселя на экране были определены соответствующие UV-координаты на текстуре. Мы можем сказать OpenGL тупо использовать цвет текселя, который находится по этим координатам:

glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ;

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

glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ) ;

Но, оказывается, этого недостаточно в случаях, когда примитив находится очень далеко от камеры. При движении камеры он будет переливаться разными цветами, так как мы будем постоянно попадать на разные тексели. Решение заключается в использовании так называемых mipmaps. Это последовательность из одной и той же текстуры, только разного размера. В нашем примере это будут текстуры размером 128 x128, 64 x64, 32 x32 и так далее до 1 x1. Чем дальше камера от примитива, тем менее детализированная текстура используется, за счет чего и ликвидируется описанный эффект переливания. Можно использовать как заранее подготовленные mipmaps, так и попросить OpenGL сгенерировать их автоматически:

Остается только указать правильный параметр GL_TEXTURE_MIN_FILTER:

Пробрасываем текстуры в шейдеры

Как вы можете помнить, за раскрашивание примитивов отвечает fragment shader. А сейчас у нас есть только созданная текстура с загруженными в нее данными и параметрами, а также VBO, содержащий координаты вершин в пространстве с UV-координатами на текстуре, притом вперемешку. Явно чего-то не хватает.

Во-первых, нужно разделить координаты вершин и UV-координаты на текстуре по разным массивам атрибутов:

glBindVertexArray ( boxVAO ) ;
glEnableVertexAttribArray ( 0 ) ;
glEnableVertexAttribArray ( 1 ) ;

glBindBuffer ( GL_ARRAY_BUFFER, boxVBO ) ;
glVertexAttribPointer ( 0 , 3 , GL_FLOAT, GL_FALSE, 5 * sizeof ( GLfloat ) ,
nullptr ) ;
glVertexAttribPointer ( 1 , 2 , GL_FLOAT, GL_FALSE, 5 * sizeof ( GLfloat ) ,
( const void * ) ( 3 * sizeof ( GLfloat ) ) ) ;

Теперь стало понятно, зачем у процедуры glVertexAttribPointer есть аргументы stride и pointer. Вы можете помнить, что в заметке, посвященной VBO, VAO и шейдерам, мы упоминали эти параметры, но не разобрались, для чего они нужны. Так вот, pointer определяет, начиная с какого смещения (в байтах) хранится первый атрибут, а stride — какой шаг (также в байтах) нужно делать для перехода к следующему атрибуту. Благодаря этим параметрам мы можем использовать один VBO для хранения разных типов данных. Пока не могу сказать, насколько это более эффективно, чем использование двух отдельных VBO. Но это точно намного удобнее и компактнее при вбивании координат вершин и UV-координат руками.

Во-вторых, нам понадобится еще одна uniform-переменная, через которую шейдер будет обращаться к текстуре. Определение переменной в коде шейдера будет показано чуть ниже. В коде на С++ мы находим эту переменную как обычно:

Связывание uniform переменных с конкретными текстурами осуществляется через так называемые texture unit. Например, так мы говорим, что переменная textureSampler соответствует texture unit с заданным номером:

В OpenGL по умолчанию всегда активен texture unit с номером ноль. Поэтому, если в шейдере не используется сразу несколько текстур, код может выглядеть как-то так:

glUniform1i ( samplerId, 0 ) ;

glBindTexture ( GL_TEXTURE_2D, someObjectTexture ) ;
// рисуем someObject

glBindTexture ( GL_TEXTURE_2D, someOtherObjectTexture ) ;
// рисуем someOtherObject

Наконец, код vertex shader:

layout ( location = 0 ) in vec3 vertexPos ;
layout ( location = 1 ) in vec2 vertexUV ;

uniform mat4 MVP ;

void main ( ) <
UV = vertexUV ;
gl_Position = MVP * vec4 ( vertexPos , 1 ) ;
>

Как видите, здесь UV-координаты просто пробрасываются далее.

Код fragment shader:

uniform sampler2D textureSampler ;

void main ( ) <
color = texture ( textureSampler , UV ) . rgb ;
>

Здесь проброшенные UV-координаты принимаются и используются для получения цвета через переменную textureSampler при помощи функции языка GLSL texture. Как обычно в мире OpenGL, все несколько запутанно, но в целом можно разобраться.

Работа со сжатыми текстурами в формате DDS

Текстуры в форматах вроде JPG или PNG используются в современном OpenGL разве что в порядке исключения. Если вы расскажете специалисту по OpenGL, что все ваши текстуры хранятся в одном из этих форматов, вас, скорее всего, поднимут на смех и посоветуют использовать DDS.

Чтобы понять, что не так с обычными форматами, попробуем шаг за шагом воспроизвести, что делает программа при их использовании. Считывается файл. Затем на CPU долго и мучительно происходит его распаковка. В итоге для текстуры 512 x512 при использовании RGBA вы получаете 1 Мб данных. Эти данные вы начинаете пересылать в GPU. Потом вы говорите glGenerateMipmap и GPU как-то генерирует из этих данных mipmaps. Притом делает он это, скорее всего, не очень качественно, так как много времени на генерацию mipmaps GPU потратить не может. Теперь представьте, что текстур у вас очень много. Более того, вся эта последовательность шагов выполняется при каждом запуске программы.

При использовании формата DDS вам не нужно ничего разжимать. Вы берете файл, читаете его заголовок, проставляете соответствующие флаги и потом просто шлете сжатые данные на GPU как есть. GPU в состоянии самостоятельно их распаковать. Притом, делает он это на лету, при обращении к текстуре, а сама текстура так и хранится в сжатом виде. Нам даже не требуется выделять память и считывать в нее файл, ведь можно воспользоваться отображением файла в память (MapViewOfFile в Windows, mmap в nix-системах). Стоит ли говорить, что этот подход куда более эффективен? Кроме того, формат DDS позволяет хранить mipmaps. На их генерацию мы можем потратить хоть два часа, что позволяет использовать сложные алгоритмы и получить более качественный результат. Ну и как небольшой бонус, DDS настолько просто парсить, что не нужно таскать за собой сторонние библиотеки.

В DDS используется сжатие с потерями. Что интересно, в отличие от JPG и прочих, степень сжатия фиксированная. Данные в файле с расширением DDS часто хранятся в одном из следующих форматов:

  • DXT1 — сжатие 8:1, позволяет хранить однобитовый альфа-канал;
  • DXT3 — сжатие 4:1, 4 бита на альфа-канал;
  • DXT5 — сжатие 4:1, альфа-канал кодируется по аналогии с цветом;

Для сохранения текстур в формате DDS я использовал плагин для Gimp. В Ubuntu Linux его можно установить так:

Сначала я хотел найти готовую библиотеку для работы с DDS. Но в IRC сразу несколько человек заверило меня, что использование самописной процедуры для решения этой задачи в мире OpenGL считается нормальной практикой. Более того, мне не смогли порекомендовать кроссплатформенный способ сделать отображение файла в память и посоветовали просто написать несколько версий, да воспользоваться ifdef. Что я, собственно, и сделал. Код получившейся процедуры loadDDSTexture вы найдете в полной версии исходников к этой заметке. То, что касается отображение файлов в память, сильно выходит за рамки этой заметки, и может быть изучено заинтересованными читателям самостоятельно при помощи man и MSDN. Что же касается парсинга заголовка DDS, то там все до безобразия просто и было одолжено мной из реализаций, найденных через Google. Кому интересны детали, может ознакомиться с прилагаемыми исходниками самостоятельно.

Заключение

В ходе изучения работы с текстурами мной была написана такая демка:

Работа с текстурами и skybox на OpenGL

Ящик находится на квадратном острове, покрытым травой. Текстура травы повторяется благодаря GL_REPEAT. Остров вращается вокруг своей оси вместе с ящиком. Небо и солнце натянуты на так называемый skybox. Камера всегда находится в центре огромного куба, на который натянута специальным образом подготовленная текстура. Если бы вместо куба использовалась сфера, это называлось бы skydome.

Как всегда, код был проверен на работу на трех компьютерах с разными GPU, работающими под Linux, Windows и MacOS. Полную версих исходников вы найдете в этом репозитории.

В качестве домашнего задания можете попробовать прикрутить какой-нибудь альтернативный skybox. Если на skybox нет солнца, то очень медленным его вращением можно создать иллюзию движения облаков. Также можете попробовать отключить мультисэмплинг или изменить параметры filtering и сравнить результаты.

Читайте также: