OpenGL Tips

Содержание:

Как избежать лишних операций очистки Z-буфера

Большинству кадров в OpenGL предшествует операция glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT), которая одновременно очищает цветовой буфер кадра, и Z-буфер. Однако при больших разрешениях время очистки может составить до нескольких миллисекунд. Эту задержку очень желательно было бы сократить, поскольку она напрямую влияет на число кадров в секунду. Ускорения можно достичь, пожертвовав 1 (одним) битом точности.

На профессиональных картах, где разрядность Z составляет 32 бита, это можно проделать безболезненно. Тем более, что при размере окна 640x480 для очистки 32-битного Z-буфера нужно заполнить 600 (!) Кб памяти перед каждым кадром.

На недорогих изделиях типа Permedia2 (16-битный буфер), выигрыш в скорости будет меньше (при том же разрешении Z-буфер занимает 300 Кб), а качество изображения заметно ухудшится.

Технология следующая: в начале работы программы мы устанавливаем glDepthRange(0.0,1.0), то есть полностью задействуем Z-буфер. Затем очищаем его вызовом glClear(GL_DEPTH_BUFFER_BIT); Дополнительно вводим переменную Even=1 (четный кадр).

Перед каждым четным кадром (если Even=1) идет код:

glDepthRange(0.0,0.499999);

glDepthFunc(GL_LESS);

Перед каждым нечетным (если Even=0):

glDepthRange(1.0,0.5);

glDepthFunc(GL_GREATER);

После каждого кадра инвертируем переменную: Even=! Even.

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

Метод применяется в играх GLQuake и Quake II. Переключается с консоли командой GL_ZTRICK с числовым параметром 1 или 0.

Ссылки:

  1. Andy Bigos, "Journal of Graphics Tools", 17 ноября 1995 года


Как переключится в полноэкранный режим в OpenGL ? Какие функции для этого существуют ?

В OpenGL нет полноэкранного режима, только оконный. Это, однако, не проблема - ничто не мешает сделать окно размером во весь экран (скажем, 1024x768); истинные размеры экрана можно получить с помощью функции GetSystemMetrics(SM_CYSCREEN).

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

И еще одно замечание. Чтобы курсор мыши не портил вид, его можно спрятать с помощью функции SetCursor(NULL), а показать обратно - используя ту же функцию, но с правильным хэндлом курсора (HCURSOR) в качестве параметра.


Есть ли поддержка .3DS-файлов в OpenGL (как .X в Direxct3D) ?

Не поддерживается не только 3DS, но и никакой другой формат. OpenGL - низкоуровневый интерфейс, служащий только для визуализации модели. Это чуть выше уровнем, чем команды графического чипа.

С целью поддержки разработчиков автор 3D Studio, фирма Autodesk (), написала 3DStudio File Toolkit - библиотеку для чтения, создания и модификации 3DS-файлов. С OpenGL библиотека не связана, но с помощью нее можно сравнительно легко получить из 3DS-файла объекты, камеры и источники света.

Затем, есть такой продукт, как OpenGL Optimizer (OO), он поддерживает формат Open Inventor (SGI).

В принципе, существуют трансляторы трехмерной модели в код OpenGL на языке Си (например, среда моделирования NuGraph компании , и конвертор форматов PolyTrans делают это), однако это ошибочный путь: даже не очень сложная сцена превращается в текстовый файл размером в сотни килобайт (помню, один раз было 600 Кб). Естественно, компиляция таких исходников идет около получаса, если, конечно, компилятор вообще не отказывается их обрабатывать. Кстати, качество тоже сильно страдает.

Короче говоря, лучше всего или написать собственную библиотеку поддержки .3DS, или использовать существующие надстройки над OpenGL; они, как правило, объектно-ориентированные.


Как вывести на экран картинку в формате .BMP ?

В OpenGL нет поддержки графических файлов. Впрочем, для x86-платформ Microsoft распространяет статическую библиотеку GLAUX.LIB - с помощью нее можно загружать в память файлы .DIB и .BMP (в формате RGB 8-8-8). Вывести загруженную картинку на экран можно, как минимум, двумя способами:

 

Первый - функция glDrawPixels, которая рисует картинку, начиная с текущей растровой позиции (glRasterPos...). Недостаток - каждый вывод картинки вызывает ее передачу по системной шине в видеокарту. Затем, могут быть проблемы с отсечением вышедшей за пределы экрана части.

 

Второй - загрузка изображения в текстурную память с помощью glTexImage2D, а затем вывод четырехугольника (GL_QUADS) с этой картинкой в качестве текстуры. Достоинство - более быстрая работа при наличии на карте большого объема памяти (от 8 Мб). Недостаток - излишняя сложность метода, и ненужные геометрические преобразования, которые вынуждено выполнять устройство.

