Cesium 源码解析 Model(一)中主要介绍了数据的前期准备工作,也就是数据下载完成(解析gltf,解析json和二进制数据,查看数据是否完整,如果有使用url的资源就去下载,这个不包括纹理的下载),对于数据的处理过程,需要根据this._state === ModelState.LOADING的状态确定下面的步骤:

代码如下:

Model.prototype.update = function (frameState) {......// 正在加载if (this._state === ModelState.LOADING) {// Transition from LOADING -> LOADED once resources are downloaded and created.// Textures may continue to stream in while in the LOADED state.// 下载并创建资源后,从加载转换到加载。在加载状态下,纹理可能会继续流入。// 没有正在下载的外部buffer数据了,数据都已经下载完了if (loadResources.pendingBufferLoads === 0) {// 没有初始化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扩展);// 版本this._sourceVersion = gltf.extras.sourceVersion;// 扩展KHR_techniques_webgl是否定义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;// 原始的glsl、uniform、attribute相关信息,与处理过的gltf.sourceKHRTechniquesWebGL信息不同this._sourceKHRTechniquesWebGL = this.gltf.extras.sourceKHRTechniquesWebGL;// Skip dequantizing in the shader if not encoded// 如果未编码,则跳过着色器中的解码this._dequantizeInShader =this._dequantizeInShader && DracoLoader.hasExtension(this);// We do this after to make sure that the ids don't change// 之后我们会这样做,以确保ID不会更改// 将buffer添加到gpu资源中, 存入ModelLoadResourcesaddBuffersToLoadResources(this);// 解析骨骼动画关节parseArticulations(this);// 将gltf中的Techniques拷贝到model的成员变量中,parseTechniques(this);// 不是从缓存中加载(缓存中是已经解析过的数据,不用再处理了)if (!this._loadRendererResourcesFromCache) {// 解析bufferviewid,顶点、索引数据, 存入ModelLoadResourcesparseBufferViews(this);// 着色器shaderid, 存入ModelLoadResourcesparseShaders(this);// 着色程序programid, 存入ModelLoadResourcesparsePrograms(this);// 纹理id, 存入ModelLoadResourcesparseTextures(this, context, supportsWebP); }parseMaterials(this);   // 解析材质(包含了真实的运行时数据存储)parseMeshes(this);      // 解析网格(包含了真实的运行时数据存储)parseNodes(this);       // 解析节点(包含了真实的运行时数据存储)// Start draco decoding  // 解析draco编码的二进制数据DracoLoader.parse(this, context);// 初始化完成loadResources.initialized = true; // 资源部初始化完成}// 解码未完成if (!loadResources.finishedDecoding()) {// 解码模型DracoLoader.decodeModel(this, context).otherwise(ModelUtility.getFailedLoadFunction(this, "model", this.basePath));}// 解码完成,资源还没解析完成if (loadResources.finishedDecoding() && !loadResources.resourcesParsed) {// 计算包围球this._boundingSphere = ModelUtility.computeBoundingSphere(this);// 包围球半径this._initialRadius = this._boundingSphere.radius;// 如果设置了cacheKey,缓存解码后的数据DracoLoader.cacheDataForModel(this);// 解析完成loadResources.resourcesParsed = true;}// 解析完成、外部链接的shader代码下载完成if (loadResources.resourcesParsed &&loadResources.pendingShaderLoads === 0) {// 显示外轮廓线if (this.showOutline) {// 轮廓线是一个扩展选项,是cesium定义的,模型生成的轮廓线的数据???ModelOutlineLoader.outlinePrimitives(this);}// 创建gpu资源createResources(this, frameState);}}if (loadResources.finished() ||     // 资源加载完成或者纹理逐渐加载(incrementallyLoadTextures &&loadResources.finishedEverythingButTextureCreation())   // 除了纹理之外的所有数据都处理完了) {// 模型装载完成(之后完成才能渲染)this._state = ModelState.LOADED;justLoaded = true;}}......
};

过程步骤如下:

1、this._state === ModelState.LOADING指明当前的数据除了纹理都已下载完成。

2、loadResources.pendingBufferLoads === 0 说明二进制数据下载完成。

3、!loadResources.initialized 说明资源还没有初始化完成,即还没将资源存储到ModelLoadResources结构中。

4、frameState.brdfLutGenerator.update(frameState); 因为pbr需要brdf的使用的相关纹理。

5、ModelUtility.checkSupportedExtensions 用来检查cesium是否支持所有模型正在使用的扩展。当前cesium支持的扩展有:

// 所有的gltf扩展内容
ModelUtility.supportedExtensions = {AGI_articulations: true,CESIUM_RTC: true,               // rtc中心EXT_texture_webp: true,         // webpKHR_blend: true,                // 混合KHR_binary_glTF: true,KHR_texture_basisu: true,KHR_draco_mesh_compression: true, // 压缩格式KHR_materials_common: true,     // 通用材质gltf1.0版本KHR_techniques_webgl: true,     // 自定义着色器KHR_materials_unlit: true,      // 无灯光材质KHR_materials_pbrSpecularGlossiness: true,  光泽度材质模型KHR_texture_transform: true,    // 纹理变换WEB3D_quantized_attributes: true,
};

6、ModelUtility.updateForwardAxis(this);  主要是gltf1.0、2.0中定义的模型前向轴的方向不同,1.0对应的是model._gltfForwardAxis = Axis.X;

7、下面的代码是将gltf2.0之前的格式转换成一种自定义的格式,目的是能统一处理。

// 是否定义了数据源版本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扩展);// 版本this._sourceVersion = gltf.extras.sourceVersion;// 扩展KHR_techniques_webgl是否定义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);}

8、updateVersion(gltf);中将2.0之前的格式转换成一种兼容格式,代码如下:

