Урок Инициализация в Windows я начинаю это пособие непосредственного с кода, разбитого на секции, каждая из которых будет подробно комментироваться. Первое, что вы должны сделать это создать проект в Visual C++



страница1/27
Дата17.11.2018
Размер7.85 Mb.
ТипУрок
  1   2   3   4   5   6   7   8   9   ...   27

Урок 1. Инициализация в Windows

Я начинаю это пособие непосредственного с кода, разбитого на секции, каждая из которых будет подробно комментироваться. Первое, что вы должны сделать - это создать проект в Visual C++. Если это для вас затруднительно, то вам стоит для начала изучить C++, а уже затем переходить к OpenGL.

После того как вы создадите новое приложение в Visual C++, Вам надо будет добавить для сборки проекта библиотеки OpenGL. В меню Project/setting, выберите закладку LINK. В строке "Object/Library Modules" добавьте "OpenGL32.lib GLu32.lib GLaux.lib". Затем кликните по OK. Теперь все готово для создания программы с OpenGL.

Первые четыре строки, которые вы введете, сообщают компилятору какие библиотечные файлы использовать. Они должны выглядят так:

#include                       // Заголовочный файл для Windows
#include                         // Заголовочный файл для OpenGL32 библиотеки
#include                        // Заголовочный файл для GLu32 библиотеки
#include                      // Заголовочный файл для GLaux библиотеки

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

Первые две строки устанавливают Контексты Рендеринга, которые связывает вызовы OpenGL с окном Windows. Контекст Рендеринга OpenGL определен как hRC. Для того чтобы рисовать в окне, вам необходимо создать Контекст Устройства Windows, который определен как hDC. DC соединяет окно с GDI. RC соединяет OpenGL с DC.

static HGLRC hRC;               // Постоянный контекст рендеринга


static HDC hDC;                 // Приватный контекст устройства GDI

Последняя переменная, в которой мы будем нуждаться, это массив, который мы используем для отслеживания нажатия клавиш на клавиатуре. Есть много путей следить за нажатиями на клавиатуре, но я использую этот путь. При этом можно отслеживать нажатие более чем одной клавиши одновременно.

BOOL    keys[256];              // Массив для процедуры обработки клавиатуры

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

GLvoid InitGL(GLsizei Width, GLsizei Height)    // Вызвать после создания окна GL
{

В следующей строке устанавливается цвет, которым будет очищен экран. Для тех, кто не знает, как устроены цвета, я постараюсь кратко объяснять. Все значения могут быть в диапазоне от 0.0f до 1.0f, при этом 0.0 самый темный, а 1.0 самый светлый. Первое число в glClearColor - это интенсивность красного, второе – зеленного, третье – синего. Наибольшее значение – 1.0f, является самым ярким значением данного цвета. Последние число - для альфа значения. Когда начинается очистка экрана, я не когда не волнуюсь о четвертом числе. Пока оно будет 0.0f. Как его использовать, я объясню в другом уроке.

Поэтому, если вы вызвали glClearColor(0.0f,0.0f,1.0f,0.0f) вы произведете очистку экрана, с последующим закрашиванием его в ярко синий цвет. Если вы вызвали glClearColor(0.5f,0.0f,0.0f,0.0f) экран будет заполнен умеренно красным цветом. Не очень ярким (1.0f) и не темным (0.0f), а именно умеренно красным. Для того чтобы сделать белый фон, вы должны установить все цвета в (1.0f). Черный - как можно ниже (0.0f).

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);   // Очистка экрана в черный цвет

Следующие три строки создают Буфер Глубины. Думайте о буфере глубины как о слоях на экране. Буфер глубины указывает, как далеко объекты находятся от экрана. Мы не будем реально использовать буфер глубины в этой программе, но любая программа с OpenGL, которая рисует на экране в 3D будет его использовать. Он позволяет сортировать объекты для отрисовки, поэтому квадрат расположенный под кругом не изображен будет поверх круга. Буфер глубины очень важная часть OpenGL.

        glClearDepth(1.0);                      // Разрешить очистку буфера глубины


        glDepthFunc(GL_LESS);                   // Тип теста глубины
        glEnable(GL_DEPTH_TEST);                // разрешить тест глубины

Следующие пять строк разрешают плавное сглаживание (антиалиасинг - antialiasing)(которое я буду объяснять позднее) и установку экрана для перспективного просмотра. Отдаленные предметы на экране кажутся меньшими, чем ближние. Это придает сцене реалистичный вид. Перспектива вычисляется под углом просмотра 45 градусов на основе ширины и высоты окна. 0.1f, 100.0f глубина экрана.

glMatrixMode(GL_PROJECTION) сообщает о том, что следующие команды будут воздействовать на матрицу проекции. glLoadIdentity() – это функция работает подобно сбросу. Раз сцена сброшена, перспектива вычисляется для сцены. glMatrixMode(GL_MODELVIEW) сообщает, что любые новые трансформации будут воздействовать на матрицу просмотра модели. Не волнуйтесь, если вы что-то не понимаете этот материал, я буду обучать всему этому в дальнейших уроках. Только запомините, что НАДО сделать, если вы хотите красивую перспективную сцену.

        glShadeModel(GL_SMOOTH);        // разрешить плавное цветовое сглаживание


        glMatrixMode(GL_PROJECTION);    // Выбор матрицы проекции
        glLoadIdentity();               // Сброс матрицы проекции
        gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
                                // Вычислить соотношение геометрических размеров для окна
        glMatrixMode(GL_MODELVIEW);     // Выбор матрицы просмотра модели
}

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

GLvoid ReSizeGLScene(GLsizei Width, GLsizei Height)
{
        if (Height==0)          // Предотвращение деления на ноль, если окно слишком мало
                Height=1;

        glViewport(0, 0, Width, Height);


                // Сброс текущей области вывода и перспективных преобразований

        glMatrixMode(GL_PROJECTION);            // Выбор матрицы проекций


        glLoadIdentity();                       // Сброс матрицы проекции

        gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);


                // Вычисление соотношения геометрических размеров для окна
        glMatrixMode(GL_MODELVIEW);     // Выбор матрицы просмотра модели
}

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

GLvoid DrawGLScene(GLvoid)
{
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                // очистка Экрана и буфера глубины
        glLoadIdentity();
                // Сброс просмотра
}

Следующая секция кода наиболее важная секция в этой программе. Это установка окна Windows, установка формата пикселя, обработка при изменении размеров, при нажатии на клавиатуру, и закрытие программы.

Первые четыре строки делают следующее: переменная hWnd – является указателем на окно. Переменная message – сообщения, передаваемые вашей программе системой. Переменные wParam и lParam содержат информацию, которая посылается вместе с сообщением, например такая как ширина и высота окна.

LRESULT CALLBACK WndProc(       HWND    hWnd,


                                UINT    message,
                                WPARAM  wParam,
                                LPARAM  lParam)

Код между скобками устанавливает формат пикселей. Я предпочитаю не использовать режим индексации цвета. Если вы не знаете, что это означает, не заботьтесь об этом. Формат описания пикселя описывает, как OpenGL будет выводить в окно. Большинство кода игнорируется, но зачастую это необходимо. Я буду помещать короткий комментарий для каждой строки. Знак вопроса означает, что я не уверен, что это строка кода делает (я только человек!).

