关于gltf模型格式文件的学习

news2024/11/27 21:04:54

目录

glTF模型

小黄鸭的gltf模型

字段分析

scene

nodes

 meshes

primitives

attributes

indices

mode

material

accessors

 bufferView

byteOffset 

count

componentType

type 

materials

textures

images

samplers

 magFilter与minFilter

wrapS与wrapT

进行解析

下载器

解析


因为笔者自己在检测自己学习webgl的质量于是写一个webgl的小框架。您如果也想自己创建一个webgl的小框架,可以收藏本文章,

webgl的小框架参考了 threejs pixijs hilo3D  Galacean Babylon.js 框架

地址:webglWF: 这个是自己学习完毕《weblg编程指南》2遍之后,打算自己先封装一个2D的给予webgl的矿建。用于检验自己的学习质量。与提升自己的webgl的能力

遇见了gltf模型的加载问题。所以我会一边学习一边完善这篇文章

glTF模型

 gltf中各个字段的对用关系

小黄鸭的gltf模型

{
    "asset": { "generator": "COLLADA2GLTF", "version": "2.0" },
    "scene": 0,
    "scenes": [{ "nodes": [0] }],
    "nodes": [
        { "children": [2, 1], "matrix": [0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 1] },
        {
            "matrix": [
                -0.7289686799049377, 0, -0.6845470666885376, 0, -0.4252049028873444, 0.7836934328079224, 0.4527972936630249, 0, 0.5364750623703003, 0.6211478114128113,
                -0.571287989616394, 0, 400.1130065917969, 463.2640075683594, -431.0780334472656, 1
            ]
        },
        { "mesh": 0 }
    ],
    "meshes": [{ "primitives": [{ "attributes": { "NORMAL": 1, "POSITION": 2, "TEXCOORD_0": 3 }, "indices": 0, "mode": 4, "material": 0 }], "name": "LOD3spShape" }],
    "accessors": [
        { "bufferView": 0, "byteOffset": 0, "componentType": 5123, "count": 12636, "max": [2398], "min": [0], "type": "SCALAR" },
        {
            "bufferView": 1,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 2399,
            "max": [0.9995989799499512, 0.999580979347229, 0.9984359741210938],
            "min": [-0.9990839958190918, -1, -0.9998319745063782],
            "type": "VEC3"
        },
        {
            "bufferView": 1,
            "byteOffset": 28788,
            "componentType": 5126,
            "count": 2399,
            "max": [96.17990112304688, 163.97000122070312, 53.92519760131836],
            "min": [-69.29850006103516, 9.929369926452637, -61.32819747924805],
            "type": "VEC3"
        },
        {
            "bufferView": 2,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 2399,
            "max": [0.9833459854125975, 0.9800369739532472],
            "min": [0.026409000158309937, 0.01996302604675293],
            "type": "VEC2"
        }
    ],
    "materials": [{ "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicFactor": 0 }, "emissiveFactor": [0, 0, 0], "name": "blinn3-fx" }],
    "textures": [{ "sampler": 0, "source": 0 }],
    "images": [{ "uri": "https://gw.alipayobjects.com/zos/OasisHub/267000040/4580/DuckCM.png" }],
    "samplers": [{ "magFilter": 9729, "minFilter": 9986, "wrapS": 10497, "wrapT": 10497 }],
    "bufferViews": [
        { "buffer": 0, "byteOffset": 76768, "byteLength": 25272, "target": 34963 },
        { "buffer": 0, "byteOffset": 0, "byteLength": 57576, "byteStride": 12, "target": 34962 },
        { "buffer": 0, "byteOffset": 57576, "byteLength": 19192, "byteStride": 8, "target": 34962 }
    ],
    "buffers": [{ "byteLength": 102040, "uri": "https://gw.alipayobjects.com/os/OasisHub/267000040/75/Duck0.bin" }]
}

字段分析

scene

在glTF中,"scene"字段用于指定默认的场景(scene)。一个glTF文件可以包含多个场景,每个场景可以包含多个节点(node),用于描述整个3D场景的内容和结构。

"scene"字段中包含一个整数值,表示默认场景的索引。当加载glTF文件时,渲染引擎会使用该索引来确定默认的场景,从而开始渲染整个3D场景。

通过指定默认场景,可以在包含多个场景的glTF文件中明确指定渲染引擎应该从哪个场景开始渲染,这有助于组织和管理复杂的3D场景结构。

文件中,入口字段

 "scene": 0,

代表从

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

中获取第0个元素也就是

{ "nodes": [0] }

nodes

此时的内容为nodes:0,也就是去

"nodes": [
        { "children": [2, 1], "matrix": [0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 1] },
        {
            "matrix": [
                -0.7289686799049377, 0, -0.6845470666885376, 0, -0.4252049028873444, 0.7836934328079224, 0.4527972936630249, 0, 0.5364750623703003, 0.6211478114128113,
                -0.571287989616394, 0, 400.1130065917969, 463.2640075683594, -431.0780334472656, 1
            ]
        },
        { "mesh": 0 }
    ],

