Cesium 源码解析 Model(三)

  • Post author:
  • Post category:其他


Cesium中处理完成gltf的数据完整性检查,默认数据的完善。根据gltf1.0扩展KHR_materials_common(非pbr材质)进行glsl字符串的拼装、attributelocation、uniform等信息的收集处理,并将这些信息添加到gltf.extensions.KHR_techniques_webgl.扩展中,并建立材质中material.extensions.KHR_techniques_webgl.technique与对应gltf.extensions.KHR_techniques_webgl.techniques之间的映射关系。、

下面时material.extensions中的样例:

"materials":[
    {
        "extensions": {
            "KHR_techniques_webgl":{
                "technique": 0,
                "values": "KHR_materials_common中的values相关的数据"
            },
    		"KHR_materials_common": {
				"technique":"BLINN",
				"values":{
					"ambient":[1.0,1.0,1.0,1.0],
					"emission":[1.0,1.0,1.0,1.0],
					"transparency":1,
					"transparent": false,
					"doubleSided": false,
					"diffuse":[1.0,1.0,1.0,1.0],
					"specular":[1.0,1.0,1.0,1.0],
					"shininess": 1.0
				}
			},
        }
    }
]

下面时gltf.extensions.KHR_techniques_webgl.techniques中的样例:

extensions":{
    "KHR_techniques_webgl":{
			"programs": [
				{
					"fragmentShader": 0,
					"vertexShader": 1
				}
			],
			"shaders": [
				{
					"type": 35632,
					"uri": "duck0FS.glsl"
				},
				{
					"type": 35633,
					"uri": "duck0VS.glsl"
				},
				{
					"bufferView":{
						
					}
				},
				"type": "WebGLConstants.VERTEX_SHADER",      
				"extras": {     
				  "_pipeline": {  
					"source": "vertexShader",   
					"extension": ".glsl",    
				  },
				},	
			],
			"techniques": [
				{
					"program": 0,
					"attributes": {
						"a_normal":  {
							"semantic": "NORMAL"
						},
						"a_position": {
							"semantic": "POSITION"
						},
						"a_texcoord0": {
							"semantic": "TEXCOORD_0"
						},
						"a_custom": {
							"semantic": "_CUSTOM_ATTRIBUTE"
						}
					},
					"uniforms": {
						"u_ambient": {
							"type": 35666
						},
						"u_diffuse": {
							"type": 35678,
							"value": {
								"index": "0  【纹理】"    
							}
						},
						"u_shininess": {
							"type": 5126
						},
						"u_light0Color": {
							"type": 35665,
							"value": [
								1,
								1,
								1
							]
						},
						"u_light0Transform": {
							"semantic": "MODEL",
							"node": 1,
							"type": 35676
						},
						"u_modelViewMatrix": {
							"semantic": "MODELVIEW",
							"type": 35676
						},
						"u_normalMatrix": {
							"semantic": "MODELVIEWINVERSETRANSPOSE",
							"type": 35675
						},
						"u_projectionMatrix": {
							"semantic": "PROJECTION",
							"type": 35676
						}
					}
				}
			]
		}
}

下面时gltf.extensions.KHR_materials_common中的样例:

extensions":{
    "KHR_materials_common":{
			"lights":[{
				"type": "ambient",
				"ambient":{
					"color": [1.0, 1.0, 1.0],
				},
				"directional":{
					"color": [1.0, 1.0, 1.0],
				},
				"point":{
					"color": [1.0, 1.0, 1.0],
					"constantAttenuation": 0,
					"linearAttenuation": 0,
					"quadraticAttenuation": 0
				},
				"spot":{
					"color": [1.0, 1.0, 1.0],
					"constantAttenuation": 0,
					"fallOffAngle": 3.14159265,
					"fallOffExponent": 0,
					"linearAttenuation": 0,
					"quadraticAttenuation": 0
				}
			}]
		}
}

上述信息建立完成后,下一步就是将gltf的数据创建对应的gpu资源,也就是渲染资源,创建之前需要分析对应的结构,将结构中的某些数据先缓存到一个地方,这个地方的cesium处理显得有些错乱。