{
        RECT    Screen;         // используется позднее для размеров окна
        GLuint  PixelFormat;
        static  PIXELFORMATDESCRIPTOR pfd=
        {
                sizeof(PIXELFORMATDESCRIPTOR),  // Размер этой структуры
        1,                              // Номер версии (?)
        PFD_DRAW_TO_WINDOW |            // Формат для Окна
        PFD_SUPPORT_OPENGL |            // Формат для OpenGL
        PFD_DOUBLEBUFFER,               // Формат для двойного буфера
        PFD_TYPE_RGBA,                  // Требуется RGBA формат
        16,                             // Выбор 16 бит глубины цвета
        0, 0, 0, 0, 0, 0,                       // Игнорирование цветовых битов (?)
        0,                              // нет буфера прозрачности
        0,                              // Сдвиговый бит игнорируется (?)
        0,                              // Нет буфера аккумуляции
        0, 0, 0, 0,                             // Биты аккумуляции игнорируются (?)
        16,                             // 16 битный Z-буфер (буфер глубины) 
        0,                              // Нет буфера траффарета
        0,                              // Нет вспомогательных буферов (?)
        PFD_MAIN_PLANE,                 // Главный слой рисования
        0,                              // Резерв (?)
        0, 0, 0                         // Маски слоя игнорируются (?)
        };

Эта секция кода обрабатывает системные сообщения. Они генерируются, когда вы выходите из программы, нажимаете на клавиши, передвигаете окно, и так далее, каждая секция "case" обрабатывает свой тип сообщения. Если вы что вставите в эту секцию, не ожидайте, что ваш код будет работать должным образом, или вообще работать.

        switch (message)        // Тип сообщения
        {

WM_CREATE сообщает программе, что оно должно быть создано. Вначале мы запросим DC (контекст устройства) для вашего окна. Помните, без него мы не можем рисовать в окно. Затем мы запрашиваем формат пикселя. Компьютер будет выбирать формат, который совпадает или наиболее близок к формату, который мы запрашиваем. Я не делаю здесь множества проверок на ошибки, чтобы сократить код, но это неправильно. Если что-то не работает, я просто добавляю необходимый код. Возможно, вы захотите посмотреть, как работают другие форматы пикселей.

        case WM_CREATE:
                hDC = GetDC(hWnd);      // Получить контекст устройства для окна
                PixelFormat = ChoosePixelFormat(hDC, &pfd);
                        // Найти ближайшее совпадение для нашего формата пикселов

Если подходящий формат пикселя не может быть найден, будет выведено сообщение об ошибке с соответствующем уведомлением. Оно будет ждать, когда вы нажмете на OK, до выхода из программы.

        if (!PixelFormat)
                {
                        MessageBox(0,"Can't Find A Suitable 
                        PixelFormat.","Error",MB_OK|MB_ICONERROR);
                        PostQuitMessage(0);
                        // Это сообщение говорит, что программа должна завершится
                break;  // Предтовращение повтора кода
                }

Если подходящий формат найден, компьютер будет пытаться установить формат пикселя для контекста устройства. Если формат пикселя не может быть установлен по какой-то причине, выскочит сообщение об ошибке, что формат пикселя не найден, и будет ожидать, пока Вы не нажмете кнопку OK, до выхода из программы.

        if(!SetPixelFormat(hDC,PixelFormat,&pfd))
                {
                        MessageBox(0,"Can't Set The 
                        PixelFormat.","Error",MB_OK|MB_ICONERROR);
                        PostQuitMessage(0);
                        break;
                }

Если код сделан, как показано выше, будет создан DC (контекст устройства), и установлен подходящий формат пикселя. Сейчас мы создадим Контекст Рендеринга, для этого OpenGL использует DC. wglCreateContext будет захватывать Контекст Рендеринга и сохранять его в переменной hRC. Если по какой-то причине Контекст Рендеринга не доступен, выскочит сообщение об ошибке. Нажмите OK для вызова программы.

        hRC = wglCreateContext(hDC);
        if(!hRC)
                {
                MessageBox(0,"Can't Create A GL Rendering 
                Context.","Error",MB_OK|MB_ICONERROR);
                PostQuitMessage(0);
                break;
                }

Сейчас мы имеем Контекст Рендеринга, и нам необходимо сделать его активным, для того чтобы OpenGL мог рисовать в окно. Снова, если по не которой причине это не может быть сделано, выскочит сообщение об ошибке. Кликните OK в окошке ошибки для выхода из программы.

        if(!wglMakeCurrent(hDC, hRC))
                {
                MessageBox(0,"Can't activate GLRC.","Error",MB_OK|MB_ICONERROR);
                PostQuitMessage(0);
                break;
                }

Если все прошло удачно, то у нас есть все для того, чтобы создать область рисования OpenGL. GetClientRect возвратит нам ширину и высоту окна. Мы запомним ширину справа, и высоту снизу. После того как мы получили ширину и высоту, инициализируем экран OpenGL. Мы делаем это при помощи вызова InitGL, передавая в параметрах право и низ (ширину и высоту).

                GetClientRect(hWnd, &Screen);
                InitGL(Screen.right, Screen.bottom);
                break;

WM_DESTROY и WM_CLOSE очень похожи. Программа будет посылать это сообщение каждый раз, когда вы выходите из программы, нажав ALT-F4, или если вы послали PostQuitMessage(0) также как мы делали, когда происходила ошибка.

ChangeDisplaySettings(NULL,0) будет переключать разрешение рабочего стола обратно, делая его таким, каким мы переключались из него в полноэкранный режим. ReleaseDC(hWnd,hDC) уничтожает контекст устройства окна. По существу это уничтожает окно OpenGL.

                case WM_DESTROY:


                case WM_CLOSE:
                ChangeDisplaySettings(NULL, 0);

                wglMakeCurrent(hDC,NULL);


                wglDeleteContext(hRC);
                ReleaseDC(hWnd,hDC);

                PostQuitMessage(0);


                break;

WM_KEYDOWN вызывается всякий раз при нажатии клавиши. Клавиша, которая была нажата, сохраняется в переменной wParam. Итак, что же делает следующий код... Скажем, я нажал 'A'. Буква фактически – это число, которое ее представляет. Поэтому в ячейку, которая представляет 'A' заносится TRUE. Позднее, в коде, если я проверю состояние ячейки и увижу TRUE, то я знаю, что клавиша 'A' действительно в этот момент нажата.

                case WM_KEYDOWN:
                keys[wParam] = TRUE;
                break;

WM_KEYUP вызывается всякий раз, когда клавиша отпускается. Клавиша, которая отжата, также сохраняется в переменной wParam. Поэтому, когда я отпускаю клавишу 'A', это делает ячейку для клавиши 'A' равной FALSE. Когда я проверю ячейку, для того чтобы увидеть нажата ли клавиша 'A', она вернет FALSE, что означает "нет, она не нажата".

                case WM_KEYUP:
                keys[wParam] = FALSE;
                break;

И последнее, что я сделаю - обработаю изменение размеров окна. Возможно, кажется, что бесмыслено добавлять этот код, когда программа запущена в полноэкранном режиме, но без этого кода, экран OpenGL не появится. Поверьте, мне это очень важно.

Всякий раз сообщение WM_SIZE посылается Windows с двумя параметрами - новая ширина, и новая высота экрана. Эти параметры сохранены в LOWORD(lParam) и HIWORD(lParam). Поэтому вызов ReSizeGLScene изменяет размеры экрана. Это передает высоту и ширину в эту секцию кода.

                case WM_SIZE:


                ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
                break;

Затем, дадим Windows обработать все сообщения, которые мы не обрабатываем и завершим процедуру.

        default:
        return (DefWindowProc(hWnd, message, wParam, lParam));
        }
        return (0);
}

Это то место, где начинается программа, где создается окно, где делается практически все, кроме рисования. Мы начинаем с создания окна.

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, 
                LPSTR lpCmdLine,int nCmdShow)
{
        MSG             msg;    // Структура сообщения Windows
        WNDCLASS        wc; // Структура класса Windows для установки типа окна
        HWND            hWnd;   // Сохранение дискриптора окна

Флаги стиля CS_HREDRAW и CS_VREDRAW принуждают перерисовать окно всякий раз, когда оно перемещается. CS_OWNDC создает скрытый DC для окна. Это означает, что DC не используется совместно нескольким приложениями. WndProc - процедура, которая перехватывает сообщения для программы. hIcon установлен равным нулю, это означает, что мы не хотим ICON в окне, и для мыши используем стандартный указатель. Фоновый цвет не имеет значения (мы установим его в GL). Мы не хотим меню в этом окне, поэтому мы используем установку его в NULL, и имя класса – это любое имя которое вы хотите.

        wc.style                = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        wc.lpfnWndProc          = (WNDPROC) WndProc;
        wc.cbClsExtra           = 0;
        wc.cbWndExtra           = 0;
        wc.hInstance            = hInstance;
        wc.hIcon                = NULL;
        wc.hCursor              = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground        = NULL;
        wc.lpszMenuName         = NULL;
        wc.lpszClassName        = "OpenGL WinClass";

Сейчас мы регистрируем класс. Если произошла ошибка, появится соответствующее сообщение. Кликните на OK в коробочку об ошибке и будете выкинуты из программы.

        if(!RegisterClass(&wc))
        {
        MessageBox(0,"Failed To Register The Window 
        Class.","Error",MB_OK|MB_ICONERROR);
        return FALSE;
        }

Сейчас мы сделаем окно. Не смотря на то, что мы делаем окно здесь, это не вызовет OpenGL до тех пор, пока сообщение WM_CREATE не послано. Флаги WS_CLIPCHILDREN и WS_CLIPSIBLINGS требуются для OpenGL. Очень важно, чтобы вы добавили их здесь. Я люблю использовать всплывающее окно, оно хорошо работает в полноэкранном режиме.

        hWnd = CreateWindow(
        "OpenGL WinClass",
        "Jeff Molofee's GL Code Tutorial ... NeHe '99", // Заголовок вверху окна

        WS_POPUP |


        WS_CLIPCHILDREN |
        WS_CLIPSIBLINGS,

        0, 0,                   // Позиция окна на экране


        640, 480,               // Ширина и высота окна

        NULL,


        NULL,
        hInstance,
        NULL);

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

        if(!hWnd)
        {
        MessageBox(0,"Window Creation Error.","Error",MB_OK|MB_ICONERROR); 
                return FALSE;
        }

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

Я не устанавливаю глубину цвета, когда я переключаю полноэкранный режим. Всякий раз, когда я пробовал переключать глубину цвета, я получал сверхъестественные запросы от Windows чтобы сделать перезагрузку компьютера для переключения нового режима цвета. Я не уверен, надо ли удовлетворять это сообщение, но я решил оставлять компьютер с той глубиной цвета, которая была до запуска GL программы.

Важно отметить, что этот код не будет скомпилирован на Cи. Это файл должен быть сохранен как .CPP файл.

DEVMODE dmScreenSettings;                       // Режим работы

memset(&dmScreenSettings, 0, sizeof(DEVMODE));          // Очистка для хранения установок


dmScreenSettings.dmSize = sizeof(DEVMODE);              // Размер структуры Devmode
dmScreenSettings.dmPelsWidth    = 640;                  // Ширина экрана
dmScreenSettings.dmPelsHeight   = 480;                  // Высота экрана
dmScreenSettings.dmFields       = DM_PELSWIDTH | DM_PELSHEIGHT; // Режим Пиксела
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
        // Переключение в полный экран

ShowWindow название этой функции говорит само за себя - она показывает окно, которое вы создали на экране. Я люблю это делать, после того как я переключусь в полноэкранный режим, хотя я не уверен, что это имеет значения. UpdateWindow обновляет окно, SetFocus делает окно активным, и вызывает wglMakeCurrent(hDC,hRC) чтобы убедиться, что Контекст рендеринга не освобожден.

        ShowWindow(hWnd, SW_SHOW);
        UpdateWindow(hWnd);
        SetFocus(hWnd);

Теперь мы создадим бесконечный цикл. Есть только один момент выхода из цикла, - когда нажали ESC. При этом программе будет отправлено сообщение о выходе, и она прервется.

        while (1)
        {
                // Обработка всех сообщений
                while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
                {
                        if (GetMessage(&msg, NULL, 0, 0))
                        {
                                TranslateMessage(&msg);
                                DispatchMessage(&msg);
                        }
                        else
                        {
                                return TRUE;
                        }
                }

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

SwapBuffers(hDC) очень важная команда. Мы имеем окно с установленной двойной буферизацией. Это означает, что изображение рисуется на скрытом окне (называемым буфером). Затем, мы говорим компьютеру переключить буфера, скрытый буфер копируется на экран. При этом получается плавная анимация без рывков, и зритель не замечает отрисовку объектов.

        DrawGLScene();                          // Нарисовать сцену


        SwapBuffers(hDC);                               // Переключить буфер экрана
        if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0);    // Если ESC - выйти
        }
}

В этом уроке я попытался объяснить как можно больше деталей каждого шага запутанной установки, и создания ваших собственных полноэкранных OpenGL программ, которые будут завершаться при нажатии ESC. Я потратил 3 дня и 13 часов для написания этого урока. Если вы имеете любые комментарии или вопросы, пожалуйста, пошлите их мне по электронной почте. Если вы ощущаете, что я некорректно комментировал что-то или что код должен быть лучше в некоторых секциях по некоторым причинам, пожалуйста, дайте мне знать. Я хочу сделать уроки по OpenGL хорошими насколько смогу. Я заинтересован в обратной связи.

Урок 2. Отображение полигонов

В предыдущем уроке было рассмотрено создание OpenGL окна. Теперь мы изучим создание таких фигур как треугольники и квадраты, при помощи GL_TRIANGLES и GL_QUADS.

Для создания приложения мы будем использовать код предыдущего примера, только добавим код в функцию DrawGLScene. Все ее изменения приводятся ниже. Если вы планируете менять предыдущий урок, просто замените функцию DrawGLScene следующим кодом, или просто добавьте те строки, которые там отсутствуют.

GLvoid DrawGLScene(GLvoid)


{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Очистка экрана
                                                        // и буфера глубины
        glLoadIdentity();                               // Сброс просмотра

Вызов функции gLoadIdentity() устанавливает начало системы координат в центр экрана, причем ось X идет слева направо, ось Y вверх и вниз, а ось Z к и от наблюдателя. Центр OpenGL экрана находится в точке 0, 0, 0. Координаты, расположенные слева, снизу и вглубь от него, имеют отрицательное значение, расположенные справа, сверху и по направлению к наблюдателю – положительное.

Функция glTranslate(x, y, z) перемещает оси координат на указанные значения. Следующая строчка кода перемещает ось X на 1.5 единиц и ось Z на 6 единиц. Следует заметить, что перевод осей координат осуществляется не относительно центра экрана, а от их текущего расположения.

        glTranslatef(-1.5f,0.0f,-6.0f);         // Сдвинемся влево на 1.5 единицы и


                                                // в экран на 6.0

Теперь, когда мы переместились в левую часть экрана и установили более удобный вид (в глубину на 6 единиц), отобразим геометрическую фигуру. Функция glBegin(GL_TRIANGLES) означает, что мы начинаем рисовать треугольник, и далее следует перечисление его вершин. После указания всех вершин, производится вызов функции glEnd(). Обычно треугольники на большинстве видеокарт отображаются наиболее быстро, но если есть желание отобразить иную фигуру, вместо GL_TRIANGLE используется другое значение, например: для создания четырехугольника указывается GL_QUADS, если вершин больше чем 4, то GL_POLYGONS.

В нашем примере рисуем только один треугольник. Если есть желание изобразить еще один треугольник, добавьте часть кода, следующую за описанием первого треугольника. В это случае все шесть строк кода следует размещать между glBegin(GL_TRIANGLES) и glEnd(). Нет нужды выделять каждый треугольник этими командами, после обработки трех вершин OpenGL сам перейдет к созданию новой фигуры. Это же относится и к четырехугольникам. Полигоны (GL_POLYGONS) в свою очередь могут иметь любое количество вершин, и поэтому нет разницы, сколько описателей располагалось между строками glBegin(GL_POLYGONS) и glEnd().

Первая строка после glBegin описывает первую вершину нашего полигона. Функция glVertex3f() получает в качестве параметров ее X, Y и Z координаты. Первая вершина треугольника смещена только от оси Y на 1, таким образом, мы расположим ее точно в центре и она будет самой верхней. Следующая вершина будет располагаться на оси Х слева от центра и на оси Y вниз от центра. Эта вершина будет расположена внизу слева. Третья вершина будет справа и снизу от центра. Функция glEnd() указывает OpenGL, что вершин больше не будет. Результатом всего этого будет залитый цветом по умолчанию треугольник.

        glBegin(GL_TRIANGLES);
                glVertex3f( 0.0f, 1.0f, 0.0f);  // Вверх
                glVertex3f(-1.0f,-1.0f, 0.0f);  // Слева снизу
                glVertex3f( 1.0f,-1.0f, 0.0f);  // Справа снизу
        glEnd();

Теперь у нас есть треугольник, изображенный в правой части экрана. Нам нужно 


переместиться в левую часть, для этого снова используем функцию glTranslate(). Так как 
мы в прошлый раз перемещались влево на 1.5 единицы, необходимо переместиться на 3.0 
единицы вправо (1.5 единицы – это будет центр, плюс еще 1.5 единицы для правого края).

        glTranslatef(3.0f,0.0f,0.0f);           // Сдвинем вправо на 3 единицы

Здесь мы изобразим квадрат. Так как он является четырехсторонним полигоном, мы будем использовать GL_QUADS. Создание квадрата напоминает создание треугольника, правда указывать нужно четыре вершины. Они будут идти в следующем порядке – левая вверху, правая вверху, правая снизу и левая снизу.

        glBegin(GL_QUADS);


                glVertex3f(-1.0f, 1.0f, 0.0f);  // Слева вверху
                glVertex3f( 1.0f, 1.0f, 0.0f);  // Справа вверху
                glVertex3f( 1.0f,-1.0f, 0.0f);  // Справа внизу
                glVertex3f(-1.0f,-1.0f, 0.0f);  // Слева внизу
        glEnd();
}

В этом примере я попытался описать как можно больше деталей, указать каждый шаг создания полигонов на экране с использованием OpenGL. Если у вас есть, какие либо замечания, вопросы или комментарии, я жду ваших писем. Если вы нашли неправильно описание или ошибки в коде, пожалуйста, сообщите мне об этом, мне очень хотелось бы, чтобы эти описания были лучше.

Урок 3. Отображение цветов

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

Вы можете использовать код из предыдущего урока, изменив лишь процедуру DrawGLScene(). Я переписал ее содержимое, и, если вы планируете изменять предыдущий урок, вам нужно полностью ее заменить, или добавить те строки, которые там отсутствуют.

GLvoid DrawGLScene(GLvoid)


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

        glTranslatef(-1.5f,0.0f,-6.0f);

        glBegin(GL_TRIANGLES);

Если вы еще помните предыдущий урок, в этой секции мы рисовали треугольник в левой части экрана. Следующие строки кода используют команду glColor3f(r, g, b). Три ее параметра указывают насыщенность цвета красной, синей и зеленой составляющей. Каждый из них может принимать значение от 0.0f до 1.0f.

Мы установим красный цвет (полный красный, без зеленого и синего). Следующие строки кода указывают, что первая вершина (верхняя) будет иметь красный цвет. Все что мы будем рисовать дальше красным, до тех пор, пока мы его не сменим цвет.

                glColor3f(1.0f,0.0f,0.0f);      // Красный цвет


                glVertex3f( 0.0f, 1.0f, 0.0f);

Мы отобразили первую вершину, установив для нее красный цвет. Теперь добавим следующую вершину (левую нижнею), но установим для нее уже зеленый цвет.

                glColor3f(0.0f,1.0f,0.0f);      // Зеленный цвет
                glVertex3f(-1.0f,-1.0f, 0.0f);

Пришло время третьей и последней вершины (правый нижний угол). Перед тем как отобразить ее, мы установим синий цвет. После выполнения команды glEnd() треугольник будет залит указанными цветами. Так как мы указали для каждой вершины свой цвет, каждая часть фигуры будет залита по-разному. При переходе в другую вершину заливка будет плавно изменять свой цвет на цвет вершины. В середине треугольника все три цвета будут слиты в один.

                glColor3f(0.0f,0.0f,1.0f);      // Синий цвет
                glVertex3f( 1.0f,-1.0f, 0.0f);
        glEnd();

        glTranslatef(3.0f,0.0f,0.0f);

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

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

        glColor3f(0.5f,0.5f,1.0f);      // Установим синий цвет только один раз
        glBegin(GL_QUADS);
                glVertex3f(-1.0f, 1.0f, 0.0f);
                glVertex3f( 1.0f, 1.0f, 0.0f);
                glVertex3f( 1.0f,-1.0f, 0.0f);
                glVertex3f(-1.0f,-1.0f, 0.0f);
        glEnd();
}

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

Урок 4. Вращение полигонов

В прошлом уроке я научил Вас как закрашивать треугольники и четырехугольники. В этом уроке я научу Вас как вращать эти закрашенные объекты вдоль их осей.

Мы будем использовать код из последнего урока, добавляя новые строчки кода. Я перепишу целую секцию кода ниже, чтобы вы могли понять, что добавлено, а что заменено.

Вначале мы добавим две переменные для хранения угла вращения каждого объекта. Мы сделаем это вначале программы. Посмотрите ниже, я добавил две строки после объявления переменной BOOL keys[256]. В этих строках объявляются две переменные с плавающей запятой, которые мы можем использовать для очень точного поворота объектов. Числа с плавающей запятой учитывают значения меньше единицы. Вместо использования 1, 2, 3 для угла, мы можем использовать 1.1, 1.7, 2.3 или даже 1.015 для точности. Вы увидете, что числа с плавающей запятой неотемлимая часть программирования на OpenGL.

#include       // Заголовочный файл для Windows
#include         // Заголовочный файл для OpenGL32 библиотеки
#include        // Заголовочный файл для GLu32 библиотеки
#include      // Заголовочный файл для GLaux библиотеки

static HGLRC hRC;       // Постоянный контекст рендеринга


static HDC hDC;         // Приватный контекст устройства GDI

BOOL    keys[256];      // Массив для процедуры обработки клавиатуры

GLfloat rtri;           // Угол для треугольник
GLfloat rquad;          // Угол для четырехугольника

Необходимо модифицировать код в DrawGLScene(). Я буду переписывать всю процедуру. Это будет сделать легко для Вас, так как Вы увидите какие изменения я сделал. Я объясню почему некоторые строки были модифицированы, и какие линии добавлены. Следующая секция кода, такая же как в последнем уроке.

GLvoid DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Очистка экрана
                                                        //      и буфера глубины
glLoadIdentity();                                       // Сброс просмотра
glTranslatef(-1.5f,0.0f,-6.0f);                         // Сдвиг в глубь экрана и влево

Следующая строка новая. glRotatef(Angle,Xtrue,Ytrue,Ztrue) отвечает за вращения объекта вдоль оси. Вы многое получите от использования этой команды. Угол некоторое число (обычно переменная), которое задает насколько Вы хотите повернуть объект. Xtrue, Ytrue и Ztrue или 0.0f или 1.0f. Если один из параметров равен 1.0f, OpenGL будет вращать объект вдоль соответствующей оси. Поэтому если Вы имеете glRotatef(10.0f,0.0f,1.0f,0.0f), объект будет поварачиваться на 10 градусов по оси Y. Если glRotatef(5.0f,1.0f,0.0f,1.0f), объект будет поварачиваться на 5 градусов по обеим осям X и Z.

Чтобы лучше понять вращения по осям X, Y и Z я объясню это на примерах.

Ось X - предположим Вы работаете за токарным станком. Заготовка перемещается слева направо (также как ось X в OpenGL). Заготовка вращается вокруг оси X. Также мы вращаем что-то вокруг оси X в OpenGL.

Ось Y- Представьте что Вы стоите посреди поля. Огромный торнадо приближается к Вам. Центр торнадо перемещается от земли в небо (верх и низ, подобно оси Y в OpenGL). Предметы захваченные торнадо кружаться вдоль оси Y (центр торнадо) слева направо или справо налево. Когда вы вращаете что-то вокруг оси Y в OpenGL, это что-то будет вращаться также.

Ось Z - Вы смотрите на переднюю часть вентилятора. Передняя часть вентилятора ближе к Вам, а дальняя часть дальше от Вас (также как ось Z в OpenGL). Лопасти вентилятора вращаются вдоль оси Z (центр вентилятора) по часовой или против часовой стрелки. Когда Вы вращаете что-то вокруг оси Z в OpenGL, это что-то будет вращаться также.

В следующей строке кода, если rtri равно 7, мы будем вращать на 7 градусов по оси Y (слева направо). Вы можете поэксперементировать с кодом. Изменяйте от 0.0f до 1.0f, и от 1.0f до 0.0f вращение треугольника по осям X и Y одновременно.

        glRotatef(rtri,0.0f,1.0f,0.0f);         // Вращение треугольника по оси Y

Следующая секция кода не изменена. Здесь будет нарисован закрашенный сглаженный треугольник. Треугольник будет нарисован с левой стороны экрана, и будет вращаться по оси Y слева направо.

        glBegin(GL_TRIANGLES);                  // Начало рисования треугольника


                glColor3f(1.0f,0.0f,0.0f);      // Верхняя точка - красная
                glVertex3f( 0.0f, 1.0f, 0.0f);  // Первая точка
                glColor3f(0.0f,1.0f,0.0f);      // Левая точка - зеленная
                glVertex3f(-1.0f,-1.0f, 0.0f);  // Вторая
                glColor3f(0.0f,0.0f,1.0f);      // Правая - синия
                glVertex3f( 1.0f,-1.0f, 0.0f);  // Третья
        glEnd();                                // Конец рисования

Посмотрите на код ниже, там мы добавим вызов glLoadIdentity(). Мы сделаем это для инициализации просмотра. Что будет если мы не сбросим просмотр? Если мы сдвинули объект после вращения, Вы получите очень неожиданные результаты. Поскольку оси вращаются, они будут указывать не в тех направлениях, о каких Вы думаете. Поэтому если мы сдвинулись влево по оси X (для установки треугольника), мы можем переместить квадрат в глубь экрана или вперед, в зависимости от того как много мы вращали по оси Y. Попробуйте убрать glLoadIdentity() и вы поймете о чем я говорю. Квадрат будет вращаться вокруг своей оси X, но и вокруг центра координат синхронно вращению треугольника.

Так как сцена сброшена, поэтому X идет слева направо, Y сверху вниз, Z от нас и далее. Теперь мы перемещаем. Как Вы видите мы сдвигаем на 1.5 вправо, вместо 3.0, как мы делали в последнем уроке. Когда мы сбрасываем экран, наш фокус перемещается в центр экрана, это означает, что мы находимся не 1.5 единицы слева, мы вернулись в 0.0. Поэтому мы не должны сдвигаться на 3.0 единицы вправо (если бы не было сброса), мы должны только сдвинуться от центра вправо на 1.5 единицы.

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

        glLoadIdentity();
        glTranslatef(1.5f,0.0f,-6.0f);          // Сдвиг вправо на 1.5
        glRotatef(rquad,1.0f,0.0f,0.0f);        // Вращение по оси X

Эта секция кода завершение предыдущей. Рисуем синий квадрат из одного четырехугольника. Квадрат будет справа на экране и там же будет вращаться.

        glColor3f(0.5f,0.5f,1.0f);              // Синий цвет
        glBegin(GL_QUADS);                      // Начнем
                glVertex3f(-1.0f, 1.0f, 0.0f);  // Верх лево
                glVertex3f( 1.0f, 1.0f, 0.0f);  // Верх право
                glVertex3f( 1.0f,-1.0f, 0.0f);  // Низ право
                glVertex3f(-1.0f,-1.0f, 0.0f);  // Низ лево
        glEnd();                                // Окончим

Следующие две строки новые. Думайте о rtri и rquad как о контейнерах. Вначале нашей программы мы сделали контейнеры (GLfloat rtri и GLfloat rquad). Когда мы построили контейнеры они были пусты. В первой строке ниже ДОБАВЛЯЕМ 0.2 в контейнер. Если мы проверим значение контейнера rtri после этой секции кода, мы увидим что оно увеличилось на 0.2. Контейнер rquad уменьшиться на 0.15. Если мы проверим значение контейнера rquad после этой секции кода, мы увидим что оно уменьшилось на 0.15. Отрицательные значения вращения приводят к тому, что объект вращается в противоположную сторону. Как если бы значения были положительные.

Попробуйте изменить + на - в строке ниже и объект будет вращаться в другом направлении. Попробуйте изменить значение с 0.2 до 1.0. С увеличением значения объект будет вращаться быстрее. С уменьшением значения будет вращаться медленее.

        rtri+=0.2f;             // Увеличение переменной вращения для треугольника


        rquad-=0.15f;           // Уменьшение переменной вращения для квадрата
}