上述nodes的字段中找第0个元素

        { "children": [2, 1], "matrix": [0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 0.009999999776482582, 0, 0, 0, 0, 1] },

 children[2,1]代表nodes[2]与nodes[1]。两个节点

  • "children": [2, 1] 表示当前节点的子节点。在这个例子中,子节点的索引分别为2和1,这意味着当前节点有两个子节点。

  • "matrix": [...] 表示了当前节点的变换矩阵。这个矩阵是一个4x4的矩阵,描述了节点的平移、旋转和缩放等变换信息。

综合起来,这段代码描述了一个节点,它有两个子节点,并且具有一个缩放变换。这些信息可以帮助构建出场景中的节点层次结构,以及节点的变换关系。

去哪里找children": [2, 1]中数据呢。当然是nodes字段自己,也就是紧跟着children下面的数据

{"matrix": [ -0.7289686799049377, 0, -0.6845470666885376, 0, -0.4252049028873444, 0.7836934328079224, 0.4527972936630249, 0, 0.5364750623703003, 0.6211478114128113, -0.571287989616394, 0, 400.1130065917969, 463.2640075683594, -431.0780334472656, 1]},
{ "mesh": 0 }

在glTF中,每个节点(node)可以代表场景中的一个对象,例如网格、相机、灯光等。每个节点可以包含一些属性,例如变换矩阵、子节点、网格等。在上述数据中,node[1] 和 node[2] 分别代表两个节点,它们的内容可以按照以下方式理解:

  • node[1]:这个节点包含了一个变换矩阵,用于描述节点的平移、旋转和缩放信息。在您的数据中,node[1]的变换矩阵描述了一个复杂的变换,包括平移、旋转和缩放等信息。这个节点可能代表场景中的一个具体对象,例如一个网格模型。

  • node[2]:这个节点包含了一个指向网格的引用(mesh)。在您的数据中,node[2]包含了一个指向索引为0的网格的引用,表示这个节点使用了索引为0的网格。这个节点可能代表场景中的一个网格对象。

通过理解节点中的内容,可以构建出场景中的层次结构,描述场景中的对象之间的关系和属性。这有助于渲染引擎正确地呈现场景中的对象,并实现正确的变换和渲染效果。

 meshes

发现nodes[2]的内容是

 { "mesh": 0 }

也就是去找meshes的字段

"meshes": [{ "primitives": [{ "attributes": { "NORMAL": 1, "POSITION": 2, "TEXCOORD_0": 3 }, "indices": 0, "mode": 4, "material": 0 }], "name": "LOD3spShape" }],

这段代码描述了一个网格(mesh),具体含义如下:

  • "primitives": 这是一个包含了网格的基本几何信息的数组。在这个例子中,只有一个primitive,表示这个网格是由一个基本几何构成的。

    • "attributes": 这个字段包含了描述网格顶点属性的信息。在这里,包含了"NORMAL"、"POSITION"和"TEXCOORD_0",分别表示法线、位置和纹理坐标等顶点属性的索引。这些索引对应了访问器(accessor)中的索引,用于获取实际的顶点数据。

    • "indices": 这个字段表示了描述网格索引的访问器(accessor)的索引。索引对应了访问器中描述网格索引数据的信息。

    • "mode": 这个字段表示了绘制网格的模式,比如4表示了以三角形扇的方式绘制网格。

    • "material": 这个字段表示了网格使用的材质的索引。索引对应了glTF中材质(material)的信息。

  • "name": 这是网格的名称,用于标识和区分不同的网格。

综合起来,这段代码描述了一个网格,包含了网格的基本几何信息、顶点属性、索引、绘制模式和材质等信息。这些信息可以帮助渲染引擎正确地呈现网格的外观和属性。

通过上述我们知道了

