推荐:使用 NSDT场景编辑器快速搭建3D应用场景
glTF 代表 GL 传输格式。
glTF 是一种用于存储和加载 3D 场景的标准化文件格式,其基本目的是由 3D 创建工具轻松生成并被任何图形应用程序使用,无论使用何种 API,处理最少。
它与其他格式的主要区别在于,glTF 将其数据作为 GPU 就绪的首要任务。这意味着在将文件数据馈送到 GPU 之前,格式化/调整/解释文件上的数据所需的处理步骤更少。
了解文件格式后,您将欣赏整个资产生成流程的点击效果;从 3D 编辑工具到如何将其输出输入图形管线,在我看来,这就是 glTF 的魔力,它以可移植和标准化的方式弥合了设计和实现之间的差距。
glTF包括:
图 1:来自 Khronos glTF 2.0 规范的 glTF2.0 概述,打开gltf编辑器,查看glb模型文件。
Blender 2.8,一个免费的3D建模工具,有一个一流的glTF 2.0导出器:
https://docs.blender.org/manual/en/2.80/addons/io_scene_gltf2.html
使用它的导出器,我从起始场景生成了一个 glTF 2.0 文件:
场景描述为:
1.- 每边 2m 的立方体,其中心位于原点,它有 8 个顶点(导出者可能会增加顶点的数量和顺序):
立方体具有默认材料分配。
2.- 具有以下功能的相机:
3.- 光源具有:
图 2:Blender 的开始场景、立方体、摄像机和光源
图3.导出此场景时,我使用了以下配置: 请注意 +Y 向上设置。Blender在+Z向上配置下工作,因此导出器将为我们更改它。
按下导出按钮后,您将拥有 2 个文件:
*.gltf
*。.bin
打开gltf文件看一看,它会分为几个部分:
"asset" : { "generator" : "Khronos glTF Blender I/O v1.1.46", "version" : "2.0" }, "scene" : 0, "scenes" : [ { "name" : "Scene", "nodes" : [ 0, 1, 2 ] } ],
"nodes" : [ { "mesh" : 0, "name" : "Cube" }, { "name" : "Light", "rotation" : [ 0.16907551884651184, 0.7558804154396057, -0.2721710503101349, 0.5709475874900818 ], "translation" : [ 4.076250076293945, 5.903860092163086, -1.0054500102996826 ] }, { "name" : "Camera", "rotation" : [ 0.483536034822464, 0.33687159419059753, -0.20870360732078552, 0.7804827094078064 ], "translation" : [ 7.358890056610107, 4.958310127258301, 6.925789833068848 ] } ],
推荐使用GTLF编辑器来3D模型的材质属性。
"materials" : [ { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "Material", "pbrMetallicRoughness" : { "baseColorFactor" : [ 0.800000011920929, 0.800000011920929, 0.800000011920929, 1 ], "metallicFactor" : 0, "roughnessFactor" : 0.4000000059604645 } } ],
图4.在搅拌机中指定剔除参数
图5.搅拌机的材料描述;基色为:R: 0.8克: 0.8深: 0.8A: 1.0 金属色设置为 0 粗糙度设置为 0.4
"meshes" : [ { "name" : "Cube", "primitives" : [ { "attributes" : { "POSITION" : 0, "NORMAL" : 1, "TEXCOORD_0" : 2 }, "indices" : 3, "material" : 0 } ] } ],
"accessors" : [ { "bufferView" : 0, "componentType" : 5126, "count" : 24, "max" : [ 1, 1, 1 ], "min" : [ -1, -1, -1 ], "type" : "VEC3" }, { "bufferView" : 1, "componentType" : 5126, "count" : 24, "type" : "VEC3" }, { "bufferView" : 2, "componentType" : 5126, "count" : 24, "type" : "VEC2" }, { "bufferView" : 3, "componentType" : 5123, "count" : 36, "type" : "SCALAR" } ],
"bufferViews" : [ { "buffer" : 0, "byteLength" : 288, "byteOffset" : 0 }, { "buffer" : 0, "byteLength" : 288, "byteOffset" : 288 }, { "buffer" : 0, "byteLength" : 192, "byteOffset" : 576 }, { "buffer" : 0, "byteLength" : 72, "byteOffset" : 768 } ],
"buffers" : [ { "byteLength" : 840, "uri" : "DefaultCube.bin" } ]
“缓冲区”指定我们的数据所在的位置及其大小。这意味着我们的立方体的几何形状存储在“DefaultCube.bin”
我们现在知道我们需要获取的数据在哪里,但我们仍然需要一种方法来获取它。
市面上有多个 Json 解释器,但我决定使用这个:
https://github.com/nlohmann/json
因为它可以用作单个标头并且易于使用。
包含“nlohmann/json.hpp”标头并添加命名空间后,可以使用下面的代码将整个 json 文件填充到可解析的 json 对象中。
#include "nlohmann/json.hpp" using json = nlohmann::json; int main(){ std::ifstream input("Content/DefaultCube.gltf"); if (!input) { std::cout << "Could not find gltf file" << std::endl; return -1; } json cubeFile; input >> cubeFile;
拥有此对象后,如果需要,可以逐个访问属性:
例如,要提取“场景”属性:
json scenes = cubeFile["scenes"];
如果你打印出场景的内容,你会得到这个:
[{“名称”:“场景”,“节点”:[0,1,2]}]
该库还使您可以轻松访问存储在其中的各个值,假设您想从“scenes”属性访问节点索引并将它们存储在int向量中:
json nodeOverview = scenes[0]["nodes"]; std::vector
使用Niels Lohmann的这个很酷的json工具,我们可以继续开始从Json和bin文件中填充数据结构。
对于此项目,我们将提取:
网格 – 位置、法线、坐标、索引和材料。
相机 – 位置和旋转。
光 – 位置和旋转。
唯一需要从.bin文件中提取其他信息的是网格。
现在,我们将它存储在易于访问的结构中,我使用 glm (https://glm.g-truc.net/0.9.9/index.html ) 作为它们的向量和矩阵结构及其各种数学函数;glm 很方便,因为它已经包含了 vec3 和 vec2 的定义,可以直接与我们的 glTF 文件中的 VEC3 和 VEC2 匹配。从下面的结构中可以看出,我将使用这些类型的向量来存储我的网格数据。
struct pbrMaterial { glm::vec4 color; float metallicFactor; float roughnessFactor;}; struct mesh { std::string name; std::vector
从文件中获取网格数据需要我们:
json nodes = cubeFile["nodes"];for (uint32_t i = 0; i < nodes.size(); i++){ if (nodes[i].find("mesh") != nodes[i].end()) { //There is a mesh in this node:
json node = nodes[i];int meshIndex = node["mesh"];
json meshes = cubeFile["meshes"];json meshPrimitives = meshes[meshIndex]["primitives"];if (meshPrimitives[0]["attributes"].find("POSITION") != meshPrimitives[0]["attributes"].end()){ positionAccessorIndex = meshPrimitives[0]["attributes"]["POSITION"];}
json accessors = cubeFile["accessors"];json bufferViews = cubeFile["bufferViews"]; std::ifstream binFile("Content/DefaultCube.bin", std::ios_base::binary); meshes.positions.resize(accessors[cubeScene.meshes[0].positionAccessorIndex]["count"]); uint32_t bufferViewIndex = accessors[positionAccessorIndex]["bufferView"];uint32_t bytesCount = bufferViews[bufferViewIndex]["byteLength"];uint32_t byteOffset = bufferViews[bufferViewIndex]["byteOffset"];binFile.seekg(0);binFile.seekg(byteOffset);binFile.read((char *)meshes.positions.data(), bytesCount);
完成此步骤后,您的仓位即可发送到任何图形 API。
大部分代码是直接从以下位置窃取的:
https://github.com/Overv/VulkanTutorial/blob/master/code/27_model_loading.cpp
删除了他们的tinyobjloader依赖项,并添加了我们自己的原始glTF解析器来获取顶点和正常数据。
图6.由 Vulkan 图形管道呈现的默认立方体。
由3D建模学习工作室 整理翻译,转载请注明出处!