В этом уроке я попробывал рассказать как можно детальнее о том как вращаются объекты вокруг осей. Поиграйте с этим кодом, попробуйте вращать объекты по оси Z, X & Y, или по всем трем ;). Если у Вас есть комментарии или вопросы пожалуйста вышлите мне письмо. Если Вы нашли ошибки или улучшения дайте мне об этом знать. Я хочу сделать уроки по OpenGL хорошими насколько смогу. Я заинтересован в обратной связи.

Урок 5. Создание фигур в 3D

Продолжая последний урок, мы сделаем объект, как ИСТИННЫЙ трехмерный объект, а не 2D объекты в 3D мире. Мы будем делать это добавлением с левой, задней и правой сторон треугольника, и с левой, правой, верхней и нижней сторон квадрата. Сделав это, мы превращаем треугольник в пирамиду с четырьмя гранями и квадрат в куб.

Мы будем смешивать цвета на пирамиде, создавая сглаженный закрашенный объект, а для квадрата мы назначим каждой грани свой цвет.

GLvoid DrawGLScene(GLvoid)


{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Очистка экрана и буфера глубины
glLoadIdentity();                               // Сброс просмотра
glTranslatef(-1.5f,0.0f,-6.0f);                 // Сдвиг влево и вглубь экрана
glRotatef(rtri,0.0f,1.0f,0.0f);                 // Вращение пирамиды по оси Y
glBegin(GL_TRIANGLES);                          // Начало рисования пирамиды

Некоторые из Вас взяли код из последнего урока и сделали свои собственные 3D объекты. Вот один вопрос, который вы задали : "как сделать, чтобы мои объекты не вращались по своим осям? Потому что кажется, что они вращаются на весь экран". Чтобы объект вращался вокруг оси, он должен быть разработан для вращения ВОКРУГ оси. Вы должны помнить, что центр любого объекта должен быть в 0 для X, 0 для Y, 0 для Z.

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

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

Мы начинаем рисовать с Передней Грани. Поскольку во все грани входит верхняя точка, мы будем делать эту точку красной во всех треугольниках. Цвет нижних двух точек треугольника будет другим. Передняя грань будет зеленной в левой точке и синей в правой точке. Треугольник с правой стороны будет синим в левой точке и зеленным в правой точке. При помощи чередования двух нижних цветов на каждой грани, мы сделаем общие закрашенные точки снизу на каждой грани.

        glColor3f(1.0f,0.0f,0.0f);                      // Красный


        glVertex3f( 0.0f, 1.0f, 0.0f);                  // Верх треугольника (Передняя)
        glColor3f(0.0f,1.0f,0.0f);                      // Зеленный
        glVertex3f(-1.0f,-1.0f, 1.0f);                  // Левая точка
        glColor3f(0.0f,0.0f,1.0f);                      // Синий
        glVertex3f( 1.0f,-1.0f, 1.0f);                  // Правая точка

Сейчас мы нарисуем правую грань. Отметим, что две нижних точки нарисованы на единицу справа от центра, верхняя точка нарисована на единицу выше оси Y, и справа от середины оси X. Поэтому эта грань имеет наклон от центральной точки сверху вниз с правой стороны.

Замечу, что левая точка нарисована синим цветом в этот раз. Так как она нарисована синей, это будет тот же самый цвет, какой у точки правого нижнего угла лицевой грани. Градиент синего цвета идет от одного угла вдоль лицевой и правой граней пирамиды.

Замечу, что все четыре грани включены внутрь тех же самых glBegin(GL_TRIANGLES) и glEnd(), словно одна сторона. Поскольку мы делаем целый объект из треугольников, OpenGL знает, что каждые три точки мы рисуем как три точки одного треугольника. Треугольник рисуется из трех точек, если больше трех точек, то OpenGL поймет, что надо рисовать другой треугольник. Если вы выведете четыре точки вместо трех, OpenGL будет рисовать первые три точки и примет четвертую точку как начальную точку нового треугольника. Но не будет нарисован Четырехугольник. Поэтому проверьте, что вы не добавили любые дополнительные точки нечаяно.

        glColor3f(1.0f,0.0f,0.0f);                      // Красная
        glVertex3f( 0.0f, 1.0f, 0.0f);                  // Верх треугольника (Правая)
        glColor3f(0.0f,0.0f,1.0f);                      // Синия
        glVertex3f( 1.0f,-1.0f, 1.0f);                  // Лево треугольника (Правая)
        glColor3f(0.0f,1.0f,0.0f);                      // Зеленная
        glVertex3f( 1.0f,-1.0f, -1.0f);                 // Право треугольника (Правая)

Сейчас задняя сторона. Снова переключим цвета. Левая точка – зеленного цвета, поскольку этот угол так же и зеленный угол правой грани.

        glColor3f(1.0f,0.0f,0.0f);                      // Красный
        glVertex3f( 0.0f, 1.0f, 0.0f);                  // Низ треугольника (Сзади)
        glColor3f(0.0f,1.0f,0.0f);                      // Зеленный
        glVertex3f( 1.0f,-1.0f, -1.0f);                 // Лево треугольника (Сзади)
        glColor3f(0.0f,0.0f,1.0f);                      // Синий
        glVertex3f(-1.0f,-1.0f, -1.0f);                 // Право треугольника (Сзади)

В завершении мы рисуем левую грань. Цвета переключаются в последний раз. Левая точка синего цвета, и смешивается с правой точкой на задней грани. Правая точка зеленного цвета, и смешивается с левой точкой на передней грани.

Мы закончили рисовать пирамиду. Поскольку пирамида только крутиться вдоль оси Y, мы не когда не увидим низ, поэтому нет необходимости выводить низ пирамиды. Если вы чувствуете потребность в экспериментах, попробуйте добавить низ используя четырехугольник, тогда вращайте по оси X для того чтобы увидеть низ, если конечно вы сделали все корректно. Проверьте, что цвет каждого угла в четырехугольнике совпадает с цветом, который использован в каждом из четырех углов пирамиды.

        glColor3f(1.0f,0.0f,0.0f);                      // Красный


        glVertex3f( 0.0f, 1.0f, 0.0f);                  // Верх треугольника (Лево)
        glColor3f(0.0f,0.0f,1.0f);                      // Синий
        glVertex3f(-1.0f,-1.0f,-1.0f);                  // Лево треугольника (Лево)
        glColor3f(0.0f,1.0f,0.0f);                      // Зеленный
        glVertex3f(-1.0f,-1.0f, 1.0f);                  // Право треугольника (Лево)
glEnd();                                                // Кончили рисовать пирамиду

Сейчас мы будем рисовать куб. Чтобы сделать это надо шесть квадратов. Все квадраты рисуются против часовой стрелке. Примем, что первая точка справа вверху, вторая точка слева вверху, третья точка слева внизу, и последняя слева внизу. Когда мы рисуем заднюю грань, то будет казаться, что мы рисуем по часовой стрелке, но вы помните, что если мы были позади куба и смотрели на него, то левая сторона экрана, фактически была бы с правой стороны квадрата, и правая сторона экрана была бы фактически с левой стороны квадрата.

Замечу, что мы сдвинули куб немного вглубь экрана в этом уроке. Поэтому размер куба будет казаться меньше размера пирамиды. Если мы переместили бы куб на 6 единиц к экрану, то куб будет казаться больше чем пирамида, и часть куба будет за пределами экрана. Вы можете поиграться с этим настройками, и сдвинув куб дальше от экрана он будет казаться меньше, а придвинув к экрану он будет казаться больше. Это происходит из-за переспективы. Объекты на расстоянии кажутся меньше :).

