在OpenGL中,这里要用到很多的纹理图,但是一般我们用的都只是一些小小的控件,所以图形一般都不是很大,一般的做法就是为每一个这样的
下图都去建立一个纹理,但是这个是很没有效率的。因为opengl对于纹理的处理是先把它放在硬件上面,处理完了然后把下一个纹理放上来,这样
如果问你过多就会产生很多没有必要的纹理替换的操作,所以很有必要把很多小图贴在一个大图形上面,这样我们只用管理一张大图就OK了,一般我们的纹理图的大小是有限制的,在G1上面,好像这个限制是1024*1024.所以很多很小的图标我们都可以放在一张大图上面。
在3D纹理贴图中,这里有一个这样的概念Texture Atlas,
纹理组合
在游戏或者可视化应用中,我们总是会遇到许多非常小的纹理,一种比较好的办法是我们把这些纹理组合成一个比较大的纹理,例
如256x256,这样驱动程序在加载纹理的video
memory的地址时候,驱动程序仅仅需要加载一次家可以了。这种方法在多个造型软件中也经常见到,例如人体造型软件Pose,它将一个人的头发,脸,眼
睛,等组合为一个纹理。
建议
: 将多个小纹理组合为一个大纹理,然后修改对应三角形定点的纹理坐标,或者使用glMatrixMode(GL_TEXTURE)对定点的纹理坐标作几何变换。
这里有一个效率不是很高的算法,来做这个事情:
private Bitmap _bmp;
private int _width;
private int _height;
private int _tileCols;
private int _tileRows;
public Texture(Bitmap bmp) {
_bmp = bmp;
_width = bmp.getWidth();
_height = bmp.getHeight();
_tileCols = 1;
_tileRows = 1;
inserted = false;
}
public Bitmap getBitmap() {
return _bmp;
}
public void cleanBitmap() {
_bmp.recycle();
}
public int getWidth() {
return _width;
}
public int getHeight() {
return _height;
}
public void setTileSize(int tileWidth, int tileHeight) {
_tileCols = _width / tileWidth;
_tileRows = _height / tileHeight;
}
public void setTileCount(int columns, int rows) {
_tileCols = columns;
_tileRows = rows;
}
public int getTileCols() {
return _tileCols;
}
public int getTileRows() {
return _tileRows;
}
}
然后就是我们纹理合并的一个类
private HashMap<Integer, Texture> _texture;
private int _textureCount;
private Bitmap _bmp;
private int _greatestWidth; //存放的是最大的一幅图的宽
private int _width;
private int _height;
public TextureAtlas() {
_texture = new HashMap<Integer, Texture>();
_textureCount = 0;
readyToLoad = false;
ready = false;
}
/**
* @return the width of the texture atlas
*/
public int getWidth() {
return _width;
}
/**
* @return the height of the texture atlas
*/
public int getHeight() {
return _height;
}
/**
* Creates a Texture and puts it onto the atlas
* @param resourceId reference to a drawable resource file, as described in R.java
* @return NULL if failed
*/
public Texture createTextureFromResource(int resourceId) {
Bitmap bmp = BitmapFactory.decodeResource(Rokon.getRokon().getActivity().getResources(), resourceId);
return createTextureFromBitmap(bmp);
}
/**
* Creates a Texture and puts it onto the atlas
* @param bmp a Bitmap object to build the texture from
* @return NLUL if failed
*/
public Texture createTextureFromBitmap(Bitmap bmp) {
Texture texture = new Texture(bmp);
if(bmp.getWidth() > _greatestWidth)
_greatestWidth = bmp.getWidth();
_texture.put(_textureCount, texture);
Debug.print("Added " + bmp.getWidth() + "x" + bmp.getHeight() + " " + _textureCount);
_textureCount++;
return texture;
}
/**
* Adds a Texture to the atlas, note: this is done automatically if createTextureXXX functions are used
* @param texture
*/
public void addTexture(Texture texture) {
_texture.put(_textureCount, texture);
_textureCount++;
}
/**
* Calculates a TextureAtlas from all the loaded Texture's
*/
public void compute() {
compute(0);
}
/**
* Calculates a TextureAtlas from all the loaded Texture's
* The Texture's are sorted into largest-first order, and a bin packing algorithm is used to squeeze
* into the atlas. There is a maximum total atlas size of 1024x1024 pixels, imposed by OpenGL.
* If your Texture's and Font's do not fit into this space, an exception will be raised.
*
* An improvement - to allow more atlases simulatenously, is being worked on.
* @param initwidth the minimum width of the atlas
*/
public void compute(int initwidth) {
_height = 0;
long now = Rokon.getTime();
int i = 0;
_width = initwidth;
if(_width == 0)
while(_width < _greatestWidth)
_width = (int)Math.pow(2, i++);
for(i = 0; i < _textureCount; i++)
_texture.get(i).inserted = false;
//Debug.print("Fitting textures into max width of " + _width + " pixels");
//这个插入的算法实在是不怎么样,但是比较好懂,我们按照从大到小的顺序找到所有纹理图,这个大小是按照面积的大小
//对于每一当前没有插入的找到的最大的纹理,我们要做下面的处理:
//
//为这张图找一个地方插入,插入的逻辑很简单,每次x+=16然后把新的x y坐标放入,看是不是很现有的重合,如果重合
//x再次+16,然后再看,当x的值都加到了边界的时候,这个时候,我们让x从左边再次开始,并且让Y值加上16
for(i = 0; i < _textureCount; i++) {
int greatestArea = 0;
int greatestIndex = -1;
for(int j = 0; j < _textureCount; j++) {
if(!_texture.get(j).inserted && _texture.get(j).getWidth() * _texture.get(j).getHeight() > greatestArea) {
greatestIndex = j;
greatestArea = _texture.get(j).getWidth() * _texture.get(j).getHeight();
}
}
if(greatestIndex != -1) {
Texture texture = _texture.get(greatestIndex);
int x = 0;
int y = 0;
boolean found = false;
while(!found) {
if(!isAnyoneWithin(x, y, x + texture.getWidth(), y + texture.getHeight())) {
texture.atlasX = x;
texture.atlasY = y;
found = true;
if(texture.atlasY + texture.getHeight() > _height)
_height = texture.atlasY + texture.getHeight();
} else {
x += 16;
if(x + texture.getWidth() > _width) {
x = 0;
y += 16;
}
}
}
_texture.get(greatestIndex).inserted = true;
} else
break;
}
//当把所有的图插入完毕的时候,我们替我们的高度也找一个刚好的2的N次方
int theight = _height;
_height = 0;
i = 0;
while(_height < theight)
_height = (int)Math.pow(2, i++);
if(_height > 1024) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024x1024");
if(_width < 64) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024px HIGH - RETRYING WITH MORE WIDTH");
compute(64);
return;
} else if(_width < 128) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024px HIGH - RETRYING WITH MORE WIDTH");
compute(128);
return;
} else if(_width < 256) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024px HIGH - RETRYING WITH MORE WIDTH");
compute(256);
return;
} else if(_width < 512) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024px HIGH - RETRYING WITH MORE WIDTH");
compute(512);
return;
} else if(_width < 1024) {
//Debug.print("CANNOT PRODUCE A TEXTURE ATLAS GREATER THAN 1024px HIGH - RETRYING WITH MORE WIDTH");
compute(1024);
return;
} else {
Debug.print("TRY AGAIN WITH FEWER TEXTURES - OR SPREAD TO ANOTHER ATLAS");
System.exit(0);
return;
}
}
_bmp = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(_bmp);
for(Texture texture : _texture.values()) {
canvas.drawBitmap(texture.getBitmap(), texture.atlasX, texture.atlasY, new Paint());
texture.cleanBitmap();
}
readyToLoad = true;
System.gc();
}
public Bitmap getBitmap() {
return _bmp;
}
public void cleanBitmap() {
_bmp.recycle();
}
private boolean isAnyoneWithin(int x, int y, int x2, int y2) {
for(Texture texture : _texture.values()) {
if(texture.inserted) {
boolean maybe = false;
if(texture.atlasX >= x && texture.atlasX <= x2)
maybe = true;
if(texture.atlasX <= x && texture.atlasX + texture.getWidth() > x)
maybe = true;
if(maybe) {
if(texture.atlasY >= y && texture.atlasY <= y2)
return true;
if(texture.atlasY <= y && texture.atlasY + texture.getHeight() > y)
return true;
}
}
}
return false;
}
}