let mesh = meshes[nodes[scenes[scene].nodes].children[0].mesh

primitives

"primitives"字段可以包含多个元素,每个元素描述一个网格的基本几何信息。

每个"primitives"数组中的元素包含了描述一个网格的基本几何信息,包括顶点属性、索引、绘制模式和材质等信息。这使得glTF能够描述复杂的网格结构,包括多个网格的属性和外观。

 这个信息基本也就是要绘制节点一全部信息了。

下面内容就是一个一个的去需按照对应的数据了,此节点包括顶点,法向量,贴图,绘制模式,material材质信息。

在上文中我们知道 primitives只有一个元素。所以我们只能获取primitives的0元素。如果是多个元素,此时我们需要一个一个去找对应的内容

attributes

在glTF中,"attributes"字段的值是一个包含了顶点属性索引的对象,每个属性对应一个访问器(accessor)的索引,用于获取实际的顶点数据。

"POSITION"、"NORMAL"和"TEXCOORD_0"分别代表了位置、法线和纹理坐标等顶点属性,而它们的值分别是对应访问器的索引,用于获取实际的顶点数据。

通过"attributes"字段,可以明确指定网格的顶点属性,帮助渲染引擎正确地呈现网格的外观和形状。

法线 let NORMAL = meshes[mesh].primitives[0].attributes.NORMAL

顶点 let POSITION = meshes[mesh].primitives[0].attributes.POSITION

贴图  let TEXCOORD_0 = meshes[mesh].primitives[0].attributes.TEXCOORD_0

indices

在glTF中,"indices"字段用于指定网格的索引数据。索引数据描述了如何将顶点连接起来以构成三角形或其他图元。这些索引通常用于描述网格的拓扑结构,以便渲染引擎能够正确地绘制出网格的外观。

"indices"字段的值是一个访问器(accessor)的索引,该访问器包含了描述网格索引数据的信息,包括二进制数据的存储位置、数据类型等。通过"indices"字段,可以明确指定网格的索引数据,帮助渲染引擎正确地构建出网格的拓扑结构。 

 此时我们可以创建一个顶点做因的变量

顶点索引 let indices =  accessors[meshes].primitives[0].indices

mode

"mode": 这个字段表示了绘制网格的模式,比如4表示了以三角形扇的方式绘制网格。

上述的问中mode标识要绘制的类型,下面是对应关系

/**
 * 根据传入的mode返回对应的绘制方式名称
 * @param {number} mode - 表示绘制方式的值
 * @returns {string} - 对应的绘制方式名称,如果mode无效则返回"UNKNOWN"
 */
function getDrawModeName(mode) {
    switch (mode) {
        case 0:
            return "POINTS";
        case 1:
            return "LINES";
        case 2:
            return "LINE_LOOP";
        case 3:
            return "LINE_STRIP";
        case 4:
            return "TRIANGLES";
        case 5:
            return "TRIANGLE_STRIP";
        case 6:
            return "TRIANGLE_FAN";
        default:
            return "UNKNOWN";
    }
}
material

"material": 这个字段表示了网格使用的材质的索引。索引对应了glTF中材质(material)的信息

此时此字段的数据需要去materials字段中需按照

accessors

在glTF中,"accessors"字段用于描述访问器(accessor),访问器是用于访问和解释二进制数据的对象。"accessors"字段包含了多个访问器的描述,每个访问器对应了一个包含了二进制数据的缓冲区视图(buffer view),并指定了如何解释这些二进制数据。

每个访问器包含了以下信息:

  • "bufferView": 指定了访问器使用的缓冲区视图的索引,用于获取二进制数据。
  • "byteOffset": 指定了访问器从缓冲区视图中的哪个字节开始读取数据。
  • "componentType": 指定了访问器中每个组件的数据类型,比如浮点数、整数等。
  • "count": 指定了访问器包含的元素数量。
  • "type": 指定了访问器中每个元素的类型,比如标量(scalar)、矢量(vector)、矩阵(matrix)等。
  • "max"和"min": 指定了访问器中数据的最大值和最小值,用于辅助渲染引擎进行优化和处理。

通过"accessors"字段,可以明确描述访问器的属性和数据结构,帮助渲染引擎正确地解释和处理二进制数据,从而呈现出正确的3D模型和场景

 "accessors": [
        { "bufferView": 0, "byteOffset": 0, "componentType": 5123, "count": 12636, "max": [2398], "min": [0], "type": "SCALAR" },
        {
            "bufferView": 1,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 2399,
            "max": [0.9995989799499512, 0.999580979347229, 0.9984359741210938],
            "min": [-0.9990839958190918, -1, -0.9998319745063782],
            "type": "VEC3"
        },
        {
            "bufferView": 1,
            "byteOffset": 28788,
            "componentType": 5126,
            "count": 2399,
            "max": [96.17990112304688, 163.97000122070312, 53.92519760131836],
            "min": [-69.29850006103516, 9.929369926452637, -61.32819747924805],
            "type": "VEC3"
        },
        {
            "bufferView": 2,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 2399,
            "max": [0.9833459854125975, 0.9800369739532472],
            "min": [0.026409000158309937, 0.01996302604675293],
            "type": "VEC2"
        }
    ],

上面我们创建了4个变量

法线 let NORMAL = meshes[mesh].primitives[0].attributes.NORMAL;

顶点 let POSITION = meshes[mesh].primitives[0].attributes.POSITION;

贴图  let TEXCOORD_0 = meshes[mesh].primitives[0].attributes.TEXCOORD_0;

顶点索引 let indices =  meshes[mesh].primitives[0].indices;

上述的4个表变量都是指向accessors字段中的值。此时直接使用数组的形势去获取就好

法线 let NORMAL2 = accessors[NORMAL];

顶点 let POSITION2 = accessors[POSITION];

贴图  let TEXCOORD_02 = accessors[TEXCOORD_0];

顶点索引 let indices2 =  accessors[indices];

下文中二进制数据指得是buffers字段中得uri地址内容,这个一个二进制得.bin文件,里面储存了,上面4个变量得具体数值内容

"buffers": [{ "byteLength": 102040, "uri": "https://gw.alipayobjects.com/os/OasisHub/267000040/75/Duck0.bin" }]

这段代码描述了一个访问器(accessor),其中包含了对应于三维向量(VEC3)的顶点属性数据。各个字段的含义:

  • "bufferView": 指定了访问器使用的缓冲区视图的索引,用于获取二进制数据。
  • "byteOffset": 指定了访问器从缓冲区视图中的哪个字节开始读取数据。
  • "componentType": 指定了访问器中每个组件的数据类型,这里的取值5126代表浮点数类型(FLOAT)。
  • "count": 指定了访问器包含的元素数量,即顶点的数量。
  • "max": 指定了访问器中数据的最大值,这里是一个包含三个浮点数的数组,表示每个分量的最大值。
  • "min": 指定了访问器中数据的最小值,同样是一个包含三个浮点数的数组,表示每个分量的最小值。
  • "type": 指定了访问器中每个元素的类型,这里是"VEC3",表示每个元素是一个三维向量。

除了上述的取值之外,"componentType"字段还可以取其他值,比如5120表示字节类型(BYTE)、5121表示无符号字节类型(UNSIGNED_BYTE)、5122表示短整型(SHORT)等。而"count"、"max"和"min"字段的取值根据实际数据而定,用于描述访问器中数据的数量和范围。

 bufferView

"bufferView": 指定了访问器使用的缓冲区视图的索引,用于获取二进制数据。

此字段应该对应bufferViews的字段中去获取对用的参数内容,此时的获取的就是具体的数值内容了

byteOffset 

"byteOffset": 指定了访问器从缓冲区视图中的哪个字节开始读取数据。

accessors与bufferViews数组中都有byteOffset字段区别如下

"byteOffset"字段在"accessors"和"bufferViews"中的含义略有不同。

在"bufferViews"中,"byteOffset"表示从缓冲区的起始位置(即整个缓冲区的起始位置)开始的偏移量,用于定位该视图的起始位置。这个偏移量是以字节为单位的,指示了该视图在缓冲区中的起始位置。

而在"accessors"中,"byteOffset"表示从关联的缓冲区视图的起始位置开始的偏移量,用于定位访问器的起始位置。同样,这个偏移量也是以字节为单位的。

count

"count": 指定了访问器包含的元素数量,即顶点的数量。

componentType

"componentType": 指定了访问器中每个组件的数据类型,这里的取值5126代表浮点数类型(FLOAT)。

下面是取值内容

// 根据componentType获取字节大小
function getSizeOfComponentType(componentType) {
    switch (componentType) {
        case 5120: // BYTE
        case 5121: // UNSIGNED_BYTE
            return 1;
        case 5122: // SHORT
        case 5123: // UNSIGNED_SHORT
            return 2;
        case 5126: // FLOAT
            return 4;
        default:
            return 0;
    }
}

type 

"type": 指定了访问器中每个元素的类型,这里是"VEC3",表示每个元素是一个三维向量。

下面是的各自取值内容

j
// 根据type获取组件数量
function getNumberOfComponents(type) {
    switch (type) {
        case "SCALAR":
            return 1;
        case "VEC2":
            return 2;
        case "VEC3":
            return 3;
        case "VEC4":
            return 4;
        default:
            return 0;
    }
}

materials

"materials": [{ "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicFactor": 0 }, "emissiveFactor": [0, 0, 0], "name": "blinn3-fx" }],