glLoadIdentity();


glTranslatef(1.5f,0.0f,-7.0f);          // Сдвинуть вправо и вглубь экрана
glRotatef(rquad,1.0f,1.0f,1.0f);        // Вращение куба по X, Y & Z
glBegin(GL_QUADS);                      // Рисуем куб

Мы начнем рисовать куб сверху. Мы сдвигаемся на одну единицу от центра куба. Отметим, что по оси Y всегда единица. Затем мы рисуем квадрат на Z плоскости. Мы начинаем рисовать с правой точки вверху экрана. Правая верхняя точка должна быть на одну единицу справа, и на одну единицу вглубь экрана. Вторая точка будет на одну единицу влево и на единицу вглубь экрана. Сейчас мы нарисуем ту часть квадрата, которая ближе к зрителю. Поэтому для того чтобы сделать это, вместо смещения вглубь экрана, мы сдвигаемся на одну единицу к экрану. Улавливаете смысл?

        glColor3f(0.0f,1.0f,0.0f);              // Синий
        glVertex3f( 1.0f, 1.0f,-1.0f);          // Право верх квадрата (Верх)
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Лево верх
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Лево низ
        glVertex3f( 1.0f, 1.0f, 1.0f);          // Право низ

Нижняя часть квадрата рисуется таким же образом, как и верхняя, но поскольку это низ, сдвигаемся вниз на одну единицу от центра куба. Замечу, что ось Y всегда минус единица. Если мы окажемся под кубом, и взглянем на квадрат, который снизу, вы заметите, что правый верхний угол – это угол ближний к зрителю. Поэтому вместо того чтобы рисовать дальше от зрителя в начале, мы рисуем ближе к зрителю, тогда левая сторона ближе к зрителю. И затем мы движемся вглубь экрана, для того чтобы нарисовать дальние две точки.

