关于model.js中用于解码几何数据的压缩部分,cesium的87版兼容了两种解码数据的过程,一种是再vs中解码压缩过的顶点数据,还有一种是使用draco解码几何数据。
第一种在vs种:
这种方式使用的是gltf的WEB3D_quantized_attributes扩展,即在gltf的json文件定义了extensionsUsed.WEB3D_quantized_attributes,在cesium中大概的几个过程是parseMeshes、createUniformsForQuantizedAttributes、modifyShaderForQuantizedAttributes、createResources、createProgram,这几个函数。因为需要在vs中解析顶点数据,所以需要在gltf中设置关于解码的glsl片段,或者cesium直接就在源码中添加了解析源码。
首先parseMeshes函数中:
// 解析网格
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);
// 网格Primitive对应的材质中的program
var programPrimitives = model._programPrimitives[programId];
if (!defined(programPrimitives)) {
programPrimitives = {};
// 网格Primitive对应的材质中的program
model._programPrimitives[programId] = programPrimitives;
}
// 网格Primitive对应的材质中的program
programPrimitives[meshId + ".primitive." + primitiveId] = primitive;
});
}
});
// 运行时用到的mesh
model._runtime.meshesByName = runtimeMeshesByName;
}
上述过程是建立了mesh对应的着色程序索引programId。
在createResources函数中:
// 创建渲染所需要的所有的gpu资源
function createResources(model, frameState) {
// 上下文
var context = frameState.context;
// 3D模式
var scene3DOnly = frameState.scene3DOnly;
// 顶点着色器中解析
var quantizedVertexShaders = model._quantizedVertexShaders;
// 所有的techniques的引用
var techniques = model._sourceTechniques;
// gltf中着色程序索引
var programs = model._sourcePrograms;
// 渲染资源
var resources = model._rendererResources;
// glsl代码
var shaders = resources.sourceShaders;
// 如果是缓存过的资源(一般不缓存)
if (model._loadRendererResourcesFromCache) {
// glsl代码
shaders = resources.sourceShaders =
model._cachedRendererResources.sourceShaders;
}
// Techniques遍历
for (var techniqueId in techniques) {
if (techniques.hasOwnProperty(techniqueId)) {
// 使用的程序索引
var programId = techniques[techniqueId].program;
// 程序
var program = programs[programId];
// 程序中使用的顶点shader
var shader = shaders[program.vertexShader];
// 程序中包含webgl扩展
ModelUtility.checkSupportedGlExtensions(program.glExtensions, context);
// 数据需要在shader中解析
if (
model.extensionsUsed.WEB3D_quantized_attributes ||
model._dequantizeInShader
) {
// 修改着色器
var quantizedVS = quantizedVertexShaders[programId];
if (!defined(quantizedVS)) {
// 修改着色器
quantizedVS = modifyShaderForQuantizedAttributes(
shader,
programId,
model
);
quantizedVertexShaders[programId] = quantizedVS;
}
shader = quantizedVS;
}
// 钩子拦截并修改着色器字符串
shader = modifyShader(shader, programId, model._vertexShaderLoaded);
}
}
。。。。。。
}
这个函数中根据model.extensionsUsed.WEB3D_quantized_attributes这个条件,如果使用了扩展则在cesium中拼接用于解码的glsl片段
// 修改着色器的glsl代码用于在顶点着色器中解码压缩过的顶点数据
function modifyShaderForQuantizedAttributes(shader, programName, model) {
var primitive;
// program对应的mesh的primitive
var primitives = model._programPrimitives[programName];
// If no primitives were cached for this program, there's no need to modify the shader
// 如果没有为此程序缓存任何基元,则无需修改着色器
if (!defined(primitives)) {
return shader;
}
var primitiveId;
for (primitiveId in primitives) {
if (primitives.hasOwnProperty(primitiveId)) {
primitive = primitives[primitiveId];
// 得到mesh对应材质中的program程序
if (getProgramForPrimitive(model, primitive) === programName) {
break;
}
}
}
// This is not needed after the program is processed, free the memory
// 释放内存(修改完glsl片段,这个对象就不在需要了)
model._programPrimitives[programName] = undefined;
var result;
// 使用了扩展
if (model.extensionsUsed.WEB3D_quantized_attributes) {
// 修改glsl用来支持解码
result = ModelUtility.modifyShaderForQuantizedAttributes(
model.gltf,
primitive,
shader
);
// 保存用于解码的uniform
model._quantizedUniforms[programName] = result.uniforms;
} else {
// draco解码后的数据
var decodedData = model._decodedData[primitiveId];
if (defined(decodedData)) {
// 修改着色器用来支持draco解码
result = ModelUtility.modifyShaderForDracoQuantizedAttributes(
model.gltf,
primitive,
shader,
decodedData.attributes // 解码后的属性??
);
} else {
// 返回着色器
return shader;
}
}
return result.shader;
}
上述函数可以看到,解码的数据来自两个过程,一个是gltf数据中包含了解码的数据,另一个来源是draco解码过的数据(下个过程分析)。
对于第一个数据来源,
// 修改glsl用来支持解码顶点数据
ModelUtility.modifyShaderForQuantizedAttributes = function (
gltf,
primitive,
shader
) {
var quantizedUniforms = {};
// 属性
var attributes = primitive.attributes;
// 遍历属性
for (var attributeSemantic in attributes) {
if (attributes.hasOwnProperty(attributeSemantic)) {
// 得到属性语义对应的glsl中的属性名
var attributeVarName = getAttributeVariableName(
gltf,
primitive,
attributeSemantic
);
// 获取语义属性对应的访问器的id
var accessorId = primitive.attributes[attributeSemantic];
// 如果有下划线前缀,去掉下划线
if (attributeSemantic.charAt(0) === "_") {
attributeSemantic = attributeSemantic.substring(1);
}
// 根据属性创建glsl中用到的uniform
var decodeUniformVarName =
"gltf_u_dec_" + attributeSemantic.toLowerCase();
// 创建解码用到的gltf_u_dec_position_scale、gltf_u_dec_position_translate
var decodeUniformVarNameScale = decodeUniformVarName + "_scale";
var decodeUniformVarNameTranslate = decodeUniformVarName + "_translate";
if (
!defined(quantizedUniforms[decodeUniformVarName]) &&
!defined(quantizedUniforms[decodeUniformVarNameScale])
) {
// 获取访问器中关于解码的扩展属性
var quantizedAttributes = getQuantizedAttributes(gltf, accessorId);
if (defined(quantizedAttributes)) {
// 解码矩阵
var decodeMatrix = quantizedAttributes.decodeMatrix;
// 新的入口
var newMain = "gltf_decoded_" + attributeSemantic;
// 使用gltf_a_dec_前缀替换所有属性中有的a_前缀
var decodedAttributeVarName = attributeVarName.replace(
"a_",
"gltf_a_dec_"
);
// 开方
var size = Math.floor(Math.sqrt(decodeMatrix.length));
// replace usages of the original attribute with the decoded version, but not the declaration
// 将glsl中除了第一个以外的a_相关的属性都替换成gltf_a_dec_开头的属性
shader = replaceAllButFirstInString(
shader,
attributeVarName,
decodedAttributeVarName
);
// declare decoded attribute
// 声明解码使用的属性
var variableType;
if (size > 2) {
variableType = "vec" + (size - 1);
} else {
variableType = "float";
}
// 声明属性vec4 gltf_a_dec_position
shader =
variableType + " " + decodedAttributeVarName + ";\n" + shader;
// splice decode function into the shader - attributes are pre-multiplied with the decode matrix
// uniform in the shader (32-bit floating point)
// 顶点都提前乘以矩阵decode matrix
var decode = "";
if (size === 5) {
// separate scale and translate since glsl doesn't have mat5
// 分辨使用缩放和平移,因为没有mat5类型的矩阵
// 拼接缩放矩阵
shader =
"uniform mat4 " + decodeUniformVarNameScale + ";\n" + shader;
// 拼接平移矩阵
shader =
"uniform vec4 " + decodeUniformVarNameTranslate + ";\n" + shader;
// 解码计算(gltf_a_dec_POSOTION = gltf_u_dec_position_scale * a_position + gltf_u_dec_position_translate)
decode =
"\n" +
"void main() {\n" +
" " +
decodedAttributeVarName +
" = " +
decodeUniformVarNameScale +
" * " +
attributeVarName +
" + " +
decodeUniformVarNameTranslate +
";\n" +
" " +
newMain +
"();\n" +
"}\n";
// 传入glsl是需要的用于解码的uniform
quantizedUniforms[decodeUniformVarNameScale] = { mat: 4 };
quantizedUniforms[decodeUniformVarNameTranslate] = { vec: 4 };
} else {
shader =
"uniform mat" +
size +
" " +
decodeUniformVarName +
";\n" +
shader;
decode =
"\n" +
"void main() {\n" +
" " +
decodedAttributeVarName +
" = " +
variableType +
"(" +
decodeUniformVarName +
" * vec" +
size +
"(" +
attributeVarName +
",1.0));\n" +
" " +
newMain +
"();\n" +
"}\n";
// 传入glsl是需要的用于解码的uniform
quantizedUniforms[decodeUniformVarName] = { mat: size };
}
// glsl入口main函数替换
shader = ShaderSource.replaceMain(shader, newMain);
shader += decode;
}
}
}
}
// 返回新shader和用于解码的uniform
return {
shader: shader,
uniforms: quantizedUniforms,
};
};
上面的代码中首先找到mesh中某个primitive的所有语义属性,根据语义属性找对应于glsl代码片段中属性,这个对照关系如下:
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"TEXCOORD_0": 1
},
"indices": 2,
"material": 0,
"mode": 4,
"extensions":"CESIUM_primitive_outline"
}
]
}
],
"attributes": {
"a_normal": {
"semantic": "NORMAL"
},
"a_position": {
"semantic": "POSITION"
},
"a_texcoord0": {
"semantic": "TEXCOORD_0"
},
"a_custom": {
"semantic": "_CUSTOM_ATTRIBUTE"
}
}
例如:在glsl中的"a_position"属性,表达的语义是"POSITION",而这个语义对应的实际数据是primitive中{"POSITION": 0},对应的位置0。
因为要解码数据,所以原来glsl中所有使用"a_position"的字段,除"attibute vec4 a_position"
之外都要重命名为"gltf_a_dec_position",主要是因为其他使用position的时候都是解码后的gltf_a_dec_position,然后解码的过程中需要解码缩放矩阵和偏移向量,即gltf_u_dec_position_scale和gltf_u_dec_position_translate,所以glsl代码中需要加入解码计算(gltf_a_dec_position = gltf_u_dec_position_scale * a_position + gltf_u_dec_position_translate)
并需要收集uniform映射quantizedUniforms,最后将glsl的入口函数进行替换ShaderSource.replaceMain(shader, newMain);
在createUniformsForQuantizedAttributes函数中:
function createUniformsForQuantizedAttributes(model, primitive) {
var programId = getProgramForPrimitive(model, primitive);
var quantizedUniforms = model._quantizedUniforms[programId];
return ModelUtility.createUniformsForQuantizedAttributes(
model.gltf,
primitive,
quantizedUniforms
);
}
之前在glsl中拼接过程中涉及到解码的矩阵和偏移的uniform变量,这些变量存储在哪里呢,下面的代码中揭示
ModelUtility.createUniformsForQuantizedAttributes = function (
gltf,
primitive,
quantizedUniforms
) {
var accessors = gltf.accessors;
var setUniforms = {};
var uniformMap = {};
var attributes = primitive.attributes;
for (var attribute in attributes) {
if (attributes.hasOwnProperty(attribute)) {
var accessorId = attributes[attribute];
var a = accessors[accessorId];
var extensions = a.extensions;
if (attribute.charAt(0) === "_") {
attribute = attribute.substring(1);
}
if (defined(extensions)) {
var quantizedAttributes = extensions.WEB3D_quantized_attributes;
if (defined(quantizedAttributes)) {
var decodeMatrix = quantizedAttributes.decodeMatrix;
var uniformVariable = "gltf_u_dec_" + attribute.toLowerCase();
switch (a.type) {
case AttributeType.SCALAR:
uniformMap[uniformVariable] = getMat2UniformFunction(
decodeMatrix
).func;
setUniforms[uniformVariable] = true;
break;
case AttributeType.VEC2:
uniformMap[uniformVariable] = getMat3UniformFunction(
decodeMatrix
).func;
setUniforms[uniformVariable] = true;
break;
case AttributeType.VEC3:
uniformMap[uniformVariable] = getMat4UniformFunction(
decodeMatrix
).func;
setUniforms[uniformVariable] = true;
break;
case AttributeType.VEC4:
// VEC4 attributes are split into scale and translate because there is no mat5 in GLSL
var uniformVariableScale = uniformVariable + "_scale";
var uniformVariableTranslate = uniformVariable + "_translate";
uniformMap[uniformVariableScale] = getMat4UniformFunction(
scaleFromMatrix5Array(decodeMatrix)
).func;
uniformMap[uniformVariableTranslate] = getVec4UniformFunction(
translateFromMatrix5Array(decodeMatrix)
).func;
setUniforms[uniformVariableScale] = true;
setUniforms[uniformVariableTranslate] = true;
break;
}
}
}
}
}
// If there are any unset quantized uniforms in this program, they should be set to the identity
for (var quantizedUniform in quantizedUniforms) {
if (quantizedUniforms.hasOwnProperty(quantizedUniform)) {
if (!setUniforms[quantizedUniform]) {
var properties = quantizedUniforms[quantizedUniform];
if (defined(properties.mat)) {
if (properties.mat === 2) {
uniformMap[quantizedUniform] = getMat2UniformFunction(
Matrix2.IDENTITY
).func;
} else if (properties.mat === 3) {
uniformMap[quantizedUniform] = getMat3UniformFunction(
Matrix3.IDENTITY
).func;
} else if (properties.mat === 4) {
uniformMap[quantizedUniform] = getMat4UniformFunction(
Matrix4.IDENTITY
).func;
}
}
if (defined(properties.vec)) {
if (properties.vec === 4) {
uniformMap[quantizedUniform] = getVec4UniformFunction([
0,
0,
0,
0,
]).func;
}
}
}
}
}
return uniformMap;
};
可以看到是从quantizedAttributes.decodeMatrix;中得到了,位置如下:
var quantizedAttributes = extensions.WEB3D_quantized_attributes;
if (defined(quantizedAttributes)) {
var decodeMatrix = quantizedAttributes.decodeMatrix;
}
第二种使用draco压缩过的几何数据,这部分分两个阶段,第一个阶段是使用wasm+worker的过程,这个过程解析完成后还会在vs中在进行数据处理才能变成普通的顶点属性数据。下一篇文章讲解