这段代码描述了一个材质(material),其中包含了PBR(Physically Based Rendering)金属粗糙度工作流(pbrMetallicRoughness)的定义。解释一下各个字段的含义:

- "pbrMetallicRoughness": 这个字段包含了PBR金属粗糙度工作流的定义,其中包括了基础颜色纹理(baseColorTexture)和金属度因子(metallicFactor)。
  - "baseColorTexture": 这里的"index"字段指定了基础颜色纹理的索引,用于获取实际的纹理数据。
  - "metallicFactor": 这个字段指定了金属度因子,用于控制材质的金属度属性。

- "emissiveFactor": 这个字段指定了发光因子,是一个包含三个分量的数组,表示材质的发光属性。

- "name": 这个字段指定了材质的名称,这里是"blinn3-fx"。

通过这些字段的定义,可以描述材质的外观特性,包括基础颜色、金属度、粗糙度和发光属性等。这些信息对于渲染引擎来说非常重要,可以帮助正确地呈现出材质的外观和特性。

希望这能够帮助您理解这段代码中材质的定义。

 baseColorTexture字段指定了textures字段中获取参数,获取textures数组中的index下角标的参数

textures

    "textures": [{ "sampler": 0, "source": 0 }],

这段代码描述了一个纹理(texture),其中包含了对应的采样器(sampler)和图像源(source)的索引。解释一下各个字段的含义:

  • "sampler": 这个字段指定了纹理的采样器索引,用于描述纹理的采样参数,比如过滤方式、寻址模式等。
  • "source": 这个字段指定了纹理的图像源索引,用于获取实际的图像数据。

通过这些字段的定义,可以将纹理与采样器和图像源进行关联,从而在渲染时正确地应用纹理,并根据采样器的参数进行采样。

 想必大家也基本理解怎么去寻找对应的字段了

let sampler = textures[0].sampler  去samplers去寻找

let source =  textures[0].source 去images

images

"images": [{ "uri": "https://gw.alipayobjects.com/zos/OasisHub/267000040/4580/DuckCM.png" }],

参数主要是存放了图片的相关素材来源

samplers

"samplers": [{ "magFilter": 9729, "minFilter": 9986, "wrapS": 10497, "wrapT": 10497 }],

在glTF中,"samplers"字段用于定义纹理采样器(sampler),它包含了纹理采样时使用的参数和选项。这些参数包括纹理的过滤方式(如最近点采样、线性采样等)和寻址模式(如重复、边缘拉伸等)等。

具体来说,"samplers"字段中的每个条目描述了一个纹理采样器的设置,通常包括以下属性:

