Array ( )
Вход:




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

OpenGL: Карта высот






В этом уроке мы научимся создавать поверхность - карту высот. Основа этого
урока взята из урока "Камера: стрейф", к этой основе мы добавим нужный функционал.
Файлик [a=http://masandilov.ru/coding/sources/Terrain.raw:a]Terrain.raw, наша карта высот.


Сначала мы создадим функционал для обработки и рисования поверхности.
Сначала создадим хидер; файл будет содержать константы и функции для обработки и
отрисовки поверхности.

Файл terrain.h:
#ifndef _TERRAIN_H
#define _TERRAIN_H

#define MAP_SIZE    1024    // Размер нашей .raw карты высот
#define STEP_SIZE   16  // Ширина и высота каждого составляющего карту квадрата
#define HEIGHT_RATIO    1.5f    // Отношение, с которым Y изменяется относительно X и Z

// Возвращает высоту (0 - 255) из карты высот для переданных X и Y
int Height(BYTE *pHeightMap, int X, int Y);

// Загружает .raw файл в переменную с передаваемой длинной
void LoadRawFile(LPSTR strName, int nSize, BYTE *pHeightMap);

// Конвертирует данные карты высот в примитивы и отрисовывает их на экран
void RenderHeightMap(BYTE *pHeightMap);


#endif



Теперь опишем реализацию нужного функционала. Замечание: на экране поверхность
может сейчас показатся маленькой, но не переживайте, она может быть и большой,
и занимать весь ваш мир, если вы захотите =)
Итак, файл terrain.cpp:
#include "main.h"

///////////////////////////////// HEIGHT \\\\\\\\\\\\\\\\*
/////
/////   Возвращает высоту в карту высот
/////
///////////////////////////////// HEIGHT \\\\\\\\\\\\\\\\*

int Height(BYTE *pHeightMap, int X, int Y)
{

    // Ф-я используется для индексации нашей карты высот.
    // При работе с массивами всегда нужно убеждатся, что мы не выходим
    // за пределы массива, так что убедимся, что этого не случилось, с a%.
    // Таким образом X и Y будут в пределах (MAX_SIZE - 1).

    int x = X % MAP_SIZE;   // Проверка для X
    int y = Y % MAP_SIZE;   // Проверка для Y

    if(!pHeightMap) return 0;   // Убедимся, что работаем с правильными данными

    // Ниже нам нужно вернуть единичный - 2д-массив.
    // Мы можем использовать формулу: index = (x + (y * arrayWidth) ).
    // Это значит, что мы используем 2д-массив[x][y],
    // иначе он будет противоположным. Теперь, имея верный индекс,
    // вернём высоту для этого индекса.

    return pHeightMap[x + (y * MAP_SIZE)];  // вернём высоту данного индекса
}

///////////////////////////////// SET VERTEX COLOR \\\\\\\\\\\\\\\\*
/////
/////   Устанавливает цветовое значение вершины, в зависимости от высоты и ширины
/////
///////////////////////////////// SET VERTEX COLOR \\\\\\\\\\\\\\\\*
void SetVertexColor(BYTE *pHeightMap, int x, int y)
{
    if(!pHeightMap) return;     // Убедимся, что данные верны

    // Здесь мы устанавливаем цвет вершины, основываясь на индексе её высоты.
    // Чтобы сделать цвет темнее, я начал с -0.15f. Также мы получим
    // значение от 0 до 1.0 делением высоту на 256.0f.
    float fColor = -0.15f + (Height(pHeightMap, x, y ) / 256.0f);

    // Применим нужный оттенок зеленого на текущую вершину
    glColor3f(0, fColor, 0 );
}

///////////////////////////////// RENDER HEIGHT MAP \\\\\\\\\\\\\\\\*
/////
/////   Рендерит карту высот как множество квадратов.
/////
///////////////////////////////// RENDER HEIGHT MAP \\\\\\\\\\\\\\\\*

void RenderHeightMap(BYTE pHeightMap[])
{
    int X = 0, Y = 0;       // Переменные для прохда по массиву
    int x, y, z;            // Переменные для чтения
    float fColor = 0.0f;        // Переменная для цвета полигона

    if(!pHeightMap) return;     // Убедимся, что данные верны

    glBegin( GL_QUADS );        // Начинаем рендерить квадраты

    // Теперь нам нужно собственно нарисовать поверхность из карты высот.
    // Чтобы это сделать, просто проходим через массив данных высот и
    // применяем высоты к нашим точкам. Сначала будут отрисованы колонки
    // (column), затем ряды (row). Заметьте, что мы применяем STEP_SIZE.
    // Оно указывает размер квадратных полигонов на карте. Чем выше этот
    // параметр, тем более резкой и "блочной" будет вытлядеть поверхность,
    // и чем ниже - тем более сглаженной. Если установим STEP_SIZE в один,
    // будет создаватся вершина для каждого пиксела карты высот. Не увлекайтесь,
    // иначе рендер может происходить слишком медленно. Конечно, можно и увеличить
    // переменную + включить свет. Вершинное освещение прикроет структурированность
    // поверхности. В этом уроке, чтобы упростить его, иы просот передадим
    // значение света для каждой вершины. Чем выше полигон, тем ярче его цвет.

    for ( X = 0; X < MAP_SIZE; X += STEP_SIZE )
        for ( Y = 0; Y < MAP_SIZE; Y += STEP_SIZE )
        {
            // Получаем X,Y,Z для нижней левой вершины
            x = X;
            y = Height(pHeightMap, X, Y );
            z = Y;

            // Устанавливаем цвет для текущей вершины
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);    // Посылаем эту вершину для рендера в OpenGL
                        // (int работают быстрее)

            // Получаем (X, Y, Z) для верхней левой вершины
            x = X;
            y = Height(pHeightMap, X, Y + STEP_SIZE );
            z = Y + STEP_SIZE ;

            // Устанавливаем её цвет
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);    // Посылаем для отрисовки

            // Получаем координаты для правой верхней вершины
            x = X + STEP_SIZE;
            y = Height(pHeightMap, X + STEP_SIZE, Y + STEP_SIZE );
            z = Y + STEP_SIZE ;

            // Устанавливаем для неё цвет
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);    // Рисуем

            // Get the (X, Y, Z) value for the bottom right vertex
            // Получаем координаты для нижней правой вершины
            x = X + STEP_SIZE;
            y = Height(pHeightMap, X + STEP_SIZE, Y );
            z = Y;

            // Устанавливаем цвет
            SetVertexColor(pHeightMap, x, z);

            glVertex3i(x, y, z);    // Отрисовываем
        }
    glEnd();

    // Сбрасываем цвет
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}


