首先我来看一下这样的一个gltf,一般来说3dtile数据是由gltf和头文件信息组建而成。而现在我们要对shaders中的着色器进行解码。shaders分别右两部分构成,一部分是顶点着色器,另外一部分是片元着色器。着色语言为webgl,即使用了glsl语言,类似于C语言进行编写。而放到shaders数组中的存放形式,可以是二进制形式,也可以是数据视图,存放着缓冲区,也可以是json格式的数据。而我们下面的数据是二进制的数据,有一个头说明"data:text/plain;base64",好了我们来看一下在Cesium中,是如何对这个数据进行解码的。
首先,3dtile数据,后缀为b3dm的格式,在数据加载的时候也是创建了一个模型,对应Model.js中的Model类,在update函数中进行转换,update函数的代码量还是比较大的,这里截取了几个转换函数,如下所示,大概在代码的4404的位置。
// We do this after to make sure that the ids don't change
addBuffersToLoadResources(this);
parseTechniques(this);
if (!this._loadRendererResourcesFromCache) {
parseBufferViews(this);
parseShaders(this);
parsePrograms(this);
parseTextures(this, context);
}
parseMaterials(this);
parseMeshes(this);
parseNodes(this);
当数据model(gltf)进入到parseShaders(this)中,看一下代码的逻辑实现。从下面的代码中,可以看出着色器,支持三种着色资源的定义,而我们这里选择的选择的第三种方式(具体的实现是根据编写的生成b3dm工具来确定)。
function parseShaders(model) {
var gltf = model.gltf;
var buffers = gltf.buffers;
var bufferViews = gltf.bufferViews;
var sourceShaders = model._rendererResources.sourceShaders;
ForEach.shader(gltf, function(shader, id) {
// Shader references either uri (external or base64-encoded) or bufferView
if (defined(shader.bufferView)) {
var bufferViewId = shader.bufferView;
var bufferView = bufferViews[bufferViewId];
var bufferId = bufferView.buffer;
var buffer = buffers[bufferId];
var source = getStringFromTypedArray(buffer.extras._pipeline.source, bufferView.byteOffset, bufferView.byteLength);
sourceShaders[id] = source;
} else if (defined(shader.extras._pipeline.source)) {
sourceShaders[id] = shader.extras._pipeline.source;
} else {
++model._loadResources.pendingShaderLoads;
var shaderResource = model._resource.getDerivedResource({
url: shader.uri
});
shaderResource.fetchText()
.then(shaderLoad(model, shader.type, id))
.otherwise(ModelUtility.getFailedLoadFunction(model, 'shader', shaderResource.url));
}
});
}
将断点打入到该函数,我们将会进入到最后一个判断的fetchText函数。
现在我们来看一下这个函数的实现,fetchText()定义如下所示。
Resource.prototype.fetchText = function() {
return this.fetch({
responseType : 'text'
});
};
fetchText函数非常简单,只不过说对Resource.js中的fetch函数传入返回类型为"text"的对象,然后调用而已。那么我们来看一下fetch函数是如何实现的。可是fetch也是非常简单的。
Resource.prototype.fetch = function(options) {
options = defaultClone(options, {});
options.method = 'GET';
return this._makeRequest(options);
};
主要是来看一下_makeRequest函数,其中定义如下所示。
Resource.prototype._makeRequest = function(options) {
var resource = this;
checkAndResetRequest(resource.request);
var request = resource.request;
request.url = resource.url;
request.requestFunction = function() {
var responseType = options.responseType;
var headers = combine(options.headers, resource.headers);
var overrideMimeType = options.overrideMimeType;
var method = options.method;
var data = options.data;
var deferred = when.defer();
var xhr = Resource._Implementations.loadWithXhr(resource.url, responseType, method, data, headers, deferred, overrideMimeType);
if (defined(xhr) && defined(xhr.abort)) {
request.cancelFunction = function() {
xhr.abort();
};
}
return deferred.promise;
};
var promise = RequestScheduler.request(request);
if (!defined(promise)) {
return;
}
return promise
.then(function(data) {
return data;
})
.otherwise(function(e) {
if (request.state !== RequestState.FAILED) {
return when.reject(e);
}
return resource.retryOnError(e)
.then(function(retry) {
if (retry) {
// Reset request so it can try again
request.state = RequestState.UNISSUED;
request.deferred = undefined;
return resource.fetch(options);
}
return when.reject(e);
});
});
};
requestFunction函数对uri数据进行了拆分,利用loadWithXhr函数对数据进行进一步的处理。我们来看一下这个函数。
Resource._Implementations.loadWithXhr = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
var dataUriRegexResult = dataUriRegex.exec(url);
if (dataUriRegexResult !== null) {
deferred.resolve(decodeDataUri(dataUriRegexResult, responseType));
return;
}
if (noXMLHttpRequest) {
loadWithHttpRequest(url, responseType, method, data, headers, deferred, overrideMimeType);
return;
}
var xhr = new XMLHttpRequest();
if (TrustedServers.contains(url)) {
xhr.withCredentials = true;
}
xhr.open(method, url, true);
if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) {
xhr.overrideMimeType(overrideMimeType);
}
if (defined(headers)) {
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
if (defined(responseType)) {
xhr.responseType = responseType;
}
// While non-standard, file protocol always returns a status of 0 on success
var localFile = false;
if (typeof url === 'string') {
localFile = (url.indexOf('file://') === 0) || (typeof window !== 'undefined' && window.location.origin === 'file://');
}
xhr.onload = function() {
if ((xhr.status < 200 || xhr.status >= 300) && !(localFile && xhr.status === 0)) {
deferred.reject(new RequestErrorEvent(xhr.status, xhr.response, xhr.getAllResponseHeaders()));
return;
}
var response = xhr.response;
var browserResponseType = xhr.responseType;
if (method === 'HEAD' || method === 'OPTIONS') {
var responseHeaderString = xhr.getAllResponseHeaders();
var splitHeaders = responseHeaderString.trim().split(/[\r\n]+/);
var responseHeaders = {};
splitHeaders.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
responseHeaders[header] = parts.join(': ');
});
deferred.resolve(responseHeaders);
return;
}
//All modern browsers will go into either the first or second if block or last else block.
//Other code paths support older browsers that either do not support the supplied responseType
//or do not support the xhr.response property.
if (xhr.status === 204) {
// accept no content
deferred.resolve();
} else if (defined(response) && (!defined(responseType) || (browserResponseType === responseType))) {
deferred.resolve(response);
} else if ((responseType === 'json') && typeof response === 'string') {
try {
deferred.resolve(JSON.parse(response));
} catch (e) {
deferred.reject(e);
}
} else if ((browserResponseType === '' || browserResponseType === 'document') && defined(xhr.responseXML) && xhr.responseXML.hasChildNodes()) {
deferred.resolve(xhr.responseXML);
} else if ((browserResponseType === '' || browserResponseType === 'text') && defined(xhr.responseText)) {
deferred.resolve(xhr.responseText);
} else {
deferred.reject(new RuntimeError('Invalid XMLHttpRequest response type.'));
}
};
xhr.onerror = function(e) {
deferred.reject(new RequestErrorEvent());
};
xhr.send(data);
return xhr;
};
这里将会进入到第一个判断的函数,如下。
function decodeDataUri(dataUriRegexResult, responseType) {
responseType = defaultValue(responseType, '');
var mimeType = dataUriRegexResult[1];
var isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
switch (responseType) {
case '':
case 'text':
return decodeDataUriText(isBase64, data);
case 'arraybuffer':
return decodeDataUriArrayBuffer(isBase64, data);
case 'blob':
var buffer = decodeDataUriArrayBuffer(isBase64, data);
return new Blob([buffer], {
type : mimeType
});
case 'document':
var parser = new DOMParser();
return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
case 'json':
return JSON.parse(decodeDataUriText(isBase64, data));
default:
//>>includeStart('debug', pragmas.debug);
throw new DeveloperError('Unhandled responseType: ' + responseType);
//>>includeEnd('debug');
}
}
因为是text类型,我们进入到switch的第二个case.
function decodeDataUriText(isBase64, data) {
var result = decodeURIComponent(data);
if (isBase64) {
var res=atob(result);
return atob(result);
}
return result;
}
到了这里,我们可以看到使用了javascript的atob函数来为我们的数据进行解码。打一断点可以看到。数据如下所示。
"precision highp float;
uniform mat4 u_modelViewMatrix;
uniform mat4 u_projectionMatrix;
attribute vec3 a_position;
attribute vec2 a_texcoord0;
attribute float a_batchid;
varying vec2 v_texcoord0;
void main(void)
{
v_texcoord0 = a_texcoord0;
gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_position, 1.0);
}
"
上面就是我们需要的返回数据,回到刚才转换着色器的第三个if中的函数,shaderLoader()函数,这个函数里面,由传入的参数model,将source资源赋给本身。这里算是将数据解析完了。
function shaderLoad(model, type, id) {
return function(source) {
var loadResources = model._loadResources;
loadResources.shaders[id] = {
source : source,
type : type,
bufferView : undefined
};
--loadResources.pendingShaderLoads;
model._rendererResources.sourceShaders[id] = source;
};
}