53

今学期、私は大学でコンピュータ グラフィックスのコースを受講しました。現時点では、高さマップ、法線の平均化、テッセレーションなどのより高度なものに取り組み始めています。

私はオブジェクト指向のバックグラウンドを持っているので、私たちが行うすべてのことを再利用可能なクラスに入れようとしています。私はカメラ クラスの作成に成功しました。これは、ほとんどが gluLookAt() への 1 回の呼び出しに依存しているためです。これは、OpenGL ステート マシンの残りの部分からはほとんど独立しています。

しかし、私は他の面でいくつかの問題を抱えています。オブジェクトを使用してプリミティブを表現することは、私にとってあまり成功していません。これは、実際のレンダー呼び出しが、現在バインドされているテクスチャなど、非常に多くの外部のものに依存するためです。特定のクラスのサーフェス法線から頂点法線に突然変更したい場合、深刻な頭痛の種になります。

オブジェクト指向の原則が OpenGL コーディングに適用できるかどうか疑問に思い始めています。少なくとも、授業の粒度を下げるべきだと思います。

これに関するスタック オーバーフロー コミュニティの見解は? OpenGL コーディングのベスト プラクティスは何ですか?

4

5 に答える 5

74

最も実用的なアプローチは、直接適用できない (遅い、ハードウェア アクセラレーションがない、またはハードウェアに適合しなくなった) OpenGL 機能のほとんどを無視することです。

OOP であろうとなかろうと、いくつかのシーンをレンダリングするために、それらは通常持っているさまざまなタイプとエンティティです。

ジオメトリ(メッシュ)。ほとんどの場合、これは頂点の配列とインデックスの配列です (つまり、三角形ごとに 3 つのインデックス、別名「三角形リスト」)。頂点は、任意の形式にすることができます (たとえば、float3 位置のみ、float3 位置 + float3 法線、float3 位置 + float3 法線 + float2 texcoord など)。したがって、必要なジオメトリの一部を定義するには:

  • その頂点フォーマットを定義します (ビットマスク、フォーマットのリストからの列挙型の可能性があります; ...)、
  • コンポーネントがインターリーブされた頂点の配列を持つ (「インターリーブ配列」)
  • 三角形の配列があります。

OOP ランドにいる場合は、このクラスをMeshと呼ぶことができます。

マテリアル- ジオメトリの一部がどのようにレンダリングされるかを定義するもの。最も単純なケースでは、これはオブジェクトの色などです。または、ライティングを適用するかどうか。または、オブジェクトをアルファ ブレンドするかどうか。または、使用するテクスチャ (またはテクスチャのリスト)。または使用する頂点/フラグメント シェーダー。など、可能性は無限大です。必要なものを材料に入れることから始めます。OOP ランドでは、そのクラスを (驚き!) Materialと呼ぶことができます。

シーン- ジオメトリの断片、マテリアルのコレクション、シーンの内容を定義する時間があります。単純なケースでは、シーン内の各オブジェクトは次のように定義できます。 - 使用するジオメトリ (メッシュへのポインタ) - レンダリング方法 (マテリアルへのポインタ) - 配置場所。これは、4x4 変換行列、または 4x3 変換行列、またはベクトル (位置)、クォータニオン (方向)、および別のベクトル (スケール) である可能性があります。これを OOP ランドのノードと呼びましょう。

カメラ。カメラは、「配置された場所」(ここでも 4x4 または 4x3 マトリックス、または位置と向き) にいくつかの投影パラメータ (視野、縦横比など) を加えたものにすぎません。

基本的にはそれだけです!メッシュとマテリアルを参照する一連のノードであるシーンがあり、ビューアがどこにあるかを定義するカメラがあります。

さて、実際の OpenGL 呼び出しをどこに配置するかは、設計上の問題にすぎません。私は、OpenGL の呼び出しを Node、Mesh、または Material クラスに入れないでください。代わりに、シーンを横断してすべての呼び出しを発行できるOpenGLRendererのようなものを作成します。または、さらに良いことに、OpenGL とは無関係にシーンを横断するものを作成し、OpenGL 依存クラスに低レベルの呼び出しを配置し​​ます。

そうです、上記のすべてはほとんどプラットフォームに依存しません。このように進むと、glRotate、glTranslate、gluLookAt などはまったく役に立たないことがわかります。すべての行列が既にあるので、それらを OpenGL に渡すだけです。とにかく、これは実際のゲーム/アプリケーションの実際のコードのほとんどがどのように機能するかです。

もちろん、上記はより複雑な要件によって複雑になる可能性があります。特に、マテリアルは非常に複雑になる場合があります。メッシュは通常、多くの異なる頂点フォーマットをサポートする必要があります (効率のためにパックされた法線など)。シーン ノードを階層に編成する必要がある場合があります (これは簡単です。ノードに親/子ポインターを追加するだけです)。一般に、スキン メッシュとアニメーションは複雑さを増します。等々。

しかし、主なアイデアは単純です。ジオメトリがあり、マテリアルがあり、シーンにオブジェクトがあります。次に、いくつかの小さなコードでそれらをレンダリングできます。

