Сайт о телевидении

Сайт о телевидении

» » Opengl разработка игр. Управляем несколькими OpenGL окнами

Opengl разработка игр. Управляем несколькими OpenGL окнами

Во время своего #shaderchallenge (заинтересовался как устроена красивая графика, какие визуальные эффекты можно делать шейдерами и тд,- и решил непременно изучить) я пришел к тому, что мне необходимо знать как устроены внутри игровые движки. И, конечно, нет способа лучше изучить устройство чем попробовать написать свой!

Так сказать позаколачивать знания в свою голову, наступая на разные грабли, изгугливая разные форумы, доски и тп.

В 2016ом я решил прокачаться в программировании графики и начал изучать программирование шейдеров. И как оказалось в этом деле никуда без математики, поэтому я хорошую часть этого года вспоминал, а по-большей части заново знакомился с линейной алгеброй и кое-каким мат. анализом.

С какими-то простыми эффектами я познакомился в Unity3d, я по роду своей деятельности программирую именно на этом движке. Делал скринэффекты, простой аутлайн и тп. Кстати, знание того, как устроен пайплайн и какие эффекты можно получить, развязывают руки и в некоторых ситуациях просто думаешь: — Ага, так можно вот шейдерочек написать.

Но, как я говорил выше, необходимы более глубокие знания. Пишу свой движок. Конечно на C++ и OpenGL. Не DirectX, потому, что мне казалось, что на консолях именно OpenGL, но это не так.

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

Про графическую библиотеку пояснил. Теперь про язык. Я очень доволен выбором именно C++, какой он крутой, ребята! Сколько всего я о нем не знал! Сколько добавилось в новых стандартах и сколько еще предстоит изучить!

Итак, встречайте, первое более-менее презентабельное демо, в моей собственном игровом движке Rudy Engine.

Первое демо

Реализовано: cubemap/skybox, загрузка моделей через assimp, half-lambert для освещения, overlay-рендерер для прицела и маркера-указателя на космическую станцию.


(очень дрожит видео, не стал искать какую-то хорошую программу для записи, воспользовался monosnap, наверное он не очень для этого подходит)

Дальнейшие планы

В дальнейшем я хочу сделать туман, shadow-mapping, и какие-то эффекты для этой демки и, возможно, переключиться на какую-то следующую. В целом, стараюсь придерживаться взрослой и мучительной разработки: дорабатывать движок таким образом, чтобы старые демо оставались рабочими. Подрихтовываю и подлатываю его. Для чего? Нужно понять этот trade-off как у Unity, чего стоит сделать универсальный инструмент?

Update 2017

Летом в 2017ом году, у меня закончился испытательный срок в крупной игровой студии и я написал в блоге, вся вот эта практика с игровым движком, мне очень помогла!

OpenGL (Open Graphics Library - открытая графическая библиотека) - спецификация, определяющая независимый от языка программирования кросс-платформенный программный интерфейс для написания приложений, использующих двумерную и трехмерную компьютерную графику.
Включает более 250-ти функций для рисования сложных трехмерных сцен из простых примитивов. Используется при создании видео-игр, САПР, виртуальной реальности, визуализации в научных исследованиях. Под Windows конкурирует с DirectX.
Спецификация
На базовом уровне, OpenGL - это просто спецификация, то есть документ, описывающий набор функций и их точное поведение. Производители оборудования на основе этой спецификации создают реализации - библиотеки функций, соответствующих набору функций спецификации. Реализация использует возможности оборудования, там где это возможно. Если аппаратура не позволяет реализовать какую-либо возможность, она должна быть эмулирована программно. Производители должны пройти специфические тесты (conformance tests - тесты на соответствие) прежде чем реализация будет классифицирована как OpenGL реализация. Таким образом, разработчикам программного обеспечения достаточно научиться использовать функции, описанные в спецификации, оставив эффективную реализацию последних разработчикам аппаратного обеспечения.
Эффективные реализации OpenGL существуют для Windows, Unix платформ, PlayStation 3 и Mac OS. Эти реализации обычно предоставляются изготовителями видеоадаптеров и активно используют возможности последних. Существуют также чисто программные реализации спецификации OpenGL, одной из которых является библиотека Mesa. Из лицензионных соображений Mesa является «неофициальной» реализацией OpenGL, хотя полностью с ней совместима на уровне кода.
Спецификация OpenGL пересматривается Консорциумом ARB (Architecture Review Board), который был сформирован в 1992 году. Консорциум состоит из компаний, заинтересованных в создании широко распространенного и доступного API. Согласно официальному сайту OpenGL, членами ARB с решающим голосом на ноябрь 2004 года являются производители профессиональных графических аппаратных средств SGI, 3Dlabs, Matrox и Evans & Sutherland (военные приложения), производители потребительских графических аппаратных средств ATI и NVIDIA, производитель процессоров Intel, и изготовители компьютеров и компьютерного оборудования IBM, Apple, Dell, Hewlett-Packard и Sun Microsystems, а также один из лидеров компьютерной игровой индустрии id Software. Microsoft, один из основоположников Консорциума, покинула его в марте 2003 года. Помимо постоянных членов, каждый год приглашается большое количество других компаний, становящихся частью OpenGL ARB в течение одного года. Такое большое число компаний, вовлеченных в разнообразный круг интересов, позволило OpenGL стать прикладным интерфейсом широкого назначения с большим количеством возможностей.
Курт Экелей (Kurt Akeley) и Марк Сегал (Mark Segal) являются авторами оригинальной спецификации OpenGL. Крис Фрэзиер (Chris Frazier) редактировал версию 1.1. Йон Лич (Jon Leech) редактировал версии с 1.2 по настоящую версию 2.0.
Архитектура
OpenGL ориентируется на следующие две задачи:
скрыть сложности адаптации различных 3D-ускорителей предоставляя разработчику единый API
скрыть различия в возможностях аппаратных платформ, требуя имплементации недостающей функциональности с помощью софтверной эмуляции
Основным принципом работы OpenGL является получение наборов векторных графических примитивов в виде точек, линий и многоугольников с последующей математической обработкой полученных данных и построением растровой картинки на экране и/или в памяти. Векторные транформации и растеризация выполняются графическим конвейером (graphics pipeline), который по сути представляет из себя дискретный автомат. Абсолютное большинство команд OpenGL попадают в одну из двух групп: либо они добавляют графические примитивы на вход в конвейер, либо конфигурируют конвейер на различное исполнение транформаций.
OpenGL является низкоуровневым, процедурным API, что вынуждает программиста диктовать точную последовательность шагов, чтобы построить результирующую растровую графику (императивный подход). Это является основным отличием от дескрипторных подходов, когда вся сцена передается в виде структуры данных (чаще всего дерева), которое обрабатывается и строится на экране. С одной стороны императивный подход требует от программиста глубокого знания законов трёхмерной графики и математических моделей, с другой стороны даёт свободу внедрения различных инноваций.
Расширения
Стандарт OpenGL, с появлением новых технологий, позволяет отдельным производителям добавлять в библиотеку функциональность через механизм расширений. Расширения распространяются с помощью двух составляющих: заголовочный файл, в котором находятся прототипы новых функций и констант, а также драйвер устройства, поставляемого разработчиком. Каждый производитель имеет аббревиатуру, которая используется при именовании его новых функций и констант. Например, компания NVIDIA имеет аббревиатуру NV, которая используется при именовании ее новых функций, как, например, glCombinerParameterfvNV(), а также констант, GL_NORMAL_MAP_NV. Может случиться так, что определенное расширение могут реализовать несколько производителей. В этом случае используется аббревиатура EXT, например, glDeleteRenderbuffersEXT. В случае же, когда расширение одобряется Консорциумом ARB, оно приобретает аббревиатуру ARB и становится стандартным расширением. Обычно, расширения, одобренные Консорциумом ARB включаются в одну из последующих спецификаций OpenGL.
Список зарегистрированных расширений можно найти в официальной базе расширений.
Дополнительные библиотеки
Существует ряд библиотек, созданных поверх или в дополнение к OpenGL. Например, библиотека GLU, являющаяся практически стандартным дополнением OpenGL и всегда её сопровождающая, построена поверх последней, то есть использует её функции для реализации своих возможностей. Другие библиотеки, как, например, GLUT и SDL, созданы для реализации возможностей, недоступных в OpenGL. К таким возможностям относятся создание интерфейса пользователя (окна, кнопки, меню и др.), настройка контекста рисования (область рисования, использующаяся OpenGL), обработка сообщений от устройств ввода/вывода (клавиатура, мышь и др.), а также работа с файлами. Обычно, каждый оконный менеджер имеет собственную библиотеку-расширение для реализации вышеописанных возможностей, например, WGL в Windows или GLX в X Window System, однако библиотеки GLUT и SDL являются кросс-платформенными, что облегчает перенос написанных приложений на другие платформы.
Библиотеки, как GLEW (>) и GLEE (>) созданы для облегчения работы с расширениями и различными версиями OpenGL. Это особенно актуально для программистов в Windows, так как, заголовочные и библиотечные файлы, поставляемые с Visual Studio, находятся на уровне версии OpenGL 1.1.
OpenGL имеет только набор геометрических примитивов (точки, линии, многоугольники) из которых создаются все трехмерные объекты. Порой подобный уровень детализации не всегда удобен при создании сцен. Поэтому поверх OpenGL были созданы более высокоуровневые библиотеки, такие как Open Inventor и VTK. Данные библиотеки позволяют оперировать более сложными трехмерными объектами, что облегчает и ускоряет создание трехмерной сцены.
Независимость от языка программирования
Для подтверждения независимости от языка программирования были разработаны различные варианты привязки (binding) функций OpenGL или полностью перенесены на другие языки. Одним из примеров может служить библиотека Java 3D, которая может использовать аппаратное ускорение OpenGL. Прямая привязка функций реализована в Lightweight Java Game Library, которая имеет прямую привязку OpenGL для Java. Sun также выпустила версию JOGL, которая предоставляет прямую привязку к C-функциям OpenGL, в отличие от Java 3D, которая не имеет столь низкоуровневой поддержки. Официальный сайт OpenGL имеет ссылки на привязки для языков Java, Fortran 90, Perl, Pike, Python, Ada и Visual Basic. Имеются также варианты привязки OpenGL для языков C++ и C#, смотрите.
История
Сегодня компьютерная графика нашла широкое распространение и применение в повседневной жизни. Ученые используют компьютерную графику для анализа результатов моделирования. Инженеры и архитекторы используют трехмерную графику для создания виртуальных моделей. Кинематографы создают удивительные спецэффекты или полностью анимированные фильмы (Shrek, Toy Story и др.). В последние годы широкое распространение получили также компьютерные игры, максимально использующие трехмерную графику для создания виртуальных миров.
Распространению компьютерной графики сопутствовали свои трудности. Лет 15 назад, разработка программного продукта, способного работать на большом количестве графического оборудования было сопряжено с большими временными и финансовыми затратами. Было необходимо отдельно создавать модули для каждого типа графических адаптеров, что порой приводило к большой дупликации исходного кода. Это сильно тормозило развитие и распространение компьютерной графики.
Silicon Graphics Incorporated специализировалась на создании высокотехнологического графического оборудования и программных средств. Являясь в то время лидером в трехмерной графике, SGI видела проблемы и барьеры в росте рынка. Поэтому было принято решение стандартизировать метод доступа к графической аппаратуре на уровне программного интерфейса.
Таким образом появился программный интерфейс OpenGL, который стандартизирует доступ к графической аппаратуре, путем смещения ответственности за создание аппаратного драйвера на производителя графического устройства. Это позволило разработчикам программного обеспечения использовать более высокий уровень абстракции от графического оборудования, что значительно ускорило создание новых программных продуктов и снизило на них затраты.
В 1992 году компания SGI возглавила OpenGL ARB - группу компаний по разработке спецификации OpenGL. OpenGL эволюционировал из 3D-интерфейса SGI - IRIS GL. Одним из ограничений IRIS GL было то, что он позволял использовать только возможности, поддерживаемые оборудованием; если возможность не была реализована аппаратно, приложение не могло её использовать. OpenGL преодолевает эту проблему за счёт программной реализации возможностей, не предоставляемых аппаратно, что позволяет приложениям использовать этот интерфейс на относительно маломощных системах…
Когда в 1995 году была выпущена библиотека Direct3D, Microsoft, SGI и Hewlett-Packard начали проект под названием Fahrenheit, который предусматривал создание более универсального программного интерфейса на основе Direct3D и OpenGL. Идея казалась достаточно обещающей, призванной навести порядок в области интерактивной трехмерной графики, однако в результате финансовых трудностей в SGI и отсутствии должной индустриальной поддержки проект был закрыт.
OpenGL 2.0
По сравнению с DirectX, говорили что главной проблемой OpenGL является Консорциум, в который входит большое количество компаний с различными интересами, что приводит к длительному периоду принятия новой версии спецификации. OpenGL версии 2.0 была представлена 3Dlabs в ответ на беспокойства относительно медленного развития и нечеткого направления OpenGL. 3Dlabs предложила ряд существенных дополнений к стандарту, наиболее значимым из которого был GLSL (OpenGL Shading Language). Это позволяет программисту заменить фиксированный конвейер OpenGL небольшими программами на специальном языке, GLSL, для создания таких эффектов, как «рельефные текстуры» («bump mapping»), волны и водная рябь. Финальная версия спецификации OpenGL 2.0 включает в себя поддержку GLSL. Однако, еще до введения в стандарт OpenGL языка GLSL существовала возможность разрабатывать спецэффекты на языках: assembler (расширения vertex_program, fragment_program) и Cg (NVidia C for Graphics). К сожалению многие предложенные возможности пока отсутствуют в версии OpenGL 2.0, хотя некоторые из них реализованы многими производителями в виде расширений.
Официальный сайт OpenGL
сайт 3Dlabs для разработчиков использующих OpenGL
сайт программирования для GPU
Neon Helium - Уроки по OpenGL
Теория 3D графики с примерами на OpenGL
Уроки с примерами для разработчиков игр.

