OpenGL / OpenGL ES の場合 KTX 形式のテクスチャの読み込みは簡単です。
ファイルを全部メモリに読み込んだと仮定して
const void* address_add( const void* a, uintptr_t b ) { return reinterpret_cast( reinterpret_cast ( a ) + b ); } GLuint KTX_Loader( const void* memory, size_t memory_size ) { const KTX_Header* hp= reinterpret_cast ( memory ); const void* image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData ); image_data= address_add( image_data, sizeof(int32_t) ); GLuint texid= 0; glGenTextures( 1, &texid ); glBindTexture( GL_TEXTURE_2D, texid ); glTexImage2D( GL_TEXTURE_2D, 0, hp->glInternalFormat, hp->pixelWidth, hp->pixelHeight, 0, hp->glBaseInternalFormat, hp->glType, image_data ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); return texid; }
このように、API に渡す必要な情報が全部ヘッダに入っています。
ヘッダの構造は下記を参照してください。
・KTX File Format Specification
圧縮テクスチャの場合も簡単です。glType が 0 なら圧縮テクスチャです。
GLuint KTX_Loader_Compressed( const void* memory, size_t memory_size ) { const KTX_Header* hp= reinterpret_cast( memory ); const void* image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData ); unsigned int image_size= *reinterpret_cast ( image_data ); image_data= address_add( image_data, sizeof(int32_t) ); GLuint texid= 0; glGenTextures( 1, &texid ); glBindTexture( GL_TEXTURE_2D, texid ); glCompressedTexImage2D( GL_TEXTURE_2D, 0, hp->glInternalFormat, hp->pixelWidth, hp->pixelHeight, 0, image_size, image_data ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); return texid; }
上の例はそのまま API に渡しているので、GPU が対応していない圧縮形式の場合
エラーが発生する可能性があります。
厳密には CPU とファイルの endian が一致していないケースを考慮しなければなりません。
ヘッダの endiannness を見て必要に応じて byte swap します。
データ作成環境と実行環境が一致していればあまり問題はないと思います。
KTX の注意点は DDS とデータの並びが異なっていることです。
例えば cubemap の場合、DDS は face 毎に mipmap が並びますが、
KTX は同じ mip level の face が先に来ます。
DDS KTX -------------------------- [X+ 32x32] [X+ 32x32] [X+ 16x16] [X- 32x32] [X+ 8x8] [Y+ 32x32] [X+ 4x4] [Y- 32x32] [X+ 2x2] [Z+ 32x32] [X+ 1x1] [Z- 32x32] [X- 32x32] [X+ 16x16] [X- 16x16] [X- 16x16] [X- 8x8] [Y+ 16x16] [X- 4x4] [Y- 16x16] [X- 2x2] [Z+ 16x16] [X- 1x1] [Z- 16x16] [Y+ 32x32] [X+ 8x8] [Y+ 16x16] [X- 8x8] [Y+ 8x8] [Y+ 8x8] [Y+ 4x4] [Y- 8x8] [Y+ 2x2] [Z+ 8x8] [Y+ 1x1] [Z- 8x8] : :
↑このようにデータの順番が異なっています。
wiki にも追記しましたが、データの入れ子構造の詳細は下記の通りです。
DDS : ( ( ( ( width * height ) * volume_depth ) * mip ) * cube_face ) * array KTX : ( ( ( ( width * height ) * volume_depth ) * cube_face ) * array ) * mip
DDS の利点は、2D のテクスチャを 6 個並べるだけで cubemap ができること。
KTX の利点は同じサイズのサーフェースを出来るだけ一度に並べようとしていることです。
KTX の画像は下記のように 32bit のデータサイズが挿入されます。
int32 imageSize; // 32x32 [X+ 32x32] [X- 32x32] [Y+ 32x32] [Y- 32x32] [Z+ 32x32] [Z- 32x32] int32 imageSize; // 16x16 [X+ 16x16] [X- 16x16] [Y+ 16x16] [Y- 16x16] [Z+ 16x16] [Z- 16x16] int32 imageSize; // 8x8 [X+ 8x8] [X- 8x8] [Y+ 8x8] [Y- 8x8] [Z+ 8x8] [Z- 8x8] int32 imageSize; // 4x4 ~
imageSize は 1画像分の byte 数で、同じサイズのサーフェースが
並んでいるためアドレス計算が簡単になります。
glCompressedTexImage2D() にそのまま渡すことができます。
mipmap に対応するとこんな感じです。
GLuint KTX_Loader_Mipmap( const void* memory, size_t memory_size ) { const KTX_Header* hp= reinterpret_cast( memory ); const void* image_data= address_add( memory, sizeof(KTX_Header) + hp->bytesOfKeyValueData ); GLuint texid= 0; glGenTextures( 1, &texid ); glBindTexture( GL_TEXTURE_2D, texid ); unsigned int mip_level= hp->numberOfMipmapLevels > 0 ? dp->numberOfMipmapLevels : 1; unsigned int width= hp->pixelWidth; unsigned int height= hp->pixelHeight; for( unsigned int mip= 0 ; mip < mip_level ; mip++ ){ unsigned int image_size= *reinterpret_cast ( image_data ); image_data= address_add( image_data, sizeof(int32_t) ); glTexImage2D( GL_TEXTURE_2D, mip, hp->glInternalFormat, width, height, 0, hp->glBaseInternalFormat, hp->glType, image_data ); width= int_max( width>>1, 1 ); height= int_max( height>>1, 1 ); image_data= address_add( image_data, (image_size+ 3) & ~3 ); } if( mip_level > 1 ){ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); }else{ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); return texid; }
これで mip + 圧縮テクスチャ や cubemap への対応も容易にできます。
KTX の一番の問題はデータの作成方法にあるようです。
ツールが絶望的に揃っていない状況で、まだあまり安定もしていません。
ARM の Texture Compression Tool は 64bit 環境でうまく動かず、
PVR の TexTool で ktx に出力すると PVRTC 以外は正しいヘッダ値が入りませんでした。
関連ページ
・Texture File Format
はじめまして。
いつも参考にさせていただいています。
KTXですがPVRTexTool(4.04 SDK Build: 2.10@905358)でPVRTCを出力してもimageSizeがないため、仕様どおりに読み込めませんでした。
pvr形式にはimageSizeがないので、それと混ざってしまっているんでしょうかね…
ありがとうございます。
確かに pvr v3 をそのまま流用してるのかも知れません。
ktx にはリファレンスとなるツールが必要ですね。