Создание 3D игр на C++Builder
Выпуск 11
Фильрация

Должен сообщить, что у меня нет возможности поддерживать дальше рассылку. Сегодня я напишу еще об одном важном моменте, о фильтрации текстур, и на этом пока остановлюсь. Так же хочу заметить, что более ранние выпуски содержали много ошибок, которые я исправил. Обновленные выпуски лежат на сайте http://morgeyz.narod.ru/ в разделе Рассылка.
Хочу попросить вас не отписываться от рассылки - возможно, если будет время, я еще напишу несколько выпусков по загрузке моделей из 3ds и md2 файлов.

Когда мы находимся далеко от стены, нет смысла накладывать текстуру на нее целиком. Мы не только проигрываем во времени, но и в качестве. Например, пусть у нас текстура кирпичной стены, являющая собой красный квадрат, расчерченный черными прямыми. Когда мы будем настолько далеко от текстуры, что она в экранных координатах будет размером лишь 1 пиклель, при небольшом смещении этот пиксель будет то черным, то красным, что будет довольно некрасиво смотреться в общей атмосфере игры. Потому очень разумно создать изображения текстуры для всех размеров от 1*1 до реального размера текстуры. Так как OpenGL не работает с текстурами, ширина или высота которых не являются степенью двойки (она их автоматически масштабирует), не разумно создавать отдельные изображения текстур с какими-либо прочими размерами. Так, для текстуры кирпичной стены размером 128*128 нам понадобятся отдельные изображения следующих размеров:
1*1
2*2
4*4
8*8
16*16
32*32
64*64
128*128
Эти изображения принято называть Mip-уровнями текстуры (от латинского multum in parvo - много вещей в небольшом месте). Для их создания применяется уже известная нам функция
glTexImage2D (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *texels);
Благодаря второму параметру как раз и можно создавать Mip-уровни. Например, если у нас есть текстура размером 64*64 в массиве bits и ее уменьшенный вариант размером 32*32 в массиве small, то создать два Mip-уровня можно так:
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);
glTexImage2D (GL_TEXTURE_2D, 1, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, small);
Но, разумеется, нам не надо создавать каждый уровень самим. Об этом позаботились разработчики библиотеки GLU (Graphic Library Utilities). Для создания Mip-уровней в glu предусмеотрено несколько функций. Мы будем пользоваться функцией
gluBuild2DMipmaps (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, void *texels);
Она при необходимости отмасштабирует текстуру до ближайшего размера, равного степени двойки, после чего создаст все Mip-уровни вплоть до 1*1 и сама вызовет для каждого функцию glTexImage2D. Например, для нашего примера нам теперь уже не нужен массив small, а для массива bits надо вызвать следующую функцию:
gluBuild2DMipmaps (GL_TEXTURE_2D, GL_RGBA, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, bits);
Есть еще одна функция, которую мы не будем применять в нашем проекте, но которая может пригодиться вам в будущем:
gluBuild2DMipmapLevels (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint level, GLint base, GLint max, void *texels);
Эта функция отличается от предыдущей тем, что создает не все Mip-уровни, а только несколько выбранных. Текущий уровень задается переменной level, а переменные base и max задают, какие уровни должны быть созданы (соответственно будут созданы все уровни от base до max).

Хочу обратить ваше внимание, что в загаловочных файлах glu.h, которые я нашел в комплекте Borland C++ Builder 6.0 и Microsoft Visual C++ 6.0 эта функция не объявлена, не смотря на то, что в библиотеке glu.dll она есть.