// 应该是将gltf1.0版本的数据改成2.0的数据
function updateVersion(gltf, options) {options = defaultValue(options, defaultValue.EMPTY_OBJECT);const targetVersion = options.targetVersion;// 版本let version = gltf.version;// 如果asset存在就使用,不存在就设置1.0gltf.asset = defaultValue(gltf.asset, {version: "1.0",});// 设置版本gltf.asset.version = defaultValue(gltf.asset.version, "1.0");version = defaultValue(version, gltf.asset.version).toString();// Invalid versionif (!Object.prototype.hasOwnProperty.call(updateFunctions, version)) {// Try truncating trailing version numbers, could be a number as well if it is 0.8if (defined(version)) {version = version.substring(0, 3);}// Default to 1.0 if it cannot be determinedif (!Object.prototype.hasOwnProperty.call(updateFunctions, version)) {version = "1.0";}}// 根据gltf的版本获取更新函数(跟新1.0到2.0)let updateFunction = updateFunctions[version];// 需要gltf版本转换,通过函数进行转换while (defined(updateFunction)) {if (version === targetVersion) {break;}updateFunction(gltf, options);version = gltf.asset.version;updateFunction = updateFunctions[version];}return gltf;
}

其中let updateFunction = updateFunctions[version];是查找转换函数:

// gltf的版本处理函数
const updateFunctions = {0.8: glTF08to10,   // 0.8转换成1.0的过程"1.0": glTF10to20,  // 1.0转换成2.0的过程"2.0": undefined,   // 如果默认是2.0就不用在转换了
};

对于gltf1.0、gltf2.0的规范:

可以看出,1.0是用json对象的方式建立索引,2.0是使用json数组的方式建立索引,json字符串的大小会减少很多。

9、addDefaults(gltf);主要是对于gltf中不完整的数据(某些缺省了)进行完善(填充完整)。

function addDefaults(gltf) {// 遍历访问器ForEach.accessor(gltf, function (accessor) {if (defined(accessor.bufferView)) {// 设置默认的访问器偏移量为0accessor.byteOffset = defaultValue(accessor.byteOffset, 0);}});// 遍历bufferviewsForEach.bufferView(gltf, function (bufferView) {if (defined(bufferView.buffer)) {// 设置默认的偏移量为0bufferView.byteOffset = defaultValue(bufferView.byteOffset, 0);}});// 遍历meshForEach.mesh(gltf, function (mesh) {// mesh的primitiveForEach.meshPrimitive(mesh, function (primitive) {// 默认绘制三角形primitive.mode = defaultValue(primitive.mode, WebGLConstants.TRIANGLES);// 材质未定义if (!defined(primitive.material)) {// 如果json中没有任何材质,材质为空数组if (!defined(gltf.materials)) {gltf.materials = [];}const defaultMaterial = {name: "default",};// 材质未定义,设置mesh的材质索引为“default”primitive.material = addToArray(gltf.materials, defaultMaterial);}});});// 遍历顶点属性数据ForEach.accessorContainingVertexAttributeData(gltf, function (accessorId) {// 找到属性对应的gltf中的accessor访问器const accessor = gltf.accessors[accessorId];// 获取对应bufferview的索引const bufferViewId = accessor.bufferView;// 是否归一化accessor.normalized = defaultValue(accessor.normalized, false);if (defined(bufferViewId)) {// 找到bufferviewconst bufferView = gltf.bufferViews[bufferViewId];// 填充步长大小bufferView.byteStride = getAccessorByteStride(gltf, accessor);// bufferview的目标是顶点数组bufferView.target = WebGLConstants.ARRAY_BUFFER;}});// 包含绘制索引ForEach.accessorContainingIndexData(gltf, function (accessorId) {// 找到索引对应的具体访问器const accessor = gltf.accessors[accessorId];// 找到索引对应的bufferViewconst bufferViewId = accessor.bufferView;if (defined(bufferViewId)) {// 设置bufferView的目标是索引缓冲const bufferView = gltf.bufferViews[bufferViewId];bufferView.target = WebGLConstants.ELEMENT_ARRAY_BUFFER;}});// 遍历材质ForEach.material(gltf, function (material) {// 材质中是否有扩展const extensions = defaultValue(material.extensions,defaultValue.EMPTY_OBJECT);// 材质中的KHR_materials_common扩展,定义光照模型,光照参数const materialsCommon = extensions.KHR_materials_common;if (defined(materialsCommon)) {// 获取自定义的光照模型,以及与光照模型相对应的values值const technique = materialsCommon.technique;const values = defined(materialsCommon.values)? materialsCommon.values: {};materialsCommon.values = values;// 定义了环境光values.ambient = defined(values.ambient)? values.ambient: [0.0, 0.0, 0.0, 1.0];// 定义了自发光values.emission = defined(values.emission)? values.emission: [0.0, 0.0, 0.0, 1.0];// 定义了透明度values.transparency = defaultValue(values.transparency, 1.0);values.transparent = defaultValue(values.transparent, false);// 定义了双面渲染values.doubleSided = defaultValue(values.doubleSided, false);// 不是恒定的if (technique !== "CONSTANT") {values.diffuse = defined(values.diffuse)? values.diffuse: [0.0, 0.0, 0.0, 1.0];// 不是兰伯特,应该是phong或者blinn,需要高光if (technique !== "LAMBERT") {// 高光values.specular = defined(values.specular)? values.specular: [0.0, 0.0, 0.0, 1.0];// 高光强度values.shininess = defaultValue(values.shininess, 0.0);}}return;}// 自发光因子material.emissiveFactor = defaultValue(material.emissiveFactor,[0.0, 0.0, 0.0]);// 透明模式material.alphaMode = defaultValue(material.alphaMode, "OPAQUE");// 双面渲染material.doubleSided = defaultValue(material.doubleSided, false);// 遮罩if (material.alphaMode === "MASK") {material.alphaCutoff = defaultValue(material.alphaCutoff, 0.5);}// 如果有KHR_techniques_webgl扩展????const techniquesExtension = extensions.KHR_techniques_webgl;if (defined(techniquesExtension)) {// 遍历单个材质中的属性ForEach.materialValue(material, function (materialValue) {// Check if material value is a TextureInfo object// 是纹理就添加纹理缓存引用if (defined(materialValue.index)) {// 纹理坐标引用addTextureDefaults(materialValue);}});}// 添加纹理坐标引用(自发光贴图、法线贴图、遮蔽图)addTextureDefaults(material.emissiveTexture);addTextureDefaults(material.normalTexture);addTextureDefaults(material.occlusionTexture);// 金属、粗糙度const pbrMetallicRoughness = material.pbrMetallicRoughness;if (defined(pbrMetallicRoughness)) {// 基本颜色pbrMetallicRoughness.baseColorFactor = defaultValue(pbrMetallicRoughness.baseColorFactor,[1.0, 1.0, 1.0, 1.0]);// 金属都因子pbrMetallicRoughness.metallicFactor = defaultValue(pbrMetallicRoughness.metallicFactor,1.0);// 粗糙度因子pbrMetallicRoughness.roughnessFactor = defaultValue(pbrMetallicRoughness.roughnessFactor,1.0);// 默认纹理坐标引用addTextureDefaults(pbrMetallicRoughness.baseColorTexture);addTextureDefaults(pbrMetallicRoughness.metallicRoughnessTexture);}// 高光光泽度模型const pbrSpecularGlossiness =extensions.KHR_materials_pbrSpecularGlossiness;if (defined(pbrSpecularGlossiness)) {// 漫反射pbrSpecularGlossiness.diffuseFactor = defaultValue(pbrSpecularGlossiness.diffuseFactor,[1.0, 1.0, 1.0, 1.0]);// 镜面pbrSpecularGlossiness.specularFactor = defaultValue(pbrSpecularGlossiness.specularFactor,[1.0, 1.0, 1.0]);// 光泽度pbrSpecularGlossiness.glossinessFactor = defaultValue(pbrSpecularGlossiness.glossinessFactor,1.0);// 光泽度纹理addTextureDefaults(pbrSpecularGlossiness.specularGlossinessTexture);}});// 动画ForEach.animation(gltf, function (animation) {ForEach.animationSampler(animation, function (sampler) {sampler.interpolation = defaultValue(sampler.interpolation, "LINEAR");});});// 动画节点const animatedNodes = getAnimatedNodes(gltf);ForEach.node(gltf, function (node, id) {const animated = defined(animatedNodes[id]);if (animated ||defined(node.translation) ||defined(node.rotation) ||defined(node.scale)) {node.translation = defaultValue(node.translation, [0.0, 0.0, 0.0]);node.rotation = defaultValue(node.rotation, [0.0, 0.0, 0.0, 1.0]);node.scale = defaultValue(node.scale, [1.0, 1.0, 1.0]);} else {node.matrix = defaultValue(node.matrix,[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,0.0, 1.0,]);}});// 采样器ForEach.sampler(gltf, function (sampler) {sampler.wrapS = defaultValue(sampler.wrapS, WebGLConstants.REPEAT);   // 默认是重复sampler.wrapT = defaultValue(sampler.wrapT, WebGLConstants.REPEAT);});// 默认场景0if (defined(gltf.scenes) && !defined(gltf.scene)) {gltf.scene = 0;}return gltf;
}

