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還是很有必要的,而且並不復雜。