OpenGL для начинающих разработчиков игр.

Посвящается Игорю Тарасову

1. Введение. Каркас OpenGL программы.

2. Перемещение объектов. Анимация.

3. Клавиатура. Мышь.

4. Рисование разных примитивов. Сложные объекты. Нормали. Дисплейные списки.

5. Материалы. Освещение. Масштабирование.

6. Текстуры.

8. Вывод текста.

9. Эффекты.

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

11. OpenGL+DirectX=...

12. Делаем настоящие игры.

Приложение 1. Установка всех необходимых компонентов и библиотек.

Наверное, вам уже давно хочется создать свою трехмерную виртуальность. Хоть какую-нибудь, но свою. В принципе, можно использовать что-нибудь вроде DarkBasic или вообще, купить себе движок Q3A, и программирование ограничится скриптовым языком. Но это все для ламеров, мы-то к ним не относимся;-). В качестве простого решения я предлагаю OpenGL и эту книжку. Почему OpenGL а не DirectX? Дело в том, что OpenGL работает как конечный автомат, и в этом смысле он гораздо проще в изучении, чем Direct3D, полностью построенный на классах и интерфейсах. OpenGL – кросс-платформенная графическая библиотека. На какой платформе вы бы не программировали, на Windows, Linux, Unix, Macintosh, весь код относящися собственно к OpenGL был бы совершенно одинаков и отличалось бы только 200-300 строк относящихся к конкретной системе. OpenGL – расширяем. Значит, если в лаборатории Nvidia или Ati открыли какую-то новейшую сверхтехнологию, то Microsoft выпускает новую версию DirectX, а SGI просто добавляет в спецификацию OpenGL новое расширение. Стандарт OpenGL почти не изменился с 1996 года, и не изменится кардинально до выхода OpenGL2. Все, что вы напишите сейчас, пользуясь OpenGL, будет работать и компилироваться через год-два, и будет точно таким же, как вы его создали сегодня. OpenGL не зависит от языка и среды программирования и мы можем писать наши программы на Fortran, Basic, Pascal, C, C++, etc. Я лично предпочитаю Ms Visual С++ 7/Windows, и работоспособность всех примеров из предлагаемой нашему любознательному читателю книги проверена именно в этой среде.

Итак, приняв решение о выборе OpenGL в качестве базового трехмерного движка, бежим в магазин покупать какую-нибуть современную среду разработки, желательно MSVC++7, т.к. далее порядок действий далее я буду описывать для неё.

Также нам понадобится библиотека Glut, которую можно скачать с моего сайта (OpenGL 3 D . fatal . ru ) (см. Прил. 1).

Запустив MSVC++7 вы увидите перед собой примерно такое:

1 – Solution Explorer. Здесь в виде дерева показаны все файлы, имеющиеся в проекте. Пока что наш проект пуст и окно девственно чисто.

2 – Dynamic Help. Если вы почти/совсем не знаете С++, советую чаше сюда заглядывать. При установленной MSDN collection просто наведите курсор на непонянтый код и получите подробную справку.