例如:

accessor.byteOffset   访问器的偏移量

bufferView.byteOffset  二进制数据的偏移量

primitive.material  如果某个mesh没有材质,则使用一个默认的材质

accessor.normalized    数据是否归一化

bufferView.byteStride    数据的步长

bufferView.target = WebGLConstants.ARRAY_BUFFER;  顶点数组缓存类型的二进制数据

bufferView.target = WebGLConstants.ELEMENT_ARRAY_BUFFER;  索引数组缓存类型的二进制数据

technique !== "CONSTANT"或"LAMBERT"指明材质所使用的光照模型,以及使用这个光照模型需要哪些对应的参数(材质的各种反射率),

材质的自发光、是否透明、是否双面渲染、透明剔除discard

自发光贴图、法线贴图、环境光遮蔽图的uv坐标索引

金属粗糙度模型的默认参数、光泽度模型的默认参数

动画的插值方式、骨骼矩阵

采样器的默认值

默认场景

10、processModelMaterialsCommon(gltf, options);是对于gltf的“KHR_materials_common”扩展的处理过程,与processPbrMaterials(gltf, options);对pbr的处理过程是互斥的。

processModelMaterialsCommon(gltf, options);中代码如下:

// 处KHR_materials_common扩展
function processModelMaterialsCommon(gltf, options) {options = defaultValue(options, defaultValue.EMPTY_OBJECT);if (!defined(gltf)) {return;}// 没有使用扩展材质就返回if (!usesExtension(gltf, "KHR_materials_common")) {return;}// 使用KHR_materials_common就必须使用KHR_techniques_webglif (!usesExtension(gltf, "KHR_techniques_webgl")) {// 没有扩展就创建扩展if (!defined(gltf.extensions)) {gltf.extensions = {};}// 添加默认数组gltf.extensions.KHR_techniques_webgl = {programs: [],     // 程序shaders: [],      // 着色器techniques: [],   // glsl代码,attibute、uniform};// 将使用的扩展加入gltf.extensionsUsed.push("KHR_techniques_webgl");gltf.extensionsRequired.push("KHR_techniques_webgl");}// 自定义glsl、uniform、attribute等信息var techniquesWebgl = gltf.extensions.KHR_techniques_webgl;// 灯光默认值lightDefaults(gltf);// 收集灯光参数,后续uniform会使用 (灯光颜色、矩阵、衰减参数等)var lightParameters = generateLightParameters(gltf);// 通过mesh的材质索引封装mesh的顶点属性信息,因为这些顶点属性会影像材质(hasNormal、hasVertexColor等)var primitiveByMaterial = ModelUtility.splitIncompatibleMaterials(gltf);var techniques = {};var generatedTechniques = false;// 遍历材质  生成techniqueForEach.material(gltf, function (material, materialIndex) {if (   // 定义了扩展defined(material.extensions) &&defined(material.extensions.KHR_materials_common)) {// 光照模型(phong、blinn等)var khrMaterialsCommon = material.extensions.KHR_materials_common;// 材质对应的顶点属性信息var primitiveInfo = primitiveByMaterial[materialIndex];// 获取Technique相关的信息,作为一个shader的唯一key,用于缓存shader,以及区别shader// 将所有影像材质的信息做成一个keyvar techniqueKey = getTechniqueKey(khrMaterialsCommon, primitiveInfo);// 缓存避免重复处理var technique = techniques[techniqueKey];// 缓存中没有找到就生成if (!defined(technique)) {// 拼接成很多个glsl片段、顶点属性片段、uniform对象, 收集attribute、uniform、组成shaders、program、techniquetechnique = generateTechnique(gltf,                       // 原始数据techniquesWebgl,            // 自定义glsl、uniform、attribute等信息,生成的technique会添加到这个数组中primitiveInfo,              // 顶点属性的一些影响材质的信息(hasNormal、hasVertexColor等)khrMaterialsCommon,         // 光照模型(模型自身材质反射率)lightParameters,            // 所有的灯光参数(灯光位置、颜色、衰减参数)options.addBatchIdToGeneratedShaders  // 添加批次表id);// 缓存到techniques中,避免重复处理techniques[techniqueKey] = technique;// 生成了techniquesgeneratedTechniques = true;}// 收集gltf中提供的uniform的值var materialValues = {};var values = khrMaterialsCommon.values;var uniformName;for (var valueName in values) {if (values.hasOwnProperty(valueName) &&valueName !== "transparent" &&   // 不是透明valueName !== "doubleSided"      // 不是双面渲染) {uniformName = "u_" + valueName.toLowerCase();materialValues[uniformName] = values[valueName];  // 添加uniform名-值}}// 添加到材质的扩展,包括technique索引和values(uniform的数值)material.extensions.KHR_techniques_webgl = {technique: technique,values: materialValues,};// 设置材质中的模式material.alphaMode = "OPAQUE";if (khrMaterialsCommon.transparent) {material.alphaMode = "BLEND";   // 透明模式为混合}// 设置材质中的双面渲染if (khrMaterialsCommon.doubleSided) { // 双面渲染material.doubleSided = true;}}});// 没有生成直接返回if (!generatedTechniques) {return gltf;}// If any primitives have semantics that aren't declared in the generated// shaders, we want to preserve them.// 确保语义存在ModelUtility.ensureSemanticExistence(gltf);return gltf;
}

