上一章是关于压缩数据的一种方式,本章讲解第二种使用draco压缩过的几何数据,这部分分两个阶段,第一个阶段是使用wasm+worker的过程,这个过程解析完成后还会在vs中在进行数据处理才能变成普通的顶点属性数据。
对于draco压缩数据使用的是gltf的extensionsRequired.KHR_draco_mesh_compression扩展,在model.js解析gltf的json中检测到这个标志就会使用wasm+worker处理。
在model.js中关于draco压缩的数据引用DracoLoader.js类来处理。在model.js中使用了
-
DracoLoader.hasExtension
-
DracoLoader.parse
-
DracoLoader.decodeModel
-
DracoLoader.cacheDataForModel
-
DracoLoader.destroyCachedDataForModel
1、对于DracoLoader.hasExtension,主要是用来检测gltf中是否使用了KHR_draco_mesh_compression扩展。
// 使用了Draco压缩扩展
DracoLoader.hasExtension = function (model) {
return (
defined(model.extensionsRequired.KHR_draco_mesh_compression) ||
defined(model.extensionsUsed.KHR_draco_mesh_compression)
);
};
2、DracoLoader.parse用来收集gltf中使用draco压缩过的数据到primitivesToDecode,将这些数据保存在一个队列中,等收集完成后,将所有的数据一个一个的进行解压缩,收集数据的代码如下:
// 解析数据
DracoLoader.parse = function (model, context) {
// 是否存在扩展
if (!DracoLoader.hasExtension(model)) {
return;
}
// 加载资源
var loadResources = model._loadResources;
// 缓存key
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;
// 遍历mesh
ForEach.mesh(gltf, function (mesh, meshId) {
// 遍历primitive
ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {
// 没有扩展
if (!defined(primitive.extensions)) {
return;
}
// 不是压缩过的数据就不用处理了
var compressionData = primitive.extensions.KHR_draco_mesh_compression;
if (!defined(compressionData)) {
return;
}
// 获取bufferView数据
var bufferView = gltf.bufferViews[compressionData.bufferView];
// 获取数据块
var typedArray = arraySlice(
gltf.buffers[bufferView.buffer].extras._pipeline.source,
bufferView.byteOffset,
bufferView.byteOffset + bufferView.byteLength
);
// 存储待解压的draco数据
loadResources.primitivesToDecode.enqueue({
mesh: meshId, // 数据属于哪一个mesh
primitive: primitiveId, // 数据是哪一个primitive的
array: typedArray, // 数据
bufferView: bufferView, // 数据的归属
compressedAttributes: compressionData.attributes, // 压缩过的属性,这些属性对应的数据
dequantizeInShader: dequantizeInShader, // 是否在着色器中解码
});
});
});
};
3、DracoLoader.decodeModel解码draco数据
// 解码模型
DracoLoader.decodeModel = function (model, context) {
// 数据是否压缩过
if (!DracoLoader.hasExtension(model)) {
return when.resolve();
}
// 加载资源
var loadResources = model._loadResources;
// 缓存key
var cacheKey = model.cacheKey;
if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
// 缓存过的数据,不用解码
var cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
// Load decoded data for model when cache is ready
if (defined(cachedData) && loadResources.pendingDecodingCache) {
return when(cachedData.ready, function () {
model._decodedData = cachedData.data;
loadResources.pendingDecodingCache = false;
});
}
// Decoded data for model should be cached when ready
// 还没有准备好
DracoLoader._decodedModelResourceCache[cacheKey] = {
ready: false,
count: 1,
data: undefined,
};
}
// 压缩过的数据不存在就返回
if (loadResources.primitivesToDecode.length === 0) {
// No more tasks to schedule
return when.resolve();
}
// 任务处理器
var decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
var decodingPromises = [];
// 调度
var promise = scheduleDecodingTask(
decoderTaskProcessor, // 调度任务
model, // 模型类
loadResources, // 正在加载的资源
context // webgl上下文
);
// 为什么第一个在循环之外??
while (defined(promise)) {
decodingPromises.push(promise);
promise = scheduleDecodingTask(
decoderTaskProcessor, // 解码
model,
loadResources,
context
);
}
// 所有任务都处理完成
return when.all(decodingPromises);
};
可以看到数据一个个从primitivesToDecode中取出来,调用scheduleDecodingTask进行处理,返回的是一个promise,最后将所有的promise通过when.all()都处理完成就返回。
对于scheduleDecodingTask个过程,
// draco解码
function DracoLoader() {}
// Maximum concurrency to use when decoding draco models
// 解码draco压缩过的模型的并发数量
DracoLoader._maxDecodingConcurrency = Math.max(
FeatureDetection.hardwareConcurrency - 1,
1
);
// Exposed for testing purposes
// 任务处理器(封装线程)
DracoLoader._decoderTaskProcessor = undefined;
DracoLoader._taskProcessorReady = false;
// 创建任务处理器
DracoLoader._getDecoderTaskProcessor = function () {
// 没有创建处理程序
if (!defined(DracoLoader._decoderTaskProcessor)) {
// 创建任务处理程序
var processor = new TaskProcessor(
"decodeDraco", // 业务处理的js脚本文件
DracoLoader._maxDecodingConcurrency // 最大并发数量
);
processor
.initWebAssemblyModule({ // 初始化wasm
modulePath: "ThirdParty/Workers/draco_decoder_nodejs.js",
wasmBinaryFile: "ThirdParty/draco_decoder.wasm",
})
.then(function () {
// 准备好了
DracoLoader._taskProcessorReady = true;
});
// 任务处理程序
DracoLoader._decoderTaskProcessor = processor;
}
// 返回任务处理程序
return DracoLoader._decoderTaskProcessor;
};
/**
* Returns true if the model uses or requires KHR_draco_mesh_compression.
*
* @private
*/
// 使用了Draco压缩扩展
DracoLoader.hasExtension = function (model) {
return (
defined(model.extensionsRequired.KHR_draco_mesh_compression) ||
defined(model.extensionsUsed.KHR_draco_mesh_compression)
);
};
// 将解压后的数据重新格式化成gltf规范,并存储到loadResources中
function addBufferToLoadResources(loadResources, typedArray) {
// Create a new id to differentiate from original glTF bufferViews
// 重新创建一个bufferViewid
var bufferViewId =
"runtime." + Object.keys(loadResources.createdBufferViews).length;
// 原始的gltf中的buffer数组
var loadResourceBuffers = loadResources.buffers;
// 原来数据的长度作为新的id
var id = Object.keys(loadResourceBuffers).length;
// 存储解压完的数据到loadResources中
loadResourceBuffers[id] = typedArray;
// 在BufferViews中添加新的数据索引,符合gltf规范
loadResources.createdBufferViews[bufferViewId] = {
buffer: id,
byteOffset: 0,
byteLength: typedArray.byteLength,
};
// 返回bufferViewid
return bufferViewId;
}
function addNewVertexBuffer(typedArray, model, context) {
var loadResources = model._loadResources;
// 依据解压后的数据重新创建buffer、bufferview规范,并将数据通过索引连接起来
var id = addBufferToLoadResources(loadResources, typedArray);
// 待创建的顶点缓存
loadResources.vertexBuffersToCreate.enqueue(id);
// 返回bufferView的所以你
return id;
}
function addNewIndexBuffer(indexArray, model, context) {
// 数据
var typedArray = indexArray.typedArray;
var loadResources = model._loadResources;
//
var id = addBufferToLoadResources(loadResources, typedArray);
loadResources.indexBuffersToCreate.enqueue({
id: id,
componentType: ComponentDatatype.fromTypedArray(typedArray),
});
// 位置
return {
bufferViewId: id,
numberOfIndices: indexArray.numberOfIndices,
};
}
function scheduleDecodingTask(
decoderTaskProcessor,
model,
loadResources,
context
) {
// 解码器程序还没有准备好就
if (!DracoLoader._taskProcessorReady) {
// The task processor is not ready to schedule tasks
return;
}
// 获取最早的解码任务
var taskData = loadResources.primitivesToDecode.peek();
// 没有待处理的任务
if (!defined(taskData)) {
// All primitives are processing
return;
}
// 调度任务,处理buffer数据
var promise = decoderTaskProcessor.scheduleTask(taskData, [
taskData.array.buffer,
]);
// 调度失败返回
if (!defined(promise)) {
// Cannot schedule another task this frame
return;
}
// 正在解码的任务数量
loadResources.activeDecodingTasks++;
// 删除解码完成的一个数据
loadResources.primitivesToDecode.dequeue();
// 等待任务处理完成
return promise.then(function (result) {
// 正在处理的任务数量减少
loadResources.activeDecodingTasks--;
// 解码后的索引数据
var decodedIndexBuffer = addNewIndexBuffer(
result.indexArray,
model,
context
);
var attributes = {};
// 获取解码后的属性数据
var decodedAttributeData = result.attributeData;
for (var attributeName in decodedAttributeData) {
if (decodedAttributeData.hasOwnProperty(attributeName)) {
var attribute = decodedAttributeData[attributeName];
// 属性的数据
var vertexArray = attribute.array;
// 创建顶点缓存
var vertexBufferView = addNewVertexBuffer(vertexArray, model, context);
// 属性的bufferView索引
var data = attribute.data;
data.bufferView = vertexBufferView;
// 类似于gltf的primitive.attributes.position等
attributes[attributeName] = data;
}
}
// 解码后的数据
model._decodedData[taskData.mesh + ".primitive." + taskData.primitive] = {
bufferView: decodedIndexBuffer.bufferViewId, // bufferViewId的索引
numberOfIndices: decodedIndexBuffer.numberOfIndices, // 数量??
attributes: attributes,
};
});
}
DracoLoader._decodedModelResourceCache = undefined;
/**
* Parses draco extension on model primitives and
* adds the decoding data to the model's load resources.
*
* @private
*/
// 解析数据
DracoLoader.parse = function (model, context) {
// 是否存在扩展
if (!DracoLoader.hasExtension(model)) {
return;
}
// 加载资源
var loadResources = model._loadResources;
// 缓存key
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;
// 遍历mesh
ForEach.mesh(gltf, function (mesh, meshId) {
// 遍历primitive
ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {
// 没有扩展
if (!defined(primitive.extensions)) {
return;
}
// 不是压缩过的数据就不用处理了
var compressionData = primitive.extensions.KHR_draco_mesh_compression;
if (!defined(compressionData)) {
return;
}
// 获取bufferView数据
var bufferView = gltf.bufferViews[compressionData.bufferView];
// 获取数据块
var typedArray = arraySlice(
gltf.buffers[bufferView.buffer].extras._pipeline.source,
bufferView.byteOffset,
bufferView.byteOffset + bufferView.byteLength
);
// 存储待解压的draco数据
loadResources.primitivesToDecode.enqueue({
mesh: meshId, // 数据属于哪一个mesh
primitive: primitiveId, // 数据是哪一个primitive的
array: typedArray, // 数据
bufferView: bufferView, // 数据的归属
compressedAttributes: compressionData.attributes, // 压缩过的属性,这些属性对应的数据
dequantizeInShader: dequantizeInShader, // 是否在着色器中解码
});
});
});
};
/**
* Schedules decoding tasks available this frame.
* 调度此帧可用的解码任务
*
* @private
*/
// 解码模型
DracoLoader.decodeModel = function (model, context) {
// 数据是否压缩过
if (!DracoLoader.hasExtension(model)) {
return when.resolve();
}
// 加载资源
var loadResources = model._loadResources;
// 缓存key
var cacheKey = model.cacheKey;
if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
// 缓存过的数据,不用解码
var cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
// Load decoded data for model when cache is ready
if (defined(cachedData) && loadResources.pendingDecodingCache) {
return when(cachedData.ready, function () {
model._decodedData = cachedData.data;
loadResources.pendingDecodingCache = false;
});
}
// Decoded data for model should be cached when ready
// 还没有准备好
DracoLoader._decodedModelResourceCache[cacheKey] = {
ready: false,
count: 1,
data: undefined,
};
}
// 压缩过的数据不存在就返回
if (loadResources.primitivesToDecode.length === 0) {
// No more tasks to schedule
return when.resolve();
}
// 任务处理器
var decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
var decodingPromises = [];
// 调度
var promise = scheduleDecodingTask(
decoderTaskProcessor, // 调度任务
model, // 模型类
loadResources, // 正在加载的资源
context // webgl上下文
);
// 为什么第一个在循环之外??
while (defined(promise)) {
decodingPromises.push(promise);
promise = scheduleDecodingTask(
decoderTaskProcessor, // 解码
model,
loadResources,
context
);
}
// 所有任务都处理完成
return when.all(decodingPromises);
};
/**
* Decodes a compressed point cloud. Returns undefined if the task cannot be scheduled.
* 解码压缩的点云,任务不能调度就返回undefined
* @private
*/
DracoLoader.decodePointCloud = function (parameters) {
// 获取任务处理程序
var decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
// 没有准备好就返回
if (!DracoLoader._taskProcessorReady) {
// The task processor is not ready to schedule tasks
return;
}
// 调度任务
return decoderTaskProcessor.scheduleTask(parameters, [
parameters.buffer.buffer,
]);
};
/**
* Decodes a buffer view. Returns undefined if the task cannot be scheduled.
* 解码bufferView数据,如果没有调度任务,则返回undefined
*
* @param {Object} options Object with the following properties:
* @param {Uint8Array} options.array The typed array containing the buffer view data.
* @param {Object} options.bufferView The glTF buffer view object.
* @param {Object.<String, Number>} options.compressedAttributes The compressed attributes.
* @param {Boolean} options.dequantizeInShader Whether POSITION and NORMAL attributes should be dequantized on the GPU.
*
* @returns {Promise} A promise that resolves to the decoded indices and attributes.
* @private
*/
DracoLoader.decodeBufferView = function (options) {
var decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor();
if (!DracoLoader._taskProcessorReady) {
// The task processor is not ready to schedule tasks
return;
}
return decoderTaskProcessor.scheduleTask(options, [options.array.buffer]);
};
/**
* Caches a models decoded data so it doesn't need to decode more than once.
* 如果设置了cacheKey,缓存解码后的数据
* @private
*/
DracoLoader.cacheDataForModel = function (model) {
var cacheKey = model.cacheKey;
if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
var cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
if (defined(cachedData)) {
cachedData.ready = true;
cachedData.data = model._decodedData;
}
}
};
/**
* Destroys the cached data that this model references if it is no longer in use.
* 删除不在使用的缓存数据
* @private
*/
DracoLoader.destroyCachedDataForModel = function (model) {
var cacheKey = model.cacheKey;
if (defined(cacheKey) && defined(DracoLoader._decodedModelResourceCache)) {
var cachedData = DracoLoader._decodedModelResourceCache[cacheKey];
if (defined(cachedData) && --cachedData.count === 0) {
delete DracoLoader._decodedModelResourceCache[cacheKey];
}
}
};
上面是创建了一个draco的处理器,这个处理会创建线程,真正解码的是decodeDraco.js,这个会调用wasm的接口
// 解码
function decodePrimitive(parameters) {
// 创建解码器
var dracoDecoder = new draco.Decoder();
// Skip all parameter types except generic
// Note: As a temporary work-around until GetAttributeByUniqueId() works after
// calling SkipAttributeTransform(), we will not skip attributes with multiple
// sets of data in the glTF.
// 数据默认必须有位置和法线
var attributesToSkip = ["POSITION", "NORMAL"];
// Primitive中的属性列表属性,属性列表对应的语义数据
var compressedAttributes = parameters.compressedAttributes;
if (!defined(compressedAttributes["COLOR_1"])) {
// 颜色
attributesToSkip.push("COLOR");
}
if (!defined(compressedAttributes["TEXCOORD_1"])) {
// 纹理
attributesToSkip.push("TEX_COORD");
}
// 后续是否还需要再vs中解码数据
if (parameters.dequantizeInShader) {
for (var i = 0; i < attributesToSkip.length; ++i) {
// 跳过的属性??
dracoDecoder.SkipAttributeTransform(draco[attributesToSkip[i]]);
}
}
// 数据
var bufferView = parameters.bufferView;
// 创建解码后的缓存
var buffer = new draco.DecoderBuffer();
// 设置数据,传递给c++解码
buffer.Init(parameters.array, bufferView.byteLength);
// 获取解码类型
var geometryType = dracoDecoder.GetEncodedGeometryType(buffer);
// 几何类型??
if (geometryType !== draco.TRIANGULAR_MESH) {
throw new RuntimeError("Unsupported draco mesh geometry type.");
}
// 创建几何
var dracoGeometry = new draco.Mesh();
// 将buffer解码到mesh??
var decodingStatus = dracoDecoder.DecodeBufferToMesh(buffer, dracoGeometry);
if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
// 解码错误
throw new RuntimeError(
"Error decoding draco mesh geometry: " + decodingStatus.error_msg()
);
}
// 删除buffer
draco.destroy(buffer);
var attributeData = {};
// 遍历属性
for (var attributeName in compressedAttributes) {
if (compressedAttributes.hasOwnProperty(attributeName)) {
// Since GetAttributeByUniqueId() only works on attributes that we have not called
// SkipAttributeTransform() on, we must first store a `dracoAttributeName` in case
// we call GetAttributeId() instead.
var dracoAttributeName = attributeName;
if (attributeName === "TEXCOORD_0") {
dracoAttributeName = "TEX_COORD";
}
if (attributeName === "COLOR_0") {
dracoAttributeName = "COLOR";
}
var dracoAttribute;
if (attributesToSkip.includes(dracoAttributeName)) {
// 获取数据对应的id
var dracoAttributeId = dracoDecoder.GetAttributeId(
dracoGeometry,
draco[dracoAttributeName]
);
// 获取数据偏移
dracoAttribute = dracoDecoder.GetAttribute(
dracoGeometry,
dracoAttributeId
);
} else {
var compressedAttribute = compressedAttributes[attributeName];
dracoAttribute = dracoDecoder.GetAttributeByUniqueId(
dracoGeometry,
compressedAttribute
);
}
// 属性对应的数据
attributeData[attributeName] = decodeAttribute(
dracoGeometry, // 解码后的几何数据
dracoDecoder, // 解码器
dracoAttribute // 解码数据对应的属性数据偏移
);
}
}
var result = {
indexArray: decodeIndexArray(dracoGeometry, dracoDecoder), // 处理索引数据
attributeData: attributeData, // 解码后的数据
};
// 销毁句柄
draco.destroy(dracoGeometry);
draco.destroy(dracoDecoder);
return result;
}
// 解码数据
function decode(parameters) {
// 解码bufferView
if (defined(parameters.bufferView)) {
return decodePrimitive(parameters);
}
return decodePointCloud(parameters);
}
// 初始化
function initWorker(dracoModule) {
draco = dracoModule;
self.onmessage = createTaskProcessorWorker(decode);
self.postMessage(true);
}
// 解码数据
function decodeDraco(event) {
var data = event.data;
// Expect the first message to be to load a web assembly module
// 配置信息
var wasmConfig = data.webAssemblyConfig;
if (defined(wasmConfig)) {
// Require and compile WebAssembly module, or use fallback if not supported
// 下载wasm模块
return require([wasmConfig.modulePath], function (dracoModule) {
if (defined(wasmConfig.wasmBinaryFile)) {
if (!defined(dracoModule)) {
dracoModule = self.DracoDecoderModule;
}
// 初始化
dracoModule(wasmConfig).then(function (compiledModule) {
// 开启线程中的线程
initWorker(compiledModule);
});
} else {
initWorker(dracoModule());
}
});
}
}
通过primitive中的语义属性为索引,直接找到bufferview数据索引,根据索引解码buffer数据,buffer解析完成后会根据face创建索引,最终返回解码后的数据
var result = {
indexArray: decodeIndexArray(dracoGeometry, dracoDecoder), // 处理索引数据
attributeData: attributeData, // 解码后的数据
};
解码完成后的数据被存储在了model._decodedData中
function scheduleDecodingTask(
decoderTaskProcessor,
model,
loadResources,
context
) {
// 解码器程序还没有准备好就
if (!DracoLoader._taskProcessorReady) {
// The task processor is not ready to schedule tasks
return;
}
// 获取最早的解码任务
var taskData = loadResources.primitivesToDecode.peek();
// 没有待处理的任务
if (!defined(taskData)) {
// All primitives are processing
return;
}
// 调度任务,处理buffer数据
var promise = decoderTaskProcessor.scheduleTask(taskData, [
taskData.array.buffer,
]);
// 调度失败返回
if (!defined(promise)) {
// Cannot schedule another task this frame
return;
}
// 正在解码的任务数量
loadResources.activeDecodingTasks++;
// 删除解码完成的一个数据
loadResources.primitivesToDecode.dequeue();
// 等待任务处理完成
return promise.then(function (result) {
// 正在处理的任务数量减少
loadResources.activeDecodingTasks--;
// 解码后的索引数据
var decodedIndexBuffer = addNewIndexBuffer(
result.indexArray,
model,
context
);
var attributes = {};
// 获取解码后的属性数据
var decodedAttributeData = result.attributeData;
for (var attributeName in decodedAttributeData) {
if (decodedAttributeData.hasOwnProperty(attributeName)) {
var attribute = decodedAttributeData[attributeName];
// 属性的数据
var vertexArray = attribute.array;
// 创建顶点缓存
var vertexBufferView = addNewVertexBuffer(vertexArray, model, context);
// 属性的bufferView索引
var data = attribute.data;
data.bufferView = vertexBufferView;
// 类似于gltf的primitive.attributes.position等
attributes[attributeName] = data;
}
}
// 解码后的数据
model._decodedData[taskData.mesh + ".primitive." + taskData.primitive] = {
bufferView: decodedIndexBuffer.bufferViewId, // bufferViewId的索引
numberOfIndices: decodedIndexBuffer.numberOfIndices, // 数量??
attributes: attributes,
};
});
}
这里的model._decodedData的结构是比照gltf中bufferView引用buffer的结构进行组织的,后续的使用能够和流程匹配上。对于DracoLoader.cacheDataForModel、DracoLoader.destroyCachedDataForModel主要是用来缓存数据的,目前没有用到。