Если Вы действительно не заботитесь о порядке рисования полигонов (по часовой или против), вы должны скопировать код для верхнего квадрата, сдвинуть вниз по оси Y на единицу, и это будет работать, но игнорируя порядок рисования квадрата можно получить неверный результат, если вы захотите сделать, например, наложение текстуры.

        glColor3f(1.0f,0.5f,0.0f);              // Оранжевый


        glVertex3f( 1.0f,-1.0f, 1.0f);          // Верх право квадрата (Низ)
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Верх лево
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Низ лево
        glVertex3f( 1.0f,-1.0f,-1.0f);          // Низ право

Сейчас мы рисуем передний квадрат. Мы сдвигаемся на одну единицу ближе к экрану, и дальше от центра для того чтобы нарисовать переднею грань. Заметим, что ось Z всегда равна единице. В гранях пирамиды ось Z не всегда единица. Вверху, ось Z равна нулю. Если Вы попробуете установить ось Z равной нулю в привиденом ниже коде, вы увидите, что угол, который вы изменили наклонен к экрану. Но это не то, что мы хотим сейчас сделать ;).

        glColor3f(1.0f,0.0f,0.0f);              // Красный
        glVertex3f( 1.0f, 1.0f, 1.0f);          // Верх право квадрата (Перед)
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Верх лево
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Низ лево
        glVertex3f( 1.0f,-1.0f, 1.0f);          // Низ право