3 – Task list. Вобще-то это органайзер, но сюда выводятся все сообщения и предупреждения об ошибках компиляции и сборки.

4 – Собственно рабочая область с закладками. Пока что она там только одна – Visual Studio start page.

Теперь, пощелкав из любопытства в разные места, приступаем к делу.

1.Щелкайте File->New->Project и в появившемся окне справа вверху щелкните Win32 Project. Наделите проект именем, например “OpenGL1” и щелкните OK.

2
. Затем выберите Application Settings и затем Console Application. Щелкните Finish и удалите из Solution Explorer (1) все файлы, кроме OpenGL1.cpp. (Правый клик по файлу -> Remove).

3. В Solution Explorer правой кнопкой мыши щелкните по надписи OpenGL1 и выберите “Properties”. Появится окно настроек проекта. В нем выберите папку “Linker”, а в ней – “Input”. Справа выберите “Additional Dependencies”.

4. Впишите туда “opengl32.lib glu32.lib glut32.lib”.

5. В папке “C++” выберите “Precompiled header” и сотрите все из второго поля.

Все! Мы готовы к нормальной работе. Теперь посмотрим (дважды кликнув по OpenGL1.cpp) что же нам сгенерировал MSVC++:

// OpenGL1.cpp: Defines the entry point for the console application.

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv)

Ерунда, правда? ;-). Удаляем все это и спокойно пишем:

#include

#include

#include

#include "glut.h"

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

glViewport(0,0,width,height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,10);

glMatrixMode(GL_MODELVIEW);

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

void display(void)

glutSwapBuffers();

А вот тут нам и предстоит рисовать разные объекты. Первая команда очищает экран и буфер глубины, а последняя – меняет буферы местами. Поясню: в OpenGL есть два режима прорисовки (рендеринга) изображения. Первый – немедленный вывод на экран. Второй – двухбуферный, когда, чтобы предотвратить мигание и дрожание изображения, вся картинка рисуется сначала в задний буфер, а потом меняется местами с передним (видимым).

glutInitWindowSize(640, 480);

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

Это – самая главная функция, которая исполняется в самом начале. Первая команда задает положение окна, вторая его размер. Третья команда устанавливает различные режимы вывода (Rgb цветовое пространство, с буфером глубины (веть у нас 3D), двойная буфферизация.

Ч
етвертая команда создает окно с названием “Opengl1 Window”

Пятая и шестая команды указывают, что в случае неактивности и во всех остальных случаях нужно исполнять функцию display.

С
ледующие четыре комманды относятся к OpenGL. C помощью них включается: тест буфера глубины, необходимый для трехмерных объектов, цветные материалы, иначе все объекты будут серые, освещение и первый источник света. Вообще, с помощью glEnable можно многое включить, а с помощью glDisable выключить то, что включили.

И, наконец, последняя команда запускает цикл, в котором будет все время повторяться функция display.

Вот мы и закончили обзор простейшей программы на основе OpenGL.

Нажмите Ctrl+F5, щелкните Yes и наслаждайтесь…

Да, к сожалению, пока ничего не видно. Но стоит в функции display вместо строки “// ...” вставить, например “glutSolidTeapot(1.0);” и мы тут же увидим значительную разницу! Одной командой мы создали чайник! Впечетляет? Давайте оценим затраты времени. В OpenGL, чтобы нарисовать чайник, нам понадобилось 43 строки кода, в Direct3D нам бы понадобилось написать 320 строк, а при написании собственного движка… Это уж сами считайте.

Правда, чайник некрасивый – белый, да и фон так себе… Теперь встравляем еще две комманды:

glClearColor(0.3,0.2,0.6,1.0);

glColor3f(0.0,0.0,1.0);

перед рисованием чайника и получаем новый результат: на сиреневом фоне – синий чайник!

Команда glClearColor заливает фон цветом, который определяют три первых параметра (четвертый – фиктивный): соотношение красного, зеленого и синего цветов . glColor3f задает текущий цвет, параметры те же: соотношение красного, зеленого, синего . Вообще, многие комманды в OpenGL содержат всякие суффиксы, которые означают количество и тип аргументов, например 3f означает 3 float. Ниже дана таблица, для лучшего понимания команд OpenGL:

Суффикс, тип, размер

Значение

Беззнаковый байт

Значение с плавающей точкой

Короткое целое

US (GLushort), 2

Беззнаковое короткое целое

Беззнаковое целое

Дробное с двойной точностью

Массив из нескольких значений

Например: 3ubv означает, что представлены три параметра типа беззнаковый байт, выраженные массивом.

Команды GLUT также имеют свои аффиксы, то есть кроме таких команд, как glutSolidTeapot, которая рисует чайник со сплошной заливкой, есть, скажем, и команда glutWireTeapot, которая рисует “проволочный” чайниик.

Упражнения.

    Модифицируйте всю программу на свой вкус и цвет. Измените размер окна, его заголовок, различные настройки.

    Кроме glutSolidTeapot, в библиотеке GLUT содержится еше множество различных команд для рисования объектов, например:

glutSolidCube(float size);

glutSolidDodecahedron();

glutSolidOctahedron();

glutSolidThetrahedron();

glutSolidIcosahedron();

Измените команду glutSolidTeapot на одну из этих и поупражняйтесь в рисовании геометрических тел. Также попробуйте применить аффикс “wire”. Примечание: размер рекомендую ставить от 0.1 до 2, помните, что наш чайник-гигант был размером только в 1.

Конечно же, у вас сразу возникло много вопросов. Действительно, очень странно, что чайник и другие объекты всегда рисуются в центре, а функции, их рисующие не имеют никаких параметров насчет координат. Все дело в том, что все объекты всегда рисуются в точке (0,0,0), а когда необходимо передвинуть или повернуть объект, мы поворачиваем или переносим систему координат относительно текущей. Но что же делать, если мы хотим один объект передвинуть, а другой оставить на месте? Ведь если мы передвинем один объект, а потом нарисуем другой, то другой объект тоже передвинется. Значит, надо все время запоминать на сколько мы сдвинулись и возвращаться назад. Слава богу, в OpenGL включены команды glPushMatrix() / glPopMatrix(), которые запоминают текущее “положение вещей” (PushMatrix) а потом восстанавливают то, что запомнили. Эти команды работают со стеком, поэтому их можно использовать вложенно. В OpenGL своя собственная система координат: ось X направлена вправо, ось Y – вверх, а ось Z – к наблюдателю. Да, и конечно, команды для поворота и переноса:

glRotatef(int angle, float x, y, z) – поворачивает систему координат на угол angle отпосительно вектора, который определен x, y и z. Я не рекомендую задавать x, y или z отличными от нуля и единицы, т.к. результат подобной операции не определен.

glTranslatef(float x, y, z) – перенос системы координат на x, y, z относительно текущей.

Посмотрите, например, на такой фрагмент кода:

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.17);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.19);

Если добавить его в программу без дополнительных комманд, то вместо трех чайников, построенных в ряд по X в левую стовону, в первую секунду мы может, ещё что-нибуть увидим, а вот во вторую секунду – уже ничего. Чайники убегут влево с неимоверной быстротой. Почему? Ведь при прорисовке кадра система координат не возвращается в изначальное положение . И мы каждый кадр сдвигаемся влево на 1.2. Как это предотвратить? Очень просто:

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.17);

glTranslatef(-0.4,0,0);

glutSolidTeapot(0.19);

Здесь команда glPushMatrix() запоминает текущее положение (0,0,0), потом рисуются чайники, каждый с относительным сдвигом в 0.2. Таким образом, третий чайник сдвинут на 0.6 относительно “абсолютного нуля” (первоначального положения). Я лично вам рекомендую обрамлять в комманды glPushMatrix()/glPopMatrix() все объекты независимо движущиеся, и всегда переносить и поворачивать их относительно “абсолютного нуля”. Иначе вы очень быстро запутаетесь, что относительно чего передвигалось. То же самое относится и к повороту, только тут запутаться проще:

glTranslatef(-0.4,0,0);