Model.prototype.update = function (frameState) {
。。。。。。

// 没有初始化
      if (!loadResources.initialized) {
        // 生成动态一张纹理brdf的纹理
        frameState.brdfLutGenerator.update(frameState);
        // 检查gltf的扩展cesium中是否都支持(不支持投递异常,日志中体现),虽然cesium支持但是浏览器不一定支持
        ModelUtility.checkSupportedExtensions(
          this.extensionsRequired,
          supportsWebP
        );
        // 更新前向轴
        ModelUtility.updateForwardAxis(this);

        // glTF pipeline updates, not needed if loading from cache
        // 是否定义了数据源版本, 将各个版本的信息统一处理成一种自定义的格式,后续统一处理
        if (!defined(this.gltf.extras.sourceVersion)) {
          var gltf = this.gltf;
          // Add the original version so it remains cached
          // 添加原始版本到缓存状态
          gltf.extras.sourceVersion = ModelUtility.getAssetVersion(gltf);
          // 定义了扩展KHR_techniques_webgl(内部包含自定义glsl等)
          gltf.extras.sourceKHRTechniquesWebGL = defined(
            ModelUtility.getUsedExtensions(gltf).KHR_techniques_webgl // 定义了KHR_techniques_webgl扩展
          );
          // gltf数据的原始版本,没有处理时的版本
          this._sourceVersion = gltf.extras.sourceVersion;
          // 定义了扩展KHR_techniques_webgl,即gltf原始数据中是否自定义了shader相关的相关信息,区别于cesium拼接过的Technique
          this._sourceKHRTechniquesWebGL = gltf.extras.sourceKHRTechniquesWebGL;

          // 应该是将gltf1.0版本的数据改成2.0的数据, 为了支持1.0中的technique在2.0中使用了KHR_techniques_webgl扩展
          updateVersion(gltf);
          // 为gltf中各个属性添加默认值(buffer、material等)
          addDefaults(gltf);

          // glsl中是否添加批次表片段
          var options = {
            addBatchIdToGeneratedShaders: this._addBatchIdToGeneratedShaders,
          };

          // 处理KHR_materials_common扩展,扩展的所有的材质相信息,扩展的gltf的默认参数设置、shader片段拼接
          processModelMaterialsCommon(gltf, options);
          // 处理pbr材质
          processPbrMaterials(gltf, options);
        }

        // gltf版本
        this._sourceVersion = this.gltf.extras.sourceVersion;
       // 定义了扩展KHR_techniques_webgl,即gltf原始数据中是否自定义了shader相关的相关信息,区别于cesium拼接过的Technique
        this._sourceKHRTechniquesWebGL = this.gltf.extras.sourceKHRTechniquesWebGL;

        // Skip dequantizing in the shader if not encoded
        // 如果未编码,则跳过着色器中的解码,解码过程可以在cpu中处理也可以在glsl中处理
        this._dequantizeInShader =
          this._dequantizeInShader && DracoLoader.hasExtension(this);

        // We do this after to make sure that the ids don't change
        // 之后我们会这样做,以确保ID不会更改

        // 将buffer添加到gpu资源中, 存入ModelLoadResources,后续会使用这些数据创建顶点缓存
        addBuffersToLoadResources(this);
        // 解析骨骼动画关节
        parseArticulations(this);
        // 将gltf中的Techniques拷贝到model的成员变量中,存入model._sourcePrograms、model._sourceTechniques中
        parseTechniques(this);
        // 不是从缓存中加载(缓存中是已经解析过的数据,不用再处理了)
        if (!this._loadRendererResourcesFromCache) {
          // 解析bufferviewid,顶点、索引数据, 存入ModelLoadResources,后续会使用这些数据创建顶点数组对象
          parseBufferViews(this);
          // 着色器shaderid, 存入model._rendererResources中,glsl字符串可能来源于bufferView,字符串、外部链接等,后续
          parseShaders(this);
          // 着色程序programid, 存入ModelLoadResources中
          parsePrograms(this);
          // 纹理id, 存入ModelLoadResources
          parseTextures(this, context, supportsWebP); 
        }
        // 下面处理运行时需要的组织结构树,树节点,节点相关的材质
        parseMaterials(this);   // 解析材质(包含了真实的运行时数据存储)
        parseMeshes(this);      // 解析网格(包含了真实的运行时数据存储)
        parseNodes(this);       // 解析节点(包含了真实的运行时数据存储)

        // Start draco decoding  // 解析draco编码的二进制数据
        DracoLoader.parse(this, context);

        // 初始化完成
        loadResources.initialized = true; // 资源部初始化完成
      }

。。。。。。

}