Задняя грань квадрата такая же ка передняя грань, но сдвинута вглубь экрана. Отметим, что ось Z всегда минус один во всех точках.

        glColor3f(1.0f,1.0f,0.0f);              // Желтый
        glVertex3f( 1.0f,-1.0f,-1.0f);          // Верх право квадрата (Зад)
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Верх лево
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Низ лево
        glVertex3f( 1.0f, 1.0f,-1.0f);          // Низ право

Сейчас нам осталось нарисовать только два квадрата. Как вы уже успели заметить одна ось всегда имеет тоже самое значение у всех точек квадрата. В этом случае ось X всегда равна минус один. Поскольку мы рисуем левую грань.

        glColor3f(0.0f,0.0f,1.0f);              // Синий
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Верх право квадрата (Лево)
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Верх лево
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Низ лево
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Низ право

И последняя грань завершит куб. Для нее ось X всегда равна единице. Рисуем против часовой стрелки. Если вы хотите, то вы можете не рисовать эту грань и получите коробку ;).

Если вы хотите поэксперементировать, вы всегда можете изменить цвет каждой точки в кубе для того чтобы сделать градиент, как в пирамиде. Вы можете посмотреть пример интерполяционной заливки куба скопировав первую демонстрацию Evil с моего сайта. Запустите ее и нажмите TAB. Вы увидите чудесный цветной куб, с изменяющимися цветами вдоль всех граней.

        glColor3f(1.0f,0.0f,1.0f);              // Фиолетовый


        glVertex3f( 1.0f, 1.0f,-1.0f);          // Верх право квадрата (Право)
        glVertex3f( 1.0f, 1.0f, 1.0f);          // Верх лево
        glVertex3f( 1.0f,-1.0f, 1.0f);          // Низ лево
        glVertex3f( 1.0f,-1.0f,-1.0f);          // Низ право
        glEnd();                                // Закончили квадраты

