В большинстве случаев, когда речь заходит об использовании Go, вспоминается backend или DevOps и в самую последнюю очередь можно подумать об использовании Go для создания мобильных или десктопных приложений. Но в действительности, благодаря возможностям интеграции с нативными библиотеками (в том числе, OpenGL и OpenAL для пространственного звука) Go может использоваться и для создания игр (в том числе для мобильной платформы). В этой статье мы обсудим несколько библиотек, которые могут помочь в создании 3D‑графики на Go и обсудим вопросы портирования приложений на мобильные платформы.
Любая 3D‑игра или приложение с трехмерной визуализацией использует возможности, предоставляемые системными библиотеками для работы с графическим адаптером, который переводит команды из абстрактного API (например, OpenGL или Angle) в концепции, понятные графическому ускорителю (или эмулирует их программно на обычном процессоре). Библиотеки OpenGL основаны на использовании шейдеров — приложений на специальном языке GLSL , которые выполняются непосредственно на графическом ускорителе.
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‑приложений доступно уже сейчас.
Go редко вспоминают в контексте 3D‑графики, но именно такие эксперименты хорошо показывают сильные стороны языка: работу с нативными библиотеками, производительность, простую модель конкурентности и возможность писать прикладной код без лишней сложности.
Глубже эти темы разбираем на курсе «Go‑разработчик. Продвинутый уровень»: от внутреннего устройства языка и оптимизации до конкурентного программирования, сетевого взаимодействия и архитектуры Go‑приложений.
Перед стартом можно пройти бесплатный вступительный тест — он поможет оценить текущий уровень и понять, какие темы стоит подтянуть.

