Этот урок был написан после нескольких e-mail-ов с вопросами о том, каким образом можно наложить текстуру на поверхность. И хоть это и очень просто, я решил всё-таки написать этот урок. Также я несколько изменил код предыдущего урока, чтобы поверхность вместо квадратов строилась из последовательности треугольников (triangle strip). В следующем уроке мы пойдем дальше — рассмотрим, как детализировать текстуры поверхности для большего реализма, а не просто накладывать одну повторяющуюся. Также стырим немного кода из урока по созданию скайбокса, чтобы в нашей сцене был симпатичный фон и она выглядела пореальней.
Текстуры поверхности созданы в Terragen.
Их размер — 512х512, так что если ваша видеокарта не держит такие большие текстуры, уменьшите их до 256 или 128. То же самое относится к текстурам поверхности.
Функционал камеры мы возьмем из последнего урока камеры.
Кроме того, добавим без изменений наш класс текстур.
В файле camera.cpp добавим возможность передвигаться по вертикали:
{
CVector3 vVector = m_vView — m_vPosition;
vVector = Normalize(vVector);m_vPosition.x += vVector.x * speed;
m_vPosition.y += vVector.y * speed;
m_vPosition.z += vVector.z * speed;
m_vView.x += vVector.x * speed;
m_vView.y += vVector.y * speed;
m_vView.z += vVector.z * speed;
}
Расширим функционал отрисовки поверхности:
Файл terrain.h:
void RenderHeightMap(BYTE *pHeightMap, GLuint TID);
Файл terrain.cpp:
/////
///// Новая функция: устанавливает текстурные координаты поверхности на основе X и Z
/////
///////////////////////////////// SET TEXTURE COORD \\\\\\\\\\\\\\\\*void SetTextureCoord(float x, float z)
{
// Нам нужно рассчитать текстурные координаты для текущей вершины. Чтобы
// сделать это, просто берём текущие X & Y, и делми их на MAP_SIZE нашей поверхности.
// Конечно, это подразумевает, что карта высот имеет такие же размеры, что и текстура.// Give OpenGL the current texture coordinate for our height map
glTexCoord2f( (float)x / (float)MAP_SIZE,
— (float)z / (float)MAP_SIZE );
}
///////////////////////////////// RENDER HEIGHT MAP «»»»»»»»»»\*
/////
///// Переписываем функцию.
/////
///////////////////////////////// RENDER HEIGHT MAP «»»»»»»»»»\*
void RenderHeightMap(BYTE pHeightMap[], GLuint TID)
{
int X = 0, Y = 0; // Переменные для прохда по массиву
int x, y, z; // Переменные для чтения
bool bSwitchSides = false;
if(!pHeightMap) return; // Убедимся, что данные верны
// Различие между прошлым способом рендера поверхности и этим в том,
// что мы больше не используем GL_QUADS, а вместо них возьмём GL_TRIANGLE_STRIP.
// Это означает, что нам больше не нужно передавать одну и ту же вершину несколько раз.
// Каждые 2 вершины соединяются со следующими двумя. Так как мы хотим
// сделать всё за один цикл, нам нужно переворачивать порядок вершин каждый ряд.
// Доходим до конца и разворачиваемся. Мы могли бы использовать glBegin
// и glEnd для каждого ряда, но этот способ работает быстрее. Учтите,
// что рендер поверхности вызовами glVertex*() ОЧЕНЬ медленный. Вообще,
// лучше бы использовать для таких целей массивы вершин.
//
// Биндим текстуру
glBindTexture(GL_TEXTURE_2D, TID);
// Мы хотим рендерить последовательность треугольников
glBegin( GL_TRIANGLE_STRIP );
// Проходим через все ряды карты вершин
for ( X = 0; X <= MAP_SIZE; X += STEP_SIZE )
{
// Проверяем, нужно ли рендерить в противоположную сторону
if(bSwitchSides)
{
// Рендерим линию поверхности для текущего X.
// Начинаем с MAP_SIZE и идём до 0.
for ( Y = MAP_SIZE; Y >= 0; Y -= STEP_SIZE )
{
// Получаем (X, Y, Z) значения для нижней левой вершины
x = X;
y = Height(pHeightMap, X, Y );
z = Y;
// Текстурные координаты для вершины
SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
// Получаем (X, Y, Z) для нижней правой вершины
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;
// Устанавливаем текстурные координаты и рендерим вершину
SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
}
}
else
{
// Рендерим линию поверхности для текущего X.
// Начинаем с 0 и идём до MAP_SIZE.
for ( Y = 0; Y <= MAP_SIZE; Y += STEP_SIZE )
{
// Получаем (X, Y, Z) для нижней правой вершины
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;
// Устанавливаем текстурные координаты и рендерим вершину
SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
// Получаем (X, Y, Z) значения для нижней левой вершины
x = X;
y = Height(pHeightMap, X, Y );
z = Y;
// Устанавливаем текстурные координаты и рендерим вершину
SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
}
}
// Меняем направление рендера
bSwitchSides = !bSwitchSides;
}
glEnd();
}
Изменений не много, но теперь у нас есть симпатичная сетка поверхности! =)
Теперь перейдём к main.cpp:
CCamera g_Camera;CTexture *Texture;TextureImage textures[7];
BYTE g_HeightMap[MAP_SIZE*MAP_SIZE];
bool g_bRenderMode = true;
// ID текстур для сторон скайбокса:
#define BACK_ID 1
#define FRONT_ID 2
#define BOTTOM_ID 3
#define TOP_ID 4
#define LEFT_ID 5
#define RIGHT_ID 6
///////////////////////////////// CREATE SKY BOX \\\\\\\\\\\\\\\\*
/////
///// Создаёт текстурированный скайбокс
/////
///////////////////////////////// CREATE SKY BOX \\\\\\\\\\\\\\\\*
void CreateSkyBox(float x, float y, float z, float width, float height, float length)
{
// Включаем текстуры
glEnable(GL_TEXTURE_2D);
// Задняя текстура — к задней стороне куда
glBindTexture(GL_TEXTURE_2D, textures[BACK_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Устанавливаем центр в x/y/z
x = x — width / 2;
y = y — height / 2;
z = z — length / 2;
// Рисуем сторону
glBegin(GL_QUADS);
// текстурные координаты и вершины задней стороны
glTexCoord2f(1.0f, 0.0f); glVertex3f(x + width, y,z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x + width, y + height, z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + height, z);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y,z);
glEnd();
// Передняя текстура к передней стороне
glBindTexture(GL_TEXTURE_2D, textures[FRONT_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Рисуем сторону
glBegin(GL_QUADS);
// Текстурные координаты и вершины передней стороны
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y,z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height, z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height, z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y,z + length);
glEnd();
// Нижняя текстура на нижнюю сторону
glBindTexture(GL_TEXTURE_2D, textures[BOTTOM_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Рисуем сторону
glBegin(GL_QUADS);
// Текстурные координаты и вершины нижней стороны
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y,z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y,z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y,z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y,z);
glEnd();
// Верхняя текстура на верхнюю сторону
glBindTexture(GL_TEXTURE_2D, textures[TOP_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Рисуем сторону
glBegin(GL_QUADS);
// Текстурные координаты и вершины верхней стороны
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height, z);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y + height, z + length);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y + height,z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height,z);
glEnd();
// Левая текстура на левую сторону
glBindTexture(GL_TEXTURE_2D, textures[LEFT_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Рисуем сторону
glBegin(GL_QUADS);
// Текстурные координаты и вершины
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height,z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + height,z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y,z + length);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y,z);
glEnd();
// Текстура правой стороны
glBindTexture(GL_TEXTURE_2D, textures[RIGHT_ID].texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Рисуем сторону
glBegin(GL_QUADS);
// Текстурные координаты и вершины
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y,z);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x + width, y,z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x + width, y + height,z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height,z);
glEnd();
}
////////////////////////////////////////////////////////////////////////////////////////
//
// Изменяем инициализацию:
//
void Init(HWND hWnd)
{
g_hWnd = hWnd;
GetClientRect(g_hWnd, &g_rRect);
InitializeOpenGL(g_rRect.right, g_rRect.bottom);
LoadRawFile(«Terrain.raw», MAP_SIZE * MAP_SIZE, g_HeightMap);
glEnable(GL_DEPTH_TEST); // Тест глубины
glEnable(GL_TEXTURE_2D); // Текстуры
glEnable(GL_CULL_FACE); // Обрезка задних полигонов
// Загружаем нужные текстуры:
Texture = new CTexture();
Texture->LoadTexture(IL_BMP,«Terrain.bmp», &textures[0]);
Texture->LoadTexture(IL_BMP,«Back.bmp», &textures[BACK_ID]);
Texture->LoadTexture(IL_BMP,«Front.bmp», &textures[FRONT_ID]);
Texture->LoadTexture(IL_BMP,«Bottom.bmp», &textures[BOTTOM_ID]);
Texture->LoadTexture(IL_BMP,«Top.bmp», &textures[TOP_ID]);
Texture->LoadTexture(IL_BMP,«Left.bmp», &textures[LEFT_ID]);
Texture->LoadTexture(IL_BMP,«Right.bmp», &textures[RIGHT_ID]);
g_Camera.PositionCamera( 280, 35, 225, 281, 35, 225, 0, 1, 0);
}
////////////////////////////////////////////////////////////////////////////////////////
//
// Отрисовка:
//
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
g_Camera.Look();
// Рендерим карту высот
RenderHeightMap(g_HeightMap, textures[0].texID);
// Просто для лучшего эффекта я добавил скайбокс, окружающий поверхность,
// и проверку, не проходит ли камера сквозь поверхность. Проверка очень проста —
// это не коллизии из прошлых уроков. Просто проверяем высоту камеры и текущие
// координаты x/z. Если камера ниже поверхности, поднимаем её вверх.
// Настоящую обработку коллизий добавим в следующих уроках.
// Создадим скайбокс
CreateSkyBox(500, 0, 500, 2000, 2000, 2000);
// Получаем текущую позицию камеры
CVector3 vPos = g_Camera.Position();
CVector3 vNewPos = vPos;
// Проверяем высоту камеры для текущей позиции x/z, и прибавляем 10,
// чтобы камера не была прямо на полу.
if(vPos.y < Height(g_HeightMap, (int)vPos.x, (int)vPos.z ) + 10)
{
// Установим новую позицию камеры = высота поверхности + 10
vNewPos.y = (float)Height(g_HeightMap, (int)vPos.x, (int)vPos.z ) + 10;
// разница позиции камеры, прошлой и новой
float temp = vNewPos.y — vPos.y;
// Прибавим к вектору взгляда эту разницу
CVector3 vView = g_Camera.View();
vView.y += temp;
// Установим камеру в новую позицию
g_Camera.PositionCamera(vNewPos.x, vNewPos.y, vNewPos.z,
vView.x, vView.y, vView.z, 0, 1, 0);
}
SwapBuffers(g_hDC);
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// Обработка событий (щелчек мыши):
case WM_LBUTTONDOWN:
g_bRenderMode = !g_bRenderMode;
if(g_bRenderMode)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
break;
В файле init.cpp увеличим видимость:
И наконец в начале main.h добавьте:
#include «terrain.h»
Если вы хотите создать свой собственный .raw файл, это можно сделать с помощью фотошопа.
Например, выберите Render->Clouds и сохраните результат в формате .raw
Или вы можете создать свой собственный код поверхности, а затем fwrite() байты в .raw-файл.
Весь арт для урока создан в программе Terragen.
Она просто великолепна!
Update!
Если не будет компилироватся с ошибкой вроде
…main.cpp(135) : error C2065: ‘GL_CLAMP_TO_EDGE’ : undeclared identifier
Добавьте в начало main.cpp:
#define GL_CLAMP_TO_EDGE 0x812F
#endif