glRotatef(-90,1,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glRotatef(90,1,0,0);

glutSolidTeapot(0.19);

Что мы ожидаем увидеть? Конечно, два чайника, один лежит на одном боку, а другой на другом. А получаем: один лежит действительно на боку, а вот другой почему-то не сдвинулся с места. Не пугайтесь, здесь магии нет, просто первый glRotatef повернул всю систему координат, а второй повернул ее обратно. Правильный код:

glTranslatef(-0.4,0,0);

glRotatef(-90,1,0,0);

glutSolidTeapot(0.15);

glTranslatef(-0.4,0,0);

glRotatef(90,1,0,0);

glutSolidTeapot(0.19);

Как видвите, glPopMatrix()/glPushMatrix могут быть вложенными.

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

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

void display(void)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glColor3f(0.0,0.0,0.8);

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

if (r>=360) {r=0;};

glutSwapBuffers();

Но у этого подхода есть один, но очень существенный недотсаток: скорость вращения чайника будет зависеть от производительности компьютера. Т.е. на одном компьютере чайник обернется вокруг двух своих осей за 1 сек., а на другом – за 10 мин. Решение этой проблемы заключается в установке мультимедийного таймера. Он может выполнять функцию с интервалом до 1 мс (0.001 сек). Мультимедийный таймер очень точен и всегда стабилен.

uint timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_PTR dwUser, UINT fuEvent) – устанавливает таймер и возвращает его идентификатор. Первый параметр – интервал таймера в миллисекундах, второй – количество миллисекунд, ограничивающих обработку каждого тика, т.е. точность. Если значение задано нулём, обработка таймера происходит с максимально возможной точностью. Третий параметр – процедура обработки тика, четвертый – значение, передаваемое этой процедуре. Последний параметр фиктивен – для установки переодического, а не одноразового таймера его надо задавать равным TIME_PERIODIC.

Вот пример его использования: (здесь я привел весь исходный код, и во избежание ошибок новые строки выделены).

#include

#include

#include

#include "glut.h"

#include

GLuint timerId;

void CALLBACK TimerProc(GLuint uTimerId, GLuint uMessage,

DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)

if (r>=360) {r=0;};

void resize(int width,int height)

glViewport(0,0,width,height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluLookAt(0,0,5, 0,0,0, 0,1,0);

glMatrixMode(GL_MODELVIEW);

void display(void)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClearColor(0.0,0.0,0.0,1.0);

glColor3f(0.0,0.0,0.8);

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

glutSwapBuffers();

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

timerId=timeSetEvent(10, 0, TimerProc, 0, TIME_PERIODIC);

timeKillEvent(timerId);

Заметьте, что последней строкой мы уничтожаем таймер функцией timeKillEvent по идентификатору таймера. В функции, относящейся к таймеру я не произвожу рисования, иначе оно замедлило бы поворот чайника рисованием сцены.

Упражнения.

    Пусть по экрану носится чайник, отталкиваясь от стенок

    Теперь чайник должен носиться, вращаясь при этом вокруг своей оси (Подсказка: мы должны сначала переместить систему координат, а потом уже, относительно новых координат, поворачиваем его).

    Сделайте программу “парад объектов”, где будет изображено много различных объектов сразу в виде таблицы. Добавьте им различные скороти вращения

Итак, все вроде знаем: объекты какие-то умеем рисовать, анимацию делать, на 3D-игры вроде похоже. Но главный недостаток всех наших программ виден сразу: никакой реакции на действия пользователя. Даже alt+F4 почему-то не работает. Не бойтесь, здесь вас тоже ничего сложного не ожидает, все просто как… Ну, как все остальное.

glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) – устанавливает обработчик с клавиатуры. Параметр – функция обработки вида: void keyfunc(unsigned char key, int x, int y), где key – клавиша, x, y – оконные координаты мыши.

Вот пример:

#include

#include

#include

#include "glut.h"

void keyboard(unsigned char key, int x, int y)

switch (key) {

case "a": r++; break ;

case "z": r--; break ;

void resize(int width,int height)

glViewport(0,0,width,height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,100);

gluLookAt(0,0,5, 0,0,0, 0,1,0);

glMatrixMode(GL_MODELVIEW);

void display(void)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClearColor(0.0,0.0,0.0,1.0);

glColor3f(0.0,0.0,0.8);

glRotatef(r,1,1,0);

glutSolidTeapot(0.7);

glutSwapBuffers();

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glutKeyboardFunc(keyboard);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

Вот и все! При нажатии клавиш ‘a’ и ‘z’ (только на английской раскладке клавиатуры) чайник будет вращаться в одну или в другую сторону, в зависимости от нажатой клавиши. Но glutKeyboardFunc не обрабатывает такие клавиши, как: стрелки, Fx, insert и многие другие. Для этих клавиш существует функция

glutSpecialFunc(void (*func)(unsigned char key, int x, int y)) – все то же самое, что и glutKeyboardFunc, но вместо кода клавиши передается специфический идентификатор, который может быть:

GLUT_KEY_Fx – Fx.

GLUT_KEY_LEFT – Стрелка влево.

GLUT_KEY_UP – Стрелка вверх.

GLUT_KEY_RIGHT – Стрелка вправо.

GLUT_KEY_DOWN – Стрелка вниз.

GLUT_KEY_PAGE_UP – Page up.

GLUT_KEY_PAGE_DOWN – Page down.

GLUT_KEY_HOME – Home.

GLUT_KEY_END – End.

GLUT_KEY_INSERT – Insert.

Примечание: delete, backspace, enter, escape передаются в функцию glutKeyboardFunc.

Все, с клавиатурой вроде разобрались. Да, конечно же, совсем забыл:

int glutGetModifiers() – возвращает, нажаты ли Alt, Control, Shift. Возвращаемые значения:

GLUT_ACTIVE_SHIFT – если нажат Shift (или Caps Lock включен).

GLUT_ACTIVE_CTRL - если нажат Control.

GLUT_ACTIVE_ALT - если нажат Alt.

А теперь мышка:

glutMouseFunc(void (*func)(int button, int state, int x, int y)) – устанавливает обработчик клика мышки. Параметр – функция, где button:

GLUT_LEFT_BUTTON – левая кнопка.

GLUT_MIDDLE_BUTTON – средняя кнопка.

GLUT_RIGHT_BUTTON – правая кнопка.

State может быть:

GLUT_UP – кнопка отпущена.

GLUT_DOWN – кнопка нажата.

X, Y – координаты мыши в окне.

Еще есть функция

glutMotionFunc(void (*func)(int x, int y)) – устанавливает обработчик, вызываемый при нажатой левой кнопке и перемещении мышки. Параметр – функция, где x,y – оконные координаты мыши.

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

#include

#include

#include

#include "glut.h"

int rx=0;

int ry=0;

Переменные для поворота по x и по y.

int oX=-1;

int oY=-1;

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

void mouserot (int x , int y )

if (oX >(-1))

ry += x - oX ;

rx += y - oY ;

Собственно функция разворота. Если мы можем получить предыдущее положение, то к rY прибавляем отличие старой и новой координаты X а к rX – отличие страрого Y от нового. Заключительный шаг: сохраняем текущую позицию в качетве старой. Заметьте, координаты несколько запутаны, но это сделано специально для большей удобности вращения.

void resize(int width,int height)

glViewport(0,0,width,height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45.0, width/height, 0.1,100);

gluLookAt(0,0,5, 0,0,0, 0,1,0);

glMatrixMode(GL_MODELVIEW);

void display(void)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClearColor(0.0,0.0,0.0,1.0);

glColor3f(0.0,0.0,0.8);

glRotatef(rx,1,0,0);

glRotatef(ry,0,1,0);

glutSolidTeapot(0.7);

glutSwapBuffers();

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glutMotionFunc(mouserot);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

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

Упражнения

    Напишите программу, которая при событиях, поступающих с клавиатуры выдавала бы подробную информацию на стандартный вывод (второе окно вашей программы, запись информации туда осуществляется с помощью printf)

    Сделайте то же самое и для мыши, но при этом записывайте все данные в log-файл.

    Напишите программу “просмотр объектов”, в которой можно будет выбирать, перемещать, изменять, вращать объект с помощью мыши и клавиатуры.

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

glutSolidSphere(GLdouble radius, GLint slices, GLint stacks) – рисует сферу. Первый параметр – радиус сферы. Второй – количество делений вокруг оси Z (как линии широты на глобусе). Третий – кол-во делений вдоль оси Z (как линии долготы).

glutSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks) – рисует конус. Первый параметр – радиус базы, второй – высота. Третий и четвертый – см. glutSolidSphere.

glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings) – рисует тор. Первый параметр – общий радиус, второй – толщина. Третий – кол-во сторон на каждом круговом делении. Четвертый – круговые деления.

Естественно, чем больше параметры slices, stacks, sides, rings, тем более качетвенная получается фигура. Уменьшая эти параметры у сферы можно добиться очень интересных эффектов, например сфера со slices=40, stacks=4 выглядит достаточно странно (для сферы, а не для клетки). (Она изображена на рисунке).