Кроме того, при втором способе размеры текстуры по горизонтали и вертикали должны быть степени числа 2. Можно, конечно, округлить размер картинки вверх до следующих степеней 2, а изображение копировать в текстурную память как часть текстуры, с помощью функции glTexSubImage2D (эта функция допускает любые размеры, в том числе и не ^2). Но это приведет к неоправданному расходованию памяти. Например, если картинка имеет размер 640x480, 24 бита/цвет, то потребуется текстура 1024x512; при этом ~40 % выделенной текстурной памяти не будет использоваться (640*480*3 / 1024*512*3 = 0.58)


Как выводить через OpenGL курсор мыши ?

Специальной поддержки мыши нет. Можно использовать картинку с каналом прозрачности (alpha) в формате RGBA или BGRA, смотрите предыдущий пункт.


Как реализуется прозрачность объектов в OpenGL ?

Прозрачность достигается с помощью смешения цветов (alpha blending)

Для этого

  1. Желательно, но не обязательно сортировать полигоны "от дальнего к ближнему"

  2. При выводе прозрачных полигонов включать режим смешивания: glEnable(GL_BLEND),

  3. Предварительно установить желаемый способ смешивания; это делается с помощью функции glBlendFunc(src, dst)

Наиболее часто используется glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), поскольку она не требует аппаратных альфа-плоскостей; последние поддерживаются далеко не всеми видеокартами.

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


Есть ли поддержка теней в OpenGL ?

Непосредственно - нет. OpenGL - это набор элементарных низкоуровневых функций. Необходимый минимум, достаточный для простого и эффективного управления устройством. Тени реализуются различными алгоритмами (достаточно сложными, и часто многопроходными, то есть сцена рендерится несколько раз с разными параметрами, или с разных позиций наблюдения). Как примеры, приведем три основных метода:

  1. "Проективные тени" - как проекция затеняющего объекта на другие. Достоинства - простота и нетребовательность к железу. Поэтому в играх применяются, в основном, проективные тени.
    Недостатки: выполняется целиком за счет центрального процессора; будет работать слишком медленно, если тень отбрасывается сложным объектом и/или на сложный объект, а не на плоскость; в отдельных случаях тени могут или "уйти" в объект, на который должны отбрасываться, или могут "висеть" над ним.
    Пследнюю проблему помогает решить принудительное смещение в Z-буфере (glPolygonOffsetEXT), но эта функция пока имеет статус расширения, и поддерживается не всеми драйверами.

  2. С помощью "стенсила" - очень красивый и эффективный метод; однако скорость и качество напрямую зависят от размер буфера стенсила (stencil).  Метод относится к многопроходным (число проходов, как минимум, равно числу источников света), и называется "shadow volumes". Многие, но не все видеокарты имеют стенсил (скажем, у продукции 3Dfx - Voodoo, Voodoo2 и т.д. его нет). У Permedia 2 стенсил - всего 1 бит, то есть возрастает число проходов.

  3. Карты теней - "shadow maps". Тени предварительно вычисляются, создаются соответствующие текстуры (карты теней) . Затем уже готовые текстуры с тенями накладываются на объект. Достоинство - эти текстуры можно повторно использовать, недостатки - размер текстуры ограничен, и это вносит искажения в вид тени. Более подробную информацию можно получить в материалах с SiGraph '97:


Что такое glPolygonOffset ?

glPolygonOffset - это новая функция в OpenGL 1.1. Пока она, к сожалению, имеет статус расширения. Ее прототип имеет вид: void glPolygonOffset(GLfloat factor, GLfloat units);

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

new_depth = depth + factor*slope + DELTA*units,

где slope - это разница между минимальным и максимальным значением depth для видимой части треугольника,

а DELTA - константа, специфичная для драйвера / библиотеки OpenGL в каждом отдельно взятом случае; по cути, это минимальное значение, которое надо прибавить к depth, чтобы гарантированно была разница при Z-тесте между depth и new_depth, даже если slope = 0

Пример нормальных параметров - glPolygonOffset(1.0, 2.0). Практически же лучше подбирать экспериментально, в зависимости от разрядности Z-буфера (для 32-битного должны сгодиться практически любые значения)

Да, и еще одно замечание: включение/выключение функции идет с помощью glEnable / glDisable(GL_POLYGON_OFFSET). Смещение полигонов (polygon offset) применяется ДО того, как выполняется Z-тест и происходит запись нового значения в Z-буфер. Но это НЕ влияет на режим обратной связи (feedback), поскольку не связано с блоком геометрии. Пример на картинке взят из SGI OpenGL 1.1 SDK.