OpenGL の場合、メッシュを設定すると、VBO オブジェクトが作成/アクティブ化/変更される可能性が高くなります。ノードをレンダリングする前に、行列を設定する必要があります。また、マテリアルの設定は、残りの OpenGL 状態 (ブレンド、テクスチャリング、ライティング、コンバイナー、シェーダーなど) のほとんどに影響します。

于 2008-10-03T12:28:22.967 に答える
3

オブジェクトの変換

OpenGL に依存して変換を行うことは避けてください。多くの場合、チュートリアルでは、変換マトリックス スタックの操作方法が説明されています。このスタックを介してのみアクセスできるマトリックスが後で必要になる可能性があるため、このアプローチの使用はお勧めしません。また、GPU バスは CPU から GPU へ高速になるように設計されているため、このスタックの使用は非常に長くなりますが、他の方法ではそうではありません。

マスターオブジェクト

3D シーンは、オブジェクトの依存関係を知るために、オブジェクトのツリーと見なされることがよくあります。このツリーのルート、オブジェクトのリスト、またはマスター オブジェクトのどちらにするかについては議論があります。

マスター オブジェクトを使用することをお勧めします。グラフィック表現はありませんが、再帰をより効果的に使用できるため、より単純になります。

シーン マネージャーとレンダラーを切り離す

OpenGL呼び出しを行う各オブジェクトにメソッドが必要であるという@ejacに同意しません。別の Renderer クラスでシーンを参照し、すべての OpenGL 呼び出しを実行すると、シーン ロジックと OpenGL コードを分離するのに役立ちます。

これにより、設計が多少難しくなりますが、OpenGL から DirectX またはその他の API 関連に変更する必要がある場合は、柔軟性が向上します。

于 2008-10-03T12:53:02.403 に答える
2

標準的な手法は、glPushAttrib/glPopAttrib スコープ内でデフォルトの OpenGL 状態からすべての変更を行うことにより、レンダリング状態に対するオブジェクトの影響を互いに隔離することです。C++ では、コンストラクターを含むクラスを定義します。

  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);

およびデストラクタを含む

  glPopClientAttrib();
  glPopAttrib();

クラスRAIIスタイルを使用して、OpenGLの状態を台無しにするコードをラップします。パターンに従えば、各オブジェクトの render メソッドは「白紙の状態」を取得し、openGL 状態の変更されたすべてのビットを必要なものにすることを心配する必要はありません。

最適化として、通常、アプリの起動時に OpenGL の状態を、すべてが必要とする状態にできるだけ近い状態に設定します。これにより、プッシュされたスコープ内で行う必要がある呼び出しの数が最小限に抑えられます。

悪いニュースは、これらの通話は安くないということです。1 秒あたり何回で済むかを実際に調査したことはありません。複雑なシーンで役立つことは間違いありません。重要なことは、設定した状態を最大限に活用することです。アーマーとスキンに異なるシェーダー、テクスチャなどを使用してレンダリングするオークの軍隊がある場合は、アーマー/スキン/アーマー/スキン/...をレンダリングするすべてのオークを反復しないでください。一度鎧の状態を設定し、すべてのオークの鎧をレンダリングしてから、すべてのスキンをレンダリングするように設定してください。

于 2008-10-03T12:39:45.253 に答える
1

自分でロールバックしたい場合は、上記の回答で十分です。言及されている原則の多くは、ほとんどのオープン ソース グラフィック エンジンに実装されています。シーングラフは、ダイレクト モードの OpenGL 描画から移行する 1 つの方法です。

OpenScenegraphは、OO 3D グラフィックスを実行するためのツールの大規模な (おそらく大きすぎる) ライブラリを提供するオープン ソース アプリの 1 つです。他にもたくさんのツールがあります。

于 2008-10-05T01:31:21.527 に答える
0

私は通常、レンダリング可能なクラスごとに、opengl呼び出しを含むdrawOpenGl()関数を持っています。その関数はrenderloopから呼び出されます。このクラスは、opengl関数呼び出しに必要なすべての情報を保持しています。位置と向きについては、独自の変換を行うことができます。

オブジェクトが相互に依存している場合。それらはより大きなオブジェクトの一部を作成し、そのオブジェクトを表す他のクラスでそれらのクラスを構成します。これには、子のすべてのdrawOpenGL()関数を呼び出す独自のdrawOpenGL()関数があるため、push-およびpopmatrixを使用して周囲の位置/方向の呼び出しを行うことができます。

久しぶりですが、テクスチャでも似たようなことができると思います。

サーフェス法線または頂点法線を切り替えたい場合は、オブジェクトにどちらか一方を記憶させ、必要に応じてdrawOpenGL()が呼び出すたびに2つのプライベート関数を設定します。確かに他のより洗練された解決策があります(たとえば、戦略デザインパターンなどを使用する)が、これは私があなたの問題を理解している限りうまくいく可能性があります

于 2008-10-03T11:58:27.440 に答える