rtri+=0.2f;             // Увеличим переменную вращения для треугольника 


rquad-=0.15f;           // Уменьшим переменную вращения для квадрата 
}

В конце этого урока, вы должны лучше понимать как объекты создаются в 3D пространстве. Вы должны представлять экран OpenGL, как гиганская милиметровка, с множеством прозрачных слоев за ней. Это похоже на гигантский куб сделаный из точек. Некоторые точки двигаются слева направо, некоторые двигаются верх и вниз, и некоторые точки двигаются взад и вперед в кубе. Если вы может представить глубину экрана, вы не будете иметь проблем с разработкой новых 3D объектов.

Если вы с трудом понимаете 3D пространство, то это не бесполезно. Это может быть сложным только вначале. Объекты подобные кубу хороший пример для обучения. Если вы заметили задняя грань рисуется также как передняя грань, только дальше от экрана. Поиграйте с этим кодом, и если вы не можете понять это, спросите меня, и я вам отвечу.

Урок 6. Наложение текстуры

Из наложения текстуры можно извлечь много полезного. Предположим, что вы хотите, чтобы ракета пролетела через экран. До этого урока мы попытались бы сделать ракету из полигонов и фантастических цветов. С помощью наложения текстуры, мы можем получить реальную картинку ракеты и заставить ее летать по экрану. Как вы думаете, что будет выглядеть лучше? Фотография или объект сделанный их треугольников и четырехугольников? Используя наложение текстуры, и выглядеть будет лучше, и ваша программа будет работать быстрее. Ракета с наложением текстуры - это всего лишь четырехугольник, движущийся по экрану. Ракета сделанная из полигонов может иметь сотни или тысячи полигонов. Отрисовка простого четырехугольника будет отнимать меньше процессорного времени.

Давайте начнем с добавления четырех новых строк в начало кода первого урока. Первые три строки задают четыре вещественных переменных - xrot, yrot и zrot. Эти переменные будут использованы для вращения куба по осям x, y, z. В четвертой строке резервируется место для одной текстуры. Если вы хотите загрузить более чем одну текстуру, измените, число один на число текстур, которые вы хотите загрузить.

#include // Заголовочный файл для Windows

#include // Заголовочный файл для OpenGL32 библиотеки

#include // Заголовочный файл для GLu32 библиотеки

#include // Заголовочный файл для GLaux библиотеки


static HGLRC hRC; // Постоянный контекст рендеринга

static HDC hDC; // Приватный контекст устройства GDI


BOOL keys[256]; // Массив для процедуры обработки клавиатуры
GLfloat xrot; // Вращение X

GLfloat yrot; // Y

GLfloat zrot; // Z
GLuint texture[1]; // Место для одной текстуры

Теперь сразу же после этого кода, до InitGL, мы добавим следующую секцию кода. Этот код загружает файл картинки, и конвертирует его в текстуру. Прежде чем я начну объяснять этот код, я сделаю нескольких ВАЖНЫХ замечаний, которые вы должны знать об изображениях, которые вы используете как текстуры. Такое изображение ДОЛЖНО иметь высоту и ширину кратной двум. При этом высота и ширина изображения должна быть не меньше чем 64 пикселя, и по причинам совместимости, не более 256 пикселов. Если изображение, которое вы используете не 64, 128 или 256 пикселов в ширину и высоту, измените его размер в программе для рисования. Имеются возможность обойти эти ограничения, но мы пока будем придерживаться стандартных размеров текстуры.

AUX_RGBImageRec *texture1 задает указатель на структуру для хранения первой картинки, которую мы загрузим и используем как текстуру. Структура содержит красную, зеленную и синею компоненты цвета, которые используются при создании изображения. Обычно так размещается в памяти загруженная картинка. Структура AUX_RGBImageRec определена в библиотеке glAux, и делает возможной загрузку картинки в память. В следующей строке происходит непосредственная загрузка. Файл картинки "NeHe.bmp" из каталога "Data" будет загружен и сохранен в структуре texture1, которую мы задали выше с помощью AUX_RGBImageRec.

// Загрузка картинки и конвертирование в текстуру

GLvoid LoadGLTextures()

{

// Загрузка картинки



AUX_RGBImageRec *texture1;

texture1 = auxDIBImageLoad("Data/NeHe.bmp");

Сейчас мы загрузили изображение как данные компонент цветов красного, зеленного и синего, далее мы построим текстуру используя эти данные. Вызовом glGenTextures(1, &texture[0]) мы скажем OpenGL, что мы хотим построить текстуру в нулевом элементе массива texture[]. Помните, в начале урока мы зарезервировали место для одной текстуры с помощью GLuint texture[1]. Хотя вы, возможно, подумали, что мы сохраним текстуру в &texture[1], но это не так. Первая действительная область для сохранения имеет номер 0. Если вы хотите две текстуры, надо задать GLuint texture[2] и вторая текстура будет сохранена в texture[1].

Во второй строке вызов glBindTexture(GL_TEXTURE_2D, texture[0]) говорит OpenGL, что texture[0] (первая текстура) будет 2D текстурой. 2D текстуры имееют и высоту (по оси Y) и ширину (по оси X). Основная задача glGenTexture указать OpenGL на доступную память. В этом случае мы говорим OpenGL, что память доступна в &texture[0]. Затем мы создаем текстуру, и она будет сохранена в этой памяти. Далее, если мы привязываемся к памяти, в которой уже находиться текстура, мы говорим OpenGL захватить данные текстуры из этой области памяти. Обычно это указатель на доступную память, или память, в которой содержиться текстура.

// Создание текстуры

glGenTextures(1, &texture[0]);

glBindTexture(GL_TEXTURE_2D, texture[0]);

В следующих двух строках мы сообщим OpenGL какой тип фильтрации надо использовать, когда изображение больше на экране, чем оригинальная текстура (GL_TEXTURE_MAG_FILTER), или когда оно меньше на экране, чем текстура (GL_TEXTURE_MIN_FILTER). я обычно использую GL_LINEAR для обоих случаев. При этом текстура выглядит сглаженной на расстоянии, и вблизи. Использование GL_LINEAR требует много работы для процессора/видеокарты, поэтому если ваша система медленная, вы можете захотеть использовать GL_NEAREST. Текстура, которая фильтруется с GL_NEAREST состоит из хорошо видимых цветовых прямоугольников, когда она вблизи. Вы можете попробовать комбинировать оба способа. Сделайте одну фильтрацию вблизи, а другую вдали.

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

В завершении мы создаем фактическую текстуру. В следующей строке мы говорим OpenGL, что текстура будет двухмерной (GL_TEXTURE_2D). Ноль задает уровень детализации, это обычно ноль. Три - число компонент цветовых данных, так как изображение сделано из трех цветовых компонент (красный, зеленный, синий). texture1- >sizeX - это ширина текстуры, автоматически. Если вы знаете ширину, вы можете указать ее тут, но проще дать компьютеру сделать это за вас. texture1->sizeY - высота текстуры. Ноль - это бордюр. Он обычно остается нулем. GL_RGB сообщает OpenGL, что данные изображения представлены в порядке следования красных, зеленных и голубых компонент цвета. GL_UNSIGNED_BYTE означает, что данные из которых состоит изображение имеют размер байта и все числа без знака, и в конце texture1->data сообщает OpenGL, где брать сами данные. В этом случае указатель на данные в записи texture1.

glTexImage2D(GL_TEXTURE_2D, 0, 3, texture1->sizeX, texture1->sizeY, 0,

GL_RGB, GL_UNSIGNED_BYTE, texture1->data);

}

