Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Карта высот - часть 2






Этот урок был написан после нескольких e-mail-ов с вопросами о том, каким образом можно
наложить текстуру на поверхность. И хоть это и очень просто, я решил всё-таки написать этот
урок. Также я несколько изменил код предыдущего урока, чтобы поверхность вместо квадратов
строилась из последовательности треугольников (triangle strip). В следующем уроке мы
пойдем дальше - рассмотрим, как детализировать текстуры поверхности для большего реализма,
а не просто нахладывать одну повторяющуюся. Также стырим немного кода из урока по созданию
скайбокса, чтобы в нашей сцене был симпатичный фон и она выглядела пореальней.
Текстуры поверхности созданы в [a=http://www.planetside.co.uk/terragen/:a]Terragen.
Их размер - 512х512, так что если ваша видеокарта не держит такие большие текстуры,
уменьшите их до 256 или 128. То же самое относится к текстурам поверхности.

Функционал камеры мы возьмем из последнего урока камеры.
Кроме того, добавим без изменений наш класс текстур.

В файле camera.cpp добавим возможность передвигатся по вертикали:

void CCamera::MoveCamera(float speed)
{
    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:
// Изменим прототип RenderHeightMap():
void RenderHeightMap(BYTE *pHeightMap,  GLuint TID);


Файл terrain.cpp:

///////////////////////////////// SET TEXTURE COORD \\\\\\\\\\\\\\\\*
/////
/////   Новая функция: устанавливает текстурные координаты поверхности на основе 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 увеличим видимость:
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height, 4 ,4000.0f);



И наконец в начале main.h добавьте:
#include "texture.h"
#include "terrain.h"


Если вы хотите создать свой собственный .raw файл, это можно сделать с помощью фотошопа.
Например, выберите Render->Clouds и сохраните результат в формате .raw
Или вы можете создать свой собственный код поверхности, а затем fwrite() байты в
.raw-файл.

Весь арт для урока создан в программе [a=http://www.planetside.co.uk/terragen/>Terragen[/a].
Она просто великолепна!



Update!
Если не будет компилироватся с ошибкой вроде
...main.cpp(134) : error C2065: 'GL_CLAMP_TO_EDGE' : undeclared identifier
...main.cpp(135) : error C2065: 'GL_CLAMP_TO_EDGE' : undeclared identifier


Добавьте в начало main.cpp:
#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE        0x812F
#endif






Исходные коды к уроку




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.