Size: a a a

2019 September 08
Stepaneko
Экспериментирую с Java + LWJGL.
Когда-то я уже использовал эту связку, но как систему сборки использовал Gradle, сейчас же использую Maven. Последний мне нравится больше. Да и вообще, в отличие от того проекта, этот не учебный, а чисто для себя. С одной стороны это снимает некоторые ограничения, а с другой стороны забросить его "проще".

Я смутно представляю чего я хочу достичь, но пока не хочу это фиксировать. Кодовое название для проекта мне любезно предложил github, это fuzzy-broccoli 🥦

#dev
источник
Stepaneko
(Да, я люблю брокколи)
источник
Stepaneko
ftvkyo2011
Экспериментирую с Java + LWJGL.
Когда-то я уже использовал эту связку, но как систему сборки использовал Gradle, сейчас же использую Maven. Последний мне нравится больше. Да и вообще, в отличие от того проекта, этот не учебный, а чисто для себя. С одной стороны это снимает некоторые ограничения, а с другой стороны забросить его "проще".

Я смутно представляю чего я хочу достичь, но пока не хочу это фиксировать. Кодовое название для проекта мне любезно предложил github, это fuzzy-broccoli 🥦

#dev
На данный момент fuzzy-broccoli состоит из нескольких модулей.
Оказалось, что можно удобно объединить концепцию Java-модуля и Maven-модуля. Поэтому сейчас в проекте есть глобальный модуль root, у которого три подмодуля - client, server и common.

Я решил приблизительно следовать паттерну MVC: в модуле common лежит общая Model (класс World), а в модуле client находятся View (различные классы с интерфейсом Drawable) и Controller (различные классы с InputProcessor).
Про модуль server пока говорить нечего, а вообще это скорее всего будет headless управление состоянием World.

Всем этим управляет класс Window. Он отвечает за создание окна и выбор начального Drawable (View) и соответствующего InputProcessor (Controller). После этого создаются шейдеры и запускается main loop.
После запуска текущий InputProcessor реагирует на события и может менять текущий Drawable и InputProcessor.
На каждом шаге main loop в Drawable передается World (Model) для отрисовки. Пока что он отрисовываться не умеет :)

Я уменьшил связность как мог, и не уверен, что стоит еще больше "отдалять" компоненты, тем более что главный принцип "заменяемости" всех трех компонентов работает - мы можем поставить другой Drawable, другой InputProcessor и открыть другой World. А переходить на обработку Event'ов и Notification'ов не хочется.

#dev
источник
Stepaneko
На данный момент реализовано три различных InputProcessor и три соответствующих им Drawable: ...MainMenu, ...Game, ...PauseMenu.

Game и PauseMenu при отображении просто показывают однотонные цвета. MainMenu же выводит треугольник, используя OpenGL 3.3 Core Profile. Экран обновляется приблизительно 60 раз в секунду (на самом деле немного реже). Каждое обновление Drawable главного меню двигает вершины треугольника к намеченным точкам на 1/100 расстояния, которое было на момент движения. Каждые 100 таких обновлений, то есть когда вершины треугольника достигли намеченных точек, выбираются новые намеченные точки (на определенном расстоянии от начальных координат) и движение продолжается.
Данные движения треугольника происходят исключительно с помощью вершинного шейдера - при каждом обновлении для него определяется новый набор "смещений" вершин треугольника. При этом меняется только один из двух Vector Arrays Object, который отвечает за эти смещения. Другой же Vector Arrays Object, отвечающий за изначальные координаты вершин треугольника, остается неизменным.

С в InputProcessor'ах заданы переходы между экранами игры, то есть переключение InputProcessor и Drawable. На данный момент существуют такие переходы:

...MainMenu (начальный экран):
- ESC => выход
- Enter => ...Game

...Game:
- ESC => ...PauseMenu

...PauseMenu:
- ESC => ...Game
- Enter => ...MainMenu

#dev
источник
Stepaneko
Удалось сделать рисование четырехугольника по четырем вершинам. Объясню, почему это не так просто, как рисование треугольника:
OpenGL работает с треугольниками, поэтому для отрисовки четырехугольника надо разделить четырехугольник на два треугольника. При этом, если мы будем просто рисовать два треугольника, нам понадобится 6 вершин, по 3 на каждый из треугольников. И среди них будет две повторяющиеся. Мы же хотим рисовать четырехугольник на четырех вершинах. Для этого мы передаем в GPU только 4 вершины и заводим дополнительных буфер, в который сохраняем порядок отрисовки наших вершин.

Например:
вершины = {
   0.5, 0.5, 0.0,
   -0.5, 0.5, 0.0,
   0.5, 0.5, 0.0,
   -0.5, -0.5, 0.0,
}

номера вершин = {
   0, 1, 2,
   1, 2, 3,
}

