В большинстве случаев, когда речь заходит об использовании Go, вспоминается backend или DevOps и в самую последнюю очередь можно подумать об использовании Go для создания мобильных или десктопных приложений. Но в действительности, благодаря возможностям интеграции с нативными библиотеками (в том числе, OpenGL и OpenAL для пространственного звука) Go может использоваться и для создания игр (в том числе для мобильной платформы). В этой статье мы обсудим несколько библиотек, которые могут помочь в создании 3D-графики на Go и обсудим вопросы портирования приложений на мобильные платформы.
Любая 3D-игра или приложение с трехмерной визуализацией использует возможности, предоставляемые системными библиотеками для работы с графическим адаптером, который переводит команды из абстрактного API (например, OpenGL или Angle) в концепции, понятные графическому ускорителю (или эмулирует их программно на обычном процессоре). Библиотеки OpenGL основаны на использовании шейдеров — приложений на специальном языке GLSL (OpenGL Shading Language), которые выполняются непосредственно на графическом ускорителе. 3D-графика в OpenGL опирается на два или более шейдера (как минимум должен быть определен вершинный шейдер, который обеспечивает трансформацию 3D-координат в проекцию на экране с учетом матричных преобразований для камеры перспективы, расположения и ориентировки объекта и наблюдателя) и фрагментный шейдер (который обеспечивает вычисление или интерполяцию цвета каждой точки сцены с учетом освещения, теней, отражений и другого). В действительности, разработка на таком низком уровне требуется редко (и доступна, например, в GoMobile, когда мы должны определить код шейдеров самостоятельно) и обычно используются один из 3D-движков, которые скрывают всю сложность за простыми понятиями сцены:
камера (расположение наблюдателя, линии взгляда и ориентировки сцены, например "направление вверх")
источники света (фоновое освещение, всенаправленные, точечные источники или прожекторы)
объекты сцены (представляются в виде 3d mesh, составленной из треугольников или многоугольников)
Также с объектами может быть связано описание движения и способа взаимодействия с другими объектами при столкновении, свойства материала для отображения поверхности (в частности, диффузный цвет, отражающая способность и цвет, соотношение диффузного и зеркального отражения, которая определяет насколько матовой или глянцевой является поверхности), текстура материала и многое другое.
Для разработки 3D-визуализаций на Go (для desktop) можно использовать одну из следующих библиотек: Azul3D, Harfang3D и G3N:
Azul3D
Azul3D — довольно старый 3D-движок на OpenGL 2, который поддерживает создание визуализаций, трехмерный звук (через OpenAL), физические симуляции (в 2D через chipmunk и в 3D в Open Dynamics Engine ODE). Для реализации 3D-графики нужно самостоятельно определить шейдеры (файлы .vert и .frag), но при этом библиотека дает возможность вычисления необходимых матриц (например, камеры), определения Mesh. Для использования доступны следующие модули:
azul3d.org/engine/gfx/window
— контекст отображения 3D-графики с поддержкой eventloop для обработки событий клавиатуры и мыши. Для запуска EventLoop используется функцияwindow.Run(gfxLoop, nil)
. Для получения ожидающих обработки событий нужно использоватьwindow.Poll
, который принимает канал с событиями и функцию для обработкиwindow.Event
, которая может быть либо событием самого окна (например, window.FramebufferResized), нажатием клавиш (keyboard.Typed
) и др. Также можно получать текущее состояние клавиатурыKeyboard()
и мышиMouse()
.
azul3d.org/engine/keyboard
— обработка событий нажатия/отпускания клавишazul3d.org/engine/mouse
— обработка событий мышиazul3d.org/engine/gfx
— непосредственно структуры для определения графики и загрузки шейдеров.gfx.Device
определяет связь с виртуальным графическим адаптером, через который можно получать информацию об области отображения —Bounds()
, отображать объекты сценыDraw(bounds, object, cam)
, управлять настройками OpenGL (например, включать Multisample Anti-Aliasing MSAA) и выполнять отрисовку сценыRender()
. Также через gfx можно создавать объекты сценыgfx.NewObject()
, определять meshgfx.NewMesh()
, цветаgfx.Color
, трансформацииgfx.NewTransform()
. К объекту сцены присоединяется Mesh, Shader для визуализации (могут быть загружены черезgfxutil.LoadShader()
из azul3d.org/engine/gfx/gfxutil).
Пример кода для построения простого треугольника с возможностью управления через клавиатуры можно посмотреть здесь.
Harfang 3D
Harfang3D — библиотека для использования 3D-графики, совместимая с OpenGL, OpenGL ES (в том числе, Android), Metal, Vulkan, DirectX, также поддерживает API для систем виртуальной реальности (SteamVR c отслеживанием глаз, Oculus Rift, HTC Vive), физическое моделирование (столкновения, механические ограничения), трехмерный звук. Сама библиотека создана на C++, но есть поддержка интеграций с Python, Lua и Go. Использует модель описания сцены через добавление компонентов, которыми могут быть как mesh-объекты, так и источники света и др. При сборке SDK нужно указать -DHG_BUILD_HG_GO=ON (для компиляции Go-связывания).
Модуль импортируется из "github.com/harfang3d/harfang-go/v3"
и предоставляет возможность загрузки asset'ов и сцены, манипуляции узлами, создания преднастроенных mesh (например, CreateSphereModel
или CreateCubeModel
), источников света (CreateSpotLight
), камеры (CreateCamera
), создания матриц и выполнения преобразований (например, TransformationMat4
), реализации eventloop для реакции на действия пользователя. Пример кода для визуализации сцены:
import (
math
hg "github.com/harfang3d/harfang-go/v3"
)
func main() {
hg.InputInit()
hg.WindowSystemInit()
//подготовка окна для рисования
var resX int32 = 1280
var resY int32 = 720
win := hg.RenderInitWithWindowTitleWidthHeightResetFlags("Harfang Sample", resX, resY, hg.RFMSAA4X)
pipeline := hg.CreateForwardPipelineWithShadowMapResolution(4096)
//создание конвейера отрисовки
res := hg.NewPipelineResources()
vtxLayout := hg.VertexLayoutPosFloatNormUInt8()
sphereMdl := hg.CreateSphereModel(vtxLayout, 0.1, 8, 16)
sphereRef := res.AddModel("sphere", sphereMdl)
//будем использовать предкомпилированные шейдеры
shader := hg.LoadPipelineProgramRefFromFile("resources_compiled/core/shader/default.hps", res, hg.GetForwardPipelineInfo())
//определение материала (для цвета задается RGB от 0 до 1)
sphereMat := hg.CreateMaterialWithValueName0Value0ValueName1Value1(shader, "uDiffuseColor", hg.NewVec4WithXYZ(1, 0, 0), "uSpecularColor", hg.NewVec4WithXYZ(1, 0.8, 0))
//создания.и конфигурация фонового цвета
scene := hg.NewScene()
scene.GetCanvas().SetColor(hg.NewColorWithRGB(0.1, 0.1, 0.1))
scene.GetEnvironment().SetAmbient(hg.NewColorWithRGB(0.1, 0.1, 0.1))
//установка камеры
cam := hg.CreateCamera(scene, hg.TransformationMat4(hg.NewVec3WithXYZ(15.5, 5, -6), hg.NewVec3WithXYZ(0.4, -1.2, 0)), 0.01, 100)
scene.SetCurrentCamera(cam)
//добавление света
hg.CreateSpotLightWithDiffuseDiffuseIntensitySpecularSpecularIntensityPriorityShadowTypeShadowBias(scene, hg.TransformationMat4(hg.NewVec3WithXYZ(-8.8, 21.7, -8.8), hg.Deg3(60, 45, 0)), 0, hg.Deg(5), hg.Deg(30), hg.ColorGetWhite(), 1, hg.ColorGetWhite(), 1, 0, hg.LSTMap, 0.000005)
//создаем объекты сцены (сферы)
rows := [][]*hg.Transform{}
for z := float32(-100.0); z < 100.0; z += 2.0 {
row := []*hg.Transform{}
for x := float32(-100.0); x < 100.0; x += 2.0 {
node := hg.CreateObjectWithSliceOfMaterials(scene, hg.TranslationMat4(hg.NewVec3WithXYZ(x*0.1, 0.1, z*0.1)), sphereRef, hg.GoSliceOfMaterial{sphereMat})
row = append(row, node.GetTransform())
}
rows = append(rows, row)
}
angle := 0.0
rect := hg.NewIntRectWithSxSyExEy(0, 0, resX, resY)
//основной цикл
for !hg.ReadKeyboard().Key(hg.KEscape) && hg.IsWindowOpen(win) {
dt := hg.TickClock()
angle += float64(hg.TimeToSecF(dt))
//поворот объектов сцены (узлов) на угол
for j, row := range rows {
rowY := math.Cos(angle + float64(j)*0.1)
for i, trs := range row {
/*pos := trs.GetPos()
pos.SetY(float32(0.1 * (rowY*math.Sin(angle+float64(i)*0.1)*6 + 6.5)))
trs.SetPos(pos)
*/
p := hg.NewVec3()
p, _ = trs.GetPosRot()
p.SetY(float32(0.1 * (rowY*math.Sin(angle+float64(i)*0.1)*6 + 6.5)))
trs.SetPos(p)
}
}
//обновление сцены
scene.Update(dt)
viewID := uint16(0)
//отправка сцены в pipeline
hg.SubmitSceneToPipelineWithFovAxisIsHorizontal(&viewID, scene, rect, true, pipeline, res)
//обновление экрана из буфера
hg.Frame()
hg.UpdateWindow(win)
runtime.GC()
}
hg.RenderShutdown()
hg.DestroyWindow(win)
}
Для подготовки ресурсов используются конверторы из 3D-форматов (включая Autodesk FBX) и компиляторы ресурсов (assetc, может быть загружен отсюда). Для определения шейдеров используется собственный язык, во многом совместимый с GLSL (за исключением нескольких важных отличий).
G3N
Движок G3N написан полностью на Go и использует библиотеки OpenGL для Windows / Linux и MacOS. Также дополнительно поддерживается пространственный звук через OpenAL. Сцена описывается иерархическим графом из узлов (при этом трансформации узла применяются ко всему поддереву, что позволяет создавать сложные анимации сцены). Поддерживается загрузка 3D-моделей в формате obj (Wavefront) и glTF, а также генерация объектов (сфера, цилиндр, куб и другие), вывод текста и анимированных спрайтов (из spritesheet). Также могут использоваться преднастроенные или собственные материалы, загрузка текстур из PNG/JPEG файлов, добавление источников света (точечных и направленных). Доступна возможность определения собственных шейдеров (вершинных, фрагментных и геометрических). Также предоставляется модуль для создания пользовательских интерфейсов из готовых (или созданных программно) виджетов. В библиотеке доступны следующие модули:
github.com/g3n/engine/animation
— определение анимации свойств узла (рассчитывает tween-значения для канала, который определяет тип интерполяции и изменяемое значение — положение, поворот, масштаб или морфинг между объектами)github.com/g3n/engine/app
— определение приложения (App), которое предоставляет доступ к событиям жизненного цикла (Subscribe
) и изменений в области отображения (например,window.OnWindowSize
) и позволяет запустить eventloop черезApp().Run(…)
. Также через функциюGls()
вApp()
можно непосредственно вызывать функции OpenGL (например, Clear для очистки сцены)github.com/g3n/engine/audio
— поддержка воспроизведения и 3D-позиционирования звука (через OpenAL), плеер создается черезaudio.NewPlayer
из файла (Ogg Vorbis или WAV)github.com/g3n/engine/camera
— определение камеры (положения, направление взгляда и ориентация сцены), также можно присоединить управляемую камеру (camera.NewOrbitControl
)github.com/g3n/engine/core
— основные абстракции сцены:core.NewNode()
создает новый узел, в который могут быть добавлены новые узлы (например, Mesh, UI-виджет или источник света)github.com/g3n/engine/geometry
— создание геометрических фигур (например,geometry.NewTorus
илиgeometry.NewSphere
)github.com/g3n/engine/gls
— константы и функции OpenGL для использования совместно сApp().Gls()
github.com/g3n/engine/graphic
— создание Mesh для добавления на сценуgraphic.NewMesh(geom, mat)
.github.com/g3n/engine/gui
— создание UI (например,gui.NewButton
для определения кнопки)github.com/g3n/engine/light
— создание источников света (NewAmbient
для фонового освещения,NewPoint
— точечный источник,NewDirectional
— направленный источник,NewSpot
— всенаправленный источник). Для каждого источника можно задать цвет, светимость и свойства затухания (а для направленных — еще и основное направление).github.com/g3n/engine/loader
— загрузка модели из Wavefront / glTF или Collada. Например, для загрузки объекта из Wavefront можно использовать loader/objobj.Decode(objpath, mtlpath).NewGroup()
возвращает core.Node для добавления в сценуgithub.com/g3n/engine/material
— определение материала из комбинации диффузного и зеркального рассеивания, прозрачности и текстуры.github.com/g3n/engine/math32
— содержит константы (например, Pi), цвета (преднастроенные и определяемые программно) и математические функции (совместимые с OpenGL)github.com/g3n/engine/renderer
— выполняет отрисовку сценыrenderer.Render(scene, cam)
для указанной камерыgithub.com/g3n/engine/text
— отображение текста (загрузка шрифтов, генерация atlas для дальнейшего отображения на сцене)github.com/g3n/engine/texture
— определение текстур (Texture2D) и загрузчиковtexture.NewTexture2DFromImage(filename)
, также позволяет создавать текстуру программно.github.com/g3n/engine/util/helper
— создание вспомогательных узлов (например отображение осей)github.com/g3n/engine/window
— константы и функции для работы с viewport и подписки на события окна.
Пример кода на G3N:
package main
import (
"github.com/g3n/engine/app"
"github.com/g3n/engine/camera"
"github.com/g3n/engine/core"
"github.com/g3n/engine/geometry"
"github.com/g3n/engine/gls"
"github.com/g3n/engine/graphic"
"github.com/g3n/engine/light"
"github.com/g3n/engine/material"
"github.com/g3n/engine/math32"
"github.com/g3n/engine/renderer"
"time"
)
func main() {
//приложение и сцена
a := app.App()
scene := core.NewNode()
//создание управляемой камеры
cam := camera.New(1)
cam.SetPosition(0, 0, 3)
scene.Add(cam)
camera.NewOrbitControl(cam)
//создание фигуры
geom := geometry.NewTorus(1, .4, 12, 32, math32.Pi*2)
mat := material.NewStandard(math32.NewColor("DarkGreen"))
mesh := graphic.NewMesh(geom, mat)
scene.Add(mesh)
//создание освещения (фонового и точечного)
scene.Add(light.NewAmbient(&math32.Color{1.0, 1.0, 1.0}, 0.8))
pointLight := light.NewPoint(&math32.Color{1, 1, 0}, 5.0)
pointLight.SetPosition(1, 0, 2)
scene.Add(pointLight)
//изменение фона в OpenGL
a.Gls().ClearColor(0.5, 0.5, 0.5, 1.0)
//запуск приложения и отрисовка кадра
a.Run(func(renderer *renderer.Renderer, deltaTime time.Duration) {
a.Gls().Clear(gls.DEPTH_BUFFER_BIT | gls.STENCIL_BUFFER_BIT | gls.COLOR_BUFFER_BIT)
renderer.Render(scene, cam)
})
}
Из рассмотренных библиотек для использования на мобильной платформе наиболее подходящей является Harfang 3D, поскольку она поддерживает взаимодействие с OpenGL ES и может быть собрана через gomobile для создания мобильного приложения. Однако сейчас эта возможность еще находится в стадии эксперимента и работает нестабильно. Но при этом создание 3D-визуализаций на Go для desktop-приложений доступно уже сейчас.
В январе состоится открытый вебинар онлайн-курса "Golang Developer. Professional", на котором руководитель курса проведет Mock-собеседование со студентом. Участники рассмотрят реальные вопросы, разберут комментарии по ответам; спикер поделится советами по прохождению собеседований. Если для кого-то актуально, зарегистрироваться на вебинар можно по ссылке.