Теперь другая проблема. Когда мы подходим вплотную к стене, текстура на ней заметно увеличивается. Те, кто еще помнят игру Doom II, помнят, какой страшной ступенчатостью обладали в ней стены. OpenGL предоставляет свое решение этой проблемы. Для этого служит так называемая фильрация. Чтобы выбрать метод фильтрации текстур, используется функция glTexParameter* (); Она принимает три параметра: первый для нас всегда GL_TEXTURE_2D, второй - фильтр для увеличения или фильтр для уменьшения мы хотим установить. Соответственно второй параметр может быть
GL_TEXTURE_MIN_FILTER - для уменьшения
GL_TEXTURE_MAG_FILTER - для увеличения
Третий параметр - какой метод фильтрации мы хотим установить. Методов фильтрации для увеличения существует два, и оба они могут быть применены и для уменьшения:

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

Существует еще четыре метода, которые могут быть применены только для уменьшения. Они тесно связаны с MIP-уровнями.

GL_NEAREST_MIPMAP_NEAREST - аналогичен GL_NEAREST, но тексель будет браться не с самой текстуры, а с ее ближайшего MIP-уровня.
GL_LINEAR_MIPMAP_NEAREST - аналогичен GL_LINEAR, но будет браться среднее взвешенное четырех текселей не с самой текстуры, а с ее ближайшего MIP-уровня.
GL_NEAREST_MIPMAP_LINEAR и GL_LINEAR_MIPMAP_LINEAR отличаются от двух преыдущих тем, что тексели берутся не с ближайшего MIP-уровня, а вычисляются как среднее взвешенное с двух ближайших MIP-уровней.

Итак мы видим, что самым качественным является GL_LINEAR_MIPMAP_LINEAR, а так как в играх на качестве не экономят, его и возьмем для уменьшения. Заметьте, что если вы хотите в своей игре дать возможность выбора между хорошим качеством и хорошей скоростью, то для второго разумнее всего на увеличение брать GL_NEAREST, а на уменьшение - GL_NEAREST_MIPMAP_NEAREST.
Мы пока такой возможности не предоставляем, а потому просто доработаем функцию InitOneTex из модуля ext_gl.cpp (col_gl.cpp):
//==============================================================================
// Загрузка одной текстуры
//  На входе - номер текстуры и ее координаты на текущем содержимом bitmap
//==============================================================================
void InitOneTex (int index, int x, int y, int w, int h)
{
    GLubyte bits[256*256][4];
    for(int i = 0; i < w; i++)
    {
      for(int j = 0; j < h; j++)
        {
            bits[h*j+i][0]= (GLbyte)GetRValue(bitmap->Canvas->Pixels[i+x][j+y]);
            bits[h*j+i][1]= (GLbyte)GetGValue(bitmap->Canvas->Pixels[i+x][j+y]);
            bits[h*j+i][2]= (GLbyte)GetBValue(bitmap->Canvas->Pixels[i+x][j+y]);
            bits[h*j+i][3]= (GLbyte)255;
        }
    }
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glGenTextures(1, &textures[index]);
    glBindTexture(GL_TEXTURE_2D, textures[index]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, bits);
}
Готовый проект вы можете найти тут:
http://morgeyz.narod.ru/subscribe/issue11.exe

Для сравнения запустите проект из девятого выпуска
http://morgeyz.narod.ru/subscribe/issue9.exe

И сравните его с новым по качеству картинки.

Если что-то осталось непонятным, что-то не компилирутеся или вы просто нашли ошибки, то пишите на
shd@bk.ru


Подпишитесь также:
Вопросы и ответы по языку программирования C и C++
E-mail:

На последок хотел посоветовать всем, кто хочет писать трехмерные игры, переходить на Visual C++. Причин тому много:
1. Размер исполняемого файла - BCB дает около 400KB ненужного двоичного кода.
2. Скорость - один и тот же код, откомпилированный на VC и BCB, различается по FPS примерно в 1,2 раза.
3. Стабильность - откомпилированный на BCB6 проект крайне не стабилен. К BCB5 это не относится.
4. Формат библиотек - вам придется использовать многие внешние библиотеки. При этом BCB имеет свой, отличный от стандартного, формат библиотек, и потому большая часть внешних библиотек просто не прилинкуются.

Удачи!