Урок 8. Смешивание
Простая прозрачность
Большинство специальных эффектов в OpenGL основано на так называемом смешивании (blending). Смешивание используется, чтобы комбинировать цвет данного пикселя, который должен быть выведен с пикселем, уже находящемся на экране. Как будут объединены цвета, будет зависеть от альфа-канала, и/или функции смешивания, которая задается. Альфа - 4-й компонент цвета, который обычно указывается в конце. В прошлом Вы использовали GL_RGB, чтобы определить цвет с тремя компонентами. GL_RGBA может использоваться, чтобы также определить альфа-канал. Теперь мы можем использовать glColor4f() вместо glColor3f().
Большинство людей думает об альфе, как о непрозрачности материала. Альфа - значение 0.0 подразумевает, что материал полностью прозрачен. Значение 1.0 означает полную непрозрачность.
Уравнение смешивания
Если Вы не дружите с математикой, и только хотите уметь просто использовать эффект прозрачности, то пропустите этот раздел. Если Вы хотите понять, как работает смешивание, этот раздел - для Вас.
(Rs Sr + Rd Dr, Gs Sg + Gd Dg, Bs Sb + Bd Db, As Sa + Ad Da)
OpenGL вычислит результат смешивания двух пикселей, основанный на уравнении выше. Буквы s и r в нижнем регистре задают пиксели источника и приемника. S и D компоненты — коэффициенты смешивания (R,G,B,A — составляющие цвета). Их значения указывают, как бы Вы хотели смешать пиксели. Обычные значения для S и D: (As, As, As, As) (так называемые альфа источника (source alpha)) для S и (1, 1, 1, 1) - (As, As, As, As) (один минус альфа источника) для D. Отсюда уравнение смешивания будет выглядеть следующим образом:
(Rs As + Rd (1 - As), Gs As + Gd (1 - As),
Bs As + Bs (1 - As), As As + Ad (1 - As))
Это уравнение определит стиль прозрачности/полу прозрачности.
Смешивание в OpenGL
Мы включаем эффект смешивания так же как что-либо другое. Затем мы задаем уравнение и выключаем буфер глубины на запись при рисовании прозрачных объектов, поскольку мы хотим увидеть объекты позади нарисованных полупрозрачных фигур. Это не идеальный способ смешивания, но в нашем простом проекте он будет работать достаточно хорошо.
Комментарий Rui Martins: более правильный путь состоит в том, чтобы вывести все прозрачные (с альфой < 1.0) полигоны после того, как Вы вывели полную сцену, и выводить их в обратном порядке глубины (сначала — дальние).
Это оттого, что смешивание двух полигонов (1 и 2) в различном порядке дает различные результаты, то есть если полигон 1 - самый близкий к наблюдателю, то нужно вывести полигон 2 и затем 1. Если провести аналогию с реальностью, то свет, проходящий сквозь эти два полигона (которые являются прозрачными), должен пройти сначала 2-й полигон и затем полигон 1 до того как достигнет глаза наблюдателя.
Вы должны СОРТИРОВАТЬ ПРОЗРАЧНЫЕ ПОЛИГОНЫ ПО ГЛУБИНЕ и выводить их ПОСЛЕ ТОГО, КАК БЫЛА ОТРИСОВАНА ПОЛНАЯ СЦЕНА с ВЫКЛЮЧЕННЫМ БУФЕРОМ ГЛУБИНЫ, или получится неправильно. Я знаю, что это иногда трудно, но это - правильный способ делать это.
Мы будем использовать код от урока семь. Мы начинаем, прибавляя две новых переменных в начало кода. Я приведу повторно полный раздел кода для ясности.
#include <windows.h> // Заголовочный файл для Windows
#include <stdio.h> // Заголовочный файл для стандартного ввода/вывода
#include <gl\gl.h> // Заголовочный файл для библиотеки OpenGL32
#include <gl\glu.h> // Заголовочный файл для для библиотеки GLu32
#include <gl\glaux.h> // Заголовочный файл для библиотеки GLaux
HDC hDC=NULL; // приватный контекст устройства GDI
HGLRC hRC=NULL; // постоянный контекст рендеринга
HWND hWnd=NULL; // содержит дискриптор нашего окна
HINSTANCE hInstance; // содержит экземпляр нашего приложения
bool keys[256]; // массив для процедуры обработки клавиатуры
bool active=TRUE; // флаг активности окна, по умолчанию TRUE
bool fullscreen=TRUE; // флаг полноэкранного режима, по умолчанию полный экран
bool blend; // Смешивание НЕТ/ДА? (НОВОЕ)
bool light; // Освещение Вкл./Выкл.
bool lp; // L Нажата?
bool fp; // F Нажата?
bool bp; // B Нажата? ( Новое )
GLfloat xrot; // Вращение вдоль оси X
GLfloat yrot; // Вращение вдоль оси Y
GLfloat xspeed; // Скорость вращения вдоль оси X
GLfloat yspeed; // Скорость вращения вдоль оси X
GLfloat z=-5.0f; // Глубина в экран.
// Задаем параметры освещения
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightPosition[]={ 0.0f, 0.0f, 2.0f, 1.0f };
GLuint filter; // Используемый фильтр для текстур
GLuint texture[3]; // Хранит 3 текстуры
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Объявление для WndProc
Спускаемся вниз до LoadGLTextures(). Найдите строку, которая говорит texture1 = auxDIBImageLoad ("Data/crate.bmp"), измените ее на строку ниже. Мы используем текстуру окрашенного стекла для этого урока вместо текстуры ящика.
// Загрузка текстуры стекла (МОДИФИЦИРОВАННО)
texture1 = auxDIBImageLoad("Data/glass.bmp");
Прибавьте следующие две строки где-нибудь в InitGL(). Первая строка задает яркость для отрисовки объекта, равную полной яркости с альфой 50 % (непрозрачность). Это означает, когда включается смешивание, объект будет на 50% прозрачный. Вторая строка задает тип смешивания.
Комментарий Rui Martins: альфа, равное 0.0 подразумевало бы, что материал полностью прозрачен. Значение 1.0 было бы сиречь полностью непрозрачно.
glColor4f(1.0f,1.0f,1.0f,0.5f); // Полная яркость, 50% альфа (НОВОЕ)
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Функция смешивания для непрозрачности,
// базирующаяся на значении альфы(НОВОЕ)
Найдите следующий раздел кода, он должен быть в самом конце урока семь.
if (keys[VK_LEFT]) // Нажата левая стрелка?
{
yspeed-=0.01f; // уменьшаем скорость
}
Под вышеупомянутым кодом мы прибавляем следующие строки. Отслеживаем нажатие ‘B’. Если она была нажато, компьютер проверит, включено ли смешивание. Если смешивание задано, компьютер выключает его. И наоборот, если смешивание выключено, включает его.
if (keys['B'] && !bp)
{
bp=TRUE;
blend = !blend; // Инвертируем blend
if(blend) // blend TRUE?
{
glEnable(GL_BLEND); // Включаем смешивание
glDisable(GL_DEPTH_TEST); // Выключаем тест глубины
}
else
{
glDisable(GL_BLEND); // Выключаем смешивание
glEnable(GL_DEPTH_TEST); // Включаем тест глубины
}
}
if (!keys['B']) // ’B’ отжата?
{
bp=FALSE; // тогда bp возвращает ложь
}
Но как мы можем определить цвет, если используем картинку текстуры? Просто, в режиме модулирования текстуры, каждый пиксель, который отображен из текстуры умножается на текущий цвет. Поэтому, если цвет для отображения (0.5, 0.6, 0.4), мы умножаем его на цвет и получаем (0.5, 0.6, 0.4, 0.2) (альфа принимается равной 1.0, если не задается явно).
Отлично! Смешивание, действительно просто сделать в OpenGL.
Примечание от 13.11.99
Я (NeHe) модифицировал код смешивания, поэтому выводимые объекты выглядят лучше. Использование альфа значений для источника и приемника приводит к появлению артефактов при смешивании. От того, что задние грани выглядят более темными, чем боковые грани. От этого объект будет выглядеть очень странно. Способ, с помощью которого я делаю смешивание, может быть не лучший, но он работает, и объекты выглядят хорошо, даже когда включено освещение. Спасибо Тому за начальный код, способ, с помощью которого он смешивал , был правильный способ для смешивания с альфа значениями, но не выглядел привлекательно и как ожидают этого люди ;).
Код был модифицирован еще раз, для того чтобы исключить проблему, которая возникала при использовании glDepthMask(). Это команда не эффективно разрешала и запрещала тест буфера глубины на некоторых видеокартах, поэтому я восстановил старый код с glEnable и glDisable теста глубины.
Альфа из картинки текстуры
Альфа значение, которое используется для прозрачности, может быть получено из картинки текстуры точно также как цвет, для того чтобы сделать это, вы должны извлечь альфа из изображения, которое Вы хотите загрузить, и затем использовать GL_RGBA для формата цвета при вызове glTexImage2D().
Вопросы?
Если у Вас есть вопросы, не стесняйтесь послать их по адресу stanis@cs.wisc.edu <mailto:stanis@cs.wisc.edu>.
© Tom Stanis
27 февраля 2002 (c) Vlad Tushevskij