Мы добавим две строчки в код InitGL. я повторно привожу эту секцию кода, для того чтобы было легко увидеть строчки, которые я добавил, и где они идут в коде.



В первой строке происходит вызов процедуры LoadGLTextures(), которая загружает изображение и делает из него текстуру. Вторая строка glEnable(GL_TEXTURE_2D) разрешает наложение текстуры. Если вы не делаете доступной наложение текстуры, ваш объект будет закрашен сплошным белым цветом, который точно не очень хорош.

GLvoid InitGL(GLsizei Width, GLsizei Height)

{

LoadGLTextures(); // Загрузка текстур



glEnable(GL_TEXTURE_2D); // Разрешение наложение текстуры

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

glClearDepth(1.0);

glDepthFunc(GL_LESS);

glEnable(GL_DEPTH_TEST);

glShadeModel(GL_SMOOTH);


glMatrixMode(GL_PROJECTION);

glLoadIdentity();


gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW);

}

Сейчас мы нарисуем куб с текстурой. Мы можете заменить код DrawGLScene на код ниже, или вы можете добавить новый код в оригинальный код первого урока. Эта секция будет сильно прокомментирована, поэтому легка для понимания. Первые две строки кода glClear() и glLoadIdentity() взяты из оригинального кода первого урока. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) очищает экран цветом, который мы выбрали в InitGL(). В этом случае, экран будет очищен в синий цвет. Буфер глубины будет также очищен. Просмотр будет сброшен с помощью glLoadIdentity().



GLvoid DrawGLScene(GLvoid)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



glLoadIdentity();

glTranslatef(0.0f,0.0f,-5.0f);

Следующие три строки кода будут вращать куб по оси X, затем по оси Y, и в конце по оси Z. Насколько велико будет вращение (угол) по каждой оси будет зависеть от значения указанного в xrot, yrot и zrot.

glRotatef(xrot,1.0f,0.0f,0.0f); // Вращение по оси X

glRotatef(yrot,0.0f,1.0f,0.0f); // Вращение по оси Y

glRotatef(zrot,0.0f,0.0f,1.0f); // Вращение по оси Z

В следующей строке кода происходит выбор какую текстуру мы хотим использовать для наложения текстуры. Если вы хотите использовать более чем одну текстуру в вашей сцене, вы должны выбрать текстуру glBindTexture(GL_TEXTURE_2D, texture[номер текстуры для использования]). Вы должны затем нарисовать несколько четырехугольников используя эту текстуру. Каждый раз, когда Вы захотите сменить текстуру, Вы должны привязать новую текстуру. Одно замечание: вы НЕ должны связывать текстуру внутри glBegin() и glEnd().

glBindTexture(GL_TEXTURE_2D, texture[0]);

Для того чтобы правильно отобразить текстуру на четырехугольник, вы должны отобразить правый верхний угол текстуры на правую верхнею вершину четырехугольника. Левый верхний угол текстуры отображается в левую верхнею вершину четырехугольника, правый нижний угол текстуры отображается в правую нижнею вершину четырехугольника, и в завершении, левый нижний угол текстуры отображается в левую нижнею вершину четырехугольника. Если углы текстуры не совпадают с углами четырехугольника, изображение может быть сдвинуто вниз, в сторону, или вообще отсутствовать.

Первый аргумент glTexCoord2f - координата X. 0.0f - левая сторона текстуры. 0.5f - середина текстуры, и 1.0f - правая сторона текстуры. Втрое значение glTexCoord2f - это Y координата. 0.0f - низ текстуры. 0.5f - середина текстуры, и 1.0f - верх текстуры.

Теперь мы знаем, что левая верхняя координата текстуры 0.0f по X и 1.0f по Y, и левая верхняя вершина четырехугольника -1.0f по X, и 1.0f по Y. Теперь осталось сделать так, чтобы оставшиеся три координаты совпали с тремя углами четырехугольника.

Попробуйте поиграться со значениями x и y в glTexCoord2f. Изменение 1.0f на 0.5f будет только рисовать левую половину текстуры от 0.5f (середина) до 1.0f (право).

glBegin(GL_QUADS);
// Передняя грань

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Низ лево

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Низ право

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Верх право

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Верх лево
// Задняя грань

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Низ право

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Верх право

glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Верх лево

glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Низ лево
// Верхняя грань

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Верх лево

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Низ лево

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Низ право

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Верх право

// Нижняя грань

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Верх право

glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Верх лево

glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Низ лево

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Низ право


// Правая грань

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Низ право

glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Верх право

glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Верх лево

glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Низ лево
// Левая грань

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Низ лево

glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Низ право

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Верх право

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Верх лево
glEnd();

Сейчас мы увеличим значения xrot, yrot и zrot. Попробуйте изменить значения каждой переменной, замедляя или ускоряя вращение куба, или изменяя ' +' на '-' заставляя куб вращаться в другом направлении.

xrot+=0.3f; // Ось вращения X

yrot+=0.2f; // Ось вращения Y

zrot+=0.4f; // Ось вращения Z

}

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



Наложение текстуры не трудно для понимания, если Вы понимаете координаты текстуры. Если Вы имеете проблемы с пониманием любой части этого урока, дайте мне знать. Или я изменю этот урок, и я отвечу Вам по почте. Развлекайтесь созданием наложения текстуры в Ваших сценах.

Урок 7. Режимы фильтрации текстур, освещение и обработка клавиатуры

В этом уроке я научу вас, как использовать три разных режима фильтрации текстур. Я научу вас, как перемещать объект, используя клавиши на клавиатуре, и я также преподам вам, как применить простое освещение в вашей OpenGL сцене. В этом уроке много материала, поэтому, если предыдущие уроки вам непонятны, вернитесь и посмотрите их вновь. Важно иметь хорошее понимание основ прежде, чем Вы перепрыгнете на этот код.

Мы собираемся снова изменить код первого урока. Как обычно, если много меняется, я привожу полную секцию кода, который был изменен. Мы начнем, добавив несколько новых переменных к программе.

 

#include   // Заголовочный файл для Windows



#include     // Заголовочный файл для стандартного ввода/вывода (ДОБАВИЛИ)

#include     // Заголовочный файл для библиотеки OpenGL32

#include    // Заголовочный файл для для библиотеки GLu32

#include  // Заголовочный файл для библиотеки GLaux

 

HDC    hDC=NULL;      // Служебный контекст GDI устройства



HGLRC  hRC=NULL;      // Постоянный контекст для визуализации

HWND   hWnd=NULL;     // Содержит дискриптор для окна

HINSTANCE hInstance;  // Содержит данные для нашей программы

 

bool keys[256];       // Массив, использующийся для сохранения состояния клавиатуры



bool active=TRUE;     // Флаг состояния активности приложения (по умолчанию: TRUE)

bool fullscreen=TRUE; // Флаг полноэкранного режима (по умолчанию: полноэкранное)

 

Строки ниже новые. Мы собираемся добавлять три логических переменных. Тип BOOL означает, что переменная может только быть ИСТИННА (TRUE) или ЛОЖЬ (FALSE). Мы создаем переменную называемую 


Каталог: forum
forum -> Лечение гепатозов
forum -> Система ведения овцеводства в крестьянско-фермерских и личных хозяйствах населения
forum -> Yaesuft-857 (ft-897) – переходник для подключения cat интерфейса к микрофонному разъему трансивера
forum -> Васильев Владимир Юрьевич
forum -> tl-wr1043ND Беспроводной гигабитный маршрутизатор серии n copyright & trademarks
forum -> Цели и задачи Контакта rtf docx
forum -> Назовите не менее трех результатов революции 1905- 1907гг. Приведите не менее трех положений, отражающих значение революции для отечественной истории начала 20 века


Поделитесь с Вашими друзьями:
  1   2   3   4   5   6   7   8   9   ...   27


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница