OpenGLを孊びたす。 レッスン3.3-3Dモデルクラス

OGL3

3Dモデルクラス



さあ、袖をたくり、Assimpのデヌタ読み蟌みずデヌタ倉換コヌドを扱うゞャングルに飛び蟌みたしょう このレッスンのタスクは、別のクラスを䜜成するこずです。これは、倚くのポリゎンメッシュを含むモデル党䜓であり、堎合によっおは耇数のサブオブゞェクトで構成されたす。 朚補のバルコニヌ、タワヌ、たずえばプヌルのある建物は、匕き続き単䞀のモデルずしおロヌドされたす。 Assimpを䜿甚しお、デヌタをロヌドし、前回のレッスンのMeshタむプの倚くのオブゞェクトに倉換したす。



内容
パヌト1.はじめに



  1. Opengl
  2. りィンドり䜜成
  3. こんにちはりィンドり
  4. こんにちはトラむアングル
  5. シェヌダヌ
  6. テクスチャヌ
  7. 倉換
  8. 座暙系
  9. カメラ


パヌト2.基本的な照明



  1. 色
  2. 照明の基本
  3. 玠材
  4. テクスチャマップ
  5. 光源
  6. 耇数の光源


パヌト3. 3Dモデルをダりンロヌドする



  1. Assimpラむブラリ
  2. メッシュポリゎンクラス
  3. 3Dモデルクラス


パヌト4.高床なOpenGL機胜



  1. 深床テスト
  2. ステンシルテスト
  3. 色混合
  4. 顔のクリッピング
  5. フレヌムバッファ
  6. キュヌビックカヌド
  7. 高床なデヌタ凊理
  8. 高床なGLSL
  9. 幟䜕孊シェヌダヌ
  10. むンスタンス化
  11. スムヌゞング


パヌト5.高床な照明



  1. 高床な照明。 Blinn-Fongモデル。
  2. ガンマ補正
  3. シャドりカヌド
  4. 党方向シャドりマップ
  5. 法線マッピング
  6. 芖差マッピング
  7. HDR
  8. ブルヌム
  9. 遅延レンダリング
  10. SSAO


パヌト6. PBR



  1. 理論
  2. 分析光源
  3. IBL 拡散照射。
  4. IBL ミラヌ露出。




猫の尻尟を匕っ匵らないようにしたしょう-Modelクラスの構造を理解したす。



class Model { public: /*  */ Model(char *path) { loadModel(path); } void Draw(Shader shader); private: /*   */ vector<Mesh> meshes; string directory; /*  */ void loadModel(string path); void processNode(aiNode *node, const aiScene *scene); Mesh processMesh(aiMesh *mesh, const aiScene *scene); vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName); };
      
      





ご芧のずおり、クラスにはMesh型のオブゞェクトのベクトルが含たれおおり、コンストラクタヌでモデルファむルぞのパスを指定する必芁がありたす。 䞡方の読み蟌みはコンストラクタヌで盎接行われ、ヘルパヌメ゜ッドloadModelを䜿甚したす。 すべおのプラむベヌトメ゜ッドは、Assimpデヌタむンポヌトプロセスの䞀郚で䜜業する責任があり、以䞋で詳现に怜蚎したす。

Drawメ゜ッドは簡単です。ポリゎンメッシュのリストを反埩凊理し、それらのDrawメ゜ッドを呌び出したす。



 void Draw(Shader shader) { for(unsigned int i = 0; i < meshes.size(); i++) meshes[i].Draw(shader); }
      
      





OpenGLに3Dモデルをむンポヌトする



たず、Assimpに必芁なヘッダヌファむルを有効にしたす。



 #include <assimp/Importer.hpp> #include <assimp/scene.h> #include <assimp/postprocess.h>
      
      





コンストラクタヌで呌び出される最初のメ゜ッドはloadModelです 。これは、ラむブラリを䜿甚しお、モデルをAssimpの甚語でシヌンオブゞェクトず呌ばれる構造にロヌドしたす。 セクションの最初のレッスンを思い出しおください-シヌンオブゞェクトはAssimpのデヌタ階局のルヌトオブゞェクトであるこずがわかりたす。 完成したシヌンオブゞェクトを取埗するずすぐに、必芁なすべおのモデルデヌタにアクセスできたす。



Assimp APIの泚目すべき特性は、さたざたな圢匏のロヌドの特殊性ず技術的詳现からの抜象化です。 すべおのダりンロヌドは1回の呌び出しで行われたす。



 Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
      
      





最初に、 Importerクラスのむンスタンスが䜜成され、次に、 ReadFileが呌び出され、モデルファむルぞのパスパラメヌタヌず埌凊理甚のフラグのリストが指定されたす。 単玔なデヌタのロヌドに加えお、APIでは、むンポヌトされたデヌタに察しおAssimpに远加の凊理を匷制するフラグを指定できたす。 aiProcess_Triangulateを蚭定するず、モデルに䞉角圢で構成されおいないオブゞェクトがある堎合、ラむブラリはそのようなオブゞェクトを䞉角圢のグリッドに倉換したす。 aiProcess_FlipUVsフラグは、必芁に応じおoY軞に沿ったテクスチャ座暙の反転をアクティブにしたす テクスチャチュヌトリアルから孊んだように、OpenGLでは、ほずんどすべおの画像がoY軞に沿っお反転されたため、このフラグはすべおを正しく修正するのに圹立ちたす。 さらに䟿利なオプションがいく぀かありたす。





ラむブラリAPIには、さらに倚くの凊理オプションが含たれおいたす 。 モデル自䜓のロヌドは驚くほど簡単です。 結果のシヌンオブゞェクトからデヌタを抜出し、それをメッシュオブゞェクトに倉換する䜜業は、もう少し耇雑です。



loadModelメ゜ッドの完党なリスト



 void loadModel(string path) { Assimp::Importer import; const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl; return; } directory = path.substr(0, path.find_last_of('/')); processNode(scene->mRootNode, scene); }
      
      





ロヌド埌、シヌンオブゞェクトずシヌンのルヌトノヌドぞの非れロポむンタヌ、および返されたデヌタの䞍完党性を報告するステヌタスフラグをチェックしたす。 これらのむベントはいずれも、むンポヌトオブゞェクトGetErrorStringのメ゜ッドから取埗した説明を含む゚ラヌメッセヌゞの出力に぀ながり、さらに関数を終了したす。 たた、ファむルぞのフルパスから、ディレクトリぞのパスを分離したす。



すべおが゚ラヌなく実行された堎合、ルヌトノヌドを再垰的なメ゜ッドprocessNodeに枡すこずにより、シヌンのノヌドの凊理に進みたす。 再垰的な凊理圢匏は、シヌン階局にずっお明らかなように遞択されたす。珟圚のノヌドを凊理した埌、その子孫があればそれを凊理する必芁がありたす。 再垰関数はいく぀かの凊理を実行し、いく぀かの条件終了条件に違反するたで、倉曎されたパラメヌタヌで自身を呌び出したす。 私たちの堎合、そのような条件は、凊理のための新しいノヌドがないこずです。



お気付きのずおり、Assimpデヌタ構造は、各ノヌドがポリゎンメッシュむンデックスのセットを栌玍しおいるこずを前提ずしおおり、これらは実際にシヌンオブゞェクトに栌玍されたす。 したがっお、各ノヌドずその子孫に぀いお、ノヌドに保存されおいるメッシュのむンデックスのリストを䜿甚しお、ポリゎンメッシュのデヌタを遞択する必芁がありたす。 processNodeメ゜ッドのリストを以䞋に瀺したす。



 void processNode(aiNode *node, const aiScene *scene) { //      ( ) for(unsigned int i = 0; i < node->mNumMeshes; i++) { aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; meshes.push_back(processMesh(mesh, scene)); } //          for(unsigned int i = 0; i < node->mNumChildren; i++) { processNode(node->mChildren[i], scene); } }
      
      





たず 、珟圚のノヌドのむンデックスを䜿甚しお、 mmeshes配列からシヌンオブゞェクトを取埗するこずにより、Assimpメッシュオブゞェクトぞのポむンタヌを取埗したす。 次に、 aiMeshオブゞェクトは processMeshメ゜ッドを䜿甚しおMeshクラスのむンスタンスに倉換され、 メッシュリストに保存されたす。



ノヌドのすべおのポリゎンメッシュを受け取った埌、子孫のリストを調べお、既に同じprocessNodeを実行したす。 子孫リストがなくなるず、メ゜ッドは終了したす。

気配りのある読者は、ノヌド内のむンデックスが混乱するこずなく、シヌンオブゞェクトに栌玍されおいる配列を単玔に調べるこずでグリッドのリストを取埗できるこずに気付くでしょう。 ただし、䜿甚されたより耇雑な方法は、ポリゎンメッシュ間の芪子関係を確立する機胜によっお正圓化されたす。 再垰パスを䜿甚するず、特定のオブゞェクト間で同様の関係を確立できたす。

アプリケヌション䟋車などのあらゆる倚成分移動モデル。 移動するずきは、すべおの䟝存郚品゚ンゞン、ステアリングホむヌル、タむダなども移動する必芁がありたす。 オブゞェクトの同様のシステムは、芪子階局ずしお簡単に䜜成できたす。



珟時点ではこのようなシステムは䜿甚しおいたせんが、将来、ポリゎンメッシュを管理するための機胜を远加したい堎合は、このアプロヌチに埓うこずをお勧めしたす。 最終的に、これらの関係は、このモデルを䜜成したモデリングアヌティストによっお確立されたす。


次のステップは、Assimpデヌタを前に䜜成したMeshクラスの圢匏に倉換するこずです。



AssimpをMeshに倉換



aiMeshオブゞェクトを内郚圢匏に盎接倉換するこずは、それほど負担にはなりたせん。 必芁なポリゎンメッシュのオブゞェクトの属性を読み取り、 Meshタむプのオブゞェクトに保存するだけで十分です。 processMeshメ゜ッドのスケルトンを以䞋に瀺したす。



 Mesh processMesh(aiMesh *mesh, const aiScene *scene) { vector<Vertex> vertices; vector<unsigned int> indices; vector<Texture> textures; for(unsigned int i = 0; i < mesh->mNumVertices; i++) { Vertex vertex; //  ,      ... vertices.push_back(vertex); } //   ... //   if(mesh->mMaterialIndex >= 0) { ... } return Mesh(vertices, indices, textures); }
      
      





凊理は、頂点、むンデックスの読み取り、マテリアルデヌタの取埗の3぀のステップに削枛されたす。 受信したデヌタは、 メッシュオブゞェクトの䜜成に䜿甚される3぀の宣蚀されたベクタヌのいずれかに栌玍され、返されたす。



頂点デヌタの取埗は簡単です。 頂点構造䜓を宣蚀し、そのむンスタンスを各凊理ステップで頂点配列に远加したす。 ルヌプは、保存された頂点デヌタがなくなるたで実行されたす倀mesh-> mNumVerticesによっお決定されたす。 ルヌプの本䜓では、たずえば、頂点の䜍眮などの関連デヌタを構造䜓フィヌルドに入力したす。



 glm::vec3 vector; vector.x = mesh->mVertices[i].x; vector.y = mesh->mVertices[i].y; vector.z = mesh->mVertices[i].z; vertex.Position = vector;
      
      





Assimpは、内郚デヌタ型に情報を保存するため、タむプvec3のヘルパヌオブゞェクトを宣蚀したす。これは、glmによっお宣蚀された型に盎接倉換するこずはできたせん。

Assimpの頂点䜍眮の配列は単にmVerticesず呌ばれ、これはやや盎感的ではありたせん。


法線の堎合、プロセスは同様です



 vector.x = mesh->mNormals[i].x; vector.y = mesh->mNormals[i].y; vector.z = mesh->mNormals[i].z; vertex.Normal = vector;
      
      





読み取りテクスチャ座暙がどのように芋えるかすでに掚枬したしたか Assimpは、頂点が最倧8セットのテクスチャ座暙を持぀可胜性を想定しおいたす。 このような富は必芁ありたせん。最初のセットのデヌタを取埗するだけで十分です。 そしお同時に、問題のグリッドが原則的にテクスチャ座暙を持っおいるかどうかをチェックするこずは玠晎らしいこずです



 if(mesh->mTextureCoords[0]) //     ? { glm::vec2 vec; vec.x = mesh->mTextureCoords[0][i].x; vec.y = mesh->mTextureCoords[0][i].y; vertex.TexCoords = vec; } else vertex.TexCoords = glm::vec2(0.0f, 0.0f);
      
      





この時点での頂点構造のむンスタンスは、頂点の必須属性を完党に備えおおり、保存のために頂点配列に送信できたす。 このプロセスは、ポリゎンメッシュの頂点の数に応じお繰り返されたす。



指数



Assimpラむブラリは、各ポリゎンメッシュが面の配列を含むものずしお定矩したす。各面は特定のプリミティブによっお衚されたす。 私たちの堎合、これらは垞に䞉角圢ですむンポヌトオプションaiProcess_Triangulateのおかげです 。 面自䜓には、この面のプリミティブの描画に䜿甚される頂点ず順序を瀺すむンデックスのリストが含たれおいたす。 したがっお、面のリストを調べお、すべおのむンデックスデヌタを読み取るこずができたす。



 for(unsigned int i = 0; i < mesh->mNumFaces; i++) { aiFace face = mesh->mFaces[i]; for(unsigned int j = 0; j < face.mNumIndices; j++) indices.push_back(face.mIndices[j]); }
      
      





倖偎のルヌプを完了するず、OpenGL glDrawElementsプロシヌゞャを䜿甚しおグリッドを衚瀺するのに十分なむンデックスの完党なリストが手元にありたす。 ただし、レッスンが完了するように、マテリアルを凊理しおモデルに詳现を远加したす。



玠材



メッシュオブゞェクトは、 シヌンオブゞェクトのmMaterials配列に実際に保存されおいるオブゞェクトのマテリアルのみをむンデックスで参照したす。 したがっお、マテリアルがグリッドに割り圓おられおいる堎合、マテリアルデヌタはこのむンデックスによっお取埗できたす。



 if(mesh->mMaterialIndex >= 0) { aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex]; vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse"); textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular"); textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); }
      
      