1、保存gltf原始信息

// gltf原始数据的版本信息

this._sourceVersion = this.gltf.extras.sourceVersion;

// gltf原始数据中是否有KHR_techniques_webgl扩展信息

this._sourceKHRTechniquesWebGL = this.gltf.extras.sourceKHRTechniquesWebGL;


2、是否在shader中解码gltf中的二进制数据



this._dequantizeInShader = this._dequantizeInShader && DracoLoader.hasExtension(this);


3、将gltf的二进制数据保存到ModelLoadResources中,后续会创建顶点属性缓存对应webgl的GL_BUFFER

addBuffersToLoadResources(this);


4、关于骨骼动画相关信息处理

parseArticulations(this);


5、将gltf.extensions.KHR_techniques_webgl的techniques、program保存到Model的成员变量中

parseTechniques(this);


6、gltf数据多次使用

一个gltf数据被多次使用时,如果缓存到了context中,只解析一次并保存gpu资源,下次使用时不用再解析了,这些数据会通过this._loadRendererResourcesFromCache标记指定,


7、将bufferViews数据存储到ModelLoadResources中,对应webgl的ARRAY_BUFFER、ELEMENT_ARRAY_BUFFER

parseBufferViews(this);


8、将gltf.extensions.KHR_techniques_webgl.shaders数据存储到model._rendererResources.sourceShaders中,对应webgl的createShader函数

parseShaders(this);


9、将gltf.extensions.KHR_techniques_webgl.programs数据存储到model._rendererResources.

programsToCreate

中,对应webgl的

createProgram

函数

parsePrograms(this);


10、将BufferView类型的图像数据保存到model._loadResources.texturesToCreateFromBufferView中,后续对应webgl的createTexture函数

parseTextures(this, context, supportsWebP);


11、将BufferView类型的图像数据保存到model._loadResources.texturesToCreateFromBufferView中,后续对应webgl的createTexture函数


12、parseMaterials(model)

对于parseMaterials(model)中主要时将材质封装再了ModelMaterial,并将其存储再了  model._runtime.materialsByName = runtimeMaterialsByName;

model._runtime.materialsById = runtimeMaterialsById;中,model._runtime主要是封装了渲染时用到的一些信息,包括node、mesh、material等。再就是对model._uniformMaps的封装,这里面是模型自身的一些uniform信息的管理,用于向着色器中传递uniform信息,只是对于模型自身特定属性如环境光反射、漫反射、纹理等,而对于投影矩阵、视图矩阵等这些是所有渲染都需要的uniform数据会存储再context的context._us中。