Да ну, зачем нам эти дурацкие торы да конусы. Нам бы попроще чего, треугольник, например. В OpenGL для всех примитивов, строящихся по точкам существуют три унифицированные комманды: glBegin, glVertex и glEnd. Перед тем, как нарисовать объект, мы вызываем glBegin c параметром, обозначающим тип объекта, он может быть:

GL_POINTS – точки

GL_LINES – линии (каждые две вершины образуют линию)

GL_LINE_STRIP – вершины последовательно соединянются линиями

GL_LINE_LOOP – то же, что GL_LINE_STRIP, но последняя вершина соединяется с первой.

GL_TRIANGLES – треугольники (каждые три вершины образуют треугольник)

GL_TRIANGLE_STRIP – связанная группа треугольников. Три первых вершины образуют первый треугольник, с первой по четвертую – второй, и т.д.

GL_TRIANGLE_FAN – первая вершина – общая для всех треугольников.

GL_QUADS – четырехугольники (каждые четыре вершины образуют четырехугольник)

GL_QUAD_STRIP – связанные четырехугольники. Первый четырехугольник – вершины 1,2,3,4, второй – 2,3,4,5. И т.д.

GL_POLYGON – выпуклый многоугольник (каждые скобки glBegin/glEnd – один многоугольник).

Потом идут команды glVertex, которые указывают координаты вершин. Эта команда имеет множество суффиксов, также как glColor,например, для обозначения точки в трехмерном пространстве, заданной массивом из float, комманда будет такой: glVertex3fv.

После того, как мы нарисовали объект, надо завершить рисование командой glEnd.

Также есть комманды:

glLineWidth(float size) – задает ширину линий, равную size.

glPointSize(float size) – задает величину точек, равную size.

glPolygonmode(int origin, int mode) – задает режим рисования полигонов (треугольников, четырехугольников, etc.) Первый параметр может принимать значения:

GL_FRONT – для лицевой стороны многоугольников

GL_BACK – для задней стороны

GL_FRONT_AND_BACK – для обоих сторон

Второй параметр может быть:

GL_POINT – точками

GL_LINE – линиями

GL_FILL – сплошной заливкой.

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

g
lBegin(GL_TRIANGLES);

glColor3f(0.0,0.0,0.8);

glVertex3f(-2,-2,0);

glColor3f(0.8,0.0,0.0);

glVertex3f(2,2,0);

glColor3f(0.0,0.8,0.0);

glVertex3f(-2,2,0);

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

Отключать/включать этот эффект можно вызвав команду glShadeModel c аргументом GL_FLAT/GL_SMOOTH.

С помощью glEnable/glDisable(GL_POLYGON_SMOOTH (или GL_LINE_SMOOTH)) можно включить/выключить сглаживание полигонов или линий.

Внимание: многие команды недоступны внутри скобок glBegin/glEnd.

Н
о наши треугольники и четырехугольники получаются странными и некрасивыми: как их не разворачивай, цвет всегда получишь одинаковый. Даже грани непонятно где: все одного цвета. Для того, чтобы полигоны выглядели реалистичнее, необходимо всегда задавать нормали. Обычная фасетная нормаль – вектор-перпендикуляр единичной длины. Нормали необходимы для правильного расчета освещения. Есть несколько видов нормалей, в том числе и гладкие, которые придают даже грубому объекту гладкость. (На рисунке: сверху – куб без просчета нормалей, снизу – с фасетными нормалями). Вот фукнция для просчета фасетной нормали треугольника:

void calcnorm(float v1, float v2, float v3, float &nx, float &ny, float &nz)

float wrki, vx1, vy1, vz1, vx2, vy2, vz2;

// Вектор-перпендикуляр к центру.

nx = vy1 * vz2 - vz1 * vy2;

ny = vz1 * vx2 - vx1 * vz2;

nz = vx1 * vy2 - vy1 * vx2;

// Чтоб он был длины 1...

wrki = sqrt(nx*nx + ny*ny + nz*nz);

if (wrki==0) {wrki=1;};

Первые три паравметра – массивы из координат точек треугольника. (Первый элемент – x, второй – y, третий – z). Следующие три – ссылки на переменные с нормалями. Примечание: создатели GLUT уже позаботились о нас, и все объекты из этой библиотеки рисуются с просчитанными нормалями.

А если мы нарисовали много объектов сразу и хотим их повторять? Нам что ли каждый раз повторять одни и те же команды? Или, например, загрузили мы объект из файла, нарисовали его в процессе загрузки, а потом приходится хранить огромные объемы памяти, для того, что бы можно было нарисовать этот объект в любой момент. К счастью, в OpenGL присутствует механизм “дисплейных списков” (display lists), с помощью которых можно запомнить почти неограниченное количество команд и потом воспроизвети их одним махом.

Для создания дисплейных списков служит команда glNewList. Первый ее аргумент обозначает имя списка (не совсем имя. Все “имена” в OpenGL являются числами типа GLuint), второй всегда должен быть GL_COMPILE. Далее следуют команды, которые, собственно и образуют список. (Ими могут быть любые команды OpenGL). Заканчивает список команда glEndList(). Чтобы вызвать список (выполнить все его команды) служит команда glCallList(GLuint name), которая вызывает список с именем name. Если непонятно, какое имя придать списку (ведь имя уже может использоваться), можно вызвать команду GLuint glGenLists(GLint range), которая возвращает подходящее незанятое имя для range списков. (О вызове нескольких списков сразу мы поговорим позже). Например:

#include

#include

#include

#include "glut.h"

int triangle;

int cube;

int scene;

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

void prepare()

triangle=glGenLists(1);

cube =glGenLists(1);

scene =glGenLists(1);

Даем каждому из них уникальное имя.

glNewList(triangle, GL_COMPILE);

glPushMatrix();

glBegin(GL_TRIANGLES);

glColor3f(1,0,0);

glVertex3f(2,2,0.1);

glColor3f(0,1,0);

glVertex3f(-2,-2,-0.1);

glVertex3f(2,-2,-0.1);

glEnd();

glPopMatrix();

glEndList();

glNewList(cube, GL_COMPILE);

glPushMatrix();

glRotatef(30,1,1,0);

glColor3f(0.3,0.3,0.7);

glutSolidCube(1.3);

glPopMatrix();

glEndList();

glNewList(scene, GL_COMPILE);

glCallList(triangle);

glCallList(cube);

glEndList();

Как видите, списки могут быть и вложенные.

void resize(int width,int height)

glViewport(0,0,width,height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45.0, height/height, 0.1,100);

gluLookAt(0,0,5, 0,0,0, 0,1,0);

glMatrixMode(GL_MODELVIEW);

void display(void)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClearColor(0.0,0.0,0.0,1.0);

glCallList(scene);

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

glutSwapBuffers();

glutInitWindowPosition(50, 50);

glutInitWindowSize(640, 480);

glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

glutCreateWindow("OpenGL1 Window");

glutIdleFunc(display);

glutDisplayFunc(display);

glutReshapeFunc(resize);

glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

prepare();

Упражнения .

    Нарисуйте что-нибудь интересное, например снеговика или автомобиль. Теперь ваши фантазии не ограничены. Пусть ваши мечты о трехмерной графике станут реальностью!

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

glLightf[v](GLenum light, GLenum name, GLfloat *params) – устанавливает какой-то параметр c именем name для источника света light (их может быть максимум 8, различные источники света задаются константами GL_LIGHT0 ... GL_LIGHT7). Параметров существует масса:

GL_POSITION – расположение источника света (Четвертый элемент здесь показывает, учитывать ли удаление объекта от источника света. Он должен быть равен 1. Первые три элемента – x,y,z.).

GL_SPOT_DIRECTION – направление источника света.

GL_AMBIENT – фоновая составляющая (цвет). Говоря короче, цвет, который поглощается объектами

GL_DIFFUSE – диффузная составляющая (цвет). Цвет, отражаемый объектами.

GL_SPECULAR – отраженный цвет. Т.е тот цвет, которым изображаются блики на поверхности объектов.

На рисунке изображен чайник, параметры источника света у которого таковы (Чайник все еше синий! Поэтому цвета сильно изменяются):

float lPos = {1.0,1.0,2.0,1.0};

float lDir = {0.0,0.0,1.0};

float lAmb = {0.5,0.0,0.0, 1.0}; // Темно-красный цвет

float lDiff = {0.0,0.5,0.0, 1.0}; // Темно-зеленый цвет

float lSpec = {1.0,1.0,1.0, 1.0}; // белый цвет

glLightfv(GL_LIGHT0, GL_POSITION, lPos);

glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lDir);

glLightfv(GL_LIGHT0, GL_AMBIENT, lAmb);

glLightfv(GL_LIGHT0, GL_DIFFUSE, lDiff);