Теперь будет рисоваться два треугольника - первый на вершинах номер 0, 1, 2, а второй на вершинах 1, 2, 3.
Ну и как всегда много времени у меня ушло на то, чтобы отладить это все, потому что этот прямоугольник довольно долго не хотел выводиться. Я пока не понял, когда можно удалять GL Buffers, так как данные из них должны в какой-то момент отправиляться в память GPU.
Сейчас сделал так, что они все сохраняются на время отрисовки, и при этом перед отрисовкой еще привязывается буфер номера вершин.

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

#dev
источник
Stepaneko
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
источник
Stepaneko
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
источник
Stepaneko
Да, если интересно, я использую использую туториалы learnopengl (на русском).
При этом проблемы все равно возникают, но в основном это связано с нелинейным характером исполнения программы (в туториале же все написано в одном классе).
источник
2019 September 09
Stepaneko
Погуглил, почитал. Да, действительно можно удалить буфер вершины и буфер номера вершин сразу после отвязки Vertex Arrays Objects. При этом на момент отвязки Vertex Arrays Objects буфер номера вершин должен быть привязан, потому что VAO должен его "запомнить". А вот буфер вершины можно отвязать (хотя и необязательно), так как информация о нем хранится в списке атрибутов для Vertex Shader.

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

Интересное: glspec33.core.pdf Appendix D, 1.2

#dev
источник
Stepaneko
Раскрасил рисуемые примитивы с помощью фрагментного шейдера :3
#dev
источник
Stepaneko
Интересно, как это работает. Потому что я передал цвет из вершинного шейдера, а я думал что он вызывается только для моих четырех (шести) вершин. А тут цвет равномерно растянут по всей поверхности. Я как бы и хотел такого добиться, но не очень понимаю как это организовано внутри, каким образом цвет из 6 вершин превратился в цвет для всех пикселей. Возможно оно смешивает цвета автоматически, надо еще почитать.

#dev
источник
Stepaneko
Нарисовал шейдерами черный овал на четырех треугольниках.
#dev
источник
Stepaneko
А если серьезно, то пообщался с разбирающимся человеком. Действительно, вершинный шейдер, как я и предполагал, запускается только для вершин (неожиданно, да?). И на выходе мы получаем столько цветов, полученных из вершинного шейдера, сколько у нас было вершин. А вот в фрагментный шейдер для каждого фрагмента передается интерполированный цвет из вершин текущего рисуемого примитива. Отсюда и получилось такое размытие. Эффект как на картинке был достигнут такими шейдерами:

Вершинный:
#version 330 core
in vec3 vPos;
out vec4 vColor;
void main()
{
  gl_Position = vec4(vPos.xyz, 1.0);
  if(gl_VertexID == 0) {
      vColor = vec4(0.0);
  } else {
      vColor = vec4(1.0);
  }
}


Фрагментный:
#version 330 core
in vec4 vColor;
out vec4 FragColor;
void main()
{
  if(length(vColor.rgb) > 0.5) {
      FragColor = vColor;
  } else {
      FragColor = vec4(0.0);
  }
}


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

#dev
источник
Stepaneko
Nice
#dev
источник
Stepaneko
Я сделаль...
#dev
источник
Stepaneko
источник
2019 September 10
Stepaneko
Удалось наладить вывод изображения, хранимого в ресурсах. Пока что код для этого не очень красивый, он в основном вытащен из примера по загрузке изображения с использованием библиотеки STB, и из него выкинуто все что мне не нужно. Впоследствии же уже более внимательно нужно отсмотреть написанный код.
Изображение накладывается как текстура на мои треугольники, при этом для каждой вершины треугольника задано, к какой точке текстуры она привязана. Вся данная информация передается в вершинный шейдер, который информацию про привязке к текстуре передает в фрагментный шейдер, где по известным координатам на текстуре с нее берется необходимый цвет для фрагмента.

#dev
источник
Stepaneko
Вот собственно и текстура, которую я вывожу. Здесь она построена на четырех треугольниках, вершины которых на углах четырехугольника и в его центре.
#dev
источник
Stepaneko
Сегодня порефакторил существующий код: сделал классы для примитивов - Vec{2, 3, 4} и Vertex{Simple, Colored, Textured}. Теперь функция для создания и загрузки VBO сама определяет какие вершины ей передали и выполняет необходимые действия для загрузки соответствующего типа вершин. При этом появилось требование к вершинному шейдеру - location вектора с цветом или с привязкой к текстуре должен быть location вектора с позицией + 1. Надо будет либо записать это в документацию, либо сделать дополнительный параметр для второго location (но мне пока что нравится и так).

Тем временем вроде бы можно считать код достаточно валидным чтобы я мог открыть репозиторий. Разработку сейчас я в основном веду в ветке graphics. Я не смог придумать какую лицензию хочу, так что поставил MIT License. Вообще мне (в общих чертах) нравится как лицензируется aseprite, но это слишком сложно.

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

#dev
источник
2019 September 11
Stepaneko
Рабочий рабочий стол :3
источник