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


В январе состоится открытый вебинар онлайн-курса "Golang Developer. Professional", на котором руководитель курса проведет Mock-собеседование со студентом. Участники рассмотрят реальные вопросы, разберут комментарии по ответам; спикер поделится советами по прохождению собеседований. Если для кого-то актуально, зарегистрироваться на вебинар можно по ссылке.

Комментарии (2)


  1. sedyh
    28.12.2022 19:49

    Ebitengine+Tetra3D?


  1. john_samilin
    29.12.2022 13:00
    +3

    CreateSpotLightWithDiffuseDiffuseIntensitySpecularSpecularIntensityPriorityShadowTypeShadowBias лучшее название метода!