glLightfv(GL_LIGHT0, GL_SPECULAR, lSpec);

Рисунок наглядно демонстрирует смысл параметров GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR. Впрочем, просто добавив этот код в функцию main, красивых бликов мы не увидим, т.к. не заданы свойства отражающей способности материала (и материал сейчас ничего не отражает). Для материала доступны те же самые параметры, что и для источника света, только изменяются они с помощью команды glMaterialf(GLenum face, GLenum pname, GLfloat param). Face может быть GL_FRONT(для лицевых сторон), GL_BACK (для задних) или GL_FRONT_AND_BACK (для всех сразу). Pname – те же самые GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR плюс GL_EMISSION и GL_SHININESS. GL_EMISSION – цвет, который излучает материал. Например, ели мы хотим сделать солнце, вокруг которого вертятся какие-нибудь планеты, код рисования солнца будет такой:

Приложение 1. Установка всех необходимых компонентов и библиотек.

    Скачайте с моего сайта OpenGL 3 D . fatal . ru из раздела “разное” библиотеку GLUT.

    Скопируйте файл glut32.dll в каталог с вашей программой.

    Скопируйте файлы glut32.dll и glut.dll в каталог \windows\system\

    Скопируйте glut.lib и glut32.lib в каталог_где_установлен_MSVS7\ Vc7\lib\

    Скопируйте glut.h в каталог с вашей программой и в каталог_где_установлен_MSVS7\Vc7\include\

OpenGL.dll (Версию OpenGL от SGI), необходимую для компиляции некоторых примеров, можно скачать с моего сайта (раздел “разное”).

  • Перевод

Перед тем как мы начнём, скажу: я знаю об OpenGL гораздо больше чем о Direct3D. Я в жизни не написал ни одной строки кода для D3D, и я писал руководства по OpenGL. Так что то что я тут расскажу, не вопрос предвзятости. Теперь это просто история.

Зарождение конфликта

Однажды, в начале 90-х, Microsoft огляделась вокруг. Они увидели замечательные Super Nintendo и Sega Genesis, на которых было много отличных игр. И они увидели DOS. Разработчики писали для DOS так же как для консолей: прямо на железе. Но, в отличии от консолей, где разработчик точно знал каким железом располагает пользователь, разработчики для DOS вынуждены были писать в расчёте на множество различных конфигураций оборудования. А это гораздо сложнее, чем кажется на первый взгляд.

У Microsoft в то время была ещё большая проблема: Windows. Windows хотела единолично распоряжаться оборудованием, в отличие от DOS, позволявшей разработчику делать всё что ему заблагорассудится. Владение оборудованием было обязательно для того чтобы упорядочить взаимодействие между приложениями. Взаимодействие-то и не нравилось разработчикам игр, потому что забирало ценные ресурсы, которые они могли использовать для своих замечательных игр.

Чтобы привлечь разработчиков игр в Windows, Microsoft нужен был единый API который был бы низкоуровневым, работал в Windows и при этом не страдал от тормозов и, самое главное, абстрагировал бы от разработчика оборудование . Единый API для графики, звука и пользовательского ввода.

И так родился DirectX.

3D ускорители появились на свет несколько месяцев спустя. И перед Microsoft встало сразу несколько проблем. Видите ли, DirectDraw, графический компонент DirectX, работал только с 2D графикой: выделением графической памяти и побитовым копированием между разными выделенными секциями памяти.

И Microsoft купили некий промежуточный драйвер и сделали из него Direct3D версии 3. Его ругали все и повсюду . И не просто так; одного взгляда на код было достаточно чтобы отшатнуться в ужасе.

Старый Джон Кармак из Id Software взглянул на этот мусор, сказал: «К чёрту!», и решил писать под другой API - OpenGL.

Другая часть этого клубка проблем состояла в том что Microsoft были очень заняты совместной работой с SGI над реализацией OpenGL для Windows. Идея была проста - привлечь разработчиков типичных рабочих GL-приложений: систем автоматического проектирования, моделирования, всего такого. Игры были последним, о чём тогда думали в Microsoft. Это всё предназначалось для Windows NT, но Microsoft решили добавить эту реализацию и в Win95.

Чтобы привлечь внимание разработчиков профессионального софта к Windows, Microsoft попробовали подкупить их доступом к новым функциям ускорителей трехмерной графики. Microsoft сделали протокол Installable Client Driver: производитель графического ускорителя мог перегрузить программную реализацию OpenGL аппаратной. Код просто автоматически использовал аппаратную реализацию если она была доступна.

Восход OpenGL

Итак, расклад сил был определён: Direct3D против OpenGL. Это действительно интересная история, учитывая насколько ужасен был D3D v3.

Комитет Архитектурных Решений OpenGL («Architectural Review Board», ARB) был организацией, ответственной за поддержку стандарта OpenGL. Они выпустили много расширений, следили за репозиторием расширений и создавали новые версии API. В Комитет входили многие влиятельные игроки графической индустрии и разработчики ОС. Apple и Microsoft в своё время тоже входили в этот комитет.

Потом появился 3Dfx с Voodoo2. Это было первое устройство, способное выполнять мультитекстурирование, чего OpenGL делать раньше не мог. Хотя 3Dfx совершенно не вписывался в стандарт OpenGL, NVIDIA, разработчик последующих графических чипов с мультитекстурированием (TNT1), положили глаз на эту реализацию. ARB пришлось выпустить расширение: GL_ARB_multitexture, которое давало доступ к мультитекстурированию.

В это же время вышел Direct3D v5. Теперь D3D стал настоящим API , а не странным куском кошачьей рвоты. Проблема? Отсутствие мультитекстурирования.

По большому счёту, это было не так важно как должно было быть, потому что люди особо не использовали мультитекстурирование. По крайней мере напрямую. Мультитекстурирование сильно ухудшало быстродействие, и во многих случаях просто не было смысла использовать его вместо multi-passing. И, естественно, разработчики игр старались убедиться что их игры заработают на старом железе, в котором мультитекстурирования не было, и многие игры его просто не использовали.

D3D это сошло с рук.

Прошло какое то время и NVIDIA выпустили GeForce 256 (не GeForce GT-250; самый первый GeForce), по большому счёту прекратив гонку вооружений в графических ускорителях на следующие два года. Основная продающая фишка - возможность делать вертексные преобразования и освещение (T&L) аппаратно. Но это не всё: NVIDIA настолько полюбили OpenGL, что их движок T&L по сути и был OpenGL. Причём буквально: насколько я понимаю, некоторые регистры действительно напрямую принимали объекты OpenGL в качестве значений.

Выходит Direct3D v6. Наконец появилось мультитекстурирование, но… нет аппаратного T&L. У OpenGL всегда был конвеер T&L, даже до того как вышел 256 он был реализован программно. Так что для NVIDIA было не очень сложно преобразовать программную реализацию в аппаратную. В D3D аппаратный T&L появился только к седьмой версии.

Рассвет шейдеров, сумерки OpenGL

Потом вышел GeForce 3 и одновременно произошло много вещей.

Microsoft решили что теперь-то они уж точно не опоздают к празднику. И вместо того чтобы смотреть что делают NVIDIA и копировать это постфактум они пришли к NVIDIA и поговорили. Потом они полюбили друг друга и от этого союза появилась маленькая игровая приставка.

Потом был болезненный развод. Но это совсем другая история.

Для PC это значило что GeForce 3 вышел одновременно с D3D v8. И несложно увидеть насколько GeForce 3 повлиял на шейдеры в восьмерке. Пиксельные шейдеры в Shader Model 1 были очень сильно привязаны к железу NVIDIA. Аппаратной абстракции от NVIDIA не было вообше; SM 1.0 по сути был тем что делал GeForce 3.

Когда ATI вступили в гонку производительных графических карт со своим Radeon 8500, обнаружилась проблема. Пиксельный конвеер 8500 был мощнее чем у NVIDIA. И Microsoft выпустил Shader Model 1.1, который по сути был «тем что делал 8500».

Это может показаться провалом со стороны D3D. Но провал и успех это вопрос относительный. Настоящий провал происходил в стане OpenGL.

NVIDIA любили OpenGL, поэтому когда вышел GeForce 3, они выпустили комплект расширений OpenGL. Проприетартных расширений, подходящих только для NVIDIA. Естественно, когда вышел 8500, он не мог использовать ни одно из них.

Видите ли, в D3D v8 вы по крайней мере можете запустить шейдеры SM 1.0 на железе ATI. Естественно, чтобы использовать вкусности 8500 Вам придётся написать новые шейдеры, но Ваш код по крайней мере работал .

