我所理解的cocos2dx - 纹理(上)
3d图形渲染最重要就是把纹理贴到物体表面,这过程主要发生在着色器工作阶段,使用光栅化阶段插值计算得出纹理坐标从纹理中采样,然后对片段着色,可以处理丰富特效,光照阴影等。
光栅化
作用是将2d图元转为帧缓冲的整数坐标的片段,每个片段包括颜色深度和模版值。首先确认视窗上哪些整数位置的片段会被图元覆盖,然后对图元进行插值计算。这些信息会被送入后续的阶段进行处理,最后结果用于更新帧缓冲上的该位置信息。片段的颜色由片段着色器决定,光栅化会生成一些易变变量。
多重采样:高分辨率信号以低分辨率表示无法准确运算出3d图形坐标时导致的图形混叠,具体就是锯齿的产生。多重采样就是解决这问题,它是通过以采样点为中心位置,对附近进行采样,共同决定这个点的颜色。多重采样的数据存储在帧缓冲额外的多重采样缓冲区。cocos2dx默认没有开多重采样,要手动设置CCEAGLView为yes开启,并且只能初始化的时候开启。
纹理坐标:纹理以左下角为原点,一个顶点在纹理中坐标通常使用uv,长宽是纹理的长宽,这是给客户端程序使用的。片段着色器使用坐标st,范围为0-1,这一规化过程在光栅化阶段完成。
像素矩阵
定义:二维像素数组,表示区域内的颜色,深度或者模版值。
像素矩阵从内存传输到服务端的过程叫解包,从服务端读取到内存叫压包。
像素存储模式:纹理像素编码用void PixelStorei(enum pname, T param)设置,会影响到像素矩阵数据传输相关指令,初始值4,合法值1248
纹理数据的传输:客户端->服务端,输入是像素数据,输出是0-1的rgba像素值,在操作的命令中,都会包含一些基本参数:
format:一个像素矩阵中数据的构成
data:ubyte或者ushot数组,数组中的元素被按照1,2,3或者4个分量形成一个组
也就是说,format决定data的分量意义和顺序。最后那个是亮度?
type:表示每个像素分量的构成(format),以及data数组中每个元素的数据类型(data里数据组成)代表上图每一个分享的大小,565说明是5红5蓝6绿的大小占位,人眼对绿色敏感。
解包:format描述像素矩阵的数据类型,type决定每个分量的构成,所以所有编码格式的像素矩阵的数量由format和type共同决定。其所有组合方式如下:byte占据一个byte,ushort占据2个。像素矩阵数据的传输,应该适当的选择内存中数据的对齐方式(c++里也有类似的思想),提升数据传输的性能。这里只是简单的对齐,会由8-4-2-1方式进行检查,哪个满足用哪个对齐,其参数为UNPACK_ALIGNMENT。
unsigned_short_4444,5551,565会被包装成一个ushort,2个byte,16位,数字代表每个分量的构成。例如5551就是rgb和1位alpha,要么透明套么不透明,适合用于字体或者蒙板。
所有类型,格式的数据被解包之后,他们的byte或者short会被转为浮点型,实际上是执行了归一化计算[-1,1],之后还需要转为rgb格式,主要针对的是LUMINANCE和LUMINANCE_ALPHA,luminance会转化为rgb3个分量。所以最后,每个组都被转化为4个分量。一个像素矩阵就被解包为一个颜色值上传到gl缓冲区。
客户端图像格式
服务端上为了渲染性能,纹理数据都是没压缩的,而客户端为了节省占用空间,一般进行了压缩,所以在传输前,需要解压图像,转为服务端可支持的格式。
纹理格式的对应关系:
图像数据格式转换:客户端需要把不同的图片格式转换成对应的客户端格式,通过convertDataToFormat实现,默认为auto,auto会尝试转为最接近的格式。实际上默认为rgba8888,如果我们不需要如此高质量的图像,可以设置为其他,玩家也看不出。
纹理对象和加载纹理
纹理在片段着色器被使用。
纹理对象是一个容器,拥有该纹理所需的所有数据,包括像素数据,过滤模式,扩展模式等。
glGenTextures(GLsizei n, GLuint *textures)创建纹理,n表示创建纹理对象的数量,textures用于保存分配的纹理名称。
glDeleteTetures(同上)删除
glBindTeture(GLenum target, GLuint texture)为了操作纹理,需要绑定一个当前操作的纹理,因为opengl不持有纹理的指针。
glTexImage2D(GLenum target, GLint level, GLenum internalFormat, width, height, border, format, type, pixels)target为纹理的一个面,level为多级纹理的级别,internalFormat为纹理在GL的存储格式,wh尺寸,format表示客户端图像数据的构成和顺序,type是gl中纹理的格式和数据类型,pixels表示客户端的图像数据缓冲对象。
纹理一旦传输到gl服务端,就会一致驻留在gpu,所以不用时记得删掉。
纹理单元和多重纹理
opengl 支持一个管线中使用多个纹理,使用纹理单元管理多个纹理的使用,每一个纹理对象都被放到一个纹理单元中,用glActiveTexture来激活纹理单元。激活实际上就是设置当前纹理单元,这样后续的命令可以将纹理绑定到这个单元上,通过宾得TextureDN来绑定纹理到纹理单元上。
纹理缩放
纹理被检测到缩放的时候,opengl会使用TEXTURE_MIN_FILTER或者TEXTURE_MAG_FILTER进行纹素过滤。
纹理缩小:多个紋素放到一个像素点去,将用texture_min_filiter来决定紋素的选择,有2个值:
GL_NEAREST:选择离坐标中心最近的紋素,速度快,导致严重失真
GL_LINEAR:选择坐标中心附近一个2*2的区域,进行双线性插值计算,得出一个合理的颜色值,损失性能,每一帧都进行,效果平滑
纹理放大:一般一个紋素用到多个像素点去,同上,nearest最近点取样,linear4个点求平均。
cocos2dx中设置过滤模式
设置过滤模式的方法有:
setTexParamters(const TexParam& texParams); //各种缩放纹理过滤模式,还能设置纹理的重复模式,来决定纹理超出尺寸的采样行为
setAntiAliansTexParameters(); //锯齿
setAliasTexParmeters(); //反锯齿
多级纹理
就预设好很多个不同size的图片,后续进行选择,其TEXTURE_MIN_FILTER新增4种模式
GL_NEAREST_MIPMAP_NEAREST:选择最近级别的纹理进行最近点采样
GL_NEAREST_MIPMAP_LINEAR:选择最近2个级别的纹理进行最近点采样,然后取线性插值
GL_LINEAR_MIPMAP_NEAREST:选择最近级别的纹理进行双线性采样
GL_LINEAR_MIPMAP_LINEAR:选择最近2个级别的纹理进行最近点采样,然后双线性采样,再取2个采样的线性插值
多级纹理的上传:level参数决定纹理的级别,一般最大尺寸为0。可以使用initWithMipmaps来上传多级纹理。这里可以针对低分辨率的设备避免上传过大的纹理,节省内存
多级纹理的生成:
glGenerateMipMap()生成,需要手动调用,但每次上传纹理需要执行,有一定性能消耗,但可减少内存占用。
使用工具,pvrtextool之类
纹理压缩
纹理需要传进gpu里,转为rgb或者rgba来处理,占用大量内存。
压缩纹理的特点:
1.解压速度:需要有较快的解压速度
2.随机读取:有时候只需要纹理的一部分,就需要随机读取的能力
3.压缩率和图像质量:一般为有损压缩
4.编码速度:没太大要求,因为一般发生在程序外
压缩纹理的实现:传统图像压缩使用可变的压缩比率,导致读某位置需要解压更多位置,不利于随机读取。压缩纹理用固定压缩比率,按比率分成多个像素块,每一个2*2或者4*4,然后对一块进行压缩,存在一个像素集合中(codebook),一块索引图(Index Map)中。读取时根据索引找个像素块,解压,读取偏移值信息,称为基于块的压缩算法。
pvrtc 和 pvrtc2:ios全产品和安卓大部分都支持这2个压缩格式。
pvrtc只支持pot纹理,网上说这个pot纹理在苹果里有个bug,会导致内存占用增多,大家都希望用npot。而pvrtc2就是增强版,支持了npot,增强画质,支持alpha预乘。两种都支持2bpp和4bpp的压缩比率。
etc,几乎所有安卓都支持这个。
支持4bpp,不支持alpha通道。有几个方式让他支持:
1.将alpha转为可见的灰度图像,拼接在一起,使得纹理高度为原来的2倍。缺点是纹理尺寸变大,限制原纹理的尺寸,有的不支持那么大
2.alpha通道单独生成一个纹理,用多重纹理技术绑定一起,后面混合即可。