- "magFilter": 定义了纹理放大时的过滤方式。
- "minFilter": 定义了纹理缩小时的过滤方式。
- "wrapS": 定义了纹理在S轴上的寻址模式。
- "wrapT": 定义了纹理在T轴上的寻址模式。
- "name": 可选的,用于指定采样器的名称。

通过"samplers"字段中的设置,可以为每个纹理指定不同的采样参数,以满足不同的渲染需求和效果。 

 magFilter与minFilter

"magFilter": 定义了纹理放大时的过滤方式。

"minFilter": 定义了纹理缩小时的过滤方式。

let magFilter = parseMinFilter(params.magFilter, "TEXTURE_MAG_FILTER");

let magFilter = parseMinFilter(params.magFilter, "TEXTURE_MAG_FILTER"); 

/**
 * 解析minFilter
 * @param {number} minFilter - minFilter的取值
 * @param {string} key - 键名
 * @returns {Object} - 包含解析结果的对象
 */
function parseMinFilter(minFilter, key) {
    switch (minFilter) {
        case 9728:
            return {
                key: key,
                value: "NEAREST",
            };
        case 9729:
            return {
                key: key,
                value: "LINEAR",
            };
        case 9984:
            return {
                key: key,
                value: "NEAREST_MIPMAP_NEAREST",
            };
        case 9985:
            return {
                key: key,
                value: "LINEAR_MIPMAP_NEAREST",
            };
        case 9986:
            return {
                key: key,
                value: "NEAREST_MIPMAP_LINEAR",
            };
        case 9987:
            return {
                key: key,
                value: "LINEAR_MIPMAP_LINEAR",
            };
        default:
            return {
                key: key,
                value: "UNKNOWN",
            };
    }
}

wrapS与wrapT

 "wrapS": 定义了纹理在S轴上的寻址模式。
 "wrapT": 定义了纹理在T轴上的寻址模式。

    let wrapS = parseWrap(params.wrapS, "S");

    let wrapT = parseWrap(params.wrapT, "T");

/**
 * 解析wrapS或wrapT
 * @param {number} wrapValue - wrapS或wrapT的取值
 * @param {string} axis - 轴向('S'或'T')
 * @returns {Object} - 包含解析结果的对象
 */
function parseWrap(wrapValue, axis) {
    switch (wrapValue) {
        case 33071:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "CLAMP_TO_EDGE",
            };
        case 33648:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "MIRRORED_REPEAT",
            };
        case 10497:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "REPEAT",
            };
        default:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "UNKNOWN",
            };
    }
}

进行解析

下载器

首先我跟需要一个方法去加载,上述的内容,需要一个地址,然后同时把.bin文件下载

export async function parseGltfAndDownloadBin(gltfUrl) {
    try {
        // 加载glTF文件
        const response = await fetch(gltfUrl);
        const gltfData = await response.json();

        // 获取.bin文件的URL
        const list = gltfData.buffers;
        for (let i = 0; i < list.length; i++) {
            let item = list[i];
            let binUrl = item.uri;
            item.data = await getBinFile(binUrl);
        }

        return gltfData;
    } catch (error) {
        throw new Error("Error loading or parsing glTF: " + error);
    }
}

async function getBinFile(binUrl) {
    try {
        // 加载.bin文件
        const response = await fetch(binUrl);
        const binBuffer = await response.arrayBuffer();
        return binBuffer;
    } catch (error) {
        throw new Error("Error loading .bin file: " + error);
    }
}

使用实例

let modelUrl = "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf";
// let modelUrl2 = "https://gw.alipayobjects.com/os/OasisHub/19748279-7b9b-4c17-abdf-2c84f93c54c8/oasis-file/1670226408346/low_poly_scene_forest_waterfall.gltf";
// let cubeMode = "https://gw.alipayobjects.com/os/bmw-prod/8cc524dd-2481-438d-8374-3c933adea3b6.gltf";
// 使用示例
const gltfData = await parseGltfAndDownloadBin(modelUrl);

解析

目前仅仅对顶点,法向量,贴图坐标进行的数据处理,会从.bin文件中获取自己对应的数据

加上了材质。

至此这个gltf解析完毕。


/**
 * 获取场景信息
 * @param {object} gltf - glTF数据对象
 * @param {number} sceneId - 场景ID
 * @returns {object} - 场景信息对象
 */
export function getSceneInfo(gltf, sceneId) {
    const scene = gltf.scenes[sceneId];
    const sceneInfo = { id: sceneId, nodes: [] };

    scene.nodes.forEach((nodeId) => {
        const nodeInfo = getNodeInfo(gltf, nodeId);
        sceneInfo.nodes.push(nodeInfo);
    });

    return sceneInfo;
}
/**
 * 获取节点信息
 * @param {object} gltf - glTF数据对象
 * @param {number} nodeId - 节点ID
 * @returns {object} - 节点信息对象
 */
function getNodeInfo(gltf, nodeId) {
    const node = gltf.nodes[nodeId];
    const nodeInfo = { id: nodeId };

    if (node.children) {
        nodeInfo.children = node.children.map((childId) => getNodeInfo(gltf, childId));
    }

    if (node.mesh !== undefined) {
        let meshes = gltf.meshes[node.mesh];
        nodeInfo.mesh = getMeshInfo(gltf, meshes);
    }

    if (node.matrix !== undefined) {
        nodeInfo.matrix = node.matrix;
    }
    if (node.name !== undefined) {
        nodeInfo.name = node.name;
    }

    return nodeInfo;
}
/**
 * 处理mesh信息
 * @param {object} gltf - glTF数据对象
 * @param {object} mesh - mesh对象
 * @returns {object} - mesh信息对象
 */