Чтобы получить хоть какие то шейдеры на 8500 в OpenGL, ATI пришлось написать несколько расширений OpenGL. Проприетартных расширений, подходящих только для ATI. Итак, Вам приходилось писать два пути выполнения кода, для NVIDIA и для ATI, чтобы иметь хоть какие то шейдеры.

Сейчас Вы наверняка спросите: «А чем занимался OpenGL ARB, чьей работой было поддержание OpenGL в актуальном состоянии?». Да тем же, чем занимаются большинство комитетов: тупил.

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

И ARB стал выпускать расширение за расширением. Каждое расширение, в названии которого присутствовало «texture_env» было очередной попыткой залатать этот стареющий дизайн. проверьте реестр: между расширениями ARB и EXT таких вышло аж восемь штук. Многие вошли в основные версии OpenGL.

Microsoft в это время были участником ARB; они ушли примерно во время выхода D3D 9. Так что в принципе, вполне возможно что они каким то образом саботировали разработку OpenGL. Я лично сомневаюсь в этой версии по двум причинам. Во первых, им пришлось бы заручиться помощью других членов комитета, поскольку у одного участника только один голос. И, что более важно, во вторых, Комитету не нужен был Microsoft чтобы прийти к такому провалу. Чуть позже мы увидим что так и получилось.

Со временем ARB, скорее всего под натиском ATI и NVIDIA (очень активных участников) проснулся настолько чтобы принять шейдеры ассемблерного типа.

Хотите увидеть еще большую глупость?

Аппаратный T&L. Который в OpenGL появился раньше . Тут вот что интересно. Чтобы выжать максимальную производительность из аппаратного T&L, Вам необходимо хранить данные в GPU. В конце концов, именно в GPU они используются.

В D3D v7 Microsoft представил концепцию вертексных буферов. Это выделенные области памяти GPU для хранения данных о вертексах.

Хотите узнать когда в OpenGL появился свой аналог этого? О, NVIDIA, будучи фанатом всего что относится к OpenGL (до тех пор пока написанное остаётся проприетарным расширением NVIDIA), выпустили расширение для вертексных массивов еще при первом выпуске GeForce 256. Но когда ARB решил официально предоставить подобный функционал?

Два года спустя . Это случилось после того как они одобрили вертексные и фрагментные шейдеры (пиксели в языке D3D). Вот сколько времени заняла у ARB разработка кроссплатформенного решения для хранения данных в памяти GPU. Именно то, что нужно чтобы выжать максимум из аппаратного T&L.

Один язык чтобы всё разрушить

Итак, разработка OpenGL была раздроблена. Нет единых шейдеров, нет единого хранилища в GPU, когда пользователи D3D уже наслаждались и тем, и другим. Могло ли стать ещё хуже?

Ну… можно сказать и так. Встречайте: 3D Labs .

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

Они разработали Язык Шейдеров OpenGL.

Дело в том что 3D Labs была умирающей компанией. Их дорогие ускорители стали ненужными когда NVIDIA усилили давление на рынок рабочих компьютеров. И, в отличие от NVIDIA, у них не было никакого присутствия на общем рынке; если бы NVIDIA победила, они бы исчезли.

Так и получилось.

И, в попытке остаться на плаву в мире, которому не нужна была их продукция, 3D Labs появились на Game Developer Conference с презентацией того что они назвали «OpenGL 2.0». Это должно было стать полностью переписанным с нуля API OpenGL. И это имело смысл; в API OpenGL было немало шероховатостей (примечание: они есть и сейчас). Просто посмотрите на что похожа загрузка и привязка текстур; это просто какая то чёрная магия.

Частью их предложения был язык шейдеров. Вот так. Однако, в отличие от текущих кросс-платформенных расширений ARB, их язык шейдеров был «высокоуровневым» (C это высокоуровневый язык для шейдеров. Нет, правда).

Итак, Microsoft в это время работали над своим собственным высокоуровневым языком шейдеров. Назвали они его, по своей давней привычке, «Высокоуровневым Языком Шейдеров» (HLSL). Но подход к языку был в корне другим.

Самая большая проблема языка шейдеров от 3D Labs была в том что он был встроенным. Видите ли, HLSL был языком, определённым Microsoft. Они выпустили для него компилятор, который генерировал ассемблерный код для Shader Model 2.0 (и последующих версий), который Вы вставляли в D3D. В дни D3D v9, HLSL никогда не вызывался из D3D напрямую. Он был удобной абстракцией, но совершенно необязательной. У разработчика всегда оставалась возможность отложить компилятор и доработать код до максимальной производительности.

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

Это означало что пользователи OpenGL были беззащитны перед ошибками разработчиков, которые только начали разбираться с компилируемыми ассемблеро-подобными языками. Баги компилятора в новом языке шейдеров OpenGL (GLSL) ходили просто табунами . Что еще хуже, если Вам удавалось правильно скомпилировать шейдер для нескольких платформ (само по себе непростая задача), Вам всё равно приходилось иметь дело с оптимизаторами того времени. Которые были не так оптимальны, как могли бы.

Хотя это было главной проблемой GLSL, но не единственной. Далеко не единственной.

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

В GLSL ничего такого не было. Вертексные и фрагментные шейдеры были собраны в единую абстракцию, которую 3D Labs назвали «программный объект». И если Вам хотелось использовать вместе вертексные и фрагментные программы, Вам приходилось строить несколько таких программных объектов. И это было причиной второй проблемы.

Видите ли, 3D Labs думали что поступают очень умно. Они основали модель компиляции в GLSL на C/C++. Вы берёте.c или.cpp и компилируете в объектный файл. Потом Вы берете один или несколько объектных файлов и линкуете их в программу. Вот так и происходит компиляция в GLSL: вы компилируете шейдер (вертексный или фрагментный) в объект шейдера. Потом Вы помещаете этот объект шейдера в программный объект и связываете их вместе чтобы получить программу.

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

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

У GLSL были и другие проблемы. Возможно неправильно винить во всём 3D Labs, потому что ARB в конце концов одобрила и приняла этот язык (но ничего кроме него из их предложения «OpenGL 2.0» не прошло). Но идея была именно их.

А вот действительно печальная часть. 3D Labs по большому счёту были правы . GLSL это не векторный язык шейдеров, каким всегда был HLSL. Так случилось потому что железо 3D Labs было скалярным железом (так же как современные карты NVIDIA), но в целом они были правы по части направления развития ускорителей.

Они также были правы с «compile-online» моделью для «высокоуровневых» языков. D3D впоследствии тоже на неё перешёл.

Проблема была в том что 3D Labs оказались правы в неправильное время . И в попытке призвать будущее слишком рано, в попытке его предугадать, они отбросили настоящее. Это также как OpenGL всегда имел возможность делать T&L. Если не считать того что конвееры T&L в OpenGL были полезны ещё до выхода аппаратной реализации, а GLSL был просто обузой прежде чем мир оказался готов его принять.

Сейчас GLSL - хороший язык. Но для своего времени он был ужасен. И OpenGL пострадал за это.

Приближается апофеоз

Хотя я и утверждаю что 3D Labs нанесли смертельный удар, именно комитет ARB забил последний гвоздь в крышку гроба OpenGL.

Эту историю вы наверняка слышали. Во времена OpenGL 2.1, OpenGL встал перед проблемой. У него было много старых неровностей. API было сложно использовать. Для каждого действия существовало по пять способов и никто не знал, какой окажется самым быстрым. Можно было «изучить» OpenGL по простым руководствам, но никто не говорил Вам какое API даст Вам максимум производительности.

И ARB решил сделать ещё одну попытку изобрести OpenGL заново. Это было похоже на «OpenGL 2.0» от 3D Labs, но лучше, потому что за ней стоял ARB. Попытку назвали «Longs Peak».

Что было не так с попыткой исправить старые API? Плохо было то что Microsoft в то время были уязвимы. Это было время выхода Vista.

В Vista Microsoft решили ввести давно необходимые изменения в драйверах дисплея. Они заставили драйверы обращаться к ОС для виртуализации видеопамяти и многих других вещей.

Хотя можно сомневаться в том было ли это необходимо, но факт остаётся фактом: Microsoft решили что D3D 10 будет только для Vista (и последующих ОС). Даже если у Вас было железо, способное выполнять функции D3D 10, Вы не могли запускать D3D 10 приложения без запуска Vista.

Вы также наверное помните, что Vista… ну, скажем просто что она получилась не очень. Итак, у Вас была тормозная ОС, новое API, работающее только на этой ОС, и новое поколение ускорителей которым было нужно API и ОС чтобы превзойти предыдущее поколение ускорителей.

