Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Статьи / Работа с расширениями OpenGL с использованием NVIDIA OpenGL SDK 5.1. (Часть 1)

Работа с расширениями OpenGL с использованием NVIDIA OpenGL SDK 5.1. (Часть 1)

Автор:

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

Что такое расширение OpenGL
Инициализация WGL-расширений
Инициализация OpenGL расширений
Расширение NV_texture_rectangle

Что такое расширение OpenGL

Если производитель обнаруживал, что OpenGL не может использовать все аппаратные возможности его "железа", он выпускал расширение для OpenGL с поддержкой новых возможностей. Причем это расширение, как правило, появлялось почти на год раньше, чем новая версия DirectX, поддерживающая новые возможности. Так, поддержка вершинных и пиксельных шейдеров для GeForce2 в OpenGL появилась намного раньше 8-й версии DirectX. А когда DirectX 8 все-таки вышел, то выяснилось, что про пиксельные шейдеры GeForce2 он ничего не знает:

Наряду с достоинствами этот подход имеет и недостатки — расширения OpenGL разных производителей часто несовместимы друг с другом, даже когда они выполняют одинаковые функции. Это становится настоящей головной болью, когда программа должна поддерживать большое количество разных видеокарт. С другой стороны, существует множество стандартных расширений, которые поддерживают практически все видеокарты (разумеется, при наличии аппаратной поддержки). Названия этих расширений обычно начинаются с GL_ARB, GL_EXT и GL_SGIS.

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

Для выполнения всех примеров вам потребуются Microsoft Visual C++ 6 и NVIDIA OpenGL SDK, который можно скачать с www.nvidia.com. В составе этого SDK имеется NVIDIA OpenGL Extension Specification, в котором описаны большинство расширений, которые поддерживаются NVIDIA.

Инициализация WGL-расширений

Перед тем, как использовать любое OpenGL-расширение, его надо инициализировать. Для начала рассмотрим инициализацию WGL-расширений. Этот класс расширений предназначен для инициализации OpenGL в ОС семейства Windows, а также для выполнения некоторых функций, которые зависят от используемой операционной системы - например, работа со внеэкранными поверхностями (буферами пикселей). В приведенном ниже примере показывается, как проверить поддержку двух расширений WGL_ARB_pixel_format и WGL_ARB_pbuffer и подготовить их к использованию. Этот и все остальные примеры из статьи полностью можно скачать.

Для начала не забудьте настроить Visual C++ для работы с NVSDK (пропишите пути к включаемым файлам и библиотекам). Теперь создайте новый проект и настройте его для работы с OpenGL (подключите библиотеки opengl.lib, glu32.lib, glut32.lib, установите точку входа mainCRTStartup и т.д.)

Напишите простейшее GLUT-приложение, создающее окно:

void main()
{
  glutInitDisplayMode(GLUT_RGBA);
  glutInitWindowSize(512 ,384);
  glutInitWindowSize(100,100);
  glutCreateWindow("wglExtDemo");
}

Не забудьте директивой #include подключить заголовочные файлы glext.h (замена gl.h) и wglext.h. Перед началом работы с WGL-расширениями надо проверить поддержку расширения WGL_ARB_extensions_string, при помощи которого осуществляется инициализация остальных WGL-расширений.

Для это мы объявляем переменную, которая является указателем на функцию wglGetExtensionsStringARB. Обратите внимание, что название типа функции начинается с префикса PFN, далее идет название расширения, а затем окончание ARBPROC (в случае расширений ARB):

PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB=0;

Теперь надо получить адрес этой функции. Для этого используется функция wglGetProcAddress. В качества параметра она принимает строку, содержащую название функции, адрес которой надо получить:

wglGetExtensionsStringARB=

    (PFNWGLGETEXTENSIONSSTRINGARBPROC) wglGetProcAddress("wglGetExtensionsStringARB");

В случае удачного вызова она возвращает адрес функции, иначе - NULL.

Теперь при помощи этой функции можно получить список всех WGL-расширений, которые поддерживает данная платформа. Для этого следует вызвать эту функцию, передав ей в качестве параметра контекст текущего устройства WGL. Функция возвращает строку, оканчивающуюся нулем и содержащую список поддерживаемых WGL-расширений, разделенных пробелами. После этого проверим, поддерживаются ли требуемые нам расширения. Для этого можно использовать функцию strstr:

//----------------
ExtensionStr=strstr(WGLExtensionsList, "WGL_ARB_pixel_format");
if (!ExtensionStr)
{
  MessageBox(0, "WGL_ARB_PIXEL_FORMAT Extension not found", "Error", MB_OK | MB_ICONSTOP);
  exit(1);
}

Если все прошло удачно, то, используя функцию wglGetProcAddress, можно получить адреса точек входа нужных нам функций:

wglCreatePbufferARB=(PFNWGLCREATEPBUFFERARBPROC) wglGetProcAddress("wglCreatePbufferARB");
if (!wglCreatePbufferARB)
{
  MessageBox(0, "wglCreatePbufferARB function not found", "Error", MB_OK | MB_ICONSTOP);
  exit(1);
}