function getMeshInfo(gltf, mesh) {
    // weights:用于蒙皮动画中的权重信息。
    // targets:用于蒙皮动画中的目标形状信息。
    // extensions:用于包含扩展信息的字段。
    // extras:用于包含自定义额外信息的字段。
    // 上述的4个属性与primitives是同级
    let meshInfo = { primitives: {}, weights: {}, targets: {}, extensions: {}, extras: {} };
    meshInfo.primitives = getPrimitiveInfo(gltf, mesh.primitives);
    meshInfo.weights = mesh.weights;
    meshInfo.targets = mesh.targets;
    meshInfo.extensions = mesh.extensions;
    meshInfo.extras = mesh.extras;
    // 处理primitives
    return meshInfo;
}
/**
 * 处理primitive信息
 * @param {object} gltf - glTF数据对象
 * @param {Array} primitives - primitive数组
 * @returns {Array} - 包含primitive信息对象的数组
 */
function getPrimitiveInfo2(gltf, primitive) {
    let primitiveInfo = { attributes: {} };
    primitiveInfo.attributes = getAttributesInfo(gltf, primitive);
    primitiveInfo.indices = getAttributeItenInfo(gltf, primitive.indices);
    primitiveInfo.material = getMaterialInfo(gltf, primitive.material);
    primitiveInfo.mode = getDrawModeName(primitive.mode);
    return primitiveInfo;
}
function getPrimitiveInfo(gltf, primitive) {
    let primitiveInfo = [];
    primitive.forEach((item, index) => {
        primitiveInfo.push(getPrimitiveInfo2(gltf, item));
    });
    return primitiveInfo;
}
/**
 * 处理单个primitive信息
 * @param {object} gltf - glTF数据对象
 * @param {object} primitive - 单个primitive对象
 * @returns {object} - primitive信息对象
 */
function getAttributesInfo(gltf, primitive) {
    let attributesInfo = {};
    for (let attributeName in primitive.attributes) {
        let accessorId = primitive.attributes[attributeName];
        attributesInfo[attributeName] = getAttributeItenInfo(gltf, accessorId);
    }
    return attributesInfo;
}
/**
 * 处理attributes信息
 * @param {object} gltf - glTF数据对象
 * @param {object} primitive - primitive对象
 * @returns {object} - 包含attributes信息的对象
 */
function getAttributeItenInfo(gltf, accessorId) {
    let accessor = gltf.accessors[accessorId];
    let bufferView = gltf.bufferViews[accessor.bufferView];
    let buffer = gltf.buffers[bufferView.buffer];
    let bufferData = new Uint8Array(buffer.data);
    let byteOffset = accessor.byteOffset + bufferView.byteOffset;
    let size = getComponentByteLength(accessor.type);
    let byteLength = accessor.count * size;
    let arrayBuffer = bufferData.slice(byteOffset, byteOffset + byteLength);
    let array = new Float32Array(arrayBuffer);
    let array2 = new DataView(buffer.data, byteOffset, byteLength);
    let target = getNumberOfTarget(accessor.target);
    return {
        value: array,
        value2: array2,
        size,
        count: accessor.count,
        byteLength,
        target,
    };
}
//处理material信息
/**
 * 以下是上述字段的类型以及它们各自包含的参数:

    pbrMetallicRoughness(基于物理的渲染属性):

    baseColorFactor: 数组 [number, number, number, number]
    baseColorTexture: 对象 { index: number }
    metallicFactor: 数字
    roughnessFactor: 数字
    normalTexture(法线纹理属性):

    index: 数字
    scale: 数字
    occlusionTexture(遮挡纹理属性):

    index: 数字
    strength: 数字
    emissiveTexture(自发光纹理属性):

    index: 数字
    emissiveFactor: 数组 [number, number, number]

    alphaMode: 字符串 ("OPAQUE", "MASK", "BLEND")

    alphaCutoff: 数字

    doubleSided: 布尔值

    这些参数描述了材质的各种属性,包括颜色、纹理、金属度、粗糙度、法线、遮挡、自发光、透明度等。通过设置这些参数,可以实现丰富多彩的材质效果。
 * @param {*} gltf 
 * @param {*} materialId 
 * @returns 
 */
function getMaterialInfo(gltf, materialId) {
    const material = gltf.materials[materialId];
    const baseColorTextureIndex = material.pbrMetallicRoughness.baseColorTexture.index;
    const texture = gltf.textures[baseColorTextureIndex];
    const sampler = gltf.samplers[texture.sampler];
    const image = gltf.images[texture.source];


    const minFilter = parseMinFilter(sampler.minFilter, "TEXTURE_MIN_FILTER");
    const magFilter = parseMinFilter(sampler.magFilter, "TEXTURE_MAG_FILTER");
    const wrapS = parseWrap(sampler.wrapS, "S");
    const wrapT = parseWrap(sampler.wrapT, "T"); 

    return {
        ...material,
        id: materialId,
        images: {
            uri: image.uri,
        },
        samplers: {
            magFilter,
            minFilter,
            wrapS,
            wrapT,
        },
    };
}

