Как сделать меню на opengl
[h]Предисловие[/h]
Здесь я постарался как можно более емко и кратко рассказать о тех возможностях openGL, которые могут пригодиться при создании модов для Minecraft. Несмотря на это, объем текста получился весьма внушительным.
Для того, чтобы по-настоящему владеть openGL, этого туториала вам все равно не хватит. Нужно будет прочитать раз в 10 больше литературы. Кроме того, нужны базовые, но уверенные познания в математике и достаточное количество терпения, чтобы понять суть матриц трансформации.
Туториал носит по большей части теоретический характер, реальных примеров в нем очень мало. Он призван немного сбалансировать огромное количество туториалов, в которых используется openGL-код без толкового объяснения происходящего.
Описываются по большей части возможности openGL 1, на котором построен Minecraft. Да, я в курсе, что он давным-давно устарел, но в процессе модификации Minecraft'а избежать использования openGL 1 всё равно не выйдет. Про новые возможности будет сказано немного и в самом конце.
[h]Небольшое FAQ по определениям[/h]
q: Что такое openGL?
a: OpenGL - это API, предоставляющий программистам доступ к возможностям видеокарты. В случае c Minecraft'ом, этот API реализует библиотека LWJGL.
q: Что делают функции openGL?
a: Функции openGL делятся на два типа: одни меняют его глобальное состояние (openGL state), в то время как другие передают в него данные (позиции вершин, UV-координаты и нормали) для отрисовки.
q: Что такое глобальное состояние openGL?
a: Состояние openGL - это набор глобальных параметров, в зависимости от которого один и тот же набор вершин отрисовывается по-разному. Описание отдельных элементов состояния openGL будет ниже.
q: В чем суть того, что делает openGL?
a: OpenGL строит изображение на экране (или в памяти) на основе поступающих в него вершин (координат) и своего состояния. В зависимости от состояния он может рисовать точки, линии и многоугольники.
q: Что такое UV-координаты?
a: UV-координаты - пары вещественных чисел от 0 до 1, соответствующие некоторым координатам на текстуре. U - горизонтальная координата, V - вертикальная. Если при рендеринге используется текстура, то, передавая вершину на отрисовку, необходимо указать еще и ее UV-координаты.
q: Что такое нормали?
a: Нормали - вектора, перпендикулярные поверхности. Они нужны для рассчета освещения. Нормалью к вершине может быть как перпендикуляр к полигону, к которому она принадлежит, так и средний вектор между перпендикулярами к нескольким полигонам, если вершина принадлежит к нескольким полигонам одновременно. Нормали должны быть нормализованы (то есть иметь единичную длину).
Подсказка: можно прописать в импортах import static org.lwjgl.opengl.GL11.* (и другие нужные версии) и не заморачиваться с постоянным повторением GL11.
[h]Немного практики[/h]
Наша пробная задача - отрендерить tile entity в виде квадрата. Будем считать, что вы уже умеете создавать блок с моделью . Код ниже должен быть в методе renderTileEntityAt(x, y, z, f, partialTickTime) нашего TileEntitySpecialRenderer.
Используя только openGL:
Этой возможностью стоит пользоваться, потому что вызывать каждый раз функции openGL - очень и очень медленно, а здесь применены некоторые оптимизации. Если наложена текстура, то вместо addVertex() нужно использовать addVertexWithUV(), а если включено освещение, то перед добавлением вершин, принадлежащих новому полигону, нужно применять setNormal().
Если с добавлением вершин все должно быть более-менее понятно, то остальное - понятно не всем. В openGL есть такое понятие, как "матрицы трансформации". Я вряд ли опишу лучше, чем здесь, но всё же попробую вкратце пересказать, а также немного дополнить.
[h]Матрицы трансформации[/h]
Матрица трансформации в openGL - это двумерный массив чисел 4х4. По сути матрицы в комьютерной графике используется для того, чтобы перейти от одной системы координат (иногда используется слово "пространство" или "space") к другой. Я не буду здесь подробно описывать арифметику матриц, потому что это займет кучу места и не очень нужно в целом, но для глубокого понимания лучше все-таки прочитать про неё отдельно.
Чаще всего применяется две операции. Их суть вы поймете чуть позже.
1) Умножение вектора на матрицу. Как результат этой операции мы получаем вектор в другой системе координат.
2) Умножение матриц. Как результат мы получаем новую матрицу, которая трансформирует вектора так же, как если бы мы умножили сначала на одну матрицу, а потом на другую.
При умножении матриц (как и при умножении вектора на две матрицы последовательно) важен порядок действий. Это легко понять, если представить две последовательности действий:
1) Пройти десять метров вперед, повернуть на 90 градусов вправо, пройти пять метров вперед.
2) Повернуть на 90 градусов вправо, пройти десять метров вперед, пройти пять метров вперед.
Очевидно, что в результате мы окажемся на разных координатах. Так же и с матрицами.
Основных систем координат (и, соответственно, матриц) три - MODEL (для перехода из пространства текущего объекта в мировое), VIEW или CAMERA (для перехода из мирового пространства в пространство камеры) и PROJECTION (для проекции сцены на 2d изображение).
По поводу MODEL и VIEW матриц стоит сделать два замечания. Во-первых, в openGL эти матрицы объединены в одну, которая называется MODELVIEW. Во-вторых, из-за огромных размеров мира в Minecraft'e пришлось отказаться от использования мирового пространства вообще.
При построении матрицы MODELVIEW для отрисовки блока с моделью выполняется последовательность действий (упрощённо):
1) Если включен вид от третьего лица, сместить на несколько метров вперед или назад
2) Если включен вид спереди, повернуть на 180 градусов вокруг оси Y
3) Повернуть камеру в соответствии со взглядом игрока
4) Сместиться на разность координат между позицией блока и камеры. Именно эта разность передается как x, y и z в метод renderTileEntityAt().
Первые три действия делает Minecraft, четвертое нужно делать самому. При умножении координат какой-либо вершины в пространстве модели на матрицу MODELVIEW эти операции будут применены в обратном порядке и мы перейдем в пространство камеры. Непосредственно перед отрисовкой модели можно применить и другие операции с матрицей MODELVIEW, такие как масштабирование или поворот.
Я почти уверен, что из моего посредственного объяснения вы вынесли очень немногое, и настоятельно рекомендую сейчас перейти по этой ссылке и прочитать ВСЮ статью. Три раза.
[h]Функции openGL, связанные с матрицами[/h]
Систему координат мы можем сдвигать, поворачивать и масштабировать. Для этого используются функции glTranslate*(x, y, z), glRotate*(angle, x, y, z) и glScale*(x, y, z). Вместо * в названии функции используется "f" или "d" в зависимости от того, какой тип данных она принимает в качестве аргументов - float или double. В glRotate аргумент angle - это угол в градусах (положительный угол = поворот против часовой стрелки), а x, y и z - вектор, вокруг которого поворачивать. Например, glRotatef(-90, 0, 1, 0) - повернуть на 90 градусов по часовой стрелке вокруг вертикальной оси.
Часто необходимо сохранить текущую матрицу с возможностью последующего восстановления. Это возможно при помощи функций glPushMatrix() и glPopMatrix(). glPushMatrix() добавляет матрицу в стек (список) сохраненных матриц. glPopMatrix() восстанавливает последнюю матрицу из стека и удаляет ее оттуда. Например:
Функция glLoadIdentity() задает единичную матрицу (которая не делает никаких трансформаций). На практике при создании модов она вряд ли понадобится.
[h]Другие параметры openGL[/h]
Кроме матриц трансформации, есть огромное количество других составляющих состояния openGL. Я попробую рассказать о тех, что наиболее важны при создании модов.
В openGL есть несколько boolean-параметров, которые включаются и отключаются при помощи функций glEnable() и glDisable(), куда в качестве аргумента передается опкод этого параметра. С некоторыми из этих параметров связаны другие функции.
GL_DEPTH_TEST включает тест глубины. В большинстве случаев он включен и нужен. Его задача - отсекать те полигоны, которые заслонены другими, даже если "дальний" полигон рисуется после "ближнего". При отрисовке кадра кроме того изображения, что позже будет выведено на экран, есть еще один буфер, который называется буфер глубины, depth buffer или z-буфер. При отрисовке пикселя в этот буфер добавляется информация о том, как далеко он расположен от камеры. Подробнее можно прочитать здесь, и я очень советую это сделать.
Сопутствующие функции:
glDepthMask(true/false) - включает/выключает запись в этот буфер. Если тест глубины выключен, то никакого влияния этот параметр не оказывает, в буфер глубины все равно ничего не будет записываться. Единственная функция в этом списке, которая часто нужна в моддинге.
glClearDepth(value) - очищает буфер глубины и заполняет его указанным числом. 1.0 - "пустой" буфер.
glDepthFunc(func) - устанавливает функцию, по которой проверяется, нужно ли отрисовывать пиксель или он чем-то заслонен. GL_LEQUAL используется в майне по умолчанию, это "меньше или равно" (less or equal). Насколько оправдано использовать другие функции - затрудняюсь ответить.
glDepthRange(zNear, zFar) - устанавливает "ближнюю" и "дальнюю" глубину в буфере. 0.0 соответствует ближней глубине, 1.0 - дальней.
GL_BLEND включает полупрозрачность (смешивание цветов). С ней есть две проблемы. Во-первых, эта опция серьезно снижает производительность, и включать ее для всего не стоит. Во-вторых, она плохо сочетается с буфером глубины. При использовании буфера глубины абсолютно безразлично, в каком порядке отрисовывать полигоны, но для корректного смешивания их нужно сортировать от дальнего к ближнему. Обычный путь - это отрисовать сначала всю непрозрачную геометрию со включенными GL_DEPTH_TEST и glDepthMask, потом выключить glDepthMask и отрисовать полупрозрачную геометрию, немножко сортируя ее по ходу дела . В Minecraft'e используется этот подход. Реализуется это через фичу, которая называется render pass. Нулевой pass - это непрозрачная геометрия, первый - полупрозрачная. Например, у сущностей есть метод shouldRenderInPass(). Получить текущий render pass можно через MinecraftForgeClient.getRenderPass().
Сопутствующая функция:
glBlendFunc(sfactor, dfactor) - параметры, по которым считается итоговый цвет пикселя.
finalColor = sfactor * srcColor + dfactor * destColor, где srcColor - цвет рисуемого полигона (например, с текстуры), а destColor - цвет того, что уже нарисовано на месте этого пикселя. Обычные применения:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - "нормальное" смешивание.
glBlendFunc(GL_ONE, GL_ONE) - аддиктивное смешивание, то есть сложение нового и старого цвета. Полезно для "энергетических" эффектов вроде огня и электричества.
glBlendFunc(GL_SRC_ALPHA, GL_ONE) - то же самое, но с учетом прозрачности с текстуры.
GL_TEXTURE_2D включает использование текстуры. В качестве "сопутствующей" функции можно было бы указать Minecraft.getMinecraft().renderEngine.bindTexture().
GL_LIGHTING включает простое освещение (с двумя источниками света на постоянных позициях). Оно НИКАК не связано с тем, как затеняются блоки ночью и в тени. Про освещение в Minecraft расскажу внизу.
Когда GL_LIGHTING нужно:
1) При рендеринге сущностей
2) При рендеринге tile entity
3) При рендеринге блоков в GUI
4) При рендеринге предмета в руках
Когда оно не нужно:
1) При рендеринге обычных блоков в мире. Там всё работает на костылях.
2) При рендеринге частиц
3) При рендеринге GUI
Сопутствующих фунций и параметров много, а рассказывать о них в контексте Minecraft'a толку мало. Включается дефолтное освещение через RenderHelper.enableStandardItemLighting(), выключается через RenderHelper.disableStandardItemLighting(), вспомогательные функции там и смотрите.
GL_ALPHA_TEST включает запрет на отрисовку пикселей в зависимости от значения alpha (непрозрачности). Обычное использование - запрет на отрисовку полностью прозрачных пикселей, потому что иначе при выключенном смешивании они будут рисоваться. Например, нарисовав квадрат с текстурой круга (на текстуре всё за пределами круга полностью прозрачно), вы всё равно получите квадрат. С этим же параметром связана ситуация, когда при включенном смешивании почти прозрачные пиксели вообще не видны. Тогда нужно отключить альфа-тест.
Сопутствующая функция:
glAlphaFunc(func, ref) - задает параметры альфа-теста. Первое значение - функция сравнения, второе - число, с которым сравнивать. Обычное использование - что-нибудь вроде glAlphaFunc(GL_GREATER, 0.1F).
GL_CULL_FACE включает запрет на отрисовку полигонов с обратной стороны. По умолчанию включен, имеет смысл выключать в случаях, когда иначе придется дублировать полигоны с обеих сторон. Сопутствующие функции указывают, что считать передней и обратной сторонами полигона и какие стороны обрезать при включенном GL_CULL_FACE. Никакого смысла трогать эти функции нет, по умолчанию передней стороной считается та, которая рисуется против часовой стрелки.
GL_NORMALIZE включает автоматическую нормализацию нормалей. Довольно затратно, на практике использовать не стоит.
GL_RESCALE_NORMAL - упрощенный и ускоренный вариант GL_NORMALIZE. Он подразумевает, что переданные в openGL нормали уже были нормализованы, но вы масшабировали матрицу трансформации (использовали glScale()). Работает верно только в тех случаях, когда матрица была масштабирована без искажений, то есть x, y и z, которые вы передали в glScale(), были равны.
GL_POLYGON_OFFSET_FILL включает смещение данных из буфера глубины при отрисовке. Звучит немного непонятно, зато решает гораздо более понятную проблему. Если попробовать отрендерить что-то поверх уже отрисованной поверхности (пример из ванильного майна - текстура разрушения блока поверх самого блока), то начнутся проблемы, связанные с точностью буфера глубины. Подробнее про них можно почитать по ссылке про этот буфер, которую я уже давал.
Сопутствующая функция:
glPolygonOffset(factor, units) - задает смещение. Обычное использование в майне - glPolygonOffset(-3.0F, -3.0F). Кроме того, перед рендерингом с использованием этой возможности обычно отключают glDepthMask().
GL_SCISSOR_TEST запрещает отрисовку за пределами указанной квадратной зоны на экране. Естественно, основное применение этой фичи - GUI (например, довольно сложно реализовать скроллящуюся панель без этой возможности).
Сопутствующая функция:
glScissor(x, y, width, height). Координаты и размеры указываются в пикселях в окне, а не в том, что называется "пикселями" в ГУИ и на практике обычно оказывается больше реальных пикселей. Кроме того, ось Y идет снизу, а не сверху. Пример использования (запретить отрисовку за пределами квадрата 100х100 в верхнем левом углу экрана):
glScissor(0, mc.diplayHeight - 100, 100, 100);
glColor4f() и tessellator.setColorRGBA_F() - задает RGBA цвет. Аргументы должны быть от 0 до 1. Со включенной текстурой тоже работает, в таком случае цвет текстуры домножается на указанный здесь цвет.
glShadeModel(GL_FLAT/GL_SMOOTH) задает простое или сглаженное освещение. GL_FLAT стоит использовать, если в качестве нормалей вы используете перпендикуляр к полигону, GL_SMOOTH - если средний вектор между перпенндикулярами к нескольким полигонам.
Я рассказал только о тех функциях и параметрах, которые кажутся мне наиболее важными для моддинга. Это где-то треть от всех возможностей даже openGL 1, не говоря о современных версиях. Если этого вам недостаточно, то ваш лучший друг - это официальная документация по openGL.
Далее о некоторых вещах, которым не место в гайде по openGL 1, но о которых стоило бы рассказать.
[h]Освещение в Minecraft[/h]
В Minecraft есть два типа освещения: от блоков и от неба. У каждого блока в игре есть два параметра от 0 до 15: освещенность от неба и от блоков-источников света. С каждым пройденным от источника света блоком освещенность падает на 1. Освещенность от неба под открытым небом равна 15 даже глубокой ночью.
Теперь о том, как же всё это отрисовывается.
Есть динамическая текстура 16х16 (mc.renderEngine.lightmapTexture) и соответствующий ей массив размером 256 int'ов (mc.renderEngine.lightmapColors). В ней хранится информация о том, какой цвет соответствует каждой комбинации освещения от неба и освещения от блоков. Каждый кадр эта динамическая текстура обновляется исходя из текущего времени суток, рандомной "мигалки" (освещение от блоков немного колеблется), настроек игры (гаммы) и наложенного ночного зрения. Некоторые объекты (например, сущности и tile entity) рендерятся в режиме мультитекстурирования: в первой текстуре забиндена обычная текстура этого объекта, а во второй - лайтмапа. UV-координаты на лайтмапе задаются через OpenGLHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, u, v). Для сущностей и тайлов эта операция производится перед рендерингом.
Отключить использование лайтмапы можно через mc.renderEngine.disableLightmap(), включить - через mc.renderEngine.enableLightmap().
[h]Введение в шейдеры[/h] (а не рассказ о том, как их использовать)
В openGL 1 вы не можете написать ни строчки кода, которая исполнялась бы на видеокарте. Всё, что вам доступно - это стандартные возможности openGL. В openGL 2.0 такая возможность появилась. Хотя официально Minecraft поддерживает все версии openGL, начиная с 1.2, использовать возможности openGL 2.0 можно относительно безбоязненно, потому что видеокарты без его поддержки уже настолько устарели, что Minecraft на них вряд ли запустится.
Шейдеры - это короткие программы на простом си-подобном языке, позво В openGL 2.0 доступно два типа шейдеров: вершинные и фрагментные (пиксельные). Вершинные шейдеры позволяют модифицировать координаты вершин и передавать данные во фрагментные шейдеры. Фрагментные шейдеры позволяют модифицировать цвет вершины на основе данных, полученных из вершинного шейдера и текстур.
С помощью шейдеров можно эффективнее использовать видеокарту для различных вычислений. Примеры использования шейдеров: симуляция воды, реалистичное освещение (с тенями), использование различных карт (normal map для рельефа, specular map для бликов, gloss map для "шероховатости" и другое), создание различных графических эффектов, пост-обработка кадра и другое. Про использование шейдеров можно написать намного больше, чем всё то, что я написал в этом гайде.
[h]Предисловие[/h]
Здесь я постарался как можно более емко и кратко рассказать о тех возможностях openGL, которые могут пригодиться при создании модов для Minecraft. Несмотря на это, объем текста получился весьма внушительным.
Для того, чтобы по-настоящему владеть openGL, этого туториала вам все равно не хватит. Нужно будет прочитать раз в 10 больше литературы. Кроме того, нужны базовые, но уверенные познания в математике и достаточное количество терпения, чтобы понять суть матриц трансформации.
Туториал носит по большей части теоретический характер, реальных примеров в нем очень мало. Он призван немного сбалансировать огромное количество туториалов, в которых используется openGL-код без толкового объяснения происходящего.
Описываются по большей части возможности openGL 1, на котором построен Minecraft. Да, я в курсе, что он давным-давно устарел, но в процессе модификации Minecraft'а избежать использования openGL 1 всё равно не выйдет. Про новые возможности будет сказано немного и в самом конце.
[h]Небольшое FAQ по определениям[/h]
q: Что такое openGL?
a: OpenGL - это API, предоставляющий программистам доступ к возможностям видеокарты. В случае c Minecraft'ом, этот API реализует библиотека LWJGL.
q: Что делают функции openGL?
a: Функции openGL делятся на два типа: одни меняют его глобальное состояние (openGL state), в то время как другие передают в него данные (позиции вершин, UV-координаты и нормали) для отрисовки.
q: Что такое глобальное состояние openGL?
a: Состояние openGL - это набор глобальных параметров, в зависимости от которого один и тот же набор вершин отрисовывается по-разному. Описание отдельных элементов состояния openGL будет ниже.
q: В чем суть того, что делает openGL?
a: OpenGL строит изображение на экране (или в памяти) на основе поступающих в него вершин (координат) и своего состояния. В зависимости от состояния он может рисовать точки, линии и многоугольники.
q: Что такое UV-координаты?
a: UV-координаты - пары вещественных чисел от 0 до 1, соответствующие некоторым координатам на текстуре. U - горизонтальная координата, V - вертикальная. Если при рендеринге используется текстура, то, передавая вершину на отрисовку, необходимо указать еще и ее UV-координаты.
q: Что такое нормали?
a: Нормали - вектора, перпендикулярные поверхности. Они нужны для рассчета освещения. Нормалью к вершине может быть как перпендикуляр к полигону, к которому она принадлежит, так и средний вектор между перпендикулярами к нескольким полигонам, если вершина принадлежит к нескольким полигонам одновременно. Нормали должны быть нормализованы (то есть иметь единичную длину).
Подсказка: можно прописать в импортах import static org.lwjgl.opengl.GL11.* (и другие нужные версии) и не заморачиваться с постоянным повторением GL11.
[h]Немного практики[/h]
Наша пробная задача - отрендерить tile entity в виде квадрата. Будем считать, что вы уже умеете создавать блок с моделью . Код ниже должен быть в методе renderTileEntityAt(x, y, z, f, partialTickTime) нашего TileEntitySpecialRenderer.
Используя только openGL:
Этой возможностью стоит пользоваться, потому что вызывать каждый раз функции openGL - очень и очень медленно, а здесь применены некоторые оптимизации. Если наложена текстура, то вместо addVertex() нужно использовать addVertexWithUV(), а если включено освещение, то перед добавлением вершин, принадлежащих новому полигону, нужно применять setNormal().
Если с добавлением вершин все должно быть более-менее понятно, то остальное - понятно не всем. В openGL есть такое понятие, как "матрицы трансформации". Я вряд ли опишу лучше, чем здесь, но всё же попробую вкратце пересказать, а также немного дополнить.
[h]Матрицы трансформации[/h]
Матрица трансформации в openGL - это двумерный массив чисел 4х4. По сути матрицы в комьютерной графике используется для того, чтобы перейти от одной системы координат (иногда используется слово "пространство" или "space") к другой. Я не буду здесь подробно описывать арифметику матриц, потому что это займет кучу места и не очень нужно в целом, но для глубокого понимания лучше все-таки прочитать про неё отдельно.
Чаще всего применяется две операции. Их суть вы поймете чуть позже.
1) Умножение вектора на матрицу. Как результат этой операции мы получаем вектор в другой системе координат.
2) Умножение матриц. Как результат мы получаем новую матрицу, которая трансформирует вектора так же, как если бы мы умножили сначала на одну матрицу, а потом на другую.
При умножении матриц (как и при умножении вектора на две матрицы последовательно) важен порядок действий. Это легко понять, если представить две последовательности действий:
1) Пройти десять метров вперед, повернуть на 90 градусов вправо, пройти пять метров вперед.
2) Повернуть на 90 градусов вправо, пройти десять метров вперед, пройти пять метров вперед.
Очевидно, что в результате мы окажемся на разных координатах. Так же и с матрицами.
Основных систем координат (и, соответственно, матриц) три - MODEL (для перехода из пространства текущего объекта в мировое), VIEW или CAMERA (для перехода из мирового пространства в пространство камеры) и PROJECTION (для проекции сцены на 2d изображение).
По поводу MODEL и VIEW матриц стоит сделать два замечания. Во-первых, в openGL эти матрицы объединены в одну, которая называется MODELVIEW. Во-вторых, из-за огромных размеров мира в Minecraft'e пришлось отказаться от использования мирового пространства вообще.
При построении матрицы MODELVIEW для отрисовки блока с моделью выполняется последовательность действий (упрощённо):
1) Если включен вид от третьего лица, сместить на несколько метров вперед или назад
2) Если включен вид спереди, повернуть на 180 градусов вокруг оси Y
3) Повернуть камеру в соответствии со взглядом игрока
4) Сместиться на разность координат между позицией блока и камеры. Именно эта разность передается как x, y и z в метод renderTileEntityAt().
Первые три действия делает Minecraft, четвертое нужно делать самому. При умножении координат какой-либо вершины в пространстве модели на матрицу MODELVIEW эти операции будут применены в обратном порядке и мы перейдем в пространство камеры. Непосредственно перед отрисовкой модели можно применить и другие операции с матрицей MODELVIEW, такие как масштабирование или поворот.
Я почти уверен, что из моего посредственного объяснения вы вынесли очень немногое, и настоятельно рекомендую сейчас перейти по этой ссылке и прочитать ВСЮ статью. Три раза.
[h]Функции openGL, связанные с матрицами[/h]
Систему координат мы можем сдвигать, поворачивать и масштабировать. Для этого используются функции glTranslate*(x, y, z), glRotate*(angle, x, y, z) и glScale*(x, y, z). Вместо * в названии функции используется "f" или "d" в зависимости от того, какой тип данных она принимает в качестве аргументов - float или double. В glRotate аргумент angle - это угол в градусах (положительный угол = поворот против часовой стрелки), а x, y и z - вектор, вокруг которого поворачивать. Например, glRotatef(-90, 0, 1, 0) - повернуть на 90 градусов по часовой стрелке вокруг вертикальной оси.
Часто необходимо сохранить текущую матрицу с возможностью последующего восстановления. Это возможно при помощи функций glPushMatrix() и glPopMatrix(). glPushMatrix() добавляет матрицу в стек (список) сохраненных матриц. glPopMatrix() восстанавливает последнюю матрицу из стека и удаляет ее оттуда. Например:
Функция glLoadIdentity() задает единичную матрицу (которая не делает никаких трансформаций). На практике при создании модов она вряд ли понадобится.
[h]Другие параметры openGL[/h]
Кроме матриц трансформации, есть огромное количество других составляющих состояния openGL. Я попробую рассказать о тех, что наиболее важны при создании модов.
В openGL есть несколько boolean-параметров, которые включаются и отключаются при помощи функций glEnable() и glDisable(), куда в качестве аргумента передается опкод этого параметра. С некоторыми из этих параметров связаны другие функции.
GL_DEPTH_TEST включает тест глубины. В большинстве случаев он включен и нужен. Его задача - отсекать те полигоны, которые заслонены другими, даже если "дальний" полигон рисуется после "ближнего". При отрисовке кадра кроме того изображения, что позже будет выведено на экран, есть еще один буфер, который называется буфер глубины, depth buffer или z-буфер. При отрисовке пикселя в этот буфер добавляется информация о том, как далеко он расположен от камеры. Подробнее можно прочитать здесь, и я очень советую это сделать.
Сопутствующие функции:
glDepthMask(true/false) - включает/выключает запись в этот буфер. Если тест глубины выключен, то никакого влияния этот параметр не оказывает, в буфер глубины все равно ничего не будет записываться. Единственная функция в этом списке, которая часто нужна в моддинге.
glClearDepth(value) - очищает буфер глубины и заполняет его указанным числом. 1.0 - "пустой" буфер.
glDepthFunc(func) - устанавливает функцию, по которой проверяется, нужно ли отрисовывать пиксель или он чем-то заслонен. GL_LEQUAL используется в майне по умолчанию, это "меньше или равно" (less or equal). Насколько оправдано использовать другие функции - затрудняюсь ответить.
glDepthRange(zNear, zFar) - устанавливает "ближнюю" и "дальнюю" глубину в буфере. 0.0 соответствует ближней глубине, 1.0 - дальней.
GL_BLEND включает полупрозрачность (смешивание цветов). С ней есть две проблемы. Во-первых, эта опция серьезно снижает производительность, и включать ее для всего не стоит. Во-вторых, она плохо сочетается с буфером глубины. При использовании буфера глубины абсолютно безразлично, в каком порядке отрисовывать полигоны, но для корректного смешивания их нужно сортировать от дальнего к ближнему. Обычный путь - это отрисовать сначала всю непрозрачную геометрию со включенными GL_DEPTH_TEST и glDepthMask, потом выключить glDepthMask и отрисовать полупрозрачную геометрию, немножко сортируя ее по ходу дела . В Minecraft'e используется этот подход. Реализуется это через фичу, которая называется render pass. Нулевой pass - это непрозрачная геометрия, первый - полупрозрачная. Например, у сущностей есть метод shouldRenderInPass(). Получить текущий render pass можно через MinecraftForgeClient.getRenderPass().
Сопутствующая функция:
glBlendFunc(sfactor, dfactor) - параметры, по которым считается итоговый цвет пикселя.
finalColor = sfactor * srcColor + dfactor * destColor, где srcColor - цвет рисуемого полигона (например, с текстуры), а destColor - цвет того, что уже нарисовано на месте этого пикселя. Обычные применения:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - "нормальное" смешивание.
glBlendFunc(GL_ONE, GL_ONE) - аддиктивное смешивание, то есть сложение нового и старого цвета. Полезно для "энергетических" эффектов вроде огня и электричества.
glBlendFunc(GL_SRC_ALPHA, GL_ONE) - то же самое, но с учетом прозрачности с текстуры.
GL_TEXTURE_2D включает использование текстуры. В качестве "сопутствующей" функции можно было бы указать Minecraft.getMinecraft().renderEngine.bindTexture().
GL_LIGHTING включает простое освещение (с двумя источниками света на постоянных позициях). Оно НИКАК не связано с тем, как затеняются блоки ночью и в тени. Про освещение в Minecraft расскажу внизу.
Когда GL_LIGHTING нужно:
1) При рендеринге сущностей
2) При рендеринге tile entity
3) При рендеринге блоков в GUI
4) При рендеринге предмета в руках
Когда оно не нужно:
1) При рендеринге обычных блоков в мире. Там всё работает на костылях.
2) При рендеринге частиц
3) При рендеринге GUI
Сопутствующих фунций и параметров много, а рассказывать о них в контексте Minecraft'a толку мало. Включается дефолтное освещение через RenderHelper.enableStandardItemLighting(), выключается через RenderHelper.disableStandardItemLighting(), вспомогательные функции там и смотрите.
GL_ALPHA_TEST включает запрет на отрисовку пикселей в зависимости от значения alpha (непрозрачности). Обычное использование - запрет на отрисовку полностью прозрачных пикселей, потому что иначе при выключенном смешивании они будут рисоваться. Например, нарисовав квадрат с текстурой круга (на текстуре всё за пределами круга полностью прозрачно), вы всё равно получите квадрат. С этим же параметром связана ситуация, когда при включенном смешивании почти прозрачные пиксели вообще не видны. Тогда нужно отключить альфа-тест.
Сопутствующая функция:
glAlphaFunc(func, ref) - задает параметры альфа-теста. Первое значение - функция сравнения, второе - число, с которым сравнивать. Обычное использование - что-нибудь вроде glAlphaFunc(GL_GREATER, 0.1F).
GL_CULL_FACE включает запрет на отрисовку полигонов с обратной стороны. По умолчанию включен, имеет смысл выключать в случаях, когда иначе придется дублировать полигоны с обеих сторон. Сопутствующие функции указывают, что считать передней и обратной сторонами полигона и какие стороны обрезать при включенном GL_CULL_FACE. Никакого смысла трогать эти функции нет, по умолчанию передней стороной считается та, которая рисуется против часовой стрелки.
GL_NORMALIZE включает автоматическую нормализацию нормалей. Довольно затратно, на практике использовать не стоит.
GL_RESCALE_NORMAL - упрощенный и ускоренный вариант GL_NORMALIZE. Он подразумевает, что переданные в openGL нормали уже были нормализованы, но вы масшабировали матрицу трансформации (использовали glScale()). Работает верно только в тех случаях, когда матрица была масштабирована без искажений, то есть x, y и z, которые вы передали в glScale(), были равны.
GL_POLYGON_OFFSET_FILL включает смещение данных из буфера глубины при отрисовке. Звучит немного непонятно, зато решает гораздо более понятную проблему. Если попробовать отрендерить что-то поверх уже отрисованной поверхности (пример из ванильного майна - текстура разрушения блока поверх самого блока), то начнутся проблемы, связанные с точностью буфера глубины. Подробнее про них можно почитать по ссылке про этот буфер, которую я уже давал.
Сопутствующая функция:
glPolygonOffset(factor, units) - задает смещение. Обычное использование в майне - glPolygonOffset(-3.0F, -3.0F). Кроме того, перед рендерингом с использованием этой возможности обычно отключают glDepthMask().
GL_SCISSOR_TEST запрещает отрисовку за пределами указанной квадратной зоны на экране. Естественно, основное применение этой фичи - GUI (например, довольно сложно реализовать скроллящуюся панель без этой возможности).
Сопутствующая функция:
glScissor(x, y, width, height). Координаты и размеры указываются в пикселях в окне, а не в том, что называется "пикселями" в ГУИ и на практике обычно оказывается больше реальных пикселей. Кроме того, ось Y идет снизу, а не сверху. Пример использования (запретить отрисовку за пределами квадрата 100х100 в верхнем левом углу экрана):
glScissor(0, mc.diplayHeight - 100, 100, 100);
glColor4f() и tessellator.setColorRGBA_F() - задает RGBA цвет. Аргументы должны быть от 0 до 1. Со включенной текстурой тоже работает, в таком случае цвет текстуры домножается на указанный здесь цвет.
glShadeModel(GL_FLAT/GL_SMOOTH) задает простое или сглаженное освещение. GL_FLAT стоит использовать, если в качестве нормалей вы используете перпендикуляр к полигону, GL_SMOOTH - если средний вектор между перпенндикулярами к нескольким полигонам.
Я рассказал только о тех функциях и параметрах, которые кажутся мне наиболее важными для моддинга. Это где-то треть от всех возможностей даже openGL 1, не говоря о современных версиях. Если этого вам недостаточно, то ваш лучший друг - это официальная документация по openGL.
Далее о некоторых вещах, которым не место в гайде по openGL 1, но о которых стоило бы рассказать.
[h]Освещение в Minecraft[/h]
В Minecraft есть два типа освещения: от блоков и от неба. У каждого блока в игре есть два параметра от 0 до 15: освещенность от неба и от блоков-источников света. С каждым пройденным от источника света блоком освещенность падает на 1. Освещенность от неба под открытым небом равна 15 даже глубокой ночью.
Теперь о том, как же всё это отрисовывается.
Есть динамическая текстура 16х16 (mc.renderEngine.lightmapTexture) и соответствующий ей массив размером 256 int'ов (mc.renderEngine.lightmapColors). В ней хранится информация о том, какой цвет соответствует каждой комбинации освещения от неба и освещения от блоков. Каждый кадр эта динамическая текстура обновляется исходя из текущего времени суток, рандомной "мигалки" (освещение от блоков немного колеблется), настроек игры (гаммы) и наложенного ночного зрения. Некоторые объекты (например, сущности и tile entity) рендерятся в режиме мультитекстурирования: в первой текстуре забиндена обычная текстура этого объекта, а во второй - лайтмапа. UV-координаты на лайтмапе задаются через OpenGLHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, u, v). Для сущностей и тайлов эта операция производится перед рендерингом.
Отключить использование лайтмапы можно через mc.renderEngine.disableLightmap(), включить - через mc.renderEngine.enableLightmap().
[h]Введение в шейдеры[/h] (а не рассказ о том, как их использовать)
В openGL 1 вы не можете написать ни строчки кода, которая исполнялась бы на видеокарте. Всё, что вам доступно - это стандартные возможности openGL. В openGL 2.0 такая возможность появилась. Хотя официально Minecraft поддерживает все версии openGL, начиная с 1.2, использовать возможности openGL 2.0 можно относительно безбоязненно, потому что видеокарты без его поддержки уже настолько устарели, что Minecraft на них вряд ли запустится.
Шейдеры - это короткие программы на простом си-подобном языке, позво В openGL 2.0 доступно два типа шейдеров: вершинные и фрагментные (пиксельные). Вершинные шейдеры позволяют модифицировать координаты вершин и передавать данные во фрагментные шейдеры. Фрагментные шейдеры позволяют модифицировать цвет вершины на основе данных, полученных из вершинного шейдера и текстур.
С помощью шейдеров можно эффективнее использовать видеокарту для различных вычислений. Примеры использования шейдеров: симуляция воды, реалистичное освещение (с тенями), использование различных карт (normal map для рельефа, specular map для бликов, gloss map для "шероховатости" и другое), создание различных графических эффектов, пост-обработка кадра и другое. Про использование шейдеров можно написать намного больше, чем всё то, что я написал в этом гайде.
Перед тем как перейти непосредственно к OpenGL вы узнаете как откомпилировать код, которым сопровождается каждый урок и как его запустить.
Чтобы работать с данными уроками вам не потребуется дополнительных навыков. Опыт в любом языке программирования (C, Java, Lisp, JavaScript и других) поможет вам лучше понимать суть, но вовсе не обязателен.
Все уроки написаны на “легком” C++ и мы потратили много усилий, чтобы сделать код настолько простым, насколько это вообще возможно. Здесь не будет шаблонов, классов и указателей, что позволит вам понять все, даже если вы знаете только Java.
Забудьте все, что вы знали об OpenGL ранее, если ваши знания касаются glBegin() и подобных функций. Здесь вы будете изучать новые стандарты OpenGL (OpenGL 3 и 4), в отличие от многих онлайн-уроков, в которых до сих пор рассматривается “старый” OpenGL (OpenGL 1 и 2).
Весь исходный код к урокам может быть скомпилирован на таких платформах, как Windows, Linux, Mac. Общая процедура для всех этих платформ будет приблизительно одинакова:
- Обновите драйверы! Обязательно сделайте это, если что мы вас предупреждали :)
- Скачайте компилятор, если у вас его до сих пор нет.
- Установите CMake
- Скачайте исходный код урока
- Создайте проект используя CMake
- Скомпилируйте проект
- Изменяйте его :)
Теперь рассмотрим процедуру подробнее для каждой платформы. Вполне возможно вам потребуется что-то адаптировать под ваши требования и возможности, для этого можете руководствоваться инструкцией для Windows.
Вы также можете запустить любой урок из Visual Studio. Для этого нажмите правой кнопкой мыши на Playground и выберете “Choose as startup project”. Для отладки используйте клавишу F5.
Существует большое количество разных дистрибутивов Linux и естественно рассмотреть особенности каждого дистрибутива выходит за рамки данного урока, поэтому не забудьте почитать документацию своего дистрибутива.
-
Установите последние драйверы. Мы настоятельно рекомендуем вам использовать закрытые драйверу. Это не GNU, но они работают. Если ваш дистрибутив не предоставляет автоматической установки, можете попробовать посмотреть в документации к Ubuntu
Установите все необходимые компиляторы, утилиты и библиотеки. Полный список выглядит так:
Используйте sudo apt-get install *** или su && yum install ****.
-
и распакуйте его, к примеру в ~/Projects/OpenGLTutorials/
Перейдите в ~/Projects/OpenGLTutorials/ и введите следующие команды:
Обратите внимание, что лучшим решением будет использовать IDE, такую как Qt Creator. Она имеет поддержку CMake и предоставляет удобные инструменты для отладки. Инструкция для QtCreator:
- В QtCreator перейдите в меню File -> Tools -> Options -> Compile&Execute -> CMake
- Установите путь к CMake. Чаще всего это /usr/bin/cmake
- File -> Open Project и выберите tutorials/CMakeLists.txt
- Выберите директорию, в которую необходимо помещать скомпилированные файлы. Лучше всего выбрать директорию вне tutorials
- Опционально установите -DCMAKE_BUILD_TYPE=Debug в Parameters и используйте Validate.
- Нажмите на иконку молота внизу. Теперь уроки могут быть запущены из директории tutorials/
- Чтобы запускать уроки непосредственно из QtCreator, перейдите в Projects -> Execution parameters -> Working Directory и выберете директорию, в которой находятся шейдеры, текстуры и модели. К примеру для Урока 2 это будет: ~/opengl-tutorial/tutorial02_red_triangle/
Mac OS не поддерживает OpenGL 3.3. Последние Маки с MacOS 10.7 Lion и совместимыми GPU могут работать с OpenGL 3.2, но не с 3.3. Поэтому используйте исходный код уроков для 2.1. В остальном процедура компиляции очень похожа на процедуру в Windows (Makefile также поддерживаются, но не будут рассматриваться):
- Установите XCode из Mac App Store и установите .dmg . Нет необходимости устанавливать утилиты командной строки. (обязательно версия 2.1) и распакуйте его к примеру в ~/Projects/OpenGLTutorials/ .
- Запустите CMake (Applications -> CMake). В первом поле ввода укажите путь к папке с распакованным исходным кодом уроков. Если сомневаетесь, то это папка, содержащая файл CMakeLists.txt. Во втором поле укажите где вы хотите сохранить исполняемые файлы. К примеру это может быть ~/Projects/OpenGLTutorials_bin_XCode/. Обратите внимание, эта папка может находиться где угодно.
- Нажмите на кнопку Configure. Так как вы используете конфигурацию впервые, то CMake спросит у вас какой компилятор вы хотите использовать. Выберете XCode.
- Нажимайте Configure до тех пор, пока не исчезнут все красные строки. Нажмите Generate. Теперь проектный файл для XCode создан и вы можете забыть о CMake, и даже можете удалить его, если захотите.
- Откройте ~/Projects/OpenGLTutorials_bin_XCode/ . Найдите и откройте файл Tutorials.xcodeproj
- Выберете урок, который хотите запустить в XCode Scheme Panel и нажмите кнопку Run для компиляции и запуска
Из-за 2 багов (один в C::B, один в CMake), вам необходимо изменить командную строку Project -> Build Options -> Make commands, как указано на скриншоте:
Вам также необходимо установить рабочую директорию: Project -> Properties -> Build targets -> tutorial N -> рабочая директория исполняемых файлов (это src_dir/tutorial_N)
Чтобы запустить исполняемый файл урока просто выполните двойной щелчок мышью на файле в нужной директории. Если вы предпочитаете командную строку, то не забудьте использовать команду cd, чтобы перейти в нужную директорию.
Если вы хотите запустить урок из IDE, То не забудьте прочитать инструкции, данные выше, чтобы установить корректную рабочую директорию.
К каждому уроку прилагается исходный код и все необходимые файлы данных, которые могут быть найдены в tutorialXX/. Тем не менее вы вряд ли будете изменять эти проекты, так как они даются для справки. Лучшим решением будет открыть playground/playground.cpp и изменять его так, как вам захочется. Если по каким-то причинам у вас его нет, то просто скопируйте код любого урока туда и все.
Мы предоставляем фрагменты кода в каждом уроке. Не стесняйтесь копировать эти фрагменты в playground, пока читаете и экспериментировать с ними - это хорошая практика, гораздо лучшая, чем просто читать готовый код или просто копировать его.
Наконец! Настало время кода OpenGL!
Хотя… Все уроки будут показывать вам низко-уровневый путь для тех или иных задач, таким образом вы будете видеть, что здесь нет никакой магии, однако открытие окна - задача скучная, поэтому мы будем использовать внешнюю библиотеку GLFW для этой работы. Если вы хотите сделать это сами, то вам необходимо использовать Win32 API в ОС Windows, X11 API в Linux, Cocoa API в Mac. Также вы можете использовать другие библиотеки, такие как: SFML, FreeGLUT, SDL и подобные (см. страницу Ссылки).
Итак, поехали! Первое что мы делаем - это разбираемся с зависимостями. Нам необходимо базовый функционал для вывода в консоль:
Далее нам необходим GLEW. Здесь есть немного магии, но перейдем к пояснениям позднее.
Мы будем использовать GLFW для обработки окна и клавиатуры, так что включаем его тоже.
Следующий заголовок не является необходимым в данный момент, так как является библиотекой трехмерной математики, но позже будет очень полезен. В нем нет никакой магии и если вы знаете математику, то вполне можете написать свой аналог. Что касается “using namespace glm”, то эта строка переводит пространство имен в glm, чтобы можно было писать “vec3”, вместо “glm::vec3”.
Если вы скопировали код представленный выше в playground.cpp, то компилятор предупредит вас, что нет функции main(), поэтому добавим ее:
В теле функции первым делом инициализируем GLFW:
Теперь мы можем создать наше первое OpenGL окно! :)
Скомпилируйте и запустите этот исходный код. Если все сделано правильно, то у вас откроется и тут же закроется окно с контекстом OpenGL и это правильно, так как мы не указали, что хотим ждать до тех пор, пока пользователь не нажмет клавишу Escape. Самое время сделать это:
И это завершает наш первый урок! Во втором уроке мы узнаем как выводить простейший треугольник. Увидимся :)
В этом уроке мы узнаем, как использовать библиотеку PyOpenGL в Python. OpenGL-это графическая библиотека, которая поддерживается несколькими платформами, включая Windows, Linux и macOS, а также доступна для использования на нескольких других языках; однако объем этой статьи будет ограничен ее использованием на языке программирования Python.
OpenGL, по сравнению с другими подобными графическими библиотеками, довольно прост. Мы начнем с настройки его в нашей системе, а затем напишем простой пример, демонстрирующий использование библиотеки.
Установка
Самый простой способ установить OpenGL с помощью Python-это через менеджер пакетов pip . Если в вашей системе установлен pip, выполните следующую команду, чтобы загрузить и установить OpenGL:
Я бы рекомендовал скопировать приведенную выше команду, чтобы избежать опечаток.
Как только эта команда завершит выполнение, если установка будет успешной, вы должны получить следующий вывод в конце:
Если это не сработает, вы также можете загрузить его вручную. Для этого эта ссылка прокрутите вниз до заголовка “загрузка и установка” и загрузите все файлы оттуда. После этого перейдите в папку, в которую вы загрузили эти файлы, и выполните следующую команду в терминале или командной строке:
Уместно отметить, что для работы с библиотеками OpenGL в Python вам требуются инструменты сборки Visual C++ 14.0, установленные в вашей системе.
Теперь, когда мы успешно установили OpenGL в нашей системе, давайте запачкаем им руки.
Упражнение по кодированию
Первое, что нам нужно сделать, чтобы использовать OpenGL в нашем коде, – это импортировать его. Для этого выполните следующую команду:
Прежде чем мы продолжим, есть несколько других библиотек, которые вам нужно импортировать всякий раз, когда вы собираетесь использовать эту библиотеку в своей программе. Ниже приведен код для этих импортных операций:
Теперь, когда мы закончили с необходимым импортом, давайте сначала создадим окно, в котором будет показана наша графика. Код для этого приведен ниже, а также его объяснение в комментариях:
Скопируйте импорт выше, а также этот код в один файл python (.py) и выполните его. Вы должны увидеть всплывающий экран белого квадратного размера. Теперь, если мы хотим нарисовать какие-либо фигуры или сделать какой-либо другой вид графики, мы должны сделать это в нашей функции “showScreen”.
Давайте теперь попробуем сделать квадрат с помощью OpenGL, но прежде чем мы это сделаем, нам нужно понять систему координат, которой следует OpenGL.
Точка (0,0)-это нижняя левая часть окна, если вы поднимаетесь оттуда, вы двигаетесь вдоль оси y, а если вы идете прямо оттуда, вы двигаетесь вдоль оси x. Таким образом, верхняя левая точка вашего окна будет (0, 500), верхняя правая – (500, 500), нижняя правая – (500, 0).
Примечание : Мы говорим об окне, которое мы создали выше, которое имело размер 500 x 500 в нашем примере, а не полный экран вашего компьютера.
Теперь, когда мы с этим покончили, давайте закодируем квадрат. Объяснение кода можно найти в комментариях.
Запуск кода выше будет рисовать квадрат, но этот квадрат не будет виден, так как его цвет будет таким же, как цвет нашего окна, поэтому нам нужно назначить ему другой цвет, для этого мы внесем некоторые изменения в “Раздел 2” кода выше, то есть в функцию showScreen . Добавьте следующую строку ниже оператора glLoadIdentity и выше оператора square() :
Однако наш код все еще не завершен. В настоящее время он рисует квадрат один раз, а затем снова очищает экран. Мы этого не хотим. На самом деле, мы даже не сможем определить момент, когда он действительно рисует квадрат, потому что он появится и исчезнет за долю секунды. Давайте напишем еще одну функцию, чтобы избежать этого.
Вызовите эту функцию итерации в “Разделе 2” приведенного выше кода. Добавьте его ниже glLoadIdentity и выше оператора glColor3d в функции showScreen .
Давайте теперь скомпилируем все это в один файл кода, чтобы не было никаких двусмысленностей:
Когда вы запустите эту программу, должно появиться окно с квадратной коробкой розового цвета.
Вывод
В этом уроке мы узнали об OpenGL, как его скачать и установить, а затем использовали его в качестве короткого примера программы. В этом примере мы также практиковались в создании базовой формы с помощью OpenGL, что дало нам представление о некоторых сложных вызовах функций, которые необходимо выполнять всякий раз, когда нам нужно что-то нарисовать с помощью этой библиотеки. В заключение следует отметить, что OpenGL очень изобретателен и становится все более и более сложным по мере того, как мы погружаемся в него глубже.
Я начинаю это пособие непосредственного с кода, разбитого на секции, каждая из которых будет подробно комментироваться. Первое, что вы должны сделать - это создать проект в Visual C++. Если это для вас затруднительно, то вам стоит для начала изучить C++, а уже затем переходить к OpenGL.
После того как вы создадите новое приложение в Visual C++ (причем Win32 приложение, а не консольное), Вам надо будет добавить для сборки проекта библиотеки OpenGL. В меню Project/setting, выберите закладку LINK. В строке "Object/Library Modules" добавьте "OpenGL32.lib GLu32.lib GLaux.lib". Затем кликните по OK. Теперь все готово для создания программы с OpenGL.
Первые четыре строки, которые вы введете, сообщают компилятору какие библиотечные файлы использовать. Они должны выглядят так:
OpenGL code
Далее, необходимо инициализировать все переменные, которые будут использованы в вашей программе. Эта программа будет создавать пустое OpenGL окно, поэтому мы не будем нуждаться в большом количестве переменных. То немногое, что мы устанавливаем - очень важно, и будет использоваться в каждой программе с OpenGL, которую вы напишите с использованием этого кода.
Первые две строки устанавливают Контексты Рендеринга, которые связывает вызовы OpenGL с окном Windows. Контекст Рендеринга OpenGL определен как hRC. Для того чтобы рисовать в окне, вам необходимо создать Контекст Устройства Windows, который определен как hDC. DC соединяет окно с GDI. RC соединяет OpenGL с DC.
OpenGL code
static HGLRC hRC; // Постоянный контекст рендеринга
static HDC hDC; // Приватный контекст устройства GDI
Последняя переменная, в которой мы будем нуждаться, это массив, который мы используем для отслеживания нажатия клавиш на клавиатуре. Есть много путей следить за нажатиями на клавиатуре, но я использую этот путь. При этом можно отслеживать нажатие более чем одной клавиши одновременно.
OpenGL code
В следующей секции кода будут произведены все настройки для OpenGL. Мы установим цвет для очистки экрана, включим глубину буфера, разрешим плавное сглаживание цветов, и что наиболее важно, мы установим рендеринг на экран в перспективе, используя ширину и высоту окна. Эта процедура не должна быть вызвана до тех пор, пока OpenGL окно будет сделано.
Opengl Code
В следующей строке устанавливается цвет, которым будет очищен экран. Для тех, кто не знает, как устроены цвета, я постараюсь кратко объяснять. Все значения могут быть в диапазоне от 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).
Opengl code
Следующие три строки создают Буфер Глубины. Думайте о буфере глубины как о слоях на экране. Буфер глубины указывает, как далеко объекты находятся от экрана. Мы не будем реально использовать буфер глубины в этой программе, но любая программа с OpenGL, которая рисует на экране в 3D будет его использовать. Он позволяет сортировать объекты для отрисовки, поэтому квадрат расположенный под кругом не изображен будет поверх круга. Буфер глубины очень важная часть OpenGL.
OpenGL code
glClearDepth(1.0); // Разрешить очистку буфера глубины
glDepthFunc(GL_LESS); // Тип теста глубины
glEnable(GL_DEPTH_TEST); // разрешить тест глубины
Следующие пять строк разрешают плавное цветовое сглаживание (которое я буду объяснять позднее) и установку экрана для перспективного просмотра. Отдаленные предметы на экране кажутся меньшими, чем ближние. Это придает сцене реалистичный вид. Перспектива вычисляется c углом просмотра 45 градусов на основе ширины и высоты окна. 0.1f, 100.0f глубина экрана.
glMatrixMode(GL_PROJECTION) сообщает о том, что следующие команды будут воздействовать на матрицу проекции. glLoadIdentity() – это функция работает подобно сбросу. Раз сцена сброшена, перспектива вычисляется для сцены. glMatrixMode(GL_MODELVIEW) сообщает, что любые новые трансформации будут воздействовать на матрицу просмотра модели. Не волнуйтесь, если вы что-то не понимаете этот материал, я буду обучать всему этому в дальнейших уроках. Только запомините, что НАДО сделать, если вы хотите красивую перспективную сцену.
OpenGl code
glShadeModel(GL_SMOOTH); // разрешить плавное цветовое сглаживание
glMatrixMode(GL_PROJECTION); // Выбор матрицы проекции
glLoadIdentity(); // Сброс матрицы проекции
gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
// Вычислить соотношение геометрических размеров для окна
glMatrixMode(GL_MODELVIEW); // Выбор матрицы просмотра модели
>
Следующая секция кода очень простая, по сравнению с предыдущим кодом. Это функция масштабирования сцены, вызываемая OpenGL всякий раз, когда вы изменяете размер окна (допустим, что вы используете окна чаще, чем полноэкранный режим, который мы не рассматриваем). Даже если вы не делаете изменение размеров окна (например, если вы находитесь в полноэкранном режиме), эта процедура все равно должна быть вызвана хоть один раз, обычно во время запуска программы. Замечу, что сцена масштабируется, основываясь на ширине и высоте окна, которое отображается.
OpenGL code
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, подождите до следующего моего урока. Сейчас все что мы сделаем, это очистка экрана цветом, который мы определили выше, очистка буфера глубины и сброс сцены.
Opengl code
Следующая секция кода наиболее важная секция в этой программе. Это установка окна Windows, установка формата пикселя, обработка при изменении размеров, при нажатии на клавиатуру, и закрытие программы.
OpenGL code
Код между скобками устанавливает формат пикселей. Я предпочитаю не использовать режим индексации цвета. Если вы не знаете, что это означает, не заботьтесь об этом. Формат описания пикселя описывает, как OpenGL будет выводить в окно. Большинство кода игнорируется, но зачастую это необходимо. Я буду помещать короткий комментарий для каждой строки. Знак вопроса означает, что я не уверен, что это строка кода делает (я только человек!).
OpenGL code
<
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 // Маски слоя игнорируются (?)
>;
OpenGl code
WM_CREATE сообщает программе, что оно должно быть создано. Вначале мы запросим DC (контекст устройства) для вашего окна. Помните, без него мы не можем рисовать в окно. Затем мы запрашиваем формат пикселя. Компьютер будет выбирать формат, который совпадает или наиболее близок к формату, который мы запрашиваем. Я не делаю здесь множества проверок на ошибки, чтобы сократить код, но это неправильно. Если что-то не работает, я просто добавляю необходимый код. Возможно, вы захотите посмотреть, как работают другие форматы пикселей.
OpenGL code
case WM_CREATE:
hDC = GetDC(hWnd); // Получить контекст устройства для окна
PixelFormat = ChoosePixelFormat(hDC, & pfd);
// Найти ближайшее совпадение для нашего формата пикселов
OpenGL code
OpenGL code
OpenGL code
hRC = wglCreateContext(hDC);
if(!hRC)
MessageBox(0,"Can't Create A GL Rendering
Context.","Error",MB_OK|MB_ICONERROR);
PostQuitMessage(0);
break;
>
OpenGl code
if(!wglMakeCurrent(hDC, hRC))
MessageBox(0,"Can't activate GLRC.","Error",MB_OK|MB_ICONERROR);
PostQuitMessage(0);
break;
>
Если все прошло удачно, то у нас есть все для того, чтобы создать область рисования OpenGL. GetClientRect возвратит нам ширину и высоту окна. Мы запомним ширину справа, и высоту снизу. После того как мы получили ширину и высоту, инициализируем экран OpenGL. Мы делаем это при помощи вызова InitGL, передавая в параметрах право и низ (ширину и высоту).
OpenGL code
ChangeDisplaySettings(NULL,0) будет переключать разрешение рабочего стола обратно, делая его таким, каким мы переключались из него в полноэкранный режим. ReleaseDC(hWnd,hDC) уничтожает контекст устройства окна. По существу это уничтожает окно OpenGL.
OpenGl code
case WM_DESTROY:
case WM_CLOSE:
ChangeDisplaySettings(NULL, 0);
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
ReleaseDC(hWnd,hDC);
WM_KEYDOWN вызывается всякий раз при нажатии клавиши. Клавиша, которая была нажата, сохраняется в переменной wParam. Итак, что же делает следующий код. Скажем, я нажал 'A'. Буква фактически – это число, которое ее представляет. Поэтому в ячейку, которая представляет 'A' заносится TRUE. Позднее, в коде, если я проверю состояние ячейки и увижу TRUE, то я знаю, что клавиша 'A' действительно в этот момент нажата.
OpenGL code
WM_KEYUP вызывается всякий раз, когда клавиша отпускается. Клавиша, которая отжата, также сохраняется в переменной wParam. Поэтому, когда я отпускаю клавишу 'A', это делает ячейку для клавиши 'A' равной FALSE. Когда я проверю ячейку, для того чтобы увидеть нажата ли клавиша 'A', она вернет FALSE, что означает "нет, она не нажата".
OpenGL code
И последнее, что я сделаю - обработаю изменение размеров окна. Возможно, кажется, что бесмыслено добавлять этот код, когда программа запущена в полноэкранном режиме, но без этого кода, экран OpenGL не появится. Поверьте, мне это очень важно.
OpenGL code
OpenGL code
Это то место, где начинается программа, где создается окно, где делается практически все, кроме рисования. Мы начинаем с создания окна.
OpenGL code
OpenGL code
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";
OpenGL code
OpenGl code
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);
OpenGL code
Следующая секция кода вызывает у многих людей массу проблем … переход в полноэкранный режим. Здесь важна одна вещь, которую вы должны запомнить, когда переключаетесь в полноэкранный режим - сделать ширину и высоту в полноэкранном режиме необходимо туже самую, что и ширина и высота, которую вы сделали в своем окне.
Важно отметить, что этот код не будет скомпилирован на Cи. Это файл должен быть сохранен как .CPP файл.
OpenGL code
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) чтобы убедиться, что Контекст рендеринга не освобожден.
OpenGL code
OpenGL code
DrawGLScene вызывает ту часть программы, которая фактически рисует объекты OpenGL. В этой программе мы оставим эту часть секции пустой, все что будет сделано - очистка экрана черным цветом. В следующих уроках я покажу, как применять OpenGL для рисования.
SwapBuffers(hDC) очень важная команда. Мы имеем окно с установленной двойной буферизацией. Это означает, что изображение рисуется на скрытом окне (называемым буфером). Затем, мы говорим компьютеру переключить буфера, скрытый буфер копируется на экран. При этом получается плавная анимация без рывков, и зритель не замечает отрисовку объектов.
OpenGL code
DrawGLScene(); // Нарисовать сцену
SwapBuffers(hDC); // Переключить буфер экрана
if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0); // Если ESC - выйти
>
>
В этом уроке я попытался объяснить как можно больше деталей каждого шага запутанной установки, и создания ваших собственных полноэкранных OpenGL программ, которые будут завершаться при нажатии ESC. Я потратил 3 дня и 13 часов для написания этого урока. Если вы имеете любые комментарии или вопросы, пожалуйста, пошлите их мне по электронной почте. Если вы ощущаете, что я некорректно комментировал что-то или что код должен быть лучше в некоторых секциях по некоторым причинам, пожалуйста, дайте мне знать. Я хочу сделать уроки по OpenGL хорошими насколько смогу. Я заинтересован в обратной связи.
Примечание переводчика: исходные коды для этого урока и других уроков, есть на сайте NeHe. Чтобы найти их, Вам надо перейти на оригинальный урок по ссылке, которая расположена вначале текста любого переведенного урока. Затем, надо перейти в конец оригинального (англоязычного) урока и там найти ссылки на нужный архив с нужным исходным кодом, для своего компилятора, или платформы, или языка (например, Delphi). Поместить все исходные коды для уроков NeHe на этом сайте, просто невозможно!
Еще одно примечание переводчика: в связи с тем, что NeHe иногда изменяет уроки, но при этом нет возможности понять когда он это сделал (нет даты последнего изменения урока), то бывает так, что текст переведенных уроков незначительно отличается от текущих оригинальных уроков. Не пугайтесь этого, смысл урока при этом значительно не меняется.
Читайте также: