はじめに
OpenGLの公式WikiのCommon Mistakes(よくある間違い)のページ(最終更新2021.5.16)を翻訳しました.
意訳重点です.また,任意に説明追加したりしています.
Common Mistakes
ごく少数であるものの,いくつかのウェブサイトに共通した間違いが見られていて,そのような間違いを含む解説プログラムがOpenGL初学者によってコピペされてしまっている問題がどうやらあるようです.
このページは,新人がトライ&エラーするよりももうちょっとよくGLプログラミングを理解できるようにするために作られました.
#Contents
- Extensions and OpenGL Versions(拡張とOpenGLのバージョン)
- The Object Oriented Language Problem(オブジェクト指向言語問題)
- RAII and hidden destructor calls(見えないデストラクタ)
- OOP and hidden binding(オブジェクト指向プログラミングと見えないバインディング)
- Texture upload and pixel reads(テクスチャのアップロードとピクセル読み込み)
- Image precision(画像のビット精度)
- Depth Buffer Precision(デプスバッファのビット精度)
- Creating a complete texture(完全なテクスチャの生成)
- Automatic mipmap generation(自動ミップマップ生成)
- Legacy Generation(古いやり方)
- gluBuild2DMipmaps
- Checking for OpenGL Errors(OpenGLのエラーチェック)
- Checking For Errors When You Compile Your Shader(シェーダをコンパイルするときのエラーチェック)
- Creating a Cubemap Texture(キューブマップテクスチャの生成)
- Texture edge color problem(テクスチャのエッジの色問題)
- Updating a texture(テクスチャの更新)
- Render To Texture(テクスチャに描画)
- Depth Testing Doesn't Work(デプステストがちゃんと動かない)
- No Alpha in the Framebuffer(フレームバッファのアルファがない)
- glFinish and glFlush
- glDrawPixels
- GL_DOUBLE
- Slow pixel transfer performance(ピクセル転送が遅い)
- Swap Buffers
- The Pixel Ownership Problem(ピクセルの所有者問題)
- Selection and Picking and Feedback Mode(選択とピッキングとフィードバックモード)
- Point and line smoothing(点と線の平滑化)
- glEnable(GL_POLYGON_SMOOTH)
- Color Index, The imaging subset(色インデックス,画像サブセット)
- Bitfield enumerators (ビットフィールドに関するenum定数)
- Triple Buffering(トリプルバッファリング)
- Paletted textures(パレット付きテクスチャ)
- Texture Unit(テクスチャユニット)
- Disable depth test and allow depth writes(デプステスト無効化とデプス値書き込み許可)
- glGetFloatv glGetBooleanv glGetDoublev glGetIntegerv
- y-axis
- glGenTextures in render function(描画関数内のglGenTextures)
- Bad znear value(ダメなznear値)
- Bad Array Size(ダメなarrayサイズ)
この他のCommon mistakesの説明記事もあります.
- GLSLのCommon Mistakes
- OpenGL使用時にありがちなUnexpected Results(予想外の結果)
- パフォーマンスの計測時に関係するMistakes
- 廃止された機能を使った場合のCommon Mistakes
Extensions and OpenGL Versions
拡張の存在の確認はしたけど,でもなんか拡張とか嫌なんで代わりに対応するコア関数を(無確認で)使用してみた,という状況でエラーが発生することがあります.
正しくは,
- 拡張APIを使いたい場合には,拡張の存在の確認する.
- コアAPIを使いたい場合には,GLバージョンを確認する.
と,確認作業を別々にやるべきです.
コア拡張の場合は,GLバージョンと拡張の存在の両方を確認します.その結果,コアAPI/拡張APIのどっちにもあったら,どっちを使っても問題ありません.
(訳注:使用しようと思っている機能が拡張APIにあったからと言って,自分の使うOpenGLのバージョンにコア機能として実装されているとは限らないので注意しろという事か.実装された情報をどこかで見たとしても,実は後のGLバージョンで実装されているだけで,自分の使うOpenGLのバージョンには実装されていない可能性がある.)
The Object Oriented Language Problem
C++のようなオブジェクト指向言語では,OpenGLオブジェクトをラップするクラスがあると便利なことがあります.例えば,次のようなコンストラクタとデストラクタを持つテクスチャオブジェクトがあったとします.
MyTexture::MyTexture(const char *pfilePath)
{
if(LoadFile(pfilePath)==ERROR)
return;
textureID=0;
glGenTextures(1, &textureID);
//More GL code...
}
MyTexture::~MyTexture()
{
if(textureID)
glDeleteTextures(1, &textureID);
}
ここで問題があります.
OpenGL関数は,スレッド内にOpenGLコンテキストが生成されていてかつアクティブでないと動きません.なので,glGenTextures
はコンテキスト生成前に呼んでもちゃんと動きませんし,glGenTextures
はコンテキストが消えた後ではちゃんと動きません.
この問題は通常コンストラクタで現れます.ユーザがテクスチャオブジェクトだとか他のOpenGLオブジェクトのラッパーをグローバルスコープで作った時が問題です.
解決策はこちら.
- OpenGLオブジェクトの初期化/削除にコンストラクタ/デストラクタを使わない.代わりに,APIユーザにメンバ関数を目的をもって使わせる.この方法はRAII原則に反するので,最善策というわけではない.
- コンテキストが作られていなかったら例外を返す.コンテキストが作られていて,かつアクティブであることが通知可能なコンテキスト生成機能の追加実装が要求される.
- OpenGL関連オブジェクトすべてを所有するクラスを作る.このクラスのコンストラクタにおいてコンテキストを生成する必要がある.
- オブジェクト生成/消失時にコンテキストがカレントになっていなければプログラムがクラッシュすることがあるよという仕様にしておく.ユーザに正確に使わせることを要求することになるが,より自然に思われる.
RAII and hidden destructor calls
C++のRAII原則によれば,あるオブジェクトがあるリソース(今回の場合OpenGLオブジェクトとか)を包含するのであれば,コンストラクタがそのリソースを生成し,かつデストラクタがそのリソースを消すべきとしています.
//OpenGLコンテキストの生成を実行.
{
MyTexture tex;
RunGameLoop(&tex); //テクスチャがここで複数回使用される.
} //ここで 'tex' のデストラクタが起動.
//値渡しの時,あるいはvectorのようなC++コンテナで作成する時に問題が発生する.
//次の関数を考えてみる.
MyTexture CreateTexture()
{
MyTexture tex;
//'tex' の中身を初期化する操作.
return tex;
}
ここで何が起こるでしょうか.
C++の規則に依れば,MyTexture型のtex
はCreateTexture関数呼び出しが終わったら消されるべきということになっています.
しかし,tex
は中でリソース(OpenGLオブジェクト)を管理しています.そして,規則に依ればそのリソースはデストラクタで消されることになっています.
返されるものはtex
自身ではなく,オブジェクトのコピーになります.
関数はコピーを返すが,その中身のリソースは関数終了時に起動するデストラクタで削除される.すなわち,返されるコピーは消されたはずのOpenGLオブジェクト名ということになってしまいます.(訳注:OpenGLオブジェクト名=glGenXXXX関数で作られる,OpenGLオブジェクトのidのこと.GLuint型.)
「デストラクタ,コンストラクタ,アサインメント処理のうち一つでも書いたら,これらを全部書かなければいけない」という,C++'s rule of 3/5に違反していることで起こります.
コンパイラが自動生成するコピーコンストラクタは意味が異なることになります.OpenGLオブジェクト名はコピーされるのですが,OpenGLオブジェクト自身のコピーとはなりません.
これは2つのC++オブジェクトが同じOpenGLオブジェクトを消してしまうという事態を引き起こし得ます.
(訳注:デフォルトの場合,=演算子などでコピーするとOpenGLオブジェクト名(整数値)だけがコピーされる(中身はクローンされない).すなわちコピーしちゃうと同じOpenGLオブジェクト名を持ったオブジェクトが複数いることになる.デストラクタでglDeleteXXXXX関数が呼ばれ,中身のOpenGLオブジェクトが消される.デストラクタが複数回呼ばれて,同じOpenGLオブジェクトを何度も消す操作が行われてしまう.エラー発生!vectorなんかだとコピーが大量に発生して…)
理想的には,RAIIラッパーのコピー関数は,OpenGLオブジェクトのデータを新たなOpenGLオブジェクトのデータにコピーする(クローン),という動作になるべきです.そうすれば,それぞれのC++オブジェクトに対して単一のOpenGLオブジェクトを持つことになります.
しかしながら,OpenGLオブジェクトデータを新たなOpenGLオブジェクトにコピーするのは大変高コストです.そもそも,静的に既知ではないステートでも追加可能という拡張性の機能のために,根本的に無理です.(訳注:OpenGLステートからでは,どのパラメータが変更されているのかユーザが自律的に管理していなければ知り得ない,ということ?)
それなので,OpenGLオブジェクトラッパークラスは,コピーを禁止すべきです.move(移譲)のみ可能にすべきです.move前のオブジェクトからリソースを奪う形になります.
class MyTexture
{
private:
GLuint obj_ = 0; //未初期化のままはダメ.
void Release()
{
glDeleteTextures(1, &obj_);
obj_ = 0;
}
public:
//他のコンストラクタは普通に定義
//テクスチャを解放
~MyTexture() {Release();}
//コピーコンストラクタ,アサインメントを削除.
MyTexture(const MyTexture &) = delete;
MyTexture &operator=(const MyTexture &) = delete;
MyTexture(MyTexture &&other) : obj_(other.obj_)
{
other.obj_ = 0; //前のオブジェクトのテクスチャは'null'にする.
}
MyTexture &operator=(MyTexture &&other)
{
//自己アサインメントじゃないことを常に確認
if(this != &other)
{
Release();
//ここで既に this->obj_ は 0 になっている.
std::swap(obj_, other.obj_);
}
}
};
これで,上記のコードは動くようになりました.
return tex;
はtexからのmoveを引き起こし,tex.obj_
が示すテクスチャオブジェクトが消される前に0になります.そして,0 textureがglDeleteTextures
されることになり,問題がなくなります.
(訳注:ここまでだと,ユーザはリソースが奪われたかどうかよく確認してプログラムしないと「なぜかテクスチャ参照できないんだけど…」状態になるはず.既にリソースが奪われているクラスオブジェクトでgetしようとしたときに,「もう移譲されてて0 textureだよ」と例外投げるなどで教えてあげると更に良い.)
OOP and hidden binding
こちらは,OpenGLをC++のような言語で使うときに起こりうる別の問題です.
次の関数を考えてみましょう.
void MyTexture::TexParameter(GLenum pname, GLint param)
{
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, pname, param);
}
問題は,テクスチャのバインディングがクラスのユーザから隠蔽されていることです.
オブジェクトのバインディングの重複が起こる可能性があります(それほどパフォーマンスに影響しないだろうが).
問題なのは正確性です.バインドされたオブジェクトはグローバルステートで,ローカルメンバ関数にも影響を及ぼしてしまいます.
多くの隠れたバグの原因になってしまいます.
(訳注:知らないでこの関数を使ったユーザが,この関数を使う前のどこかでglBindTextureしているかもしれない.するとバインディングの状況が崩れるのでバグの温床.)
安全な実装方法はこちら.
void MyTexture::TexParameter(GLenum pname, GLint param)
{
GLuint boundTexture = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*) &boundTexture);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, pname, param);
glBindTexture(GL_TEXTURE_2D, boundTexture);
}
(訳注:このプログラムは,関数実行時に既にバインドされているテクスチャを照会(glGetIntegerv)し,用が済んだらバインドしなおしてあげている(最後のglBindTexture).)
この方法はパフォーマンスを無視した正確性の担保になります.
glGetIntegerv
の呼び出しは速くないかもしれないです.
より効果的な方法は,Direct State Accessを使うことです.
OpenGL 4.5
かARB_direct_state_access
,古くはEXT_direct_state_access
が必要です.
void MyTexture::TexParameter(GLenum pname, GLint param)
{
glTextureParameteri(textureID, pname, param);
}
Texture upload and pixel reads
テクスチャのストレージの生成,ピクセルのアップロードはglTexImage2D
で行いますよね.あるいは同様の関数で.
アップロード中にプログラムがクラッシュした場合や,結果画像に対角線が現れた場合というのは,これはピクセル配列の各水平線のアライメントが4の倍数でなかった場合です.これは典型的にはユーザがロードした画像がRGBまたはBGRフォーマット(24BPP画像とか)だった時に起こります.元画像データに依存します.
401x500の画像を例にしてみます,問題は高さでなく,横幅です.
計算してみると,401 pixels x 3 bytes = 1203.これは4で割れません.
いくつかのファイルフォーマットは伝統的に各行で4バイトアライメントされますが,いくつかはしません.しない場合,各行は1203バイトになります.
OpenGLの行アライメントは画像データの行アライメントにフィットするように変更できます.これはglPixelStorei(GL_UNPACK_ALIGNMENT, #)
で行われます.#は所望のアライメントです.デフォルトは4です.
大体のGPUチャンクは4バイトです.
つまり,各コンポーネントが1バイトである場合,GL_RGBAやGL_BGRAが好まれます.
GL_RGBとGL_BGRは,大体のGPU,CPU,その他チップは24bitsを扱ってないので,異様(bizarre)とみなされます.その場合,ドライバはGL_RGB,GL_BGRをGPUに適した形に変換します.典型的にはRGBA/BGRAです.
同様に,glReadPixels
でバッファを読む場合,同様な問題に直面すると思います.
GL_PACK/UNPACK_ALIGNMENTがあります.
デフォルトのアライメントは4で,1行が4の倍数サイズになります.
GL_BGRAやGL_RGBAのようなフォーマットのバッファを読む場合,特に問題なく4の倍数で読みだせばよいです.GL_BGRやGL_RGBのようなフォーマットの場合では,リスクが出てきます.
GL_PACK/UNPACK_ALIGNMENTは 1, 2, 4, 8のみです.3はダメです.
packed RGB/BGRデータを使いたい場合,1を設定することになります.(それよりも,RGBA/BGRAへの変換をお勧めします.)
Image precision
(実際にそうしろと言えませんが,)glTexImage2D(GL_TEXTURE_2D, 0, X, width, height, 0, format, type, pixel)
の X に,1, 2, 3, 4を設定できます.ピクセルのコンポーネントの数に相当します(GL_RED
, GL_RG
, GL_RGB
, GL_RGBA
に相当).
実際に使われる画像フォーマットを使うの好ましいです.
OpenGLの実装が使おうとしていたフォーマット・精度をサポートしていない場合,ドライバは内部的にサポートしている何れかに変換します.
OpenGL 3.x 以上は指定のすべての画像フォーマットを実装することを要求しています.
Note: Immutable Storage Texturesの生成はサイズ決めされてない画像フォーマットの使用をアクティブに禁止しています.
チュートリアルサイトでは,このような記述が一般的に見られます.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
OpenGLのAPI的にはGL_RGBを受理しますが,ドライバは勝手に都合よく精度を決めます.
なので,次のようにちゃんとGL_RGB8と書くことをお勧めします.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
ドライバに,実際にR8G8B8 formatで確保してほしいと伝えることを意味しています.
殆どのGPUは内部的にGL_RGB8をGL_RGBA8に変換しています.なのでこれがGL_RGB8を扱うのに一番都合がよいと思います.
Windowsのようないくつかのプラットフォームでは,ピクセルアップロードのフォーマットとしてGL_BGRAが好まれます.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
GL_RGBA8を内部フォーマットに使います.GL_RGBAとGL_UNSIGNED_BYTE(あるいはGL_UNSIGNED_INT_8_8_8_8でピクセル配列それぞれ指定.こうするとドライバはCPUベース変換処理をしなくなり,DMAが直にデータをビデオカードに投げるようになる.Windows+NVidia or ATI/AMDのベンチマークでは,最適なフォーマット指定だった).
最適なピクセル転送フォーマット・型の情報は各OpenGL実装から入手できます.
Depth Buffer Precision
ウィンドウのピクセルフォーマットの選択の際,デプスバッファを付けるようすると,デプスバッファは典型的にビットデプスが16, 24, 32ビットの正規化整数で確保されます.
Note: 真に浮動小数点数のフォーマットのデプスバッファも作ることができます.しかし,これはフレームバッファオブジェクトでのみ指定可能で,デフォルトフレームバッファには適用できません.
OpenGLでは,すべてのデプスの値域は[0, 1]です.整数正規化処理で単に浮動小数値域が整数値に適当な精度で変換されます.この整数値がデプスバッファに格納されます.
典型的には,24ビットデプスバッファはパディングされて32ビットになります.余りの8ビットは使われません.しかし,8ビットステンシルバッファをデプスバッファと一緒に使う場合は,2画像は一つのデプス/ステンシル画像として結合されます.24ビットはデプス,残りの8ビットがステンシルになります.
デプスバッファが浮動小数点数であるという誤解は解けたと思いますが,さて次の関数呼び出しは何が良くないでしょうか.
glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, mypixels);
この場合,デプスフォーマットは正規化整数フォーマットなので,ドライバは正規化変数を浮動小数値に変換するのにCPUを使うことになります.なので遅いです.
次のようにするのが良いでしょう.
if(depth_buffer_precision == 16)
{
GLushort mypixels[width*height];
glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, mypixels);
}
else if(depth_buffer_precision == 24)
{
GLuint mypixels[width*height]; //There is no 24 bit variable, so we'll have to settle for 32 bit
glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8, mypixels); //No upconversion.
}
else if(depth_buffer_precision == 32)
{
GLuint mypixels[width*height];
glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, mypixels);
}
depth/stencilフォーマットを使っているなら,このようにdepth/stencilデータを得られます.
GLuint mypixels[width*height];
glReadPixels(0, 0, width, height, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, mypixels);
Creating a complete texture
次のコードは何が良くないでしょうか.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
このテクスチャは不完全なので正常動作しません.
デフォルトのGL_TEXTURE_MIN_FILTER
はGL_NEAREST_MIPMAP_LINEAR
となっています.
そして,OpenGLはデフォルトのGL_TEXTURE_MAX_LEVEL
を1000にしています.OpenGLはこのミップマップのレベル全部を定義することを期待します.今,1レベルだけミップマップを定義しているので,OpenGLはこのテクスチャが不完全だとみなしてしまいます.GL_TEXTURE_MAX_LEVELを適切に設定するか,GL_TEXTURE_MIN_FILTERをミップマップを使わない設定にする必要があります.
テクスチャのストレージの確保にTexture Storage関数を使い,glTexSubImage2D
を使ってアップロードするのが良いです.OpenGL 4.2
か ARB_texture_storage
が必要です.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
ミップマップレベル1つのテクスチャを生成し,すべてのパラメータを適切に設定してます.
複数ミップマップにしたいときは,"1"のところをを使いたい数に変えればよいです.そして,それぞれのミップマップに対し個別にglTexSubImage2D
を呼びます.
上記関数が使えない時は,こうすると同様の効果が得られます.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
複数ミップマップにしたいときは,GL_TEXTURE_MAX_LEVELを(使用したい数-1)(base/maxレベルはclosed range.)と設定して,glTexImage2Dをそれぞれのミップマップに対し設定すればよいです.
Automatic mipmap generation
テクスチャのミップマップはglGenerateMipMap
関数で自動生成できます.
OpenGL3.0以上,あるいはGL_ARB_framebuffer_object
が必要です.
動作は単純で,テクスチャが作られるときに,ミップマップが生成されます.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexStorage2D(GL_TEXTURE_2D, num_mipmaps, GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glGenerateMipmap(GL_TEXTURE_2D); //Generate num_mipmaps number of mipmaps here.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
テクスチャストレージが使えない場合は,こちらの古いAPIが使えます.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glGenerateMipmap(GL_TEXTURE_2D); //Generate mipmaps now!!!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
注意:いくつかのATIドライバでは,glGenerateMipmap(GL_TEXTURE_2D)はglEnable(GL_TEXTURE_2D)を予め呼んでおかないと無効果になるという報告があります.この場合,テクスチャをバインドし,glEnableして,glGenerateMipmapするという流れになります.
ATIのバグのようです.これを読んでいるころには直っているのではと思います(2011年報告).
Legacy Generation
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
こちらはOpenGL1.4以降が必要です.
GL_GENERATE_MIPMAP
はテクスチャオブジェクトとステートの一部であって,フラグ(GL_TRUE
/GL_FALSE
)で設定します.
GL_TRUE
にした場合,テクスチャレベル0が更新されたときに,全ミップマップが再生成されます.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
GL3.0では,GL_GENERATE_MIPMAP
は廃止されています.3.1以降では,削除されています.なので,これ以降のバージョンの場合は,glGenerateMipmap
でないとダメです.
gluBuild2DMipmaps
絶対使わないでください.
上記機能を使ってください.
Checking for OpenGL Errors
ちゃんとOpenGLのエラーチェックをしましょう.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); //Requires GL 1.4. Removed from GL 3.1 and above.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
こちらのコードはOpenGLエラーのチェックをしていません.
エラーチェックをしていれば,このコードがGL_INVALID_ENUMを投げていることが分かります.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR)
で発生しています.この拡大フィルタはミップマップに適用できません.縮小フィルタにのみ適用可能です.
OpenGLのエラーの発見と場所の特定には2つの方法があります.
- デバッグ出力コールバックの設定
- OpenGLの関数を使うときに毎回glGetErrorする
前者の方が簡単です.詳しくはOpenGL Errorを見てください.
Checking For Errors When You Compile Your Shader
Creating a Cubemap Texture
回り込み(Wrap)のフォーマットにはGL_CLAMP_TO_EDGE
が最適です.
ちゃんと全6面に対して設定するのを忘れないようにしてください.不完全とみなされてしまいます.
キューブマップは3Dテクスチャ座標を要求するので,GL_TEXTURE_WRAP_R
も設定するのを忘れないようにしてください.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0);
//Define all 6 faces
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face1);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face2);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face3);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face4);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels_face5);
glTexImage2D
の代わりにgltexStorage2D
を使用する場合は,まずGL_TEXTURE_CUBE_MAP
に対してglTexStorage2D
を一度だけ実行します.その後,6面それぞれに対してglTexSubImage2D
を設定します.
ミップマップを自動生成したい場合,GL_TEXTURE_CUBE_MAP
をターゲットにして,前述の機能が使えます.OpenGLはキューブマップに対するミップマップ生成時に複数テクスチャをブレンドしません.シームレスキューブマップテクスチャを有効にしていないと,低ミップレベルでつなぎ目が見えてしまいます.
Texture edge color problem
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
GL_CLAMP
を使ってはいけません.代わりにGL_CLAMP_TO_EDGE
を使いましょう.GL_CLAMP
はOpenGL 3.1 コアプロファイル以降では削除されています.
Note: ちなみに,GL_CLAMP
は,テクスチャのエッジのテクセルを境界テクセルとブレンドするという操作です.これは固定の境界色でクランプするGL_CLAMP_TO_BORDER
とは異なる挙動です.GL_CLAMP
の挙動は特殊な境界テクセルに合わせるというものです.シームレステクスチャを簡単に行うには便利なのですが,ハードウェア実装できないので削除されました.
Updating a texture
既存の2Dテクスチャの値の変更には,glTexSubImage2D
を使います.
glBindTexture(GL_TEXTURE_2D, textureID); //A texture you have already created storage for
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glTexImage2D
はテクスチャのストレージを生成し,サイズ/フォーマットを定義して以前のピクセルデータを全消去します.glTexSubImage2D
はピクセルデータのみを更新します.全ピクセル,一部ピクセルの更新のどちらにも使えます.
フレームバッファからのテクセルのコピーには,glCopyTexSubImage2D
を使います.
glBindTexture(GL_TEXTURE_2D, textureID); //A texture you have already created storage for
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height); //Copy current read buffer to texture
ちなみにglCopyTexImage2D
がありますが,こちらは画像全体のコピーで,また画像サイズ,フォーマットなどの指定をするバージョンです.
Render To Texture
コピーではなく直接テクスチャにレンダリングするには,フレームバッファオブジェクトを使います.
注意:NVIDIAのOpenGLドライバは不完全なテクスチャを使うときに問題があることが知られています.テクスチャが完全でない場合,FBOはGL_FRAMEBUFFER_UNSUPPORTED
あるいはGL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
とされます.これはドライバのバグで,OpenGLはそのような実装を許していません.とはいえ,全ミップマップレベルに対してちゃんと設定し,glTexParameteriの値もちゃんと適切に設定しておくべきでしょう.例えば,拡大/縮小フィルタがLINEARに設定されていない積分テクスチャは完全ではありません.
Depth Testing Doesn't Work
初めに,デプステストがアクティブになっているか確認してください.
glEnableが呼ばれていること,glDepthFuncが適切に設定されていることを確かめてください.
glDepthRangeがデプス関数とマッチしているのも確認してください.
これらが正しく設定されているとしたら,フレームバッファにデプスバッファがないかもしれません.フレームバッファを作っているところを確認しましょう.
デフォルトフレームバッファでは,OpenGLコンテキストを作った時の状況に依ります.
例えば,GLUTを使っているのであれば,glutInitDisplayMode
にてGLUT_DEPTH
を設定していることを確認します.
No Alpha in the Framebuffer
レンダーターゲットがアルファチャンネルを持っているか確認します.
フレームバッファオブジェクトにレンダリングするところを確認します.
デフォルトフレームバッファでは,OpenGLコンテキストを作った時の状況に依ります.
例えば,GLUTを使っているのであれば,glutInitDisplayMode
にてGLUT_ALPHA
を設定していることを確認します.
glFinish and glFlush
デフォルトフレームバッファの前バッファをレンダリングするときにglFlushを使います.
本当はダブルバッファリングが良いのですが,ウィンドウに直に描画する必要に迫られた場合は,glFlushしましょう.
多くのチュートリアルサイトに,こうしたほうがいいと書いてありますが…
glFlush();
SwapBuffers();
この場合,glFlushは不要です.ちゃんとSwapBuffersが内部でフラッシュしています.
glFlushとglFinishはCPUとGPUの同期をとるのに使います.
多くの場合,明示的な同期は不要です.
Sync Objectsを使うときに必要になる場合があります.任意の画像の読み書きでも必要になることがあります.
つまるところ,「同期しない」と仕様で明言されている動作にのみglFinishを使うべきです.
(訳注:glFlushは,これまでの処理キューを実行するように促すだけの非ブロッキング関数で,glFinishはOpenGLの全処理を待つブロッキング関数です.
例1:毎フレームglFlushしないと,余裕があると思われたら命令キューに引き続き溜め込んで全然処理してくれない(非同期ではあるけどいつまでたってもウィンドウに描画してくれない)可能性があります.上記のようにSwapBuffersしている場合は改めて書く必要はないです.
例2:glReadPixelsの前にglFinishしないと,描画が終わっていない可能性があります.
これを見るといっぱいflush/finishしたくなると思いますが,必要のない場所でこれらをやると結構大きいオーバヘッドになるので,そういうのやめて適切に使いましょう.
例3:意味もなく毎フレームglFinishしていると,せっかくCPUの処理とGPUの処理が並列に動く利点があるのに,CPU処理を待ってしまうのでフレームレートが落ちます.)
glDrawPixels
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
パフォーマンスのためには,GPUで直接サポートされているフォーマットを使うのが良いです.GPUへのmemcpyをドライバに行わせるようなフォーマットを使いましょう.ほとんどのグラフィックカードはGL_BGRAをサポートしています.
glDrawPixels(width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
しかしながら,テクスチャを使って glTexSubImage2D
で更新するだけの方がよいです.こちらはバッファオブジェクトへの非同期転送が可能です.
GL_DOUBLE
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
double型を扱う必要のある関数glLoadMatrixd, glRotated等があります.
多くのGPUではGL_DOUBLEをサポートしておらず,その場合ドライバはデータをGL_FLOATに変換してからGPUに送ります.GL_DOUBLEデータをVBOに入れた場合,おそらくイミディエイトモード(=glBegin, glVertex, glEnd)を使うよりもパフォーマンスが悪いです.OpenGLはGPUが好む最適な方法を提示する方法を持ちません.
Slow pixel transfer performance
ピクセル転送のパフォーマンス向上のためには,実装が直接動作するようなピクセル転送のフォーマットを使う必要があります.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
こちらの例で使用しているピクセルフォーマットGL_RBGAは,GL_RGBA8フォーマットを直にサポートしているわけではないという問題があります.
また,GPUによっては,GL_BGRAの方が好ましいことがあります.この場合,GL_RGBAを指定するとドライバは遅いスワップ操作をする必要が出てきます.一方,GL_BGRAを指定するとピクセル転送が高速化します.
ただし、GL_BGRA指定をするとしても,3番目の引数はGL_RGBA8にしておかなければならないことに注意してください.
後ろから3番目の引数は自分で用意したデータのピクセル順がどうなっているかを示すパラメータです.画像フォーマットはテクスチャに保存される実際のピクセル順の定義ではありません.GPUは内部的にBGRAでの格納が許可されているわけです.
GL_RGBA8画像にアップロードするときはGL_BGRAピクセル転送フォーマットそれだけが好まれます.GL_RGBA16やGL_RGBA8UI,GL_RGBA8_SNORMのようなその他のフォーマットを扱う場合は,通常のGL_RGBA順が好まれると思われます.
どのプラットフォームでGL_BGRAが好まれるのでしょうか?リストの作成には紙面が足りないので全部は挙げませんが,例えばMicrosoft Windowsがそうです.
GL4.3あるいはARB_internalformat_query2においては,glGetInternalFormativ(GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_FORMAT, 1, &preferred_format)
で実装を聞いてみるとよいです.
Swap Buffers
モダンOpenGLプログラムでは常にダブルバッファリングすべきです.
モダン3D OpenGLプログラムならばデプスバッファを持つべきです.
レンダリングの流れはこんな感じになると思います.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
RenderScene();
SwapBuffers(hdc); //For Windows
全バッファはフレーム毎に毎回クリアすべきです.
だいぶ古いハードウェアでは,シーンをクリアしないという裏技があったのですが,やや最近のハードウェアではそれをやってしまうと遅くなります.なので常にクリアしましょう.
The Pixel Ownership Problem
ウィンドウが全部隠れているか,一部分隠れているか,デスクトップ領域の外の場合,GPUはその部分を描画しないでしょう.なので,その部分を読み込もうとするとゴミデータが出てくることがあります.
ピクセルオーナシップテストに引っかかっているせいです.未定義の値になってしまいます.
これが問題な場合(遮蔽されている領域のデータも読みたい場合),フレームバッファオブジェクトへの描画が解決策になります.フレームバッファに描いてからデフォルトフレームバッファへのblitをしましょう.
Selection and Picking and Feedback Mode
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
モダンOpenGLプログラムではセレクションバッファやフィードバックモードを使わないべきです.
それらは3Dグラフィックレンダリングの機能ではないのに,OpenGL1.0から実装されてきた機能です.
セレクションやフィードバックはソフトウェア的に(CPU側で)実行されます.いくつかの実装では,VBOを使っているとき,パフォーマンスがお粗末になっていたそうです.
モダンOpenGLプログラムでは,カラーピッキング(オブジェクトを個別の色で塗ってglReadPixelsでマウスがどのオブジェクトに居るか見る)とかサードパーティの数学ライブラリを使ってのピッキングを行いましょう.
Point and line smoothing
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
いくつかの実装を試していると,点や線のレンダリングの結果が実装間で若干異なっていることに気づくでしょう.
これはOpenGLの仕様が柔軟性を許容しているためです.
glPointSize(5.0);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_POINT_SMOOTH);
RenderMyPoints();
いくつかのハードウェアでは円に見え,別のものでは四角に見えるでしょう.
いくつかの実装では,glEnable(GL_POINT_SMOOTH) や glEnable(GL_LINE_SMOOTH) を呼んでシェーダを同時実行した場合,実行速度が0.1 FPSに激落ちすることがあります.
ドライバがソフトウェアレンダリングしてしまっているためです.
この問題はAMT/ATIのGPU/ドライバで発生するようです.
glEnable(GL_POLYGON_SMOOTH)
アンチエイリアスとしては推奨されない方法です.マルチサンプリングを使いましょう.
Color Index, The imaging subset
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
OpenGL仕様の3.6.2節はイメージングサブセットの話になっています.
カラーテーブルと関連の操作はこのサブセットの範疇です.
この機能は典型的には通常のGPUではサポートされず,ソフトウェアエミュレーションになります.なので,もし避けられるのであれば使わないほうが良いでしょう.
テクスチャメモリ消費が高すぎたのが分かった場合,テクスチャ圧縮を使いましょう.
もしパレットカラーインデックスのテクスチャを使いたいのであれば,テクスチャとシェーダを使って自己実装しましょう.
Bitfield enumerators
OpenGL列挙子のうち,いくつかはビットフィールド表現になっています.
名前が _BIT で終わる(拡張サフィックスの前)ものです.
glEnable(GL_BLEND | GL_DRAW_BUFFER); // invalid
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // valid
1番目の行は間違っています.どの列挙子も _BIT で終わっていませんが,これらはbitfieldではなく,OR演算が無意味です.
対照的に,2行目はちゃんと動きます.どの列挙子も _BIT で終わっており,bitfieldであることを示しています.
(訳注:glEnableするときはor演算子で結ぶのではなく,glEnableを複数書きましょう.)
Triple Buffering
ドライバがトリプルバッファリングをするかどうかは制御できません.
FBOで自己実装しましょう.
しかしドライバがトリプルバッファリングを既に行っているのであれば,クアドラプルバッファリングになります.ちょっとやりすぎですね.
Paletted textures
EXT_paletted_texture のサポートはメジャーGLベンダから外されました.
新ハードウェアにおいてパレットテクスチャをしたい場合,シェーダを使いましょう.
//Fragment shader
#version 110
uniform sampler2D ColorTable; //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;
void main()
{
//What color do we want to index?
vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
//Do a dependency texture read
vec4 texel = texture2D(ColorTable, myindex.xy);
gl_FragColor = texel; //Output the color
}
ColorTable
はGL_RGGBA8のように自分で選んだフォーマットになります.
ColorTable
は256 x 1ピクセルサイズのテクスチャです.
MyIndexTexture
はどのようなフォーマットでもいけますが,GL_R8がいいでしょう(GL_R8はGL3.0から利用可能です).MyIndexTexture
は64 x 32のようにどのようなサイズでも大丈夫です.
MyIndexTexture
を読み,その結果をColorTexture
の読み出しに使うtexcoordに使います.
パレットアニメーションをやりたい場合,あるいはカラーテーブル中の色を変えたい場合,glTexSubImage2D
を使ってColorTable
に新しい値を送信します.
カラーテーブルがGL_RGBAフォーマットの場合はこんな感じ.
glBindTexture(GL_TEXTURE_2D, myColorTableID);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_BGRA, GL_UNSIGNED_BYTE, mypixels);
Texture Unit
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
マルチテクスチャが導入された当時は,テクスチャユニットの数はこのように受け取れました.
int MaxTextureUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &MaxTextureUnits);
今は,モダンGPUでは小さい数字を出すので使うべきではありません.
昔のOpenGLでは,それぞれのテクスチャユニットはそれぞれのテクスチャ環境ステート(glTexEnv),テクスチャ行列,テクスチャ座標生成(glTexGen),texcoords(glTexCoord),Clampモード,ミップマップモード,テクスチャLOD,異方性パラメータを持っていました.
プログラマブルGPUの時代がやってきました.もはやテクスチャユニットは有りません.
テクスチャイメージユニット(TIU)を使います.数はこのように取得できます.
int MaxTextureImageUnits;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits);
TIUは,Clampingやミップマップのようなテクスチャオブジェクトのステートを格納します.
テクスチャ座標からは独立しました.適当なTIUををサンプルして,それに対して自由にテクスチャ座標が使えるようになりました.
各シェーダステージはそれぞれ別にテクスチャイメージユニットの最大数があります.
GL_MAX_TEXTURE_IMAGE_UNITSはフラグメントシェーダの最大数のみ返します.
全シェーダステージにおけるイメージユニットの数はGL_MAX_COMBINED_TEXTURE_IMAGE_UNITで得られます.これは各1回におけるテクスチャ数上限です.そしてこれはglActiveTexture
やglBindSampler
のような関数に渡せるイメージユニット数の上限です.
最新のハードウェアでは,イメージユニットは各ステージにおいて少なくとも8つあることになっています.古めのハードウェアでは,バーテックスシェーダでは上限が4です.3.xが動作するハードウェアでは各ステージ16となっています.
結論として,シェーダベースGL2.0以上でのプログラムでは,GL_MAX_COMBINED_TEXTURE_IMAGE_UNITだけ使いましょう.
テクスチャ座標の数は無視されます.一般のバーテックスアトリビュートを使いましょう.
Disable depth test and allow depth writes
オブジェクトのレンダリング時に,デプステストを行わないが,デプスバッファの更新は継続したい場合があります(訳注:デプステストはしないけど,デプス値の書き込みはデプスバッファにしておいて,後でそれを使いたい(glReadPixelsとか)場合).デプステストを無効にすると(glDisable(GL_DEPTH_TEST)),OpenGLはデプスバッファへの書き出しも無効にしてしまいます.glDepthFunc(GL_ALWAYS)でデプステスト結果を無視するのが正解です.遠くのオブジェクトを最後にレンダリングした場合,デプスバッファは遠くのオブジェクトの値を含んでしまうことには注意しましょう.
glGetFloatv glGetBooleanv glGetDoublev glGetIntegerv
これらは低速です.
低速なのは通常動作です.glGet系関数は遅いです.nVidiaやATI/AMDはなるべく使わないことを推奨しています.OpenGLドライバ(GPU含む)はデータ受け取りはなるべく効率的に行えるように努力しています.もし使わずに済むのであればglGet系関数の呼び出しは避けましょう.
y-axis
ほとんどすべてのOpenGLはXが右で,Yが上という座標系を採用しています.これにはピクセル転送関数やテクスチャ座標も含まれます.
例えば,glReadPixels
はx, y位置を取ります.y軸は0が下で上がなにがし値になります.y軸が逆のOS(ウィンドウのy軸が上から下で,マウス座標が上から下)を使っている人たちにとっては直感的ではないように感じられてしまいます.解決はシンプル.windowHeight - mouseY - 1
.
テクスチャにおいては,OpenGLではy軸が下から上で,下が0.0で上が1.0です.ビットマップをOpenGLテクスチャにロードしてみたものの,なんでか上下逆になってておかしいぞと思った人が居るでしょう.解決はシンプル.ビットマップかテクスチャ座標モデルを1.0 - v
.
glGenTextures in render function
テクスチャオブジェクト(オブジェクトID)をレンダ関数の中で勝手に作っている人たちがいるようです.
やめてください.
glGen系関数で作るようにしましょう.
レンダ関数でモデルファイルを読んだりVBOを作ったりしてはいけません.
プログラムの最初でリソースを割り当てするようにするようにしましょう.
プログラム終了時に全リソース開放するようにしましょう.
より悪いのは,自分のレンダ関数で作ったいくつかの生成テクスチャ(とか他のOpenGLオブジェクト)を全くglDeleteTextures
で消さないことです.そのようなレンダ関数が呼ばれる度に,古いものが解放されずに新しいテクスチャが生成されてしまいます!
(訳注:そんなことをしたらGPUメモリが枯渇してプログラムどころかOSがクラッシュしますよ!)
Bad znear value
注意:これは古いOpenGL APIに関する説明で,OpenGL 3.1 コアプロファイル以降では削除されています.この機能の使用を使わないことが推奨されています.
gluPerspectiveやglFrustumを使うときにzNearを0.0にしている人たちがいるようです.
が,すぐにzバッファが機能しないことが分かります.
zNearを0.0以下にするのはダメです.
もしzNearを0.0にした場合,プロジェクション行列の3行4列目が0.0になってしまいます.負値にした場合,スクリーンに間違ったレンダリング結果が出てきます.
zNear, zFarともに0.0より大きい必要があります.gluPerspectiveはGLエラーを出しません.glFrustumはGL_INVALID_VALUE
を出します.
glOrthoの場合は,zNear, zFarに負の値を使えます.
頂点変換パイプラインにてどのように頂点が変換されるのかが説明されています.
Bad Array Size
ここでは,OpenGL1.1で説明しますが,同じ現象はVBOを使っているときや他のOpenGL関数を使っているときにも起こり得ます.
次のコードの何がおかしいでしょうか?
GLfloat vertex[] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0};
GLfloat normal[] = {0.0, 0.0, 1.0};
GLfloat color[] = {1.0, 0.7, 1.0, 1.0};
GLushort index[] = {0, 1, 2, 3};
glVertexPointer(3, GL_FLOAT, sizeof(GLfloat)*3, vertex);
glNormalPointer(GL_FLOAT, sizeof(GLfloat)*3, normal);
glColorPointer(4, GL_FLOAT, sizeof(GLfloat)*4, color);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_SHORT, index);
意図は,一つの四角を描こうとしているものの,バッファサイズが不適合であるということです.
1つの四角ににつき1法線を指定しているようですが,本来GLは1頂点に1法線が必要です.
また,RGBA色が1つだけ指定されていますが,1頂点に1色必要です.
与えられた法線および色配列のサイズを超えて読みだそうとするので,クラッシュする恐れがあります.
この問題はFAQでも説明されています.