阅读 1024

OpenGL 3D渲染技术:glTF基础知识

大家好,我是程序员kenney,今天给大家介绍glTF的基础知识。

glTF是什么?

它是GL Transmission Format的缩写,是khronos推出的一种描述3D模型的格式,目标是使其成为一种通用的3D模型格式。它的官方github是github.com/KhronosGrou…

我们知道3D模型有各种各样的格式,如objFBXdea等,格式的多种多样带来了一个问题的就是各种软件、渲染引擎、游戏引擎之间的3D模型的不通用,例如在3D设计软件中设计好一个3D模型之后,想把它放到一个渲染引擎中渲染,如果没有一个通用的格式,渲染引擎可能也有一套自己的格式,这样就要做格式转换,而有些格式是不开源的,需要官方提供转换工具,非常麻烦。

而如果有一个通用的格式大家都支持,这就好办了,就像图片中的jpeg``一样,通常的图片编辑软件都能导出jpeg,通常的图片查看软件也都能打开,这就非常方便,而glTF就是要成为3D模型界的"jpeg

glTF是一用json格式编写的,目前现在推出了2.0版本,本篇文章也是讲解2.0版本,glTF格式中可以描述的信息相当丰富,本篇文章先给大家介绍一些基础的字段:

  • 场景和节点描述:scenesnodes
  • 网格描述:meshes
  • 数据描述:buffersbufferViewsaccessors
  • 材质描述:materials
  • 纹理、图片和采样器描述:texturesimagessamplers
  • 摄像机描述:cameras

总览

我们先来从一张图总体上看一下glTF文件的结构,每一种类型的字段,基本是都是用数组来组织,在每个字段中,又可以通过索引去引用其它字段。

它类似一个树状,但又不是严格的树。最顶层的是场景,场景中包含了一些节点,节点中又可以套子节点,节点中可以有mesh、摄像机,如果是mesh,还可以为它指定材质及mesh网格数据,材质中可以指定纯色及图片纹理及其采样配置,还可以描述蒙皮及动画。

场景和节点

场景和节点分别用scenesnodes字段描述,场景就是代表要渲染的一些东西的集合,场景里面包含一些节点,每个节点可以认为是一个物体(也可以是摄像机),节点也可以是空的,节点中可以继续挂子结点。

来看上面这个例子,scene字段指定了使用第0个场景,一般来说,同一时刻只会渲染一个场景。scenes字段是一个场景数组,描述所有场景,这个例子中场景只有一个,它里面的nodes字段描述这个场景中有哪些节点,这里是用索引为0、1、2的节点,它就对应了下面nodes数组中第0、1、2个节。nodes字段描述了所有节点,节点里可以用children描述子节点,子节点的描述方式也是索引号。这样,通过上图左中的这段json,我们就描述了如上图右中的那样一个场景结构。

可以为每个结点指定它的变换参数,这个变换参数是它相对于父节点的变换,可以用变换矩阵描述,也可以分别指定平移、旋转和缩放。还可以指定它的mesh及摄像机,mesh就描述了这个节点是什么物体,而camera会影响这个节点被观察的样子。

网格描述

有了节点之后,我们要描述节点的东西是什么,一个东西首先要有形状,比如一个立方体,或者一个圆柱,这就需要meshs来描述:

meshs同样也是一个数组,每个mesh中由primitives描述图元,其中的mode是图元的种类,也就是点、线或者三角形,indices表示accessors中的索引,后面会讲到。attributes描述的是attribute的结构,里面可以有顶点坐标、纹理坐标、法向量等,同样也是以索引的方式引用accessors。最后的material表示这个mesh的材质,也是用索引来引用。

数据描述

glTF中用buffersbufferViewsaccessors来描述数据:

buffers是数据块数组,每个buffer由长度和路径描述,bufferViews同样也是数组,其中每个bufferView表示一个buffer的视图。这个怎么理解呢?buffer是一堆纯数据,它本身是不描述这堆数据用来做什么的,而bufferView就可以理解成如何去看待这堆数据。

其中buffer表示它对应的buffers中的索引。

target描述了它是用来干什么的,比如上图中的34963就对应了GL_ELEMENT_ARRAY_BUFFER,这个值是和OpenGL库里定义的值是一致的,这样使用起来就相当友好,glBindBuffer()可以直接传这里解析出来的target值而不用做映射转换。

byteOffset描述的是这块buffer的偏移字节,byteLength是字节长度,bytteStride描述的是一个数据的开始距离下一个数据开始的跨度,如果相同语义的数据不是连续存在放的,bytteStride就会比一份数据的长度大,因为它要跨过其它类型的数据。

再往下是accessors,它同样也是一个数组,其中每个accessor描述了对应bufferView中的数据以什么样的方式进行访问,在上图上,偏移了4个字段,以VEC2的方式取数据,即二维向量,向量每个成份的类型componentType是5126,它对应的是GL_FLOATcount表示取数据的个数。

材质描述

materials中每个元素是一个material

例如上图中的例子通过金属度-粗糙度模型来描述材质,其中有一些材质纹理图,比如基础纹理图、法向图、遮挡纹理图等,每个纹理图通过index来对应纹理图数组中的索引,纹理坐标也是类似的。