///////////////////////////////// LOAD RAW FILE \\\\\\\\\\\\\\\\*
/////
/////   Загружает .raw файл в массив байт. Каждое значение - значение высоты.
/////
///////////////////////////////// LOAD RAW FILE \\\\\\\\\\\\\\\\*

void LoadRawFile(LPSTR strName, int nSize, BYTE *pHeightMap)
{
    FILE *pFile = NULL;

    // Откроем файл для бинарного чтения
    pFile = fopen( strName, "rb" );

    // Проверяем, корректно ли открылся файл
    if ( pFile == NULL )
    {
        // Выводим ошибку и останавливаем функцию
        MessageBox(NULL, "Can't find the height map!", "Error", MB_OK);
        return;
    }

    // Здесь мы загружаем файлы из .raw файла в массив pHeightMap.
    // Мы читаем в '1', и размер = (width * height)
    fread( pHeightMap, 1, nSize, pFile );

    // После чтения данных неплохо бы проверить, все ли прочиталось верно
    int result = ferror( pFile );

    // Проверим, получили ли мы ошибку
    if (result)
    {
        MessageBox(NULL, "Can't get data!", "Error", MB_OK);
    }

    // Закроем файл
    fclose(pFile);
}

 


Это самый простой способ отрисовывать поверхность из карты высот. На следующем
этапе мы научимся делать quad-дерево, и будем отрисовывать только те вершины,
которые нам видны в конусе взгляда камеры (frustum). Это позволит нам создавать
очень большую поверхность, но не обрабатывать каждый кадр все вершины.

Пройдем по шагам всё, что мы сделали:

1) Сначала читаем карту высот из файла .raw. Это просто, так как в .raw файлах нет
никаких заголовков, просто биты изображения.

2) После прочтения карты высот, нам нужно вывести её на экран. Это тоже не слишком
сложно, мы просто рендерим квадраты с установленным размером (STEP_SIZE). Я выбрал
размер 16х16, но вы можете увеличить или уменьшить его, как вам покажется лучше.
Основываясь на карте высот, рисуем эти квадраты в цикле. Вместо задания освещения
окрашиваем вершины в разные оттенки зеленого цвета.


Теперь немного изменим camera.cpp:
//////////////////////////////////////////////////////////////////////////////////
//
// В этом файле мы просто увеличим скорость перемещения + добавим возможность
// пользователю двигатся по вертикали, немного изменив функцию MoveCamera():
//

// Увеличим скорость перемещения:
#define kSpeed 5.0f


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;
}



В файле main.h включим новый хидер:
#include "terrain.h"


В файле init.cpp увеличим дистанцию
отображения до 4000:
void SizeOpenGLScreen(int width, int height)
{
.......
.......
.......

gluPerspective(45.0f,(GLfloat)width/(GLfloat)height, 4 , 4000.0f);
}



Переходим к самой отрисовке.
Файл main.cpp:
//////////////////////////////////////////////////////////////////////////////
//
// В начале файла добавим новые переменные:
//

// Данные карты высот
BYTE g_HeightMap[MAP_SIZE*MAP_SIZE];

// Переменная для хранения типа рендера
bool  g_bRenderMode = true;

// Класс камеры
CCamera g_Camera;



//////////////////////////////////////////////////////////////////////////////
//
// В функции Init() загрузим карту высот:
//

    // Читаем файл "Terrain.raw" и загружаем данные в массив:
    LoadRawFile("Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap);

    // Устанавливаем камеру
    g_Camera.PositionCamera(1200, 300, 1150,  1199, 300, 1150,  0, 1, 0);






//////////////////////////////////////////////////////////////////////////////
//
// В функции WinProc() добавим обработку мыши:
//

    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;







//////////////////////////////////////////////////////////////////////////////
//
// Функция RenderScene():
//

void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();


    // Передаём OpenGL позицию камеры
    g_Camera.Look();

    // Передаём данные высот для отрисовки
    RenderHeightMap(g_HeightMap);                       // Render the height map



    SwapBuffers(g_hDC);
}
 







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




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

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












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