Данная статья является продолжением рассмотра основ GLTF и GLB форматов. Вы можете найти первую часть статьи здесь. В первой части мы рассмотрели с вами зачем изначально планировался формат, а также такие артефакты и их атрибуты GLTF формата как Scene, Node, Buffer, BufferView, Accessor и Mesh. В данной же статье мы рассмотрим Material, Texture, Animations, Skin, Camera, а также закончим формировать минимальный валидный GLTF файл.


image

Material и Texture


С мешем неразрывно связаны материалы и текстуры. При необходимости меш может быть анимирован. Материал хранит информацию о том, как модель будет отрендерена движком. GLTF определяет материалы, используя общий набор параметров, которые основаны на Physical-Based Rendering (PBR). PBR модель позволяет создавать “физически корректное” отображение объекта в разных световых условиях благодаря тому, что шейдинговая модель должна работать с “физическими” свойствами поверхности. Есть несколько способов описания PBR. Самая распространенная модель — это metallic-roughness model, которая и используется по умолчанию в GLTF. Также можно использовать и specular-glosiness модель, но только при помощи отдельного расширения (extenstion). Основные атрибуты материала следующие:


  1. name — имя меша.
  2. baseColorFactor/baseColorTexture — хранит инфомрацию о цвете. В случае атрибута Factor информация хранится в числовом значении для RGBA, в случае Texture — хранится ссылка на текстуру в объекте textures.
  3. metallicFactor — хранит информцию о Metallic
  4. roughnessFactor — хранит информцию об Roughness
  5. doubleSided — имеет значение true либо false (значение по умолчанию) и указывает на то, будет ли меш рендериться с обоих сторон или только с "лицевой" стороны.
    "materials": [
        {
            "pbrMetallicRoughness": {
                "baseColorTexture": {
                    "index": 0
                },
                "metallicFactor": 0.0,
                "roughnessFactor": 0.800000011920929
            },
            "name": "Nightshade_MAT",
            "doubleSided": true
        }
    ],

Metallic или значение “металичности”. Этот параметр описывает как сильно отражающая способность схожа с настоящим металлом, т.е. насколько сильно свет отражается от поверхности. Значение измеряется от 0 до 1, где 0 — это диэлектрик, а 1 — чистый металл.


Roughness или «шероховатость». Данный атрибут отображает насколько “шероховата” поверхность, тем самым воздействуя на рассеяние света от поверхности. Измеряется от 0 до 1, где 0 — идеально плоская, а 1 — полностью шероховатая поверхность, которая отражает лишь небольшое количество света.


Texture — объект, который хранит в себе текстурные карты (Texture maps). Такие карты придают реалистичности модели. Благодаря ним можно обозначить внешний вид модели, придать различных свойств таких как металличность, шероховатость, естественное затемнение от окружения и даже свойств свечения. Текстуры описываются тремя высокоуровневыми массивами: textures, samplers, images. Объект Textures использует индексы для ссылок на sampler и image экземпляры. Самым важным объектом является image, т.к. именно он хранит информацию об местоположении карты. В textures он описывается словом source. Картинка может находится как где-то на жестком диске (например "uri": “duckCM.png”) так и закодирована в GLTF ("bufferView": 14, "mimeType": “image/jpeg”). Samplers — это объект, который определяет параметры фильтров и упаковки (wrapping) соответствующие GL типам.


В нашем примере с треугольником нету текстур, но я приведу JSON из других моделей, с которыми работал. В данном примере текстуры были записаны в буфер, поэтому они тоже считываются с buffer при помощи BufferView:


"textures": [
        {
            "sampler": 0,
            "source": 0
        }
    ],
    "images": [
        {
            "bufferView": 1,
            "mimeType": "image/jpeg"
        }
    ],

Animations


GLTF поддерживает сочлененную (articulated), скиновую (skinned) и морф таргет (morph target) анимации с помощью ключевых кадров (key frames). Информация этих кадров хранится в буферах и ссылается на анимации при помощи аксессоров. GLTF 2.0 определяет только хранилище анимации, поэтому в нём не определено какое-либо конкретное поведение во время выполнения, например такое как порядок воспроизведения, автозапуск, циклы, отображение временных шкал и т. д. Все анимации хранятся в массиве Animations и они определяются как набор каналов (атрибут channel), а также набор сэмплеров, которые определяет акссессоры (Accessor) обрабатывающие информацию о ключевых кадрах (key frames) и методом интерполяции (атрибут samples)


