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还是很有必要的,而且并不复杂。