Cesium源码分析&3dtile数据着色器解码

首先我来看一下这样的一个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;
        };
    }

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yGIS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值