首页 > 代码库 > Cesium原理篇:6 Renderer模块(2: Texture)

Cesium原理篇:6 Renderer模块(2: Texture)

       Texture也是WebGL中重要的概念,使用起来也很简单。但有句话叫大道至简,如果真的想要用好纹理,里面的水其实也是很深的。下面我们来一探究竟。

       下面是WebGL中创建一个纹理的最简过程:

var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
// 创建纹理句柄
var texture = gl.createTexture();
// 填充纹理内容
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// 设置纹理参数
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// 释放
gl.bindTexture(gl.TEXTURE_2D, null);

       如果你觉得上面的这段代码简单易懂,不妨在看看WebGL中提供的gl.glTexImage2D的重载方法:

void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);
void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels);
void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels);
void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels);
void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels);

     一个再简单的纹理调用,在实际中也会有变幻无穷的方式,而这就是实现功能和产品封装上的区别,Cesium中提供了Texture类,整体上考虑了主要的使用场景,在代码设计上简化了学习成本,当然在编码上也较为优雅,我们不妨看一下Cesium中创建纹理的伪代码:

function Texture(options) {
    // 如下三个if判断,用来查看是否是深度纹理、深度模版纹理或浮点纹理
    //  并判断当前浏览器是否支持,数据类型是否满足要求
    if (pixelFormat === PixelFormat.DEPTH_COMPONENT) {
    }

    if (pixelFormat === PixelFormat.DEPTH_STENCIL) {
    }

    if (pixelDatatype === PixelDatatype.FLOAT) {
    }

    var preMultiplyAlpha = options.preMultiplyAlpha || pixelFormat === PixelFormat.RGB || pixelFormat === PixelFormat.LUMINANCE;
    var flipY = defaultValue(options.flipY, true);

    var gl = context._gl;
    var textureTarget = gl.TEXTURE_2D;
    var texture = gl.createTexture();

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(textureTarget, texture);

    if (defined(source)) {
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
        // Y轴方向是否翻转
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);

        if (defined(source.arrayBufferView)) {
            // 纹理数据是arraybuffer的形式下,调用此方法
            gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, source.arrayBufferView);
        } else if (defined(source.framebuffer)) {
            // 纹理数据是纹理缓冲区中的数据时,调用此方法
            if (source.framebuffer !== context.defaultFramebuffer) {
                source.framebuffer._bind();
            }

            gl.copyTexImage2D(textureTarget, 0, internalFormat, source.xOffset, source.yOffset, width, height, 0);

            if (source.framebuffer !== context.defaultFramebuffer) {
                source.framebuffer._unBind();
            }
        } else {
            // 纹理数据是其他类型: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
            gl.texImage2D(textureTarget, 0, internalFormat, pixelFormat, pixelDatatype, source);
        }
    } else {
        // 纹理数据为空
        gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, null);
    }
    gl.bindTexture(textureTarget, null);
}

       Cesium.Texture支持纹理贴图,还有深度和模版,以及浮点纹理等扩展性的用法,保证了Cesium可以支持深度值,模版等操作,满足一些复杂情况下的需求,同时,通过Texture.fromFramebuffer方式,可以支持FBO作为一张纹理,实现离屏渲染的效果。因此,在纹理数据创建上,Cesium还是比较完整的。

       同时,Cesium.Sample类提供了数据的一些显示风格设置,比如TextureWrap,Filter的设置,在Texture类中有一个sampler的属性,用户在赋值时自动设置:

sampler : {
    get : function() {
        return this._sampler;
    },
    set : function(sampler) {
        // ……

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
        gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
        gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
        if (defined(this._textureFilterAnisotropic)) {
            gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
        }
        gl.bindTexture(target, null);

        this._sampler = sampler;
    }
},

       另外,为了解决纹理闪烁的情况,Cesium中提供了MipMap的设置方式:

Texture.prototype.generateMipmap = function(hint) {
    hint = defaultValue(hint, MipmapHint.DONT_CARE);

    var gl = this._context._gl;
    var target = this._textureTarget;

    gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(target, this._texture);
    gl.generateMipmap(target);
    gl.bindTexture(target, null);
};

       当然,这种方式比较方便,浏览器内部自己创建MipMap,相当于一个影像金字塔的过程,如果你出于效率和效果的优化,希望自己创建MipMap也是可以的,不过目前的Cesium.Texture还不支持这种情况。

       个人认为,目前Texture实现的中规中矩,基本支持了各种纹理情况,能够满足后面模版缓存,深度缓存等高级用法,并对这一部分做了一个很好的封装,能够满足各类应用。但如果想要用好纹理,其实里面还有很多可以扩展的地方,比如支持压缩纹理,这对于显存的意义,特别是Cesium这种比较消耗显存的应用(特别是移动端),还是很有意义的。对纹理压缩技术感兴趣的,可以读一下这篇《为什么需要纹理压缩》,当然效率高也是有代价了,比如效果和兼容性,另外,随着对纹理创建的增加,个人认为增加一个纹理管理器TextureManager还是很有必要的,而且并不复杂。

Cesium原理篇:6 Renderer模块(2: Texture)