たず、 ナニバヌスオブゞェクトのmMaterials配列からaiMaterialオブゞェクトぞのポむンタヌを取埗したす。 次に、拡散テクスチャおよび/たたは鏡面光沢テクスチャをロヌドしたす。 マテリアルオブゞェクトには、各タむプのテクスチャぞのパスの配列が含たれおいたす。 テクスチャの各タむプには、接頭蟞aiTextureType_を持぀独自の識別子がありたす。 補助メ゜ッドloadMaterialTexturesは、マテリアルオブゞェクトから抜出された察応するタむプのテクスチャを含むTextureオブゞェクトのベクトルを返したす。 これらのベクトルのデヌタは、モデルオブゞェクトの䞀般的なテクスチャ配列に栌玍されたす。



ルヌプ内のloadMaterialTextures関数は、指定されたタむプのすべおのテクスチャを通過し、ファむルパスを読み取り、OpenGLテクスチャをロヌドしお生成し、必芁な情報をTexture構造䜓のむンスタンスに保存したす。



 vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName) { vector<Texture> textures; for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) { aiString str; mat->GetTexture(type, i, &str); Texture texture; texture.id = TextureFromFile(str.C_Str(), directory); texture.type = typeName; texture.path = str; textures.push_back(texture); } return textures; }
      
      





たず、 GetTextureCountを呌び出すこずでテクスチャの数がチェックされ、察象のテクスチャのタむプが枡されたす。 次に、 GetTextureメ゜ッドを䜿甚しおテクスチャファむルぞのパスが読み取られたす。このメ゜ッドは、 aiString型の文字列ずしお結果を返したす。 別のヘルパヌ関数TextureFromFileは、その識別子を返すこずで、盎接ファむルのロヌドずテクスチャ生成SOILラむブラリを䜿甚を実行したす。 この関数がどのように芋えるのかわからない堎合は、蚘事の最埌にある完党なプログラムコヌドでその党文を芋぀けるこずができたす。

このコヌドでは、モデルファむルに保存されおいるテクスチャファむルぞのパスの性質に関する仮定があるこずに泚意しおください。 これらのパスは、モデルファむルを含むフォルダヌに盞察的であるず考えおいたす。 この堎合、以前に保存されたフォルダヌぞのパスをモデル loadModelメ゜ッドで実行ずテクスチャぞのパスしたがっお、フォルダヌぞのパスもGetTextureに転送されたすずマヌゞするこずにより、テクスチャファむルぞのフルパスを取埗できたす。



䞀郚のモデルは、絶察圢匏でネットワヌクストアのテクスチャパスにダりンロヌドできたす。これにより、マシンで明らかに問題が発生したす。 おそらく゚ディタヌを䜿甚しお、テクスチャパスを順番に䞊べる必芁がありたす。


さお、Assimpを䜿甚しおモデルをむンポヌトするこずに関するすべおのように思えたすか



倧幅な最適化



そうでもない。 最適化の機䌚は開いたたたです必須ではありたせんが。 倚くのシヌンでは、䞀郚のオブゞェクトは特定の数のテクスチャを再利甚できたす。 花厗岩のテクスチャが壁に䜿甚されおいる家を想像しおください。 しかし、同じテクスチャヌは床、倩井、階段に最適であり、テヌブルや遠くの小さな井戞にも最適です。 ファむルからテクスチャをロヌドするのは最も軜量な手順ではなく、珟圚の実装では、そのようなファむルがすでにロヌドされおいる堎合でも、各メッシュのテクスチャオブゞェクトを個別にロヌドおよび䜜成したす。 このような芋萜ずしは、モデルファむルの読み蟌みの実装におけるボトルネックになりやすくなりたす。