В центре - без polygon offset, справа - с включенным polygon offset. Как видно из рисунка, даже если координаты совпадают, нужный объект все равно ляжет поверх других.

Возможные применения glPolygonOffset: проективные тени (projective shadows), следы колес автомобиля на дороге, дырки от пуль на стене.


Проблемы с Z-буфером; 16-и бит недостаточно для получения нормального изображения. Что делать ?

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

Проблема с Z-буфером заключается в Z-алиасинге (то есть ситуации, когда элементы более удаленных от наблюдателя объектов "пролезают" через более близкие объекты). Причина кроется в следующем: матрица проекции (GL_PROJECTION)   размером 4x4 задается функций glFrustrum, и имеет следующий вид:

2 * near


right - left

0 right + left

right - left

0
0 2 * near

top - bottom

top + bottom

top - bottom

0
0 0 - (far + near)

far - near

- 2 * far * near

far - near

0 0 -1 0

Принципиальное значение для Z-разрешения имеют дроби, содержащие near и far - расстояние от глаза до экрана, и дальность обзора соответственно. Поскольку near обычно на 1-2 порядка меньше, чем far, можно сказать, что Z-разрешение определяется отношением far / near. Если это отношение больше, чем 2 в степени разрядность Z-буфера (2Z-bits), то depth test начинает работать неправильно. Например, если far=10,000, а near=0.1, отношение far / near=100,000 > 65,536 (216), и возникает Z-алиасинг.

В любом случае, число потерянных битов Z-буфера приближенно определяется по формуле Log2(far / near). Поэтому near никогда не должно быть 0 (однако отрицательные значения допустимы, так, gluOrtho2D устанавливает near=-1 и far=1)


После масштабирования объекта (функция glScale) он неправильно освещается истоником света. Это проблема моей видеокарты, или OpenGL ?

Если речь идет о странных аномалиях освещения, связанных с масштабированием матрицы modelview (glScale...), то это проблема OpenGL вообще. Дело в том, что умножению на modelview подвергаются не только координаты вершин, но и векторы нормалей. По идее, их нужно только поворачивать в пространстве, но они такжесдвигаются и масштабируются (glTranslate..., glScale).

Для исправления проблемы нужно включать автоматическую ренормализацию нормалей

glEnable(GL_NORMALIZE),

то есть если после умножения на modelview вектор-нормаль состоит из компонент (x,y,z), то GL_NORMALIZE делает следующее:

L=sqrt(x2 + y2 + z2) - вычисляется длина вектора нормали, затем

x=x / L

y=y / L

z=z / L

Естественно, это снижает скорость (sqrt, все-таки)

Поэтому в OpenGL 1.2 введено расширение GL_EXT_rescale_normal, которое просто делит нормаль на соответствующие элементы матрицы modelview, и получает правильное значение вектора нормали. Деление быстрее нормализации, а результат будет тот же.


Можно ли в OpenGL получить прямой доступ к видеопамяти, как это делается в DirectX ?

Нет, OpenGL - клиент-серверная идеология; связь между клиентом и сервером осуществляется по потенциально медленному каналу связи (как пример - скорость шин PCI и AGP намного ниже, чем внутренних трактов графического процессора, и шины 3D-чип - локальная видеопамять).

Видеопамять находится на сервере, в роли которого может выступать как Ваша видеокарта, так и другой компьютер. В связи с этим функции вроде glReadPixels, glDrawPixels, glTexImage... и т.д. работают ужасно медленно, и, кроме этого, вызывают блокировку 3D-конвейера до завершения операции.


Где хранятся дисплейные списки (display lists) ? Можно ли получить какой-либо доступ к этой области памяти ?

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

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


Как мне установить нужное разрешение в OpenGL на 3Dfx Voodoo ?

На текущий момент полноценного OpenGL-драйвера для семейства Voodoo не существует, поэтому использующиеся вместо него врапперы вроде 3Dfx GL 2.0 / 2.1, miniGL и т.д.  опираются в своей работе на собственную низкоуровневую библиотеку компании 3Dfx - GLide.

GLide работает только в полноэкранном режиме. Разрешение выбирается автоматически, исходя из размеров окна Вашей программы. Например, окно 640x480 активизирует стандартный видеорежим 640x480, но если скажем, размер окна 320x240, будет выбран ближайший режим - 512x384.


Как мне узнать, используется ли мой 3D-акселератор, или OpenGL работает в режиме программной эмуляции ?

В первую очередь, это заметно по скорости ;-)

С помощью функции DescribePixelFormat можно получить описание видеоформата для окна - в виде структуры PIXELFORMATDESCRIPTOR.