Основные атрибуты объекта Animations следующие:


  1. name — название анимации (если таковое имеется)
  2. channel — массив, который соединяет выходные значения ключевых кадров анимации с определённой нодой в иерархии.
  3. sampler — атрибут, который ссылающийся на Accessor, который обрабатывает ключевые кадры (key frames) из буфера.
  4. target — это объект, который определяет какую ноду (объект Node) нужно анимировать используя атрибут node, а также какое свойство ноды нужно анимировать используя атрибут path — translation, rotation, scale, weights и т.п. Неанимированные атрибуты сохраняют свои значения во время анимаций. Если node не определена, то атрибут channel желательно опустить.
  5. samplers — определяет входные и выходные пары: набор скалярных значений с плавающей запятой, представляющих линейное время в секундах. Все значения (input/output) хранятся в буфере и доступны через акссессоры. Атрибут interpolation хранит значение интерполяции между ключами.

В простейшем GLTF нету анимаций. Пример взят из другого файла:


"animations": [
        {
            "name": "Animate all properties of one node with different samplers",
            "channels": [
                {
                    "sampler": 0,
                    "target": {
                        "node": 1,
                        "path": "rotation"
                    }
                },
                {
                    "sampler": 1,
                    "target": {
                        "node": 1,
                        "path": "scale"
                    }
                },
                {
                    "sampler": 2,
                    "target": {
                        "node": 1,
                        "path": "translation"
                    }
                }
            ],
            "samplers": [
                {
                    "input": 4,
                    "interpolation": "LINEAR",
                    "output": 5
                },
                {
                    "input": 4,
                    "interpolation": "LINEAR",
                    "output": 6
                },
                {
                    "input": 4,
                    "interpolation": "LINEAR",
                    "output": 7
                }
            ]
        },

Skin


Инфомрация о скиннинге, также известном как "шкуринг", a.k.a. костная анимация, хранится в массиве skins. Каждый скин определяется при помощи атрибута inverseBindMatrices, который ссылается на акссессор с IBM (inverse bind matrix) данными. Эти данные используются для переноса координат координат в то же пространство, что и каждый сустав/joint, а также атрибут массива joints, который перечисляет индексы узлов, используемые в качестве суставов/joints для анимации кожи. Порядок соединений определяется в массиве skin.joints и должен соответствовать порядку данных inverseBindMatrices. Атрибут skeleton указывает на объект Node, который является общим корнем иерархии суставов/joints или на прямую или косвенную родительскую ноду общего корня.


Пример использования объекта skin (отсутствует в примере с треугольником):


    "skins": [
        {
            "name": "skin_0",
            "inverseBindMatrices": 0,
            "joints": [ 1, 2 ],
            "skeleton": 1
        }
    ]

Основные атрибуты:


  1. name — название скиннинга
  2. inverseBindMatrices — указывает на номер акссессора, хранящего информацию об Inverse Bind Matrix
  3. joints — указывает на номер акссессора, храняшего информацию о суставах/joints
  4. skeleton — указывает на номер акссессора, храняшего информацию о "корневом"
    суставе/joint с которого начинается скелет модели

Camera


Камера определяет матрицу проекции, которая получается трансформацией “вида” (view) в координаты клипа. Если проще, то камеры определяют визуальный вид (угол обзора, направления “взгляда” и т.п.), который видит пользователь при загрузке модели.


Проекция может быть “Перспективной” и “Ортогональной”. Камеры содержатся в нодах (nodes) и могут иметь трансформации. Камеры закреплены в объектах Node и, таким образом, могут иметь трансформации. Камера определена так, что локальна ось +X направлена вправо, объектив смотрит в направлении локальной оси -Z, а верх камеры совмещена с локальной осью +Y. Если же трансформация не указана, то камера находится в начале координат. Камеры хранятся в массива cameras. Каждая из них определяет атрибут type, который назначает тип проекции (перспектива или ортогональный), а также такие атрибуты как perspective или orthographic, в которых уже хранится более детальная информация. В зависимости от наличия атрибута zfar, камеры с типом "перспектива" могут использовать конечную или бесконечную проекцию.


Пример камеры в JSON с типом perspective. Не актуально для примера минимального корректного GLTF файла (треугольника):


"cameras": [
        {
            "name": "Infinite perspective camera",
            "type": "perspective",
            "perspective": {
                "aspectRatio": 1.5,
                "yfov": 0.660593,
                "znear": 0.01
            }
        }
    ]

Основные атрибуты объекта Camera:


  1. name — название скиннинга
  2. type — тип камеры, perspective или orthographic.
  3. perspective/orthographic — атрибут, содержащий детали соответствущего type значения
  4. aspectRatio — Соотношение сторон экрана (fov).
  5. yfov — угол вертикального поля зрения (fov) в радианах
  6. zfar — расстояние до дальней плоскости отсечения (clipping plane)
  7. znear — расстояние до ближней плоскости отсечения
  8. extras — данные, специфичные для приложения

Минимальный валидный GLTF файл


В начале статьи я писал о том, что мы соберём минимальный GLTF файл, который будет содержать в себе 1 треугольник. JSON со встроенным буфером можно найти ниже. Просто скопируйте его в текстовый файл, измените формат файла на .gtlf. Для просмотра 3D ассета в файле можете использовать любой просмотрщик, поддерживающий GLTF, но лично я использую этот


{
  "scenes" : [
    {
      "nodes" : [ 0 ]
    }
  ],

  "nodes" : [
    {
      "mesh" : 0
    }
  ],

  "meshes" : [
    {
      "primitives" : [ {
        "attributes" : {
          "POSITION" : 1
        },
        "indices" : 0
      } ]
    }
  ],

  "buffers" : [
    {
      "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
      "byteLength" : 44
    }
  ],
  "bufferViews" : [
    {
      "buffer" : 0,
      "byteOffset" : 0,
      "byteLength" : 6,
      "target" : 34963
    },
    {
      "buffer" : 0,
      "byteOffset" : 8,
      "byteLength" : 36,
      "target" : 34962
    }
  ],
  "accessors" : [
    {
      "bufferView" : 0,
      "byteOffset" : 0,
      "componentType" : 5123,
      "count" : 3,
      "type" : "SCALAR",
      "max" : [ 2 ],
      "min" : [ 0 ]
    },
    {
      "bufferView" : 1,
      "byteOffset" : 0,
      "componentType" : 5126,
      "count" : 3,
      "type" : "VEC3",
      "max" : [ 1.0, 1.0, 0.0 ],
      "min" : [ 0.0, 0.0, 0.0 ]
    }
  ],

  "asset" : {
    "version" : "2.0"
  }
}

Что в итоге?


В заключении хочу отметить растущую популярность GLTF и GLB форматов, многие компании уже активно используют его, а некоторые уже активно стремятся к этому. Сильно способствует популяризации формата легкость его использования в социальной сети Facebook (3D посты и, с недавних пор, 3D Photos), активное использование GLB в Oculus Home, а также ряд нововведений, которые были озвучены в рамках GDC 2019. Легковесность, быстрая скорость рендеринга, простота использования, продвижение Khronos Group и стандартизация формата – вот главные плюсы, которые, как я уверен, со временем сделают свое дело в дальнейшей его популяризации!

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


  1. SobakaRU
    16.04.2019 15:59

    Автор публикации зачем-то фрагментарно перевел часть спецификации формата на русский язык. Причем перевёл довольно условно — с использованием странных лингвистических уродцев типа «морф таргет».

    Попытки неподдерживаемых кустарных переводов живых спецификаций в большинстве случаев — это абсолютное зло. Не обижайтесь, но, на мой взгляд, было бы куда интереснее почитать о практических кейсах и подводных камнях (раз уж автор позиционирует себя, как специалист, с личным опытом использования glTF в Facebook, Oculus, UE и Microsoft).


    1. BloodyPoSTaL Автор
      16.04.2019 18:08

      Видимо вы поняли моя слова о личном опыте как о достаточно глубоком опыте, там же вопрос был о том, где этот формат используется и я ответил, что имел возможность использовать его в тех программах, которые перечислил. Большинство времени я работал с данным форматом в рамках CAD программ, Facebook, Oculus и UE были использованы для проверки корректности импорта специфических случаев.
      Здесь я старался объединить мои знания о практической реализации и несколько документаций, поэтому, с большего, данные статьи выглядят как перевод, хотя это не совсем так. Некоторых «лингвистических уродцев» я решил не переводить как раз из-за того, что в обиходе понятнее использовать именно их, а к примеру слово «сетка», в отличии от «меша», под которым обычно подразумевает «полигональный меш/сетку», может трактоваться не однозначно.
      К практическим случая можно и нужно переходить, но мне кажется рано это делать без рассказа об основах данного формата. К сожалению я не нашёл на территори ру нета такой информации и решил исправить данное упущение, ведь вещь действительно стоящая.


  1. customizer
    16.04.2019 16:50
    +1

    Написание статьи с использованием жаргонных слов конечно некрасив. Например, в первой части (кстати, а зачем вы разбили статью на две части? — ведь они не так и велики):

    Данный «срез» описывается при помощи 2х пропертей: — проперти — это наверное свойства?
    нода — узел, меш — сетка, translation — это скорее перенос, чем смещение, смещение — это offset и т.д.

    Я понимаю, что так вам привычней, но статья же пишется для того, чтобы её прочитали и, по возможности, поняли, как можно большее число людей, с разными уровнями вовлеченности в описываемую тему.
    Я сам перевожу на русский язык документацию по three.js и понимаю трудности перевода и как нелегко подобрать наиболее точное слово… но у вас все как-то уж «пофигистично».

    И да, переводы нужны и кто их сделает лучше, как не люди с опытом в описывемой сфере. Но, к сожалению, специалисты не стремятся делать переводы, им и так все понятно, и поэтому переводят люди, начинающие осваивать данную тему, так что «ляпы» неизбежны.


    1. SobakaRU
      16.04.2019 17:15
      +1

      Переводы технической документации — вообще очень скользкая тема. В том смысле, что очень часто это скорее вредит чем помогает (в том числе из-за трудностей перевода и разночтений в толковании терминов).
      Тут хороший пример — документация и интерфейсы самолетов, которые принципиально никогда не переводят с английского (если мы говорим о гигантах вроде Boeing, Airbus, Bombardier, Embraer).

      В IT-сфере проблемы переводов документации упираются во-первых в трудности с поддержкой (ситуация «спека поменялась, а перевод актуализировать некому»), а во-вторых в тот факт, что большая часть описанного практического опыта чаще всего лежит в англоязычном пласте информации. Как следствие, специалистам проще оперировать английской терминологией и переводы выглядят чужеродно и искуственно.

      В целом я двумя руками «за» переводы статей с практическим опытом и резко против кустарных неподдерживаемых переводов спецификаций и документации.
      Но это все мое imho, конечно. А в чем, с вашей точки зрения, необходимость перевода документации того же three.js?


      1. customizer
        16.04.2019 18:53

        Мне нужно понять как устроен three.js, вернее, как устроена трехмерная графика. Вариант с библиотекой three.js мне показался наиболее гибким. Я задумал показать графически как протекает электрический ток, т.е. почему он течет, почему у него такая скорость и отчего она такая, почему ток течет по пути наименьшего сопротивления и т.д.
        А для этого нужно показать этот ток (движение заряда) хотя бы в двумерном варианте, т.е. написать детектор столкновений, движок и все остальное. Все имеющиеся русскоязычные руководства в основном «заточены» под создание игр, а у меня более узкая задача и все, встреченные мной описания, как-то обходят самые простые вопросы. Поэтому я взялся за перевод.


    1. BloodyPoSTaL Автор
      16.04.2019 18:16

      Благодарю за «проперти», это проскочило из моей документации, которую я готовил для внутреннего использования. Сам не фанат такого, плюс не на руку сыграло ещё и то, что я впервые занимаюсь такого рода публикациями. Касательно слов «меш» или «сетка»: как я писал выше, я считаю, что слово «сетка», в отличии от «меша», под которым обычно подразумевает «полигональный меш/сетку», может трактоваться не однозначно, поэтому такие слова я оставлял. Также тому, кто может начать осваивать данную тематику, например 3Д моделирование и работу с данным форматом, будет проще ориентироваться в объектах по их практическому названию, т.е. если он читал инфу по «узлу», а потом в работе встречается «нода», то, по моему мнению, это будет не удобно. И благодарю за ваше мнение!


    1. BloodyPoSTaL Автор
      16.04.2019 18:19
      -1

      customizer Забыл упомянуть о разбиении статьи: изначально я писал всё в рамках одной, это намного удобнее, но ограничение по коливеству символов не дало мне такой возможности. Статья обрезалась по середине главы **Materials and Textures**


      1. customizer
        16.04.2019 18:59

        Как то я не знал про ограничение по количеству символов. Это что-то новенькое. Совсем я от жизни отстал.