纹理、图片和采样器描述

纹理、图片和采样器分别通过texturesimagessamplers描述,看下面的例子:

textures中每个元素通过source索引到images数组中,从而找到对应的纹理数据,images数组中的元素可以以图片路径的方式提供图片,也可以用bufferView的方式,bufferView最终又对应到一个数据块上。samplers则描述了采样器的配置,大家一看里面的字段就会很熟悉,同样的,它也是直接对应了OpenGL库里定义的值,解析出来直接使用,非常方便。

摄像机描述

摄像机用cameras描述:

熟悉OpenGL的同学一眼就能看出里面定义了一个透视投影相机和正交投影相机,以及视角、近/远平面等参数,这个比较简单,没有太多好说的。

完整例子

我们来看一下完整的例子,glTF官方提供了很多sample modelsgithub.com/KhronosGrou…

我选取了一个比较简单的立方体,大家看完文章可以尝试阅读一下它的glTF文件,看看是否能理解其中的描述:

{
   "accessors" : [
      {
         "bufferView" : 0,
         "byteOffset" : 0,
         "componentType" : 5123,
         "count" : 36,
         "max" : [
            35
         ],
         "min" : [
            0
         ],
         "type" : "SCALAR"
      },
      {
         "bufferView" : 1,
         "byteOffset" : 0,
         "componentType" : 5126,
         "count" : 36,
         "max" : [
            1.000000,
            1.000000,
            1.000001
         ],
         "min" : [
            -1.000000,
            -1.000000,
            -1.000000
         ],
         "type" : "VEC3"
      },
      {
         "bufferView" : 2,
         "byteOffset" : 0,
         "componentType" : 5126,
         "count" : 36,
         "max" : [
            1.000000,
            1.000000,
            1.000000
         ],
         "min" : [
            -1.000000,
            -1.000000,
            -1.000000
         ],
         "type" : "VEC3"
      },
      {
         "bufferView" : 3,
         "byteOffset" : 0,
         "componentType" : 5126,
         "count" : 36,
         "max" : [
            1.000000,
            -0.000000,
            -0.000000,
            1.000000
         ],
         "min" : [
            0.000000,
            -0.000000,
            -1.000000,
            -1.000000
         ],
         "type" : "VEC4"
      },
      {
         "bufferView" : 4,
         "byteOffset" : 0,
         "componentType" : 5126,
         "count" : 36,
         "max" : [
            1.000000,
            1.000000
         ],
         "min" : [
            -1.000000,
            -1.000000
         ],
         "type" : "VEC2"
      }
   ],
   "asset" : {
      "generator" : "VKTS glTF 2.0 exporter",
      "version" : "2.0"
   },
   "bufferViews" : [
      {
         "buffer" : 0,
         "byteLength" : 72,
         "byteOffset" : 0,
         "target" : 34963
      },
      {
         "buffer" : 0,
         "byteLength" : 432,
         "byteOffset" : 72,
         "target" : 34962
      },
      {
         "buffer" : 0,
         "byteLength" : 432,
         "byteOffset" : 504,
         "target" : 34962
      },
      {
         "buffer" : 0,
         "byteLength" : 576,
         "byteOffset" : 936,
         "target" : 34962
      },
      {
         "buffer" : 0,
         "byteLength" : 288,
         "byteOffset" : 1512,
         "target" : 34962
      }
   ],
   "buffers" : [
      {
         "byteLength" : 1800,
         "uri" : "Cube.bin"
      }
   ],
   "images" : [
      {
         "uri" : "Cube_BaseColor.png"
      },
      {
         "uri" : "Cube_MetallicRoughness.png"
      }
   ],
   "materials" : [
      {
         "name" : "Cube",
         "pbrMetallicRoughness" : {
            "baseColorTexture" : {
               "index" : 0
            },
            "metallicRoughnessTexture" : {
               "index" : 1
            }
         }
      }
   ],
   "meshes" : [
      {
         "name" : "Cube",
         "primitives" : [
            {
               "attributes" : {
                  "NORMAL" : 2,
                  "POSITION" : 1,
                  "TANGENT" : 3,
                  "TEXCOORD_0" : 4
               },
               "indices" : 0,
               "material" : 0,
               "mode" : 4
            }
         ]
      }
   ],
   "nodes" : [
      {
         "mesh" : 0,
         "name" : "Cube"
      }
   ],
   "samplers" : [
      {}
   ],
   "scene" : 0,
   "scenes" : [
      {
         "nodes" : [
            0
         ]
      }
   ],
   "textures" : [
      {
         "sampler" : 0,
         "source" : 0
      },
      {
         "sampler" : 0,
         "source" : 1
      }
   ]
}

复制代码

glTF有很多方法能方便地查看效果,可以在线看,比如gltf-viewer.donmccurdy.com,上面那个glTF例子的渲染效果是这样的:

谢谢阅读!如有疑问,欢迎在评论区交流~

欢迎关注我的github:www.github.com/kenneycode

文章分类
Android
文章标签