Урок 46. Полноэкранное сглаживание.
Привет всем, дружелюбный соседский таракан (the Friendly Neighborhood Roach) здесь с интересным примером, который поможет вашим приложениям достигнуть небывалых вершин. Мы все сталкиваемся с большой проблемой, когда хотим чтобы наши программы на OpenGL лучше выглядели. Этот эффект называется «пикселизация» (или зубчатость или ступенчатость - aliasing), и представляет из себя квадратные зубцы на диагональных гранях относительно квадратных пикселей на вашем экране. Решение проблемы — Anti-Aliasing (Сглаживание граней) используется для размытия таких зубцов, что позволяет создавать более гладкие грани объектов. Один из способов, позволяющих получить сглаживание, называется “Multisampling” (мультисэмплинг, или мультиопрос, или мультивыборка, или далее переводится как метод множественной выборки). Идея состоит в том, чтобы для каждого пикселя выбрать окружающие его пиксели (или субпиксели) с целью определить, нуждается ли данная грань в сглаживании (если пиксель не принадлежит грани, то сглаживать его не надо), но обычно мы избавляемся от ступенчатости при помощи "размазывания" самого пикселя.
Fullscreen AntiAliasing (полноэкранное сглаживание) — это то, в чем программы визуализации, не в масштабах реального времени, всегда имели преимущество. Однако с сегодняшним аппаратным обеспечением мы способны добиться схожего эффекта в реальном времени. Расширение ARB_MULTISAMPLE позволяет нам это сделать. По существу, каждый пиксель представлен своими соседями для определения оптимального сглаживания. Как бы то ни было этот процесс дорого обходится и может замедлить производительность. Например, требуется больше видеопамяти:
Vid_mem = sizeof(Front_buffer) +
sizeof(Back_buffer) +
num_samples * (sizeof(Front_buffer) +sizeof(Z_buffer))
Более подробную информацию относительно сглаживания, также как о том, что я собираюсь рассказать, можно найти по этим адресам:
GDC2002 -- OpenGL Multisample (http://developer.nvidia.com/attach/3464)
OpenGL Pixel Formats and Multisample Antialiasing (http://developer.nvidia.com/attach/2064)
Антиалиасинг сегодня (http://www.nvworld.ru/docs/fsaa2.html)
Вот краткий обзор того, как наш метод будет работать, с учетом вышесказанного. В отличие от других расширений, касающихся визуализации OpenGL, расширение ARB_MULTISAMPLE включается в работу при создании окна визуализации.
Наш процесс выглядит следующим образом:
· Создается обычное окно
· Собираем возможные значения форматов пикселей для последующего сглаживания (InitMultisample)
· Если сглаживание возможно, то уничтожаем окно и создаем его заново, с новым форматом пикселя.
· Для частей, которые мы хотим сгладить, просто вызываем функцию glEnable(GL_ARB_MULTISAMPLE).
Начнем с начала и поговорим о нашем исходном файле — arbMultiSample.cpp. Начинаем со стандартного включения заголовочных файлов gl.h и glu.h, а также windows.h. О arb_multisample.h мы поговорим позже.
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include "arb_multisample.h"
Две строчки ниже определяют точки входа в список строк WGL. Мы будем их использовать при доступе к атрибутам формата пикселя для определения формата нашего типа. Две другие переменные нужны для доступа к нашим данным.
// Объявления, которые мы будем использовать
#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
bool arbMultisampleSupported = false;
int arbMultisampleFormat = 0;
Следующей функцией, о которой мы поговорим, является WGLisExtensionSupported, которая будет использована для сбора информации о WGL расширениях для определения поддерживаемого формата на нашей системе. Мы будем давать описания кода по мере продвижения по нему, так как это легче чем прыгать по странице туда сюда.
Примечание: Код внизу написан Генри Гоффином. Его изменения внесли: Улучшенный разбор расширений GL и решили проблему с выпаданием кода, если первая проверка была не успешной.
bool WGLisExtensionSupported(const char *extension)
{
const size_t extlen = strlen(extension);
const char *supported = NULL;
// попытка использовать wglGetExtensionStringARB на текущем контексте, если возможно
PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");
if (wglGetExtString)
supported = ((char*(__stdcall*)(HDC))wglGetExtString)(wglGetCurrentDC());
// Если проверка не пройдена, то попытаемся использовать стандартное расширение OpenGL
if (supported == NULL)
supported = (char*)glGetString(GL_EXTENSIONS);
// Если и это не поддерживается, тогда работаем без расширений
if (supported == NULL)
return false;
// Начинаем проверку с начала строки, увеличиваем на 1, при false совпадение
for (const char* p = supported; ; p++)
{
// Продвигаем p до следующего возможного совпадения
p = strstr(p, extension);
if (p == NULL)
return false; // Совпадения нет
//Убедимся, что есть совпадение в начале строки,
//или первый символ — пробел, или может быть случайное
//совпадение "wglFunkywglExtension" с "wglExtension"
// Также убедимся, что текущий символ пустой или пробел
// или еще "wglExtensionTwo" может совпасть с "wglExtension"
if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' '))
return true; // Совпадение
}
}
Примечание переводчика:
Работа с wglGetProcAddress описана в уроке 22. Функция const char *wglGetExtensionsStringARB(HDC hdc) возвращает строку с перечнем расширений, hdc — контекст устройства.
Следующая функция — собственно суть всей программы. В сущности, мы собираемся выяснить поддерживается ли наше arb расширение на нашей системе. По сему мы будем запрашивать контекст устройства с целью выяснить наличие метода множественной выборки. Опять… давайте просто перейдем к коду.
bool InitMultisample(HINSTANCE hInstance,HWND hWnd,PIXELFORMATDESCRIPTOR pfd)
{
// посмотрим, есть ли строка в WGL!
if (!WGLisExtensionSupported("WGL_ARB_multisample "))
{
arbMultisampleSupported=false;
return false;
}
// Возьмем наш формат пикселя
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB =
(PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
if (!wglChoosePixelFormatARB)
{
// Мы не нашли поддержки для метода множественной выборки, выставим наш флаг и выйдем.
arbMultisampleSupported=false;
return false;
}
// Получаем контекст нашего устройства. Нам это необходимо для того, что
// спросить у OpenGL окна, какие атрибуты у нас есть
HDC hDC = GetDC(hWnd);
int pixelFormat;
bool valid;
UINT numFormats;
float fAttributes[] = {0,0};
// Эти атрибуты — биты, которые мы хотим протестировать в нашем типе
// Все довольно стандартно, только одно на чем мы хотим
// действительно сфокусироваться - это SAMPLE BUFFERS ARB и WGL SAMPLES
// Они выполнят главную проверку на предмет: есть или нет
// у нас поддержка множественной выборки
int iAttributes[] = {
WGL_DRAW_TO_WINDOW_ARB,GL_TRUE, // Истинна, если формат пикселя может быть использован в окне
WGL_SUPPORT_OPENGL_ARB,GL_TRUE, // Истинна, если поддерживается OpenGL
WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, // Полная аппаратная поддержка
WGL_COLOR_BITS_ARB,24, // Цветность
WGL_ALPHA_BITS_ARB,8, // Размерность альфа-канала
WGL_DEPTH_BITS_ARB,16, // Глубина буфера глубины
WGL_STENCIL_BITS_ARB,0, // Глубина буфера шаблона
WGL_DOUBLE_BUFFER_ARB,GL_TRUE, // Истина, если используется двойная буферизация
WGL_SAMPLE_BUFFERS_ARB,GL_TRUE, // Что мы и хотим
WGL_SAMPLES_ARB, 4 , // проверка на 4x тип
0,0};
// Сначала посмотрим, сможем ли мы получить формат пикселя для 4x типа
valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
// Если вернулось True, и наш счетчик форматов больше 1
if (valid && numFormats >= 1)
{
arbMultisampleSupported = true;
arbMultisampleFormat = pixelFormat;
return arbMultisampleSupported;
}
// Формат пикселя с 4x выборкой отсутствует, проверяем на 2x тип
iAttributes[19] = 2;
valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
if (valid && numFormats >= 1)
{
arbMultisampleSupported = true;
arbMultisampleFormat = pixelFormat;
return arbMultisampleSupported;
}
// возвращаем годный формат
return arbMultisampleSupported;
}
Примечание переводчика:
Функция:
BOOL wglChoosePixelFormatARB(HDC hdc,
const GLint *piAttribIList,
const GLfloat *pfAttribFList,
GLuint nMaxFormats,
GLint *piFormats,
GLuint *nNumFormats);
Выбирает форматы пикселя согласно запрашиваемым атрибутам. hdc — контекст устройства, piAttribIList или pfAttribFList — список желаемых атрибутов (пары {тип, значение} формате целого числа или в формате с плавающей запятой, в конце списка {0,0}, значения типов атрибутов задаются объявлениями define выше или взяты из wglext.h, значение зависит от типа). nMaxFormats — максимальное число форматов, которое будет возвращено. piFormats — массив индексов форматов пикселов, которые совпадают с запрашиваемым. Наилучший формат будет первым. nNumFormats — сколько форматов найдено при запросе.
Теперь, когда у нас есть готовый код запроса, мы должны изменить процесс создания окна. Сперва, мы должны включить наш заголовочный файл arb_multisample.h и поместить прототипы DestroyWindow и CreateWindow в начало файла.
#include <windows.h> // Заголовочный файл библиотеки Windows
#include <gl/gl.h> // Заголовочный файл библиотеки OpenGL32
#include <gl/glu.h> // Заголовочный файл библиотеки GLu32
#include "NeHeGL.h" // Заголовочный файл NeHeGL основного кода
// ЗДЕСЬ ПРОБЕЖАЛ ТАРАКАН
#include "ARB_MULTISAMPLE.h"
BOOL DestroyWindowGL (GL_Window* window);
BOOL CreateWindowGL (GL_Window* window);
// ENDТАРАКАН
Следующий кусок кода должен быть добавлен в функцию CreateWindowGL, код функции был оставлен без изменений, дабы вы могли вносить свои модификации. В общем, мы делаем часть «уничтожения» до конца работы. Мы не можем запросить формат пикселя, для определения типа множественной выборки, пока мы не создадим окно. Но мы не можем создать окно, пока не знаем формат пикселя, который оно поддерживает. Сродни вечному вопросу о курице и яйце. Итак, все, что я сделал — это маленькая двухпроходная система; мы создаем окно, определяем формат пикселя, затем уничтожаем (пересоздаем) окно, если метод множественной выборки поддерживается. Немного круто…
window->hDC = GetDC (window->hWnd); // Забираем контекст данного окна
if (window->hDC == 0) // Мы получили контекст устройства?
{
// Нет
DestroyWindow (window->hWnd); // Уничтожаем окно
window->hWnd = 0; // Обнуляем указатель
return FALSE; // возвращаем False
}
// ЗДЕСЬ ПРОБЕЖАЛ ТАРАКАН
// Наш первый проход, множественная выборка пока не подключена, так что мы создаем обычное окно
// Если поддержка есть, тогда мы идем на второй проход
// это значит, что мы хотим использовать наш формат пикселя для выборки
// и так, установим PixelFormat в arbMultiSampleformat вместо текущего
if(!arbMultisampleSupported)
{
PixelFormat = ChoosePixelFormat (window->hDC, &pfd); // найдем совместимый формат пикселя
if (PixelFormat == 0) // мы нашли его?
{
// Нет
ReleaseDC (window->hWnd, window->hDC); // Освобождаем контекст устройства
window->hDC = 0; // Обнуляем контекст
DestroyWindow (window->hWnd); // Уничтожаем окно
window->hWnd = 0; // Обнуляем указатель окна
return FALSE; // возвращаем False
}
}
else
{
PixelFormat = arbMultisampleFormat;
}
// ENDТАРАКАН
// пытаемся установить формат пикселя
if (SetPixelFormat (window->hDC, PixelFormat, &pfd) == FALSE)
{
// Неудача
ReleaseDC (window->hWnd, window->hDC); // Освобождаем контекст устройства
window->hDC = 0; // Обнуляем контекст
DestroyWindow (window->hWnd); // Уничтожаем окно
window->hWnd = 0; // Обнуляем указатель окна
return FALSE; // возвращаем False
}
Теперь окно создано и у нас есть правильный указатель, чтобы запросить поддержку множественной выборки. Если поддержка есть, то мы уничтожаем окно и опять вызываем CreateWindowGL с новым форматом пикселя.
// Сделаем контекст визуализации нашим текущим контекстом
if (wglMakeCurrent (window->hDC, window->hRC) == FALSE)
{
// Не удалось
wglDeleteContext (window->hRC); // Уничтожаем контекст визуализации
window->hRC = 0; // Обнуляем контекст визуализации
ReleaseDC (window->hWnd, window->hDC); // Освобождаем контекст устройства
window->hDC = 0; // Обнуляем его
DestroyWindow (window->hWnd); // Уничтожаем окно
window->hWnd = 0; // Обнуляем указатель окна
return FALSE; // возвращаем False
}
// ЗДЕСЬ ПРОБЕЖАЛ ТАРАКАН
// Теперь, когда наше окно создано, мы хотим узнать какие типы доступны.
// Мы вызываем нашу функцию InitMultiSample для создания окна
// если вернулся правильный контекст, то мы уничтожаем текущее окно
// и создаем новой, используя интерфейс множественной выборки.
if(!arbMultisampleSupported && CHECK_FOR_MULTISAMPLE)
{
if(InitMultisample(window->init.application->hInstance,window->hWnd,pfd))
{
DestroyWindowGL (window);
return CreateWindowGL(window);
}
}
// ENDТАРАКАН
ShowWindow (window->hWnd, SW_NORMAL); // Сделаем окно видимым
window->isVisible = TRUE;
Ок, и так настройка теперь завершена! Мы настроили наше окно для работы с методом множественной выборки. Теперь повеселимся, собственно делая это! К счастью, ARB решила сделать поддержку метода множественной выборки динамической, это позволяет нам включать и выключать ее вызовами glEnable / glDisable.
glEnable(GL_MULTISAMPLE_ARB);
// Визуализируем сцену
glDisable(GL_MULTISAMPLE_ARB);
Вот собственно и все! Далее идет код примера, в котором простые квадраты вращаются, чтобы вы могли увидеть, как хорошо метод множественной выборки работает. НАСЛАЖДАЙТЕСЬ!
© Colt "MainRoach" McAnlis
( duhroach@hotmail.com )
© Jeff Molofee (NeHe)
3 ноября 2003 (c) Олег Столоногов