Каждый бит поля dwFlags в ней указывает на наличие какой-либо возможности (например, GL_STEREO - стереоскопическое изображения). В данном случае, для нас сейчас важны биты PFD_GENERIC_ACCELERATED и PFD_GENERIC_FORMAT. Возможны их следующие сочетания:

PFD_GENERIC_ACCELERATED

PFD_GENERIC_FORMAT

Смысл

0 0 полноценный ICD-драйвер с функциями ускорения
0 1 <недопустимая комбинация>
1 0 режим программной эмуляции; всю работу выполняет центральный процессор
1 1 MCD-драйвер; аппаратно реализуется только часть функций ускорения

Кроме того, функция glGetString(GL_RENDERER) возвращает строку с названием устройства, которая содержит имя вашей видеокарты, установленного на ней 3D-чипа, или один из следующих текстов:

  • "GDI Generic" - программный Microsoft OpenGL
  • "Generic/MMX" - программный SGI OpenGL
  • "Mesa" - при использовании

Чем отличается OpenGL 1.1.0 от 1.1.2 ?

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


Как связаны угол обзора в OpenGL, и длина объектива камеры (в мм) ?

Угол обзора в OpenGL берётся по оси Y, то есть в вертикальной плоскости.

Что касается угла обзора для реальной камеры, то это зависит от типа используемой плёнки. Например, у стандартной 35-миллиметровой каждый кадр имеет размер 36x24 мм (W x H, то есть ширина x высота). Таким образом, при длине объектива F=50 мм тангенс половины вертикального угла обзора будет

tan(angle/2) = (H/2) / F, или tan(angle/2) = (24/2 мм) / 50 мм; тогда

angle = 2 * arctan(12 мм / 50 мм) = 2 * 13.5° = 27° (предполагается, что функция arctan возвращает угол в градусах).


Где мне найти описания форматов 2D- и 3D-файлов ?

Прекрасные коллекции документации на эту тему находятся на серверах:

ftp://sgi.felk.cvut.cz/pub/avalon


Мне нужно выделить работу с OpenGL в отдельный поток (thread). Как мне это сделать ?

Основные положения многопоточности OpenGL таковы:

  1. Каждый поток в OpenGL может иметь свой, активный контекст устройства (RC), но только один.
    
    Именно туда поступают все gl-команды из потока.
  2. Контекст не может быть активным одновременно у нескольких потоков.

Для этого существует функция wglMakeCurrent. Однако, поскольку периодические активизация/деактивизация контекста могут серьёзно замедлить программу, лучше всего будет "захватывать" контекст при запуске потока, и "отпускать" при его завершении.

Например:

DWORD WINAPI ThreadFunc(LPVOID lpData)
{
// Получение входных параметров из блока, передаваемого по адресу lpData
hDC=....
hRC=....

// Получить контекст в своё распоряжение
wglMakeCurrent(hDC,hRC);

// Код потока
.....
....
//

// "Отпустить" контекст
wglMakeCurrent(hDC,NULL);
return 0;
}

Разумеется, нужно самому предусмотреть синхронизацию этого и основного потоков, а также механизм передачи "задания".

 Есть ещё один важный момент. Если все текстуры загружены и дисплейные списки (display lists - DL) созданы ещё ДО запуска такого потока - прекрасно; если же их нужно подгружать и удалять в процессе работы, нужно ещё кое-что.

Можно создать ВТОРОЙ контекст устройства (в невидимом окне размером 1x1 пиксел), который будет активным у основного потока программы. Для объединения адресных пространств двух контекстов используется wglShareLists(dest,source).

Начиная с OpenGL версии 1.1, эта функция импортирует не только адресное пространство дисплейных списков (glGenLists,glDeleteLists), но и текстурных объектов (glGenTextures,glDeleteTextures). Все изменения, сделанные в одном контексте, будут автоматически отображаться в другой.

wglShareLists нужно вызывать сразу же после создания контекстов, пока ещё не определено никаких текстур и DL. Желательно также, но совсем не обязательно, чтобы оба контекста имели одинаковый формат (SetPixelFormat).

Примечание: многопоточность OpenGL невозможна на враперах вроде 3Dfx GL / miniGL из-за ограничений их нижнего этажа, GLide. В этих случаях контексты будут успешно создаваться, и каждый поток получит контекст в своё распоряжение, однако только один из них будет реально подключен к устройству (последний созданный, или тот, который сделали текущим для какого-либо потока с помощью функции wglMakeCurrent в последнюю очередь).

Попытка выводить что-то из других контекстов вызовет ошибку защиты памяти в коде враппера.



Главная страница
 
Hosted by uCoz