/**
 * 处理单个attribute信息
 * @param {object} gltf - glTF数据对象
 * @param {number} accessorId - accessor ID
 * @returns {object} - attribute信息对象
 */
function getComponentByteLength(type) {
    switch (type) {
        case "SCALAR":
            return 1;
        case "VEC2":
            return 2;
        case "VEC3":
            return 3;
        case "VEC4":
            return 4;
        default:
            return 0;
    }
}
// 辅助函数:根据type获取组件数量
function getNumberOfTarget(type) {
    switch (type) {
        case 34962:
            return "ARRAY_BUFFER";
        case 34963:
            return "ELEMENT_ARRAY_BUFFER";
        default:
            return 0;
    }
}
/**
 * 根据传入的mode返回对应的绘制方式名称
 * @param {number} mode - 表示绘制方式的值
 * @returns {string} - 对应的绘制方式名称,如果mode无效则返回"UNKNOWN"
 */
function getDrawModeName(mode) {
    switch (mode) {
        case 0:
            return "POINTS";
        case 1:
            return "LINES";
        case 2:
            return "LINE_LOOP";
        case 3:
            return "LINE_STRIP";
        case 4:
            return "TRIANGLES";
        case 5:
            return "TRIANGLE_STRIP";
        case 6:
            return "TRIANGLE_FAN";
        default:
            return "UNKNOWN";
    }
}
/**
 * 解析minFilter
 * @param {number} minFilter - minFilter的取值
 * @param {string} key - 键名
 * @returns {Object} - 包含解析结果的对象
 */
function parseMinFilter(minFilter, key) {
    switch (minFilter) {
        case 9728:
            return {
                key: key,
                value: "NEAREST",
            };
        case 9729:
            return {
                key: key,
                value: "LINEAR",
            };
        case 9984:
            return {
                key: key,
                value: "NEAREST_MIPMAP_NEAREST",
            };
        case 9985:
            return {
                key: key,
                value: "LINEAR_MIPMAP_NEAREST",
            };
        case 9986:
            return {
                key: key,
                value: "NEAREST_MIPMAP_LINEAR",
            };
        case 9987:
            return {
                key: key,
                value: "LINEAR_MIPMAP_LINEAR",
            };
        default:
            return {
                key: key,
                value: "UNKNOWN",
            };
    }
}

/**
 * 解析wrapS或wrapT
 * @param {number} wrapValue - wrapS或wrapT的取值
 * @param {string} axis - 轴向('S'或'T')
 * @returns {Object} - 包含解析结果的对象
 */
function parseWrap(wrapValue, axis) {
    switch (wrapValue) {
        case 33071:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "CLAMP_TO_EDGE",
            };
        case 33648:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "MIRRORED_REPEAT",
            };
        case 10497:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "REPEAT",
            };
        default:
            return {
                key: `TEXTURE_WRAP_${axis}`,
                value: "UNKNOWN",
            };
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1399445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

好物周刊#37:元气桌面

https://github.com/cunyu1943/JavaPark https://yuque.com/cunyu1943 村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. MallChat 一个既能购物又能聊天的电商系统。以互联网企业级开发规范的…

canvas能压缩图片?

之前写过一篇使用命令行工具压缩图片的博文&#xff1a;使用yx-tiny命令行工具进行图片压缩&#xff0c;大家感兴趣可以去瞅一眼。 这篇简单说一下使用canvas压缩图片 其实思路很简单&#xff0c;我们选择了图片之后&#xff0c;会获取到对应的文件流对象&#xff0c;然后我们…

计算机组成原理 第一弹

ps&#xff1a;本文章的图片来源都是来自于湖科大教书匠高老师的视频&#xff0c;声明&#xff1a;仅供自己复习&#xff0c;里面加上了自己的理解 这里附上视频链接地址&#xff1a;1-2 计算机的发展_哔哩哔哩_bilibili ​​ 目录 &#x1f680;计算机系统 &#x1f680;计…

中小企业如何快速融资-----股权融资的四种方式(上)

’在企业融资的多种手段中&#xff0c;股权质押融资、股权交易增值融资、股权增资扩股融资和股权的私募融资&#xff0c;逐渐成为中小企业利用股权实现融资的有效方式。随着市场体系和监管制度的完善&#xff0c;产权市场为投融资者搭建的交易平台日益成熟&#xff0c;越来越多…

【linux驱动】详细剖析第一个hello word驱动程序

文章目录 驱动程序的框架驱动程序的使用示例 驱动程序的框架 Linux 驱动的基本框架主要由模块加载函数&#xff0c;模块卸载函数&#xff0c;模块许可证声明&#xff0c;模块参数&#xff0c;模块导出符号&#xff0c;模块作者信息等几部分组成&#xff0c;其中模块参数&#…

钉钉副总裁李智勇:AI超级助理,提升大模型时代生产力

微软比尔盖茨此前曾预言:“五年内&#xff0c;每个人都将拥有AI私人助理Agent&#xff0c;Agent将颠覆软件行业 。” 近日以来&#xff0c;在GPT store正式上线点爆情绪之后&#xff0c;无论国内外&#xff0c;Agent都是创业圈里炙手可热的新贵。一场关于Agent创业比拼大赛&am…

