1. ファイルの変換方法
[準備] パスを追加 $ vi ~/.bash_profile export PATH=$PATH:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin [利用方法] $ texturetool --bits-per-pixels-2 --channel-weighting-linear ¥ -e PVRTC -fPVR -o output.pvr input.png --bits-per-pixels-2 2bit(サイズ1/16) --bits-per-pixels-4 4bit(サイズ1/8) --channel-weighting-linear 誤差をRGBに均等に分散 --channel-weighting-perceptual 緑を優先に -m ミップマップをつける(ファイルサイズが 1.3倍になる) [変換サイズ例] input.png 328kB(パレットカラー) 非圧縮だと4MB output-2.pvr 256kB output-4.pvr 512kB
2. テクスチャとして読み込む方法
const char PVRIdentifier[4] = { 'P', 'V', 'R', '!' }; enum Constants { PVR_FLAG_TYPE_PVRTC_2 = 24, PVR_FLAG_TYPE_PVRTC_4 = 25, PVR_FLAG_TYPE_MASK = 0xff, PVR_MAX_SURFACES = 16 }; struct Header { uint32_t headerSize; uint32_t height; uint32_t width; uint32_t numMipmaps; uint32_t flags; uint32_t dataSize; uint32_t bpp; uint32_t bitmaskRed; uint32_t bitmaskGreen; uint32_t bitmaskBlue; uint32_t bitmaskAlpha; uint32_t tag; uint32_t numSurfaces; }; struct Surface { GLuint size; const void* bits; }; static void _loadCompressedTexture( const char* fname, uint *texture, uint *_width, uint *_height) { char *buf; GLuint width, height; GLenum format; //bool hasAlpha; GLuint numSurfaces; Surface surfaces[PVR_MAX_SURFACES]; NSString* fileLocation = [[NSBundle mainBundle] pathForResource:[NSString stringWithUTF8String:fname] ofType:@""]; std::ifstream f; f.open([fileLocation UTF8String]); if(!f.good()) return; f.seekg(0, std::ios::end); int size = f.tellg(); f.seekg(0, std::ios::beg); buf = new char[size]; f.read(buf, size); const Header& header = *reinterpret_cast(buf); uint32_t tag = OSSwapLittleToHostInt32(header.tag); if(PVRIdentifier[0] != ((tag >> 0) & 0xff) || PVRIdentifier[1] != ((tag >> 8) & 0xff) || PVRIdentifier[2] != ((tag >> 16) & 0xff) || PVRIdentifier[3] != ((tag >> 24) & 0xff)) { return; } uint32_t flags = OSSwapLittleToHostInt32(header.flags); uint32_t formatFlags = flags & PVR_FLAG_TYPE_MASK; if(formatFlags == PVR_FLAG_TYPE_PVRTC_4 || formatFlags == PVR_FLAG_TYPE_PVRTC_2) { if(formatFlags == PVR_FLAG_TYPE_PVRTC_4) format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; else if(formatFlags == PVR_FLAG_TYPE_PVRTC_2) format = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; else return; // false; width = OSSwapLittleToHostInt32(header.width); height = OSSwapLittleToHostInt32(header.height); // hasAlpha = OSSwapLittleToHostInt32(header.bitmaskAlpha) ? true : false; numSurfaces = 0; GLuint w = width; GLuint h = height; GLuint offset = 0; GLuint size = OSSwapLittleToHostInt32(header.dataSize); const uint8_t* pBytes = reinterpret_cast(buf) + sizeof(header); while(offset < size && numSurfaces < PVR_MAX_SURFACES) { GLuint blockSize, widthBlocks, heightBlocks, bpp; Surface& surface = surfaces[numSurfaces++]; if (formatFlags == PVR_FLAG_TYPE_PVRTC_4) { blockSize = 4 * 4; widthBlocks = w / 4; heightBlocks = h / 4; bpp = 4; } else { blockSize = 8 * 4; widthBlocks = w / 8; heightBlocks = h / 4; bpp = 2; } if (widthBlocks < 2) widthBlocks = 2; if (heightBlocks < 2) heightBlocks = 2; surface.size = widthBlocks * heightBlocks * ((blockSize * bpp) / 8); surface.bits = &pBytes[offset]; (w >>= 1) || (w = 1); (h >>= 1) || (h = 1); offset += surface.size; } if(numSurfaces <= 0) return; // テクスチャに登録 w = width; h = height; (*_width) = width; (*_height) = height; for(GLuint i = 0 ; i < numSurfaces ; ++i) { const Surface& surface = surfaces[i]; glGenTextures(1, texture); glBindTexture(GL_TEXTURE_2D, (*texture) ); // テクスチャの設定を行う // glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glCompressedTexImage2D(GL_TEXTURE_2D, i, format, w, h, 0, surface.size, surface.bits); (w >>= 1) || (w = 1); (h >>= 1) || (h = 1); } glBindTexture(GL_TEXTURE_2D, 0 ); free(buf); } }
3. 結果
圧縮画像を使用するとメモリ使用量が50%以下に低減した(1/8, 1/16まで小さくはならなかった)。2bit/4bitの圧縮はイラストだと線の描画でジャギーが目立つ。
自然画像を多用する場合は、iPhoneに限りPVTRCを利用することを検討するのが良いと思う。