function parseMaterials(model) {
  var gltf = model.gltf;
  var techniques = model._sourceTechniques;

  var runtimeMaterialsByName = {};
  var runtimeMaterialsById = {};
  var uniformMaps = model._uniformMaps;   // 模型的uniform映射

  // 遍历材质
  ForEach.material(gltf, function (material, materialId) {
    // Allocated now so ModelMaterial can keep a reference to it.
    // uniform数据
    uniformMaps[materialId] = {
      uniformMap: undefined,          // uniform映射
      values: undefined,              // uniform数据
      jointMatrixUniformName: undefined,  // 骨骼动画相关的uniform
      morphWeightsUniformName: undefined, // 变形动画相关的uniform
    };

    // 封装材质信息
    var modelMaterial = new ModelMaterial(model, material, materialId);

    // 遍历technique
    if (
      defined(material.extensions) &&
      defined(material.extensions.KHR_techniques_webgl)
    ) {
      // 材质所使用的techniqueId
      var techniqueId = material.extensions.KHR_techniques_webgl.technique;
      // 材质使用的glsl的索引
      modelMaterial._technique = techniqueId;
      // 程序索引
      modelMaterial._program = techniques[techniqueId].program;

      // 遍历材质的灯光数据等uniform数据
      ForEach.materialValue(material, function (value, uniformName) {
        if (!defined(modelMaterial._values)) {
          modelMaterial._values = {};
        }

        // 存储uniform数据(如灯光等数据)
        modelMaterial._values[uniformName] = clone(value);
      });
    }

    // 通过材质名、材质id存储对应的材质
    runtimeMaterialsByName[material.name] = modelMaterial;
    runtimeMaterialsById[materialId] = modelMaterial;
  });

  // 存储运行时的材质信息(包含了真实的数据如颜色值)
  model._runtime.materialsByName = runtimeMaterialsByName;
  model._runtime.materialsById = runtimeMaterialsById;
}


13、parseMeshes(this);

将gltf中.meshs数据封装在ModelMesh中,并将其存储到model._runtime.meshesByName = runtimeMeshesByName;中,后续遍历gltf中场景节点,会遍历到这些信息。

// 解析网格
function parseMeshes(model) {
  var runtimeMeshesByName = {};
  // 所有的材质字典
  var runtimeMaterialsById = model._runtime.materialsById;

  ForEach.mesh(model.gltf, function (mesh, meshId) {

    // 封装模型的网格信息
    runtimeMeshesByName[mesh.name] = new ModelMesh(
      mesh,
      runtimeMaterialsById,
      meshId
    );

    if (
      defined(model.extensionsUsed.WEB3D_quantized_attributes) ||   // 使用了压缩属性
      model._dequantizeInShader
    ) {
      // Cache primitives according to their program
      ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {
        // 查询mesh对应的材质中的program
        var programId = getProgramForPrimitive(model, primitive);
        // 程序对应的图元
        var programPrimitives = model._programPrimitives[programId];
        if (!defined(programPrimitives)) {
          programPrimitives = {};
          // 程序对应的网格的Primitive
          model._programPrimitives[programId] = programPrimitives;
        }
        // 对应的图元
        programPrimitives[meshId + ".primitive." + primitiveId] = primitive;
      });
    }
  });

  // 运行时用到的mesh
  model._runtime.meshesByName = runtimeMeshesByName;
}


14、parseNodes(this);

这个函数主要是对gltf中node封装在ModelNode中,并存储在了下面三个成员中

model._runtime.nodes = runtimeNodes;

model._runtime.nodesByName = runtimeNodesByName;

model._runtime.skinnedNodes = skinnedNodes;