探索JAVA神秘运行机制:揭秘JVM内存区域

目录 1. 前文回顾 2.内存区域的划分 2.1 存放类的方法区 2.2 程序计数器 2.3 Java虚拟机栈 2.4 Java堆内存 2.5 其他内存区域 3. 核心内存区域运行流程 4. 总结 1. 前文回顾 上一篇我们一起探索了Java的整体运行流程&#xff0c;类加载器以及类的加载机制&#xff0…

手把手教你使用 VS Code 运行和调试 Python 程序

本文以 Ubuntu 系统为例&#xff0c;介绍如何在 VS Code 上配置 Python 的编程环境&#xff0c;并把 Python 程序运行、调试起来。由于 Python 是解释型语言&#xff0c;并且 VS Code 中提供了内置的调试器可用于调试 Python 代码&#xff0c;因此配置和操作流程比调试 C/C 代码…

【 Qt 快速上手】-①- Qt 背景介绍与发展前景

文章目录 1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt的应用场景1.7 Qt的成功案例1.8 Qt的发展前景及就业分析行业发展方向就业方面的发展前景 1.1 什么是 Qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供了建立…

8 python快速上手

总结 总结1. 代码规范1.1 名称1.2 注释1.3 todo1.4 条件嵌套1.5 简单逻辑先处理1.6 循环1.7 变量和值 2.知识补充2.1 pass2.2 is 比较2.3 位运算 3.阶段总结 各位小伙伴想要博客相关资料的话关注公众号&#xff1a;chuanyeTry即可领取相关资料&#xff01; 总结 1. 代码规范 …

线性规划案例分享

今天想写一个最优传输的简单实现&#xff0c;结果学歪了&#xff0c;学到线性规划去了&#xff0c;这里我发现了一个宝藏网站 虽然是讲计量经济的&#xff0c;但是里面提供的公式和代码我很喜欢&#xff0c;有时间可以好好读一下 https://python.quantecon.org/lp_intro.html …

Civil 3D安装教程,免费使用,带安装包和工具,一分钟轻松搞的安装

前言 Civil 3D是一款面向基础设施行业的建筑信息模型&#xff08;BIM&#xff09;解决方案。它为基础设施行业的各类技术人员提供了强大的设计、分析以及文档编制功能&#xff0c;广泛适用于勘察测绘、岩土工程、交通运输、水利水电、市政给排水、城市规划和总图设计等众多领域…

什么是比特币?

比特币 比特币 &#xff08;英语&#xff1a;Bitcoin&#xff0c;缩写&#xff1a;BTC &#xff09;是一种基于 去中心化&#xff0c;采用 点对点网络&#xff0c;开放源代码&#xff0c;以 区块链 作为底层技术的 加密货币。比特币由 中本聪&#xff08;Satoshi Nakamoto&…

vscode配置web开发环境(WampServer)

这里直接去下载了集成的服务器组件wampserver&#xff0c;集成了php&#xff0c;MySQL&#xff0c;Apache 可能会出现安装问题&#xff0c;这里说只有图上这些VC包都安装了才能继续安装&#xff0c;进入报错里提供的链接 在页面内搜索相关信息 github上不去可以去镜像站 下载…

机器视觉技术与应用实战(平均、高斯、水平prewitt、垂直prewitt、水平Sobel、垂直Sobel、拉普拉斯算子、锐化、中值滤波)

扯一点题外话&#xff0c;这一个月经历了太多&#xff0c;接连感染了甲流、乙流&#xff0c;人都快烧没了&#xff0c;乙流最为严重&#xff0c;烧了一个星期的38-39度&#xff0c;咳嗽咳到虚脱。还是需要保护好身体&#xff0c;感觉身体扛不住几次连续发烧&#xff01;&#x…

Redis 持久化之 RDB AOF

1、简介 Redis 是一个基于内存的 key-value 类型的 Nosql 数据库&#xff0c;经常用来做缓存操作&#xff0c;但是一旦Redis 宕机&#xff0c;重启之后数据会丢失&#xff0c;因此&#xff0c;需要将内存数据进行持久化&#xff0c;保证服务重启后数据能够恢复之前的状态。Redi…

淘金城镇新人赚钱攻略(定制开发·源码定制智创开发)

​ 在淘金城镇中&#xff0c;玩家可以通过完成任务、升级角色、参与活动等方式获得丰厚的奖励和经验值&#xff0c;这不仅可以提升角色的能力&#xff0c; 还可以让玩家在游戏中获得更多的乐趣。最重要的是&#xff0c;淘金城镇的玩法非常精致&#xff0c;玩家可以通过游戏中…

C++——vector的使用及其模拟实现

vector的使用及其模拟实现 文章目录 vector的使用及其模拟实现1. vector的使用1.1 构造函数construct1.2 获取当前存储的数据个数size()和最大容量capacity()1.3 访问1.3.1 operator[]运算符重载1.3.2 迭代器访问1.3.3 范围for 1.4 容量相关reserve()和resize()1.5 增&#xff…

软件测试的工作描述

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【算法练习Day50】下一个更大元素II接雨水

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 下一个更大元素II接雨水单调…