前提
『マルチプラットフォームのためのOpenGL ES入門 基礎編―Android/iOS対応グラフィックスプログラミング』
の抜粋メモです。10章の内容。
コードについては断片のみなので、本書を読んでください。
テクスチャのパラメータ
UV範囲の変更
const GLfloat uv[] = {
// v0(left, top)
0.25f, 0.25f,
// v0(left, bottom)
0.25f, 0.75f,
// v0(right, top)
0.75f, 0.25f,
// v0(right, bottom)
0.75f, 0.75f
};
const GLfloat uv[] = {
// v0(left, top)
0.25f, 0.25f,
// v0(left, bottom)
0, 0.75f,
// v0(right, top)
1.0f, 0.25f,
// v0(right, bottom)
0.75f, 0.75f
};
テクスチャのラッピング
UV を 0-1 の範囲を超えて指定してみる。
const GLfloat uv[] = {
// v0(left, top)
-1.0f, -1.0f,
// v0(left, bottom)
-1.0f, 2.0f,
// v0(right, top)
2.0f, -1.0f,
// v0(right, bottom)
2.0f, 2.0f
};
OpenGL ES では、テクセルの範囲外にアクセスした場合、画像がそこに敷き詰められて(ラッピング)いるように扱う。
ラッピング方法は3種類あり、glTexParameteri コマンドの引数によって指定できる。
void glTexParameteri(GLenum target, GLenum pname, GLint param)
- pname
- GL_TEXTURE_WRAP_S : 横方向のラッピングを指定
- GL_TEXTURE_WRAP_T : 縦方向のラッピングを指定
- param
- GL_REPEAT : 画像が無限に敷き詰められる(NPOT制限あり)
- GL_CLAMP_TO_EDGE : エッジのピクセルの色が敷き詰められる(NPOT制限なし)
- GL_MIRRORED_REPEAT : 画像が反転しながら敷き詰められる(NPOT制限あり)
NPOT は、Non Power Of Two(2のべき乗でない)の意味。テクスチャは基本的に1辺が2のn乗であることを推奨し、それ以外に対して強い制限がある。上の NPOT 制限ありのパラメータは、NPOT のテクスチャに対して設定できない。
サンプルでは
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
テクスチャのフィルタ
描画時、テクセルとディスプレイのピクセルが 1:1(Dot by Dot) にならないことがある。例えば、テクスチャを拡大したり縮小したりして表示するとき。Photoshop などの画像ツールでは、その際に画質の劣化を防ぐための「フィルタ」が存在する。例えば、
- Nearest Nabor
- Bilinear
- Bicubic
GL_NEAREST フィルタリングと GL_LINEAR フィルタリング
サンプル:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
これは、拡大時、縮小時のフィルタリング方法をそれぞれ指定している。指定できるオプションはそれぞれ、
- GL_TEXTURE_MAG_FILTER
- GL_NEAREST : 処理対象に最も近いテクセルの色を参照する
- GL_LINEAR : 処理対象の近傍4テクセルの色を参照して平均を求める
- GL_TEXTURE_MIN_FILTER
- GL_NEAREST : 処理対象に最も近いテクセルの色を参照する
- GL_LINEAR : 処理対象の近傍4テクセルの色を参照して平均を求める
- GL_NEAREST_MIPMAP_NEAREST : 最適なミップマップを選択した上で、処理対象に最も近いテクセルの色を参照
- GL_LINEAR_MIPMAP_NEAREST : 最適なミップマップを選択した上で、処理対象の近傍4テクセルの色を参照して平均を求める
- GL_NEAREST_MIPMAP_LINEAR : 最適な2つのミップマップを選択し、処理対象に最も近いテクセルの色を求める。その後2つの色の平均から最終的な色を求める。
- GL_LINEAR_MIPMAP_LINEAR : 最適な2つのミップマップを選択し、処理対象の近傍4テクセルの色を参照して平均を求める。その後2つの色の平均から最終的な色を求める。
GL_LINEAR はその特性上、画像のエッジが損なわれてしまう(ボケる)。ドット絵などは、NEAREST の方が画質が良く見えたりもする。
MIPMAPは後述。
Mipmap の利用
縮小時に大きな効果をもたらすフィルタ。縮小処理のみに適用できる。
NEAREST や LINEAR はリアルタイムに計算を行うが、Mipmap は縮小処理を自前に行うことで高品位な縮小画像を利用できる。
512x512 の場合、256x256, 128x128, 64x64,,,という画像を事前に用意しておくことで、OpenGL ES がそれらのテクスチャを描画先の解像度に合わせて選択してくれる。
サンプル(sample_texture_op_mipmap_GL_NEAREST_MIPMAP_NEAREST.c)では、
int mipmap_level = 0;
const char *file_names[] = {
//
// mipmap level 0
"texture_rgb_512x512.png",
// mipmap level 1
"texture_rgb_256x256.png",
// mipmap level 2
"texture_rgb_128x128.png",
// mipmap level 3
"texture_rgb_64x64.png",
// mipmap level 4
"texture_rgb_32x32.png",
// mipmap level 5
"texture_rgb_16x16.png",
// mipmap level 6
"texture_rgb_8x8.png",
// mipmap level 7
"texture_rgb_4x4.png",
// mipmap level 8
"texture_rgb_2x2.png",
// mipmap level 9
"texture_rgb_1x1.png", };
for (mipmap_level = 0; mipmap_level < 10; ++mipmap_level) {
RawPixelImage *image = NULL;
image = RawPixelImage_load(app, file_names[mipmap_level], TEXTURE_RAW_RGBA8);
// 正常に読み込まれたかをチェック
assert(image != NULL);
// VRAMへピクセル情報をコピーする
glTexImage2D(GL_TEXTURE_2D, mipmap_level, GL_RGBA, image->width, image->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixel_data);
assert(glGetError() == GL_NO_ERROR);
// コピー後は不要になるため、画像を解放する
RawPixelImage_free(app, image);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// フィルタをMIPMAP対応に変更する
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
assert(glGetError() == GL_NO_ERROR);
glTexImage2D を10回読み込んで、VRAM にコピー(glTexImage2D)する際に mipmap レベルを指定している。最大レベルが0。
その後、glTexParameteri() で、縮小時のフィルタを MIPMAP_NEAREST に設定している。
その後、描画時にポリゴンが少しずつ小さくなるようにサイズを動的に設定。
Mipmap は画質を向上させるための最適解だが、トレードオフとしてメモリ使用量と画像読み込み時間・それをテクスチャにアップロードするだけの時間が増すことになる。
glGenerateMipmap
Mipmap 分の画像を事前に用意できない場合(ユーザのアップロードなど)、glGenerateMipmap コマンドを利用すると、OpenGL ES が自動的にレベル1以降すべての Mipmap を生成してくれる。
void glGenerateMipmap(GLenum target)
- target : GL_TEXTURE_2D または GL_TEXTURE_CUBE_MAP
使い方は、レベル0のテクスチャをアップロードしてフィルタを設定した後に、上記コマンドを呼び出すだけ。(ただし Photoshop などで作った場合に比べてもちろん品質は落ちる)。また、圧縮テクスチャを利用しておらず、一辺のテクセル数が 2 の n 乗である必要がある。
※ NPOT制限は、「正方形である」必要はない。長方形の Mipmap も生成できるが、実行環境の GPU が GL_OES_texture_npot 拡張に対応している必要はある。
Mipmap の問題点
描画が行われる際に Mipmap レベルは「ポリゴン単位」で選択される。これは GPU がテクスチャを効率よく処理できるが、歪んだ形状のポリゴンを描画する際に問題になることがある。
たとえば、複数の三角形に分割したときそのサイズが大きく違う台形などでは、それぞれの三角形で Mipmap レベルが変わってくる。「床」や「壁」といった斜めに見ることが多いポリゴンに影響が出てくる。
これを回避するためには、ある程度ポリゴンを細かく分割することが必要になる(p.231)
NPOT テクスチャの利用
OpenGL に限らず 3D 描画ライブラリは、Power of Two なピクセル数のテクスチャを効率的に扱うことができる。OpenGL ES 1.1 までは基本的にサポートされていなかったが、2.0 では一部制限ありで NPOT テクスチャが利用できるようになった。
ただしラッピングは基本的に GL_CLAMP_TO_EDGE のみ利用できる。かっこわるい。
サンプルでは、拡張機能に対応しているかどうかを確認して、互換シェーダを使い分ける方法も提示されている(略)。