// 解析node组织结构
function parseNodes(model) {
  var runtimeNodes = {};
  var runtimeNodesByName = {};
  var skinnedNodes = [];

  var skinnedNodesIds = model._loadResources.skinnedNodesIds;
  var articulationsByName = model._runtime.articulationsByName;

  // 遍历node
  ForEach.node(model.gltf, function (node, id) {
    var runtimeNode = {
      // Animation targets

      // 动画相关
      matrix: undefined,
      // 平移旋转缩放
      translation: undefined,
      rotation: undefined,
      scale: undefined,

      // Per-node show inherited from parent
      // 是否显示取决于父节点
      computedShow: true,

      // Computed transforms 计算转换

      //根矩阵
      transformToRoot: new Matrix4(),
      // 计算矩阵
      computedMatrix: new Matrix4(),
      // 动画脏了
      dirtyNumber: 0, // The frame this node was made dirty by an animation; for graph traversal

      // Rendering 渲染
      commands: [], // empty for transform, light, and camera nodes

      // Skinned node 骨骼动画
      inverseBindMatrices: undefined, // undefined when node is not skinned
      bindShapeMatrix: undefined, // undefined when node is not skinned or identity
      joints: [], // empty when node is not skinned
      computedJointMatrices: [], // empty when node is not skinned

      // Joint node 关节节点
      jointName: node.jointName, // undefined when node is not a joint

      // 权重
      weights: [],

      // Graph pointers  图形指针
      children: [], // empty for leaf nodes  空叶子节点
      parents: [], // empty for root nodes  根节点

      // Publicly-accessible ModelNode instance to modify animation targets
      publicNode: undefined,
    };

    // 运行时用到的节点
    runtimeNode.publicNode = new ModelNode(
      model,
      node,
      runtimeNode,
      id,
      ModelUtility.getTransform(node)
    );

    // 运行时用到的节点结构
    runtimeNodes[id] = runtimeNode;
    runtimeNodesByName[node.name] = runtimeNode;

    if (defined(node.skin)) {
      skinnedNodesIds.push(id);
      skinnedNodes.push(runtimeNode);
    }

    if (
      defined(node.extensions) &&
      defined(node.extensions.AGI_articulations)
    ) {
      var articulationName = node.extensions.AGI_articulations.articulationName;
      if (defined(articulationName)) {
        var transform = Matrix4.clone(
          runtimeNode.publicNode.originalMatrix,
          scratchArticulationStageInitialTransform
        );
        var articulation = articulationsByName[articulationName];
        articulation.nodes.push(runtimeNode.publicNode);

        var numStages = articulation.stages.length;
        for (var s = 0; s < numStages; ++s) {
          var stage = articulation.stages[s];
          transform = applyArticulationStageMatrix(stage, transform);
        }
        runtimeNode.publicNode.matrix = transform;
      }
    }
  });

  // 运行时用到的节点
  model._runtime.nodes = runtimeNodes;
  model._runtime.nodesByName = runtimeNodesByName;
  model._runtime.skinnedNodes = skinnedNodes;
}


15、DracoLoader.parse(this, context);



这个函数主要是处理使用Draco算法压缩过的数据,后续会通过wasm的方式进行解压索

// 解析数据
DracoLoader.parse = function (model, context) {
  // 是否存在扩展
  if (!DracoLoader.hasExtension(model)) {
    return;
  }

  var loadResources = model._loadResources;
  var cacheKey = model.cacheKey;
  // 缓存
  if (defined(cacheKey)) {
    if (!defined(DracoLoader._decodedModelResourceCache)) {
      if (!defined(context.cache.modelDecodingCache)) {
        context.cache.modelDecodingCache = {};
      }

      DracoLoader._decodedModelResourceCache = context.cache.modelDecodingCache;
    }

    // Decoded data for model will be loaded from cache
    var cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
    if (defined(cachedData)) {
      cachedData.count++;
      // 临时缓存解码后的数据
      loadResources.pendingDecodingCache = true;
      return;
    }
  }

  // 着色器中解析
  var dequantizeInShader = model._dequantizeInShader;
  var gltf = model.gltf;

  ForEach.mesh(gltf, function (mesh, meshId) {
    ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {

      // 没有扩展
      if (!defined(primitive.extensions)) {
        return;
      }

      // 不是压缩过的数据就不用处理了
      var compressionData = primitive.extensions.KHR_draco_mesh_compression;
      if (!defined(compressionData)) {
        return;
      }

      var bufferView = gltf.bufferViews[compressionData.bufferView];
      // 获取数据
      var typedArray = arraySlice(
        gltf.buffers[bufferView.buffer].extras._pipeline.source,
        bufferView.byteOffset,
        bufferView.byteOffset + bufferView.byteLength
      );
      // 存储数据
      loadResources.primitivesToDecode.enqueue({
        mesh: meshId,
        primitive: primitiveId,
        array: typedArray,            // 数据
        bufferView: bufferView,
        compressedAttributes: compressionData.attributes,   // 压缩过的属性
        dequantizeInShader: dequantizeInShader,    // 是否在着色器中解码
      });
    });
  });
};

最后loadResources.initialized = true;标记代表着数据初始化完成,后续是用这些数据创建gpu资源的过程了。

上述过程中有很多的数据会再Model中缓存,缓存的原因时这些数据会被改变,例如model._rendererResources.sourceShaders、model._sourceTechniques、model._uniformMaps



版权声明:本文为tianyapai原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。