这次还是说纹理,来看看纹理的尺寸问题以及纹理中的Mipmapping的概念。在看完本篇文章你将了解到:
- WebGL中最大纹理限制是多少,如何知道
- 如何选择合理的WebGL纹理尺寸
- Mipmapping的概念和使用场景
WebGL中最大的纹理尺寸
在WebGL中最大的纹理尺寸可以通过下面的方法获取:
var maxTexSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
通过 gl.MAX_TEXTURE_SIZE
常量可以拿到当前浏览器支持的最大纹理尺寸。在我的浏览器中,这个数值为16384,即2的14次方。那么这个数值代表的含义是什么?是纹理图像的像素数?还是长度、宽度?不幸的是在WebGL文档里并没有明确说明这个值的含义,我所读过的webgl的书籍中也都没有提到。于是我去查看OpenGL的相关文档,发现是这么说的:
The value gives a rough estimate of the largest texture that the GL can handle. The value must be at least 64.
好吧,还是没说清楚。但是我们获得了这样的信息:首先这是一个粗略地估计,其次这个值至少为64。我们再来看glTexImage2D这个方法的文档,其中有针对width和height的说明,不论宽度还是高度,都有如下这段描述:
All implementations support 2D texture images that are at least 64 texels wide and cube-mapped texture images that are at least 16 texels wide.
我们看到这里要求宽度和高度至少拥有64个texel,texel就是指pixel(像素),只不过为了将最终显示在屏幕上的pixel与纹理的pixel区分开来,才用texture与pixel合成在一起造了一个新词。我们看最大纹理尺寸要求不能小于64,纹理的宽高也都不能小于64,那么由此推断,纹理的尺寸就是指纹理的宽度和高度,纹理的最大值尺寸就是要求纹理的宽度和高度的最大值。以我的浏览器为例,要求高度和宽度都不能超过16384个像素。但通过实验发现,当宽度和高度都为16384时,纹理就绘制不出来了。由于是粗略估计,在实际使用时还是要取一个小于最大值的图来用。
如果想保持所有平台使用相同尺寸的纹理,降低程序复杂度,4096x4096会是一个比较保险的值,但是在手机浏览器上要减半。
纹理的尺寸该如何选择
我们还是拿做砖墙来举例,一张砖墙的纹理选择多大尺寸呢?200x200还是400x400?256x256还是512x512?选择尺寸需要考虑如下几个因素:
纹理尺寸对内存的影响
一个大家都知道的基本常识是纹理尺寸越大占用的内存就越大,包括创建DOM所用的内存,以及GPU的缓存,这毫无疑问。还有一点,如果长宽是2的整数次幂,比如128、256、512,那么在存储时使用内存的效率较高,否则容易产生内存碎片。
纹理尺寸对GPU运算的影响
纹理附着在物体表面最终显示在屏幕上,这里有一个从纹理像素texel到屏幕像素pixel的映射过程,也就是需要针对纹理的所有像素点进行计算,纹理尺寸最大,运算量也越大。
纹理尺寸对显示效果的影响
只要纹理的尺寸和最终显示在屏幕上的尺寸存在差别,那么就会涉及到像素的插值运算,纹理要么被缩小了,要么就被放大了。不论是放大还是缩小,最终显示出来总会和原始图片有差别,我们当然希望这个差别越小越好。这里的原则就是尽可能让纹理以差不多原始尺寸的大小显示在屏幕上。
其他
还有某些纹理的功能仅支持长度宽度是2的整数次幂的纹理。比如我们后面马上就要介绍的mipmapping技术。
综上,纹理的尺寸要满足以下条件:
- 长度和宽度是2的整数次幂。也可以使用任意尺寸,但是此时不可以使用mipmapping技术,且wrap的方式必须是
gl.CLAMP_TO_EDGE
。 - 大小尽可能和最终显示的大小相当
Mipmapping
上面说到纹理尽可能以原始尺寸的大小显示在屏幕上,但是在现实的3D场景中这几乎是不可能的。首先纹理附着在三维物体表面肯定会有形变,再有随着视角位置的变化三维物体的位置可近可远,较近时会放大拉伸纹理,较远则会压缩纹理尺寸。
如果我们仍旧想满足上面的尺寸要求,那么就要根据物体在屏幕上的大小动态选择和使用不同尺寸的纹理,这么做当然可以,可是却很复杂。WebGL提供了一套机制可以轻松实现此方案:Mipmapping。
Mipmapping会自动生成若干小尺寸的纹理,根据当前三维物体在屏幕上的大小来自动选择最合适的尺寸。使用mipmapping要求纹理的长度和宽度必须是2的整数次幂。比如,你的纹理尺寸为256x256,那么mipmapping操作后会自动生成128x128、64x64、32x32、16x16、8x8、4x4、2x2、1x1这些尺寸的纹理。如下图所示:
代码调用方式为:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D);
还有一种方式,需要你事先生成好不同尺寸的纹理,然后通过 texImage2D
来设置mipmap:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image_0); gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image_1); gl.texImage2D(gl.TEXTURE_2D, 2, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image_2);
在上面代码中,我们调用了三次 texImage2D
,每次传递不同的纹理图片。注意第二个参数,它是来指定mipmap的level,0表示原始级别,1表示缩小一半的级别、2表示再缩小一半的级别。
还有很重要的一点不要忘记,我们需要指定纹理缩小时需要采取的采样方式也为mipmapping,比如:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
与mipmapping相关的参数有四个,分别是:
NEAREST_MIPMAP_NEAREST
:选择与显示尺寸最相近的一个纹理,然后运用NEAREST算法进行采样。LINEAR_MIPMAP_NEAREST
:选择与显示尺寸最相近的一个纹理,然后运用LINEAR算法进行采样。NEAREST_MIPMAP_LINEAR
:选择两个与显示尺寸相近的纹理,然后都运用NEAREST算法进行采样,最终再算一个加权平均值。LINEAR_MIPMAP_LINEAR
:选择两个与显示尺寸相近的纹理,然后都运用LINEAR算法进行采样,最终再算一个加权平均值。这种模式也被称为trilinear filtering(三线性过滤),运算量大效果也最好。
在这里插入一个话题,不知道是否有人对mipmap这个名称来源感兴趣,mip实际上是三个拉丁词的首字母:multum in parvo,翻译成英文就是:much in little,其意思也就是说可以在一个纹理对象中生成很多不同尺寸的纹理。
这里有两个完整的示例,可以看到使用mipmapping和不使用的区别。
通过界面截图可以看出差别:
上面为使用了mipmapping的效果,下面为未使用。
Tiling
最后再说说纹理运用的另外一个技术:Tiling。其核心思想是,对于一块较大的纹理,我们可以事先将其切分成若干较小的纹理(每个小块则称为一个tile),每个纹理独立加载和使用。这样的好处是每个纹理可以单独请求和使用,不必等待一张大纹理加载完成才能展现。我们还可以判断物体的某个部分是否可见,不可见的部分所对应的纹理就没有必要加载。
比如传统的web地图就是基于Tiling思想实现的,程序根据窗口内视野所在的范围,加载对应的地图图块,视野外的部分则不需要加载。Chrome在渲染页面的时候也采用了Tiling技术,可以在这里详细了解。
留言