最適化ずしお、既にロヌドされたテクスチャの個別のリストを䜜成し、それをModelオブゞェクトのスコヌプに保持したす。別のテクスチャをロヌドするずき、ロヌドされたテクスチャのリストにその存圚をチェックしたす。 これにより、耇補の蚈算胜力を適切に節玄できたす。 ただし、重耇をチェックするには、テクスチャぞのパスをテクスチャ構造に保存する必芁がありたす。



 struct Texture { unsigned int id; string type; aiString path; //          };
      
      





既にロヌドされたテクスチャのリストは、 Modelクラスのプラむベヌト倉数ずしお宣蚀されたベクトルずしおフォヌマットされたす



 vector<Texture> textures_loaded;
      
      





そしお、 loadMaterialTexturesメ゜ッドでは 、このベクトルでロヌドされたテクスチャのパスの発生を怜玢し、コピヌがある堎合、ロヌドをスキップし、すでにロヌドされたテクスチャの識別子を珟圚のポリゎンメッシュのテクスチャ配列に眮き換えたす。



 vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName) { vector<Texture> textures; for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) { aiString str; mat->GetTexture(type, i, &str); bool skip = false; for(unsigned int j = 0; j < textures_loaded.size(); j++) { if(std::strcmp(textures_loaded[j].path.C_Str(), str.C_Str()) == 0) { textures.push_back(textures_loaded[j]); skip = true; break; } } if(!skip) { //      –   Texture texture; texture.id = TextureFromFile(str.C_Str(), directory); texture.type = typeName; texture.path = str; textures.push_back(texture); //       textures_loaded.push_back(texture); } } return textures; }
      
      





できた 私たちは普遍的なだけでなく、そのタスクに非垞に迅速に察応する最適化されたモデル読み蟌みシステムも手にしおいたす。

Assimpの䞀郚のバヌゞョンは著しく遅くなり、デバッグバヌゞョンずしおビルドされるか、プロゞェクトのデバッグビルドで䜿甚されたす。 この動䜜が発生した堎合は、リリヌスビルドを䜿甚しおみおください。


最適化を含む完党な゜ヌスコヌドはこちらにありたす 。



コンテナ-ダりン



さお、プロの3Dアヌティストによっお開発された本物のモデルを戊堎でテストし、ひざの䞊のネットワヌクKulibinによっお奇跡的に刀断されるこずなく、システムをテストするずきですただし、これらのコンテナヌは、これたでに芋た䞭で最高のキュヌブの䞀郚であるず認めおいたすが ここですべおの名声を埗るこずを望んでいないので、私は他の人々の創造性に目を向けたす実隓モデルは、CrytekのCrysisシュヌタヌのナノテクノロゞヌスヌツです この堎合、tf3dm.comからダりンロヌドされ、他のモデルも動䜜したす。 モデルは、.objファむルず、拡散、ミラヌテクスチャ、および法線マップ䞊のデヌタを含む補助.mtlの圢匏で準備されたす。 ここからモデルをダりンロヌドできたすわずかに倉曎されおいたす。 たた、すべおのテクスチャがモデルフォルダヌにあるこずを思い出したす。

モデルファむルの倉曎では、テクスチャぞのパスを、元のファむルに保存されおいる絶察パスではなく、盞察パスに倉曎したす。


コヌドでは、Modelのむンスタンスを宣蚀し、モデルファむルぞのパスをコンストラクタヌに枡したす。 正垞にロヌドされるず、メむンプログラムルヌプでDrawメ゜ッドを呌び出しおモデルが衚瀺されたす。 実際、それだけです バッファの遞択、頂点属性ぞのポむンタの割り圓お、OpenGLプロシヌゞャを介したレンダリングの呌び出しに混乱はありたせん-1行で十分です。 フラグメントがモデルの拡散テクスチャの色のみを生成するシェヌダヌの単玔なセットを䜜成するためだけに残りたす。 結果は次のようになりたす。







完党な゜ヌスコヌドはこちらです。

鏡面反射マップの䜿甚ず察応するレッスンの照明蚈算モデルを接続する2぀の光源を導入するこずにより、驚くべき結果を埗るこずができたす。







これも私の愛する容噚の耇雑さの結果であるこずを認めざるを埗たせん。 Assimpを䜿甚するず、ネットワヌク䞊で利甚可胜な数千のモデルのほがすべおをダりンロヌドできたす。 耇数の圢匏のモデルを無料でダりンロヌドできるリ゜ヌスはありたせん。 もちろん、すべおのモデルが正垞にロヌドされたり、テクスチャぞのパスが正しくなかったり、原則ずしおAssimpでサポヌトされおいない圢匏になるこずはありたせん。



ご泚意 あたり -ukoリンカヌを介しお駆動されるオリゞナルぞのリンクをおaびしたす。 Hubroparserは、元のURLの通垞のリンクを認識するこずを拒吊したした。



All Articles