其中lightDefaults(gltf);主要是完善灯光信息(gltf中的灯光使用了默认值,这里需要完善一下)

function generateLightParameters(gltf)函数主要是收集灯光信息,这些信息主要是为后续的glsl字符串的拼接、uniform的数据的收集使用。

var primitiveByMaterial = ModelUtility.splitIncompatibleMaterials(gltf);函数会收集顶点属性等信息,这些信息为后续拼接glsl以及attibute等有帮助。

对于每一个材质var techniqueKey = getTechniqueKey(khrMaterialsCommon, primitiveInfo)会依据材质所使用的所有关键信息拼接成一个唯一值,以区别不同的材质。

technique = generateTechnique(gltf,                       // 原始数据techniquesWebgl,            // 自定义glsl、uniform、attribute等信息,生成的technique会添加到这个数组中primitiveInfo,              // 顶点属性的一些影响材质的信息(hasNormal、hasVertexColor等)khrMaterialsCommon,         // 光照模型(模型自身材质反射率)lightParameters,            // 所有的灯光参数(灯光位置、颜色、衰减参数)options.addBatchIdToGeneratedShaders  // 添加批次表id);

上述代码会根据各方面的信息生成technique,并将这个索引添加到material.extensions.KHR_techniques_webgl中,后续是由这个结构生成着色程序。

// 生成Technique,其中包括shader、uniform、attribute所有的信息
function generateTechnique(gltf,techniquesWebgl,primitiveInfo,khrMaterialsCommon,lightParameters,            // 灯光参数addBatchIdToGeneratedShaders
) {// 未定义if (!defined(khrMaterialsCommon)) {khrMaterialsCommon = {};}// 批次表的id片段addBatchIdToGeneratedShaders = defaultValue(addBatchIdToGeneratedShaders,false);// gltf中自带的数据:用户自定义的(一般不会自定义,这些是在默认值中创建的)var techniques = techniquesWebgl.techniques;      // 包括解析后的glsl的顶点属性片段、shader片段、以及uniform封装对象var shaders = techniquesWebgl.shaders;            // 自定义的着色器片段var programs = techniquesWebgl.programs;          // 自定义的程序片段var lightingModel = khrMaterialsCommon.technique.toUpperCase();   // 灯光模式(恒定、blinn、phong)var lights;if (  // 定义了通用材质defined(gltf.extensions) &&defined(gltf.extensions.KHR_materials_common)) {// 获取灯光数组(环境光、点光源、聚光灯)lights = gltf.extensions.KHR_materials_common.lights;}// 与khrMaterialsCommon.technique对应的模型反射率等var parameterValues = khrMaterialsCommon.values;// 骨骼节点数var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);var skinningInfo;var hasSkinning = false;var hasVertexColors = false;// gltf中mesh中存储的索引信息if (defined(primitiveInfo)) {// 骨骼信息、顶点颜色skinningInfo = primitiveInfo.skinning;hasSkinning = skinningInfo.skinned;hasVertexColors = primitiveInfo.hasVertexColors;}// 拼接shader分为几部分:/* vertexShader\fragmentShader : 函数外的attribute、uniform、varing在vertexShader字符串中,vertexShaderMain: 函数内的处理过程在vertexShaderMain字符串中,techniqueUniforms: uniform相关信息,techniqueAttributes: attribute相关信息*/// 默认高精度var vertexShader = "precision highp float;\n";var fragmentShader = "precision highp float;\n";// 光照模型不是恒定的,可能是lambert等,就需要法线参数var hasNormals = lightingModel !== "CONSTANT";// Add techniques 添加uniform数据矩阵信息var techniqueUniforms = {u_modelViewMatrix: {     // 模型视图矩阵semantic: usesExtension(gltf, "CESIUM_RTC")   // 如果使用了rtc,在glsl中使用CESIUM_RTC_MODELVIEW关键字,否则使用MODELVIEW? "CESIUM_RTC_MODELVIEW"  : "MODELVIEW",type: WebGLConstants.FLOAT_MAT4,              // 类型是mat4},u_projectionMatrix: {                           // 定义投影矩阵,代码中的名称是u_projectionMatrix,对应glsl中的关键字是PROJECTIONsemantic: "PROJECTION",type: WebGLConstants.FLOAT_MAT4,},};if (hasNormals) {                               // 存在法线techniqueUniforms.u_normalMatrix = {semantic: "MODELVIEWINVERSETRANSPOSE",      // 模型视图矩阵的逆矩阵type: WebGLConstants.FLOAT_MAT3,            // 类型是mat3};}if (hasSkinning) {                              // 存在骨骼techniqueUniforms.u_jointMatrix = {count: jointCount,                          // 节点数semantic: "JOINTMATRIX",                    // 节点矩阵,glsl中的uniform关键字type: WebGLConstants.FLOAT_MAT4,};}// Add material values  添加材质值反射率信息var uniformName;var hasTexCoords = false;for (var name in parameterValues) {               ////generate shader parameters for KHR_materials_common attributes//(including a check, because some boolean flags should not be used as shader parameters)if (parameterValues.hasOwnProperty(name) &&       // 本身的数据name !== "transparent" &&                     // 透明name !== "doubleSided"                        // 双面渲染) {var uniformType = getKHRMaterialsCommonValueType( // 获取值的类型name,parameterValues[name]);// 拼接uniform名称uniformName = "u_" + name.toLowerCase();// 存在纹理坐标if (!hasTexCoords && uniformType === WebGLConstants.SAMPLER_2D) {hasTexCoords = true;}// 添加uniform相关的名称与类型,一次拼接glsl中的uniform,没有语义techniqueUniforms[uniformName] = {type: uniformType,};}}// Give the diffuse uniform a semantic to support color replacement in 3D Tiles// 为漫反射均匀提供语义,以支持3D瓦片中的颜色替换if (defined(techniqueUniforms.u_diffuse)) {techniqueUniforms.u_diffuse.semantic = "_3DTILESDIFFUSE";}// Copy light parameters into technique parameters// 赋值unifrom的灯光信息if (defined(lightParameters)) {for (var lightParamName in lightParameters) {if (lightParameters.hasOwnProperty(lightParamName)) {uniformName = "u_" + lightParamName;techniqueUniforms[uniformName] = lightParameters[lightParamName];}}}// Add uniforms to shaders 将uniform添加到glsl中for (uniformName in techniqueUniforms) {if (techniqueUniforms.hasOwnProperty(uniformName)) {var uniform = techniqueUniforms[uniformName];   var arraySize = defined(uniform.count) ? "[" + uniform.count + "]" : "";  // 如果uniform是数组if (  // 不是mat3\mat4并且是用在片段着色器中(uniform.type !== WebGLConstants.FLOAT_MAT3 &&uniform.type !== WebGLConstants.FLOAT_MAT4) ||uniform.useInFragment    // 用在片段着色器中) {// 拼接片段着色器中的uniform常量fragmentShader +="uniform " +webGLConstantToGlslType(uniform.type) +" " +uniformName +arraySize +";\n";// 使用完了就删除delete uniform.useInFragment;} else {// 拼接顶点着色器中的unifrom常量vertexShader +="uniform " +webGLConstantToGlslType(uniform.type) +" " +uniformName +arraySize +";\n";}}}// Add attributes with semantics 添加语义属性// 顶点着色器main函数var vertexShaderMain = "";// 存在骨骼的情况,在顶点着色器的main中拼接if (hasSkinning) {// 根据骨骼索引找到骨骼矩阵,骨骼矩阵乘以权重vertexShaderMain +="    mat4 skinMatrix =\n" +"        a_weight.x * u_jointMatrix[int(a_joint.x)] +\n" +"        a_weight.y * u_jointMatrix[int(a_joint.y)] +\n" +"        a_weight.z * u_jointMatrix[int(a_joint.z)] +\n" +"        a_weight.w * u_jointMatrix[int(a_joint.w)];\n";}// Add position always  添加顶点属性中的顶点var techniqueAttributes = {a_position: {       // semantic: "POSITION", // 语义是顶点},};// 顶点着色器中的顶点属性位置属性、转递属性(相机空间中的坐标)vertexShader += "attribute vec3 a_position;\n";vertexShader += "varying vec3 v_positionEC;\n";// 存在骨骼if (hasSkinning) {// 计算骨骼变换后的相机空间坐标vertexShaderMain +="  vec4 pos = u_modelViewMatrix * skinMatrix * vec4(a_position,1.0);\n";} else {// 直接计算相机空间坐标vertexShaderMain +="  vec4 pos = u_modelViewMatrix * vec4(a_position,1.0);\n";}// 转递的相机空间坐标vertexShaderMain += "  v_positionEC = pos.xyz;\n";// 投影vertexShaderMain += "  gl_Position = u_projectionMatrix * pos;\n";// 像素着色器中接收的相机空间坐标fragmentShader += "varying vec3 v_positionEC;\n";// Add normal if we don't have constant lighting 如果没有恒定照明,则添加“正常”if (hasNormals) {// 存在法线techniqueAttributes.a_normal = {semantic: "NORMAL",};// 法线vertexShader += "attribute vec3 a_normal;\n";vertexShader += "varying vec3 v_normal;\n";if (hasSkinning) {// 骨骼中会计算法线vertexShaderMain +="  v_normal = u_normalMatrix * mat3(skinMatrix) * a_normal;\n";} else {vertexShaderMain += "  v_normal = u_normalMatrix * a_normal;\n";}// 像素着色器中接收的法线坐标 fragmentShader += "varying vec3 v_normal;\n";}// Add texture coordinates if the material uses them  天啊及纹理坐标var v_texcoord;if (hasTexCoords) {techniqueAttributes.a_texcoord_0 = {  // 纹理坐标semantic: "TEXCOORD_0",};// 顶点着色器中的纹理坐标v_texcoord = "v_texcoord_0";vertexShader += "attribute vec2 a_texcoord_0;\n";vertexShader += "varying vec2 " + v_texcoord + ";\n";// 转递纹理坐标vertexShaderMain += "  " + v_texcoord + " = a_texcoord_0;\n";// 像素着色器中接收的纹理坐标 fragmentShader += "varying vec2 " + v_texcoord + ";\n";}if (hasSkinning) {// 骨骼节点属性techniqueAttributes.a_joint = {semantic: "JOINTS_0",};// 权重属性techniqueAttributes.a_weight = {semantic: "WEIGHTS_0",};// 骨骼矩阵索引,权重属性,都是vec4的vertexShader += "attribute vec4 a_joint;\n";vertexShader += "attribute vec4 a_weight;\n";}if (hasVertexColors) {// 颜色属性techniqueAttributes.a_vertexColor = {semantic: "COLOR_0",};// 顶点颜色、传递顶点颜色vertexShader += "attribute vec4 a_vertexColor;\n";vertexShader += "varying vec4 v_vertexColor;\n";vertexShaderMain += "  v_vertexColor = a_vertexColor;\n";// 像素着色器中接收的顶点颜色 fragmentShader += "varying vec4 v_vertexColor;\n";}// 批次id属性if (addBatchIdToGeneratedShaders) {techniqueAttributes.a_batchId = {   //semantic: "_BATCHID",             // 语义};// 顶点着色器中添加批次idvertexShader += "attribute float a_batchId;\n";}// 存在高光var hasSpecular =hasNormals &&(lightingModel === "BLINN" || lightingModel === "PHONG") &&   // 灯光模式为blinn或者phongdefined(techniqueUniforms.u_specular) &&        // 存在高光、高光强度defined(techniqueUniforms.u_shininess) &&techniqueUniforms.u_shininess > 0.0;// Generate lighting code blocks  生成灯光代码块var hasNonAmbientLights = false;var hasAmbientLights = false;var fragmentLightingBlock = "";   // 像素着色器代码段for (var lightName in lights) {  // 遍历灯光if (lights.hasOwnProperty(lightName)) {var light = lights[lightName];var lightType = light.type.toLowerCase(); // 灯光类型(环境光、点光源、聚光灯)var lightBaseName = light.baseName;fragmentLightingBlock += "  {\n";var lightColorName = "u_" + lightBaseName + "Color";  // 灯光颜色var varyingDirectionName;var varyingPositionName;if (lightType === "ambient") {  // 如果是换进光hasAmbientLights = true;fragmentLightingBlock +=      // "    ambientLight += " + lightColorName + ";\n";   // glsl中的颜色相加} else if (hasNormals) {        // 存在法线hasNonAmbientLights = true;   varyingDirectionName = "v_" + lightBaseName + "Direction";  // 方向光varyingPositionName = "v_" + lightBaseName + "Position";    // 方向光的位置if (lightType !== "point") {  // 不是点光源vertexShader += "varying vec3 " + varyingDirectionName + ";\n";  // v传递方向光fragmentShader += "varying vec3 " + varyingDirectionName + ";\n"; // f接收方向光vertexShaderMain +="  " +varyingDirectionName +" = mat3(u_" +lightBaseName +"Transform) * vec3(0.,0.,1.);\n";if (lightType === "directional") {fragmentLightingBlock +="    vec3 l = normalize(" + varyingDirectionName + ");\n";}}if (lightType !== "directional") {  // 不是方向光vertexShader += "varying vec3 " + varyingPositionName + ";\n";fragmentShader += "varying vec3 " + varyingPositionName + ";\n";vertexShaderMain +="  " +varyingPositionName +" = u_" +lightBaseName +"Transform[3].xyz;\n";fragmentLightingBlock +="    vec3 VP = " + varyingPositionName + " - v_positionEC;\n";fragmentLightingBlock += "    vec3 l = normalize(VP);\n";fragmentLightingBlock += "    float range = length(VP);\n";fragmentLightingBlock +="    float attenuation = 1.0 / (u_" +lightBaseName +"Attenuation.x + ";fragmentLightingBlock +="(u_" + lightBaseName + "Attenuation.y * range) + ";fragmentLightingBlock +="(u_" + lightBaseName + "Attenuation.z * range * range));\n";} else {fragmentLightingBlock += "    float attenuation = 1.0;\n";}// 聚光灯if (lightType === "spot") {fragmentLightingBlock +="    float spotDot = dot(l, normalize(" +varyingDirectionName +"));\n";fragmentLightingBlock +="    if (spotDot < cos(u_" + lightBaseName + "FallOff.x * 0.5))\n";fragmentLightingBlock += "    {\n";fragmentLightingBlock += "      attenuation = 0.0;\n";fragmentLightingBlock += "    }\n";fragmentLightingBlock += "    else\n";fragmentLightingBlock += "    {\n";fragmentLightingBlock +="        attenuation *= max(0.0, pow(spotDot, u_" +lightBaseName +"FallOff.y));\n";fragmentLightingBlock += "    }\n";}fragmentLightingBlock +="    diffuseLight += " +lightColorName +"* max(dot(normal,l), 0.) * attenuation;\n";if (hasSpecular) {if (lightingModel === "BLINN") {fragmentLightingBlock += "    vec3 h = normalize(l + viewDir);\n";fragmentLightingBlock +="    float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess)) * attenuation;\n";} else {// PHONGfragmentLightingBlock +="    vec3 reflectDir = reflect(-l, normal);\n";fragmentLightingBlock +="    float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess)) * attenuation;\n";}fragmentLightingBlock +="    specularLight += " +lightColorName +" * specularIntensity;\n";}}fragmentLightingBlock += "  }\n";}}// 不存在环境光if (!hasAmbientLights) {// Add an ambient light if we don't have onefragmentLightingBlock += "  ambientLight += vec3(0.2, 0.2, 0.2);\n";}// 存在换进光if (!hasNonAmbientLights && lightingModel !== "CONSTANT") {// 自定义了灯光颜色fragmentShader += "#ifdef USE_CUSTOM_LIGHT_COLOR \n";fragmentShader += "uniform vec3 gltf_lightColor; \n";fragmentShader += "#endif \n";// 未定义灯光颜色fragmentLightingBlock += "#ifndef USE_CUSTOM_LIGHT_COLOR \n";fragmentLightingBlock += "    vec3 lightColor = czm_lightColor;\n";  // 使用cesium灯光颜色fragmentLightingBlock += "#else \n";fragmentLightingBlock += "    vec3 lightColor = gltf_lightColor;\n";  // 使用gltf灯光颜色fragmentLightingBlock += "#endif \n";fragmentLightingBlock += "  vec3 l = normalize(czm_lightDirectionEC);\n"; // 相机空间下灯光方向var minimumLighting = "0.2"; // Use strings instead of values as 0.0 -> 0 when stringified// 漫反射颜色fragmentLightingBlock +="  diffuseLight += lightColor * max(dot(normal,l), " +minimumLighting +");\n";// 存在高光if (hasSpecular) {// blinn灯光if (lightingModel === "BLINN") {fragmentLightingBlock += "  vec3 h = normalize(l + viewDir);\n";fragmentLightingBlock +="  float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess));\n";} else {// PHONG灯光fragmentLightingBlock += "  vec3 reflectDir = reflect(-l, normal);\n";fragmentLightingBlock +="  float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess));\n";}fragmentLightingBlock +="  specularLight += lightColor * specularIntensity;\n";}}// 拼接mainvertexShader += "void main(void) {\n";vertexShader += vertexShaderMain;vertexShader += "}\n";// 处理像素着色器fragmentShader += "void main(void) {\n";var colorCreationBlock = "  vec3 color = vec3(0.0, 0.0, 0.0);\n";// 法线if (hasNormals) {fragmentShader += "  vec3 normal = normalize(v_normal);\n";if (khrMaterialsCommon.doubleSided) {fragmentShader += "  if (czm_backFacing())\n";fragmentShader += "  {\n";fragmentShader += "    normal = -normal;\n";fragmentShader += "  }\n";}}var finalColorComputation;// 不是恒定灯光if (lightingModel !== "CONSTANT") {// 漫反射if (defined(techniqueUniforms.u_diffuse)) {if (techniqueUniforms.u_diffuse.type === WebGLConstants.SAMPLER_2D) {fragmentShader +="  vec4 diffuse = texture2D(u_diffuse, " + v_texcoord + ");\n";} else {fragmentShader += "  vec4 diffuse = u_diffuse;\n";}fragmentShader += "  vec3 diffuseLight = vec3(0.0, 0.0, 0.0);\n";colorCreationBlock += "  color += diffuse.rgb * diffuseLight;\n";}// 高光if (hasSpecular) {if (techniqueUniforms.u_specular.type === WebGLConstants.SAMPLER_2D) {fragmentShader +="  vec3 specular = texture2D(u_specular, " + v_texcoord + ").rgb;\n";} else {fragmentShader += "  vec3 specular = u_specular.rgb;\n";}fragmentShader += "  vec3 specularLight = vec3(0.0, 0.0, 0.0);\n";colorCreationBlock += "  color += specular * specularLight;\n";}// 透明if (defined(techniqueUniforms.u_transparency) && false) {finalColorComputation ="  gl_FragColor = vec4(color * diffuse.a * u_transparency, diffuse.a * u_transparency);\n";} else {finalColorComputation =//"  gl_FragColor = vec4(color * diffuse.a, diffuse.a);\n";"  gl_FragColor = vec4(diffuse.rgb * 1.5, 1.0);\n";}} else if (defined(techniqueUniforms.u_transparency)) {// 恒定灯光下的透明finalColorComputation ="  gl_FragColor = vec4(color * u_transparency, u_transparency);\n";} else {// 最终的颜色finalColorComputation = "  gl_FragColor = vec4(color, 1.0);\n";}// 顶点颜色if (hasVertexColors) {colorCreationBlock += "  color *= v_vertexColor.rgb;\n";}// 自发光if (defined(techniqueUniforms.u_emission)) {if (techniqueUniforms.u_emission.type === WebGLConstants.SAMPLER_2D) {fragmentShader +="  vec3 emission = texture2D(u_emission, " + v_texcoord + ").rgb;\n";} else {fragmentShader += "  vec3 emission = u_emission.rgb;\n";}colorCreationBlock += "  color += emission;\n";}// 环境光if (defined(techniqueUniforms.u_ambient) || lightingModel !== "CONSTANT") {if (defined(techniqueUniforms.u_ambient)) {// 纹理中的环境光漫反射if (techniqueUniforms.u_ambient.type === WebGLConstants.SAMPLER_2D) {fragmentShader +="  vec3 ambient = texture2D(u_ambient, " + v_texcoord + ").rgb;\n";} else {fragmentShader += "  vec3 ambient = u_ambient.rgb;\n";}} else {fragmentShader += "  vec3 ambient = diffuse.rgb;\n";}// 颜色相加colorCreationBlock += "  color += ambient * ambientLight;\n";}// fragmentShader += "  vec3 viewDir = -normalize(v_positionEC);\n";fragmentShader += "  vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n";// Add in light computations  拼接灯光计算片段fragmentShader += fragmentLightingBlock;fragmentShader += colorCreationBlock;fragmentShader += finalColorComputation;fragmentShader += "}\n";// Add shaders 着色片段数组中添加一个顶点片段// 将新生成的片段添加到原来的gltf中,var vertexShaderId = addToArray(shaders, {type: WebGLConstants.VERTEX_SHADER,       // 属于顶点着色器extras: {     // 额外的数据_pipeline: {    // 管线source: vertexShader,   // 顶点着色片段extension: ".glsl",     //glsl},},});// 将新生成的片段添加到原来的gltf中,var fragmentShaderId = addToArray(shaders, {type: WebGLConstants.FRAGMENT_SHADER,     // 属于像素着色器extras: {_pipeline: {source: fragmentShader,extension: ".glsl",},},});// Add program  将新生成的片段添加到原来的gltf中索引中,添加着色程序片段var programId = addToArray(programs, {fragmentShader: fragmentShaderId,vertexShader: vertexShaderId,});// 添加到techniques数组中,返回索引位置var techniqueId = addToArray(techniques, {attributes: techniqueAttributes,        // 属性片段program: programId,                     // shader片段uniforms: techniqueUniforms,            // uniform片段});return techniqueId;
}

Cesium 源码解析 Model(二)相关推荐

  1. Cesium 源码解析 Model(一)

    Cesium中对于3DTiles会解析成Model,特别是3DTile中的B3DM,过程主要是对gltf在Cesium中是如何解析并生成绘制命令的. content._model = new Mode ...

  2. Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理)

    快速导航(持续更新中-) Cesium源码解析一(terrain文件的加载.解析与渲染全过程梳理) Cesium源码解析二(metadataAvailability的含义) Cesium源码解析三(m ...

  3. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  4. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(二)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(二) 3. Joint optimization 3.1 Marginalization ...

  5. PyCrypto密码学库源码解析(二)RSA参数生成

    Python Crypto库源码解析(二) RSA参数生成 * 版权声明 * 引用请注明出处,转载请联系: h0.1c@foxmail.com 本文主要讲解pycrypto库中RSA参数生成的实现方法 ...

  6. 【DETR源码解析】二、Backbone模块

    目录 前言 一.Backbone整体结构 一.CNN-Backbone 二.Positional Encoding Reference 前言 最近在看DETR的源码,断断续续看了一星期左右,把主要的模 ...

  7. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  8. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  9. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  10. Mybatis 源码解析 -- 基于配置的源码解析(二)

    为什么80%的码农都做不了架构师?>>>    mapper解析 接着上篇的配置,本篇主要讲解mappers标签 <?xml version="1.0" e ...

最新文章

  1. BZOJ 1821 [JSOI2010] Group 部落划分 Group
  2. 屏幕显示密度dpi_PPI和DPI有什么区别?
  3. java学习笔记(12) —— Struts2 通过 xml /json 实现简单的业务处理
  4. Linux sed 写命令常见使用案例
  5. 如何启用SAP C4C OData Event Notification
  6. linux红帽6架设apache,linux Redhat6.5 中 编译安装apache
  7. 真的很OK!雷军成科创板大赢家 相关投资企业3家已上市
  8. 和nginx比较_谈谈Nginx和LVS各自的优缺点以及使用
  9. 001.从零到1之Linux高性能服务器开发
  10. JavaScript内置对象→对象、系统函数、Date日期对象、String字符串对象、Math对象、Number数字对象、Object对象、Boolean对象、Error对象
  11. 一个APP开发有那么难吗?
  12. 北大 CTSC 2013
  13. ai背景合成_AI突破次元壁又火了!飞屋环游记动漫角色一秒变真人,网友:小罗的“猫王发型”有点酷...
  14. 美国低速自动驾驶在公共交通应用详解 | 自动驾驶系列
  15. java 图片与base64相互转化
  16. 公有云、私有云和混合云介绍
  17. 第三方支付机构是什么
  18. 什么是百度SEO?百度SEO优化怎么做?
  19. 数据透视表的发明历史
  20. 拨乱反正:“通过RAMDirectory缓写提高性能”是错误的!

热门文章

  1. vs以管理员身份运行
  2. unbuntu下pytorch安装
  3. C# DLL HRESULT:0x8007000B
  4. html调起苹果手机摄像头_Html5调用手机摄像头并实现人脸识别的实现
  5. Android 长按3Dtouch快捷方式
  6. c datetime 格式化
  7. 2、智慧变电站 - 地面、围墙绘制及动态贴图
  8. 面对妖艳的配置文件,python小技巧来帮你!
  9. xenu工具如何扫描网站
  10. 什么是“理解”?如何在人工智能中定义“理解”?(what is understanding ?)