В большинстве случаев, когда речь заходит об использовании 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(), определять mesh gfx.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/obj obj.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‑приложений.

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