Как видно, инициализировать WGL-расширения непосредственно через OpenGL API довольно неудобно, особенно если надо подключить больше десятка различных расширений, каждое из которых содержит больше десятка функций. Но в NVIDIA OpenGL SDK входит библиотека OpenGL Helper Library, которая заметно упрощает работу с расширениями. Ее использование показано в следующем примере. Для начала вставьте в программу следующие директивы (после #include wglext.h):

#define GLH_EXT_SINGLE_FILE
#include <glh_nveb.h> 
#include <glh_extensions.h>
#include "glh_obs.h"

В этом случае, инициализировать все необходимые расширения можно с помощью одной функции glh_init_extensions, которой передается список необходимых расширений, разделенных пробелами. В случае успешного завершения функция возвращает True. В противном случае, используя функцию glh_get_unsupported_extensions(), можно получить список неподдерживаемых расширений:

if (!glh_init_extensions("WGL_ARB_pbuffer WGL_ARB_pixel_format"))
{
  MessageBox(NULL, glh_get_unsupported_extensions(), "Unsupported extensions", MB_OK | MB_ICONSTOP);
  exit(-1);
}

Это все! Теперь вы можете вызывать функции указанных расширений как обычные функции.

Инициализация OpenGL расширений

В принципе, инициализация GL-расширений должна была выполняться очень просто, путем включения в проект файла glext.h. Но на практике это не так. Причина этого кроется в том, что файл opengl.lib, поставляемый с Visual C++ 6, не содержит функций GL-расширений (если бы он их содержал, программа бы отказывалась запускаться на видеокартах, драйверы которых не полностью поддерживают все GL-расширения).

Поэтому инициализация GL-расширений выполняется аналогично инициализации WGL-расширений с единственным отличием - для получения списка GL-расширений используется функция glGetString(GL_EXTENSIONS).

Если же вы используете NVIDIA OpenGL Helper Library, то никакой разницы в инициализации GL и WGL расширений нет - функция glh_init_extensions умеет инициализировать оба вида расширений.

Использование расширений OpenGL на примере GL_EXT_separate_specular_color

Рассмотрим небольшую программу, которая рисует на экране вращающийся чайник с нанесенной текстурой. Клавишей F1 можно включать/выключать вращение. При нажатой левой кнопке мыши можно вращать чайник, а при правой - перемещать. Клавишей F2 можно сменить плоскость, в которой перемещается чайник.

Текстура чайника хранится в файле wood.tga. Для его загрузки используется библиотека NV_UTIL из состава NVSDK, которая содержит множество процедур для работы с наиболее распространенными графическими форматами. Перед использованием этой библиотеки нужно подключить к проекту библиотеку nv_util.lib.

pImage=tga::read("wood.tga");

Функции для работы с файлами формата TGA находятся в файле nv_tga.h в пространстве имен tga. Следовательно, для доступа к ним надо использовать либо директиву using namespace tga, либо указывать перед названием функций и типов этой библиотеки префикс tga::.

Загрузка файла в память осуществляется функцией tga::read, которой передается имя файла. Функция возвращает указатель на структуру tga::tgaImage. Нас интересуют следующие поля структуры:
· GLsizei width - ширина рисунка;
· GLsizei height - высота рисунка;
· GLubyte *pixels - указатель на массив пикселей;
· GLenum format - формат пикселей рисунка.

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

Хотя в примерах NVSDK это нигде не показано, я считаю, что перед удалением указателя надо удалить из памяти еще и указатель на массив пикселей (pixels), иначе он останется находится в памяти до окончания работы программы.

Теперь можно использовать эту информацию для создания текстуры.

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pImage->width, pImage->height, 
             0, pImage->format, GL_UNSIGNED_BYTE, pImage->pixels);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

Изображение
Рисунок 1.

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

Но что делать, если надо получить блик именно белого цвета на деревянном чайнике. Одно из решений проблемы - наложение текстуры на чайник в 2 прохода. Сначала мы рисуем чайник с наложенной текстурой с отключенными бликами, а затем, на втором проходе, рисуем блики без текстуры. Пример реализации этого алгоритма можно найти в книге М. Красного "OpenGL. Графика в проектах Delphi" (глава 4, пример 83).

Однако самый оптимальный способ - использовать расширение GL_EXT_separate_specular_color, которое позволяет использовать новую модель освещения: SPEPARATE_SPECULAR_COLOR_EXT. В этом случае цвета вычисляются по другому алгоритму: сначала рассчитывается цвет материала без учета бликов, затем на полученный цвет накладывается текстура. После этого на результирующий цвет накладываются блики.

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

Прежде всего в процедуру Init() добавим инициализацию расширения и сроку для включения режима освещения GL_SEPARATE_SPECULAR_COLOR_EXT. Для этого используется процедура glLightModeli с новыми параметрами.

if (!glh_init_extensions("GL_EXT_separate_specular_color"))
{
  MessageBox(NULL, glh_get_unsupported_extensions(), "Unsupported extensions", 
             MB_OK | MB_ICONSTOP);
  exit(-1);
}

//--------------------------------

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT, GL_SEPARATE_SPECULAR_COLOR_EXT);

Параметр GL_SEPARATE_SPECULAR_COLOR_EXT включает режим наложения бликов после наложения текстуры. Для возвращения к классической схеме вычисления цвета достаточно вызвать функцию с параметром GL_SINGLE_COLOR_EXT.

Изображение
Рисунок 2.

Как видно на рис. 2, результат говорит сам за себя. При этом скорость работы программы практически не изменяется, так как расширение осуществляет наложение бликов за 1 проход.

Расширение GL_SEPARATE_SPECULAR_COLOR_EXT поддерживается всеми видеокартами NVIDIA, за исключением видеокарт семейства Riva128/128ZX.

Страницы: 1 2 Следующая »

22 февраля 2002

#расширения OpenGL, #NVIDIA, #OpenGL, #SDK, #текстурирование, #текстуры


Обновление: 17 сентября 2009

2001—2018 © GameDev.ru — Разработка игр