Однако, разработчики могли бы получить доступ к фунциям уровня D3D 10 через OpenGL. Ну, смогли бы, если бы ARB не были так заняты работой над Longs Peak.

По большому счёту, ARB потратил полтора-два года на то чтобы сделать API лучше. Когда вышел OpenGL 3.0, время Vista уже заканчивалось, на горизонте появилась Win7, и большинство разработчиков уже не интересовались фунцкиями D3D-10. В конце концов, железо для которого предназначался D3D 10 замечательно работало с D3D 9. А с расцветом портов с PC на приставки (или PC-разработчиков, занявшихся разработкой для приставок) функции класса D3D 10 оказались невостребованы.

Если бы у разработчиков был доступ к этим функциям раньше, через OpenGL на машинах с WinXP, разработка OpenGL получила бы так необходимый импульс. Но ARB упустил эту возможность. И знаете что самое плохое?

Несмотря на то что два ценных года были потрачены на разработку API с нуля… они всё равно провалились и вынуждены были вернуться к предыдущей версии.

То есть, ARB не только упустили замечательную возможность, но и не сделали ту задачу, ради которой возможность была упущена. Просто полный провал.

Это и есть история борьбы OpenGL и Direct3D. История упущенных возможностей, огромной глупости, слепоты и просто безрассудства.

Всем привет! Что-то последнее время много уроков по конструкторам игр, и не так много по различным языкам программирования. Что бы устранить эту не справедливость, я решил запилить урок по OpenGL C++, надеюсь вы оцените)

Сразу же перейдем к геймдеву и будем писать маленькую игрульку нашего детства.

Для начала подключаем библиотеку OpenGL к своей среде разработки, я лично программирую в Microsoft Visual Studio 2013.

200?"200px":""+(this.scrollHeight+5)+"px");">
#include "stdafx.h"
#include
#include
#include
#include // подключаем все необходимые инклюды.

Int N = 30, M = 20; // т.к. змейка будем ездить по квадратикам, создадим их, для нашего окна в идеале будет 30x20 квадратов
int scale = 25; // размер квадрата. Когда OpenGL будет расчерчивать поле для игры, расстояние между гранями квадрата будет 25 пикселей

Int w = scale*N; // ширина поля
int h = scale*M; // его высота

Int dir, num = 4; // 4 направления и начальный размер змеи.
struct { int x; int y; } s; // структура змеи, X и Y координаты, массив с длинной.

Class fruct // класс фруктов, тех самых, которые будет есть наша змея
{
public:
int x, y; //координаты фруктов, что и где будет находится

Void New() // паблик с новыми фруктами. Он будет вызываться в начале игры и в тот момент, когда змея съест один из фруктов
{
x = rand() % N; // вычисление X координаты через рандом
y = rand() % M; // вычисление Y координаты через рандом
}

Void DrawFruct() // паблик, отрисовывающий фрукты
{
glColor3f(0.0, 1.0, 1.0); // цвет фруктов. в openGL он задается от 0 до 1, а не от 0 до 256, как многие привыкли
glRectf(x*scale, y*scale, (x + 1)*scale, (y + 1)*scale); // "Закрашиваем" квадрат выбранным цветом, таким образом в нем "появляется" фрукт
}
} m; // масив с фруктами, таким образом, у нас появится одновременно 5 фруктов в разных местах, а не один, как мы привыкли

Void Draw() // функция, которая отрисовывает линии
{
glColor3f(1.0, 0.0, 0.0); // цвет наших линий, в данном слуае - красный
glBegin(GL_LINES); // начинаем рисовать и указываем, что это линии
for (int i = 0; i < w; i+= scale) // отрисовываем линии в ширину
{
glVertex2f(i, 0); glVertex2f(i, h); // рисуем прямую
}
for (int j = 0; j < h; j += scale) //отрисовываем линии в высоту
{
glVertex2f(0, j); glVertex2f(w, j); // рисуем ту же самую прямую, но в другом направлении
}

GlEnd(); // конец отрисовки
}

Void tick() // функция в которой будет все обновляться (двигаться змея и т.д.)
{
for (int i = num; i > 0; --i) // движение змеи. Система остроумна и проста: блок перемешается вперед, а остальные X блоков, на X+1(2 блок встанет на место 1, 3 на место 2 и т.д...)
{
s[i].x = s.x; // задаем Х координату i блока координатой i - 1
s[i].y = s.y; // то же самое делаем и с Y координатой
}
// далее у нас система направлений.
if (dir == 0) s.y += 1; // если направление равно 0, то первый фрагмент массива перемещается на один по Y
if (dir == 1) s.x -= 1; // если направление равно 1, то первый фрагмент массива перемещается на минус один по X
if (dir == 2) s.x += 1; // аналогиная система
if (dir == 3) s.y -= 1; // аналогичная система

For (int i = 0; i < 10; i++) //цикл, в котором наша змея будет расти
{
if ((s.x == m[i].x) && (s.y == m[i].y)) // Если голова нашей змеи находится в одном блоке с фруктом, то...
{
num++; //...увеличиваем размер нашей змеи на 1
m[i].New(); // ... запускаем функцию отрисовки нового фрукта.
}
}
// Следующее нужно, что бы змея не выходила за рамка поля. Действует это просто: если змея выходит за рамки поля, то задаем
if (s.x > N) dir = 1; // Ей обратное направление. Например, если она выйдет за экран по высоте, то задаем ей направление, при котором она ползет
if (s.y > M) dir = 3; // вниз
if (s.x < 0) dir = 2;
if (s.y < 0) dir = 0;

For (int i = 1; i < num; i++) // с помощью этого цикла мы "обрежем" змею, если она заползет сама на себя
if (s.x == s[i].x && s.y == s[i].y) // проверка координат частей змеи, если X и Y координата головной части равно координате любого
num = i; // другого блока змеи, то задаем ей длину, при которой "откушенная" часть отпадает.
}

Void Snake() // выводим змейку на экран
{
glColor3f(0.1, 1.0, 0.0); //цвет змеи
for (int i = 0; i < num; i++) // цикл отрисовки.
{
glRectf(s[i].x*scale, s[i].y*scale, (s[i].x + 0.9)*scale, (s[i].y + 0.9)*scale); //Рисуем квадраты, те самые "блоки" змеи
}
}

Void Key(int key, int a, int b) // функция нажатия клавиш
{
switch (key) // используем оператор switch
{
case 101: dir = 0; break; // при нажатии клавиш, задаем направление змеи(вверх, вниз, влево, вправо)
case 102: dir = 2; break;
case 100: dir = 1; break;
case 103: dir = 3; break;
}
}

Void Display() //функция общий отрисовки
{
glClear(GL_COLOR_BUFFER_BIT); // очищаем окно перед началом отрисовки

Draw(); // вызов функции Draw, отвечающей за отрисовку фруктов
Snake(); // вызов функции Snake, отвечающей за отрисовку змейки

For (int i = 0; i < 5; i++) // заполнение карты фруктами
m[i].DrawFruct();

GlFlush(); // выводим на экран все вышеописанное
glutSwapBuffers();
}

Void timer(int = 0) // Таймер игры(промежуток времени, в котором будет производится все процессы)
{
Display(); // Вызов функций
tick();
glutTimerFunc(100, timer, 0); // новый вызов таймера(100 - промежуток времени(в милисекундах), через который он будет вызыватся, timer - вызываемый паблик)
}

Int main(int argc, char **argv) // Главная функция
{
std::cout << "Snake by Alexey Ovchinnikov:P\n Loading..."; // крутой текст в консоле при загрузке
srand(time(0));
for (int i = 0; i<10; i++) // начальная, самая первая отрисовка фруктов
m[i].New();

S.x = 10; // начальное положение змейки по X
s.y = 10; // и Y координате
// следующие функции абсолютно идиентичных почти во всех программах на OpenGL, так то запоминать их не обязательно, кроме...
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(w, h); // ... этой, она создаем окно (w - ширина, h - высота)
glutCreateWindow("Game"); // ... этой, она задает название окна
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 640, 0, 480);
glutDisplayFunc (Display); // ... и этой, она вызывает начальную функцию, в нашем случае это главная функция отрисовки - Display
glutSpecialFunc(Key);
glutTimerFunc(50, timer, 0); // ... Ну и в начале программы задаем рекурсивный таймер.
glutMainLoop();

Return 0;
}

И так, на этом все, запускаем, играем. Постарался объяснить максимально понятно, если будут вопросы - задавайте.
P.S. И пожалуйста, не кидайтесь тапками если что-то не понятно или не так написал, первый урок в моей жизни.