From 5fff30d0b2cd7d33e2d8e13a649739aca97acf11 Mon Sep 17 00:00:00 2001 From: Meow J Date: Tue, 14 Jul 2015 23:16:29 +0800 Subject: [PATCH] Testing --- 03 Model Loading/02 Mesh.md | 60 +++++++++++++++++++------------------ mkdocs.yml | 5 +++- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/03 Model Loading/02 Mesh.md b/03 Model Loading/02 Mesh.md index 2424a64..5ef8e6c 100644 --- a/03 Model Loading/02 Mesh.md +++ b/03 Model Loading/02 Mesh.md @@ -1,10 +1,12 @@ -本文作者JoeyDeVries,由[Django](http://bullteacher.com/20-mesh.html)翻译自[http://learnopengl.com](http://learnopengl.com/) +鏈枃浣滆匤oeyDeVries锛岀敱[Django](http://bullteacher.com/20-mesh.html)缈昏瘧鑷猍http://learnopengl.com](http://learnopengl.com/) -使用Assimp可以把多种不同格式的模型加载到程序里,但是一旦载入,它们就都被储存为Assimp自己的数据结构。我们最终的想法是把这些数据转变为OpenGL可读的数据,用来渲染物体。我们从前面的教程了解到,一个网格代表一个可绘制实体,现在我们就定义一个自己的网格类。 +# Assimp -先来复习一点目前学到知识,考虑一个网格最少需要哪些数据。一个网格应该至少需要一组顶点,每个顶点包含一个位置向量,一个法线向量,一个纹理坐标向量。一个网格也应该包含一个索引绘制用的索引,以纹理(diffuse/specular map)形式表现的材质数据。 +浣跨敤Assimp鍙互鎶婂绉嶄笉鍚屾牸寮忕殑妯″瀷鍔犺浇鍒扮▼搴忛噷锛屼絾鏄竴鏃﹁浇鍏ワ紝瀹冧滑灏遍兘琚偍瀛樹负Assimp鑷繁鐨勬暟鎹粨鏋勩傛垜浠渶缁堢殑鎯虫硶鏄妸杩欎簺鏁版嵁杞彉涓篛penGL鍙鐨勬暟鎹紝鐢ㄦ潵娓叉煋鐗╀綋銆傛垜浠粠鍓嶉潰鐨勬暀绋嬩簡瑙e埌锛屼竴涓綉鏍间唬琛ㄤ竴涓彲缁樺埗瀹炰綋锛岀幇鍦ㄦ垜浠氨瀹氫箟涓涓嚜宸辩殑缃戞牸绫汇 -为了在OpenGL中定义一个顶点,现在我们设置有最少需求一个网格类: +鍏堟潵澶嶄範涓鐐圭洰鍓嶅鍒扮煡璇嗭紝鑰冭檻涓涓綉鏍兼渶灏戦渶瑕佸摢浜涙暟鎹備竴涓綉鏍煎簲璇ヨ嚦灏戦渶瑕佷竴缁勯《鐐癸紝姣忎釜椤剁偣鍖呭惈涓涓綅缃悜閲忥紝涓涓硶绾垮悜閲忥紝涓涓汗鐞嗗潗鏍囧悜閲忋備竴涓綉鏍间篃搴旇鍖呭惈涓涓储寮曠粯鍒剁敤鐨勭储寮曪紝浠ョ汗鐞嗭紙diffuse/specular map锛夊舰寮忚〃鐜扮殑鏉愯川鏁版嵁銆 + +涓轰簡鍦∣penGL涓畾涔変竴涓《鐐癸紝鐜板湪鎴戜滑璁剧疆鏈夋渶灏戦渶姹備竴涓綉鏍肩被: ```c++ @@ -15,7 +17,7 @@ struct Vertex glm::vec2 TexCoords; }; ``` -我们把每个需要的向量储存到一个叫做Vertex的结构体中,它被用来索引每个顶点属性。另外除了Vertex结构体外,我们也希望组织纹理数据,所以我们定义一个Texture结构体: +鎴戜滑鎶婃瘡涓渶瑕佺殑鍚戦噺鍌ㄥ瓨鍒颁竴涓彨鍋歏ertex鐨勭粨鏋勪綋涓紝瀹冭鐢ㄦ潵绱㈠紩姣忎釜椤剁偣灞炴с傚彟澶栭櫎浜哣ertex缁撴瀯浣撳锛屾垜浠篃甯屾湜缁勭粐绾圭悊鏁版嵁锛屾墍浠ユ垜浠畾涔変竴涓猅exture缁撴瀯浣擄細 ```c++ @@ -25,9 +27,9 @@ struct Texture String type; }; ``` -我们储存纹理的id和它的类型,比如diffuse纹理或者specular纹理。 +鎴戜滑鍌ㄥ瓨绾圭悊鐨刬d鍜屽畠鐨勭被鍨嬶紝姣斿diffuse绾圭悊鎴栬卻pecular绾圭悊銆 -知道了顶点和纹理的实际表达,我们可以开始定义网格类的结构: +鐭ラ亾浜嗛《鐐瑰拰绾圭悊鐨勫疄闄呰〃杈撅紝鎴戜滑鍙互寮濮嬪畾涔夌綉鏍肩被鐨勭粨鏋勶細 ```c++ @@ -45,9 +47,9 @@ private: void setupMesh(); } ``` -如你所见这个类一点都不复杂,构造方法里我们初始化网格所有必须数据。在setupMesh函数里初始化缓冲。最后通过Draw函数绘制网格。注意,我们把shader传递给Draw函数。通过把shader传递给mesh,在绘制之前我们设置几个uniform(就像链接采样器到纹理单元)。 +濡備綘鎵瑙佽繖涓被涓鐐归兘涓嶅鏉傦紝鏋勯犳柟娉曢噷鎴戜滑鍒濆鍖栫綉鏍兼墍鏈夊繀椤绘暟鎹傚湪setupMesh鍑芥暟閲屽垵濮嬪寲缂撳啿銆傛渶鍚庨氳繃Draw鍑芥暟缁樺埗缃戞牸銆傛敞鎰忥紝鎴戜滑鎶妔hader浼犻掔粰Draw鍑芥暟銆傞氳繃鎶妔hader浼犻掔粰mesh锛屽湪缁樺埗涔嬪墠鎴戜滑璁剧疆鍑犱釜uniform锛堝氨鍍忛摼鎺ラ噰鏍峰櫒鍒扮汗鐞嗗崟鍏冿級銆 -构造函数的内容非常直接。我们简单设置类的公有变量,使用的是构造函数相应的参数。我们在构造函数中也调用setupMesh函数: +鏋勯犲嚱鏁扮殑鍐呭闈炲父鐩存帴銆傛垜浠畝鍗曡缃被鐨勫叕鏈夊彉閲忥紝浣跨敤鐨勬槸鏋勯犲嚱鏁扮浉搴旂殑鍙傛暟銆傛垜浠湪鏋勯犲嚱鏁颁腑涔熻皟鐢╯etupMesh鍑芥暟锛 ```c++ @@ -60,13 +62,13 @@ Mesh(vector vertices, vector indices, vector textures) this->setupMesh(); } ``` -这里没什么特别的,现在让我们研究一下setupMesh函数。 +杩欓噷娌′粈涔堢壒鍒殑锛岀幇鍦ㄨ鎴戜滑鐮旂┒涓涓媠etupMesh鍑芥暟銆 -##初始化 +##鍒濆鍖 -现在我们有一大列的网格数据可用于渲染,这要感谢构造函数。我们确实需要设置合适的缓冲,通过顶点属性指针(vertex attribute pointers)定义顶点着色器layout。现在你应该对这些概念很熟悉,但是我们我们通过介绍了结构体中使用顶点数据,所以稍微有点不一样: +鐜板湪鎴戜滑鏈変竴澶у垪鐨勭綉鏍兼暟鎹彲鐢ㄤ簬娓叉煋锛岃繖瑕佹劅璋㈡瀯閫犲嚱鏁般傛垜浠‘瀹為渶瑕佽缃悎閫傜殑缂撳啿锛岄氳繃椤剁偣灞炴ф寚閽堬紙vertex attribute pointers锛夊畾涔夐《鐐圭潃鑹插櫒layout銆傜幇鍦ㄤ綘搴旇瀵硅繖浜涙蹇靛緢鐔熸倝锛屼絾鏄垜浠垜浠氳繃浠嬬粛浜嗙粨鏋勪綋涓娇鐢ㄩ《鐐规暟鎹紝鎵浠ョ◢寰湁鐐逛笉涓鏍凤細 ```c++ @@ -102,9 +104,9 @@ void setupMesh() glBindVertexArray(0); } ``` -如你所想代码没什么特别不同的地方,在Vertex结构体的帮助下有了一些小把戏。 +濡備綘鎵鎯充唬鐮佹病浠涔堢壒鍒笉鍚岀殑鍦版柟锛屽湪Vertex缁撴瀯浣撶殑甯姪涓嬫湁浜嗕竴浜涘皬鎶婃垙銆 -C++的结构体有一个重要的属性,那就是在内存中它们是连续的。如果我们用结构体表示一列数据,这个结构体只包含结构体的连续的变量,它就会直接转变为一个float(实际上是byte)数组,我们就能用于一个数组缓冲(array buffer)中了。比如,如果我们填充一个Vertex结构体,它在内存中的排布等于: +C++鐨勭粨鏋勪綋鏈変竴涓噸瑕佺殑灞炴э紝閭e氨鏄湪鍐呭瓨涓畠浠槸杩炵画鐨勩傚鏋滄垜浠敤缁撴瀯浣撹〃绀轰竴鍒楁暟鎹紝杩欎釜缁撴瀯浣撳彧鍖呭惈缁撴瀯浣撶殑杩炵画鐨勫彉閲忥紝瀹冨氨浼氱洿鎺ヨ浆鍙樹负涓涓猣loat锛堝疄闄呬笂鏄痓yte锛夋暟缁勶紝鎴戜滑灏辫兘鐢ㄤ簬涓涓暟缁勭紦鍐诧紙array buffer锛変腑浜嗐傛瘮濡傦紝濡傛灉鎴戜滑濉厖涓涓猇ertex缁撴瀯浣擄紝瀹冨湪鍐呭瓨涓殑鎺掑竷绛変簬锛 ```c++ @@ -114,33 +116,33 @@ vertex.Normal = glm::vec3(0.0f, 1.0f, 0.0f); vertex.TexCoords = glm::vec2(1.0f, 0.0f); // = [0.2f, 0.4f, 0.6f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f]; ``` -感谢这个有用的特性,我们能直接把一个作为缓冲数据的一大列Vertex结构体的指针传递过去,它们会翻译成glBufferData能用的参数: +鎰熻阿杩欎釜鏈夌敤鐨勭壒鎬э紝鎴戜滑鑳界洿鎺ユ妸涓涓綔涓虹紦鍐叉暟鎹殑涓澶у垪Vertex缁撴瀯浣撶殑鎸囬拡浼犻掕繃鍘伙紝瀹冧滑浼氱炕璇戞垚glBufferData鑳界敤鐨勫弬鏁帮細 ```c++ glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW); ``` -自然地,sizeof函数也可以使用于结构体来计算字节类型的大小。它应该是32字节(8float * 4)。 +鑷劧鍦帮紝sizeof鍑芥暟涔熷彲浠ヤ娇鐢ㄤ簬缁撴瀯浣撴潵璁$畻瀛楄妭绫诲瀷鐨勫ぇ灏忋傚畠搴旇鏄32瀛楄妭锛8float * 4锛夈 -一个预处理指令叫做offsetof(s, m)把结构体作为它的第一个参数,第二个参数是这个结构体名字的变量。这是结构体另外的一个重要用途。函数返回这个变量从结构体开始的字节偏移量(offset)。这对于定义glVertexAttribPointer函数偏移量参数效果很好: +涓涓澶勭悊鎸囦护鍙仛offsetof(s, m)鎶婄粨鏋勪綋浣滀负瀹冪殑绗竴涓弬鏁帮紝绗簩涓弬鏁版槸杩欎釜缁撴瀯浣撳悕瀛楃殑鍙橀噺銆傝繖鏄粨鏋勪綋鍙﹀鐨勪竴涓噸瑕佺敤閫斻傚嚱鏁拌繑鍥炶繖涓彉閲忎粠缁撴瀯浣撳紑濮嬬殑瀛楄妭鍋忕Щ閲忥紙offset锛夈傝繖瀵逛簬瀹氫箟glVertexAttribPointer鍑芥暟鍋忕Щ閲忓弬鏁版晥鏋滃緢濂斤細 ```c++ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal)); ``` -偏移量现在使用offsetof函数定义了,在这个例子里,设置法线向量的字节偏移量等于法线向量在结构体的字节偏移量,它是3float,也就是12字节(一个float占4字节)。注意,我们同样设置步长参数等于Vertex结构体的大小。 +鍋忕Щ閲忕幇鍦ㄤ娇鐢╫ffsetof鍑芥暟瀹氫箟浜嗭紝鍦ㄨ繖涓緥瀛愰噷锛岃缃硶绾垮悜閲忕殑瀛楄妭鍋忕Щ閲忕瓑浜庢硶绾垮悜閲忓湪缁撴瀯浣撶殑瀛楄妭鍋忕Щ閲忥紝瀹冩槸3float锛屼篃灏辨槸12瀛楄妭锛堜竴涓猣loat鍗4瀛楄妭锛夈傛敞鎰忥紝鎴戜滑鍚屾牱璁剧疆姝ラ暱鍙傛暟绛変簬Vertex缁撴瀯浣撶殑澶у皬銆 -使用一个像这样的结构体,不仅能提供可读性更高的代码同时也是我们可以轻松的扩展结构体。如果我们想要增加另一个顶点属性,我们把它可以简单的添加到结构体中,由于它的可扩展性,渲染代码不会被破坏。 +浣跨敤涓涓儚杩欐牱鐨勭粨鏋勪綋锛屼笉浠呰兘鎻愪緵鍙鎬ф洿楂樼殑浠g爜鍚屾椂涔熸槸鎴戜滑鍙互杞绘澗鐨勬墿灞曠粨鏋勪綋銆傚鏋滄垜浠兂瑕佸鍔犲彟涓涓《鐐瑰睘鎬э紝鎴戜滑鎶婂畠鍙互绠鍗曠殑娣诲姞鍒扮粨鏋勪綋涓紝鐢变簬瀹冪殑鍙墿灞曟э紝娓叉煋浠g爜涓嶄細琚牬鍧忋 -##渲染 +##娓叉煋 -我们需要为Mesh类定义的最后一个函数,是它的Draw函数。在真正渲染前我们希望绑定合适的纹理,然后调用glDrawElements。可因为我们从一开始不知道这个网格有多少纹理以及它们应该是什么类型的,所以这件事变得很困难。所以我们该怎样在着色器中设置纹理单元和采样器呢? +鎴戜滑闇瑕佷负Mesh绫诲畾涔夌殑鏈鍚庝竴涓嚱鏁帮紝鏄畠鐨凞raw鍑芥暟銆傚湪鐪熸娓叉煋鍓嶆垜浠笇鏈涚粦瀹氬悎閫傜殑绾圭悊锛岀劧鍚庤皟鐢╣lDrawElements銆傚彲鍥犱负鎴戜滑浠庝竴寮濮嬩笉鐭ラ亾杩欎釜缃戞牸鏈夊灏戠汗鐞嗕互鍙婂畠浠簲璇ユ槸浠涔堢被鍨嬬殑锛屾墍浠ヨ繖浠朵簨鍙樺緱寰堝洶闅俱傛墍浠ユ垜浠鎬庢牱鍦ㄧ潃鑹插櫒涓缃汗鐞嗗崟鍏冨拰閲囨牱鍣ㄥ憿锛 -解决这个问题,我们需要假设一个特定的名称惯例:每个diffuse纹理被命名为texture_diffuseN,每个specular纹理应该被命名为texture_specularN.N是一个从1到纹理才抢其允许使用的最大值之间的数。可以说,在一个网格中我们有3个diffuse纹理和2个specular纹理,它们的纹理采样器应该这样被调用: +瑙e喅杩欎釜闂锛屾垜浠渶瑕佸亣璁句竴涓壒瀹氱殑鍚嶇О鎯緥锛氭瘡涓猟iffuse绾圭悊琚懡鍚嶄负texture_diffuseN,姣忎釜specular绾圭悊搴旇琚懡鍚嶄负texture_specularN.N鏄竴涓粠1鍒扮汗鐞嗘墠鎶㈠叾鍏佽浣跨敤鐨勬渶澶у间箣闂寸殑鏁般傚彲浠ヨ锛屽湪涓涓綉鏍间腑鎴戜滑鏈3涓猟iffuse绾圭悊鍜2涓猻pecular绾圭悊锛屽畠浠殑绾圭悊閲囨牱鍣ㄥ簲璇ヨ繖鏍疯璋冪敤锛 ```c++ @@ -150,10 +152,10 @@ uniform sampler2D texture_diffuse3; uniform sampler2D texture_specular1; uniform sampler2D texture_specular2; ``` -使用这样的惯例,我们能定义我们在着色器中需要的纹理采样器的数量。如果一个网格真的有(这么多)纹理,我们就知道它们的名字应该是什么。这个惯例也使我们能够处理一个网格上的任何数量的纹理,通过定义合适的采样器开发者可以自由使用希望使用的数量(虽然定义少的话就会有点浪费绑定和uniform调用了)。 +浣跨敤杩欐牱鐨勬儻渚嬶紝鎴戜滑鑳藉畾涔夋垜浠湪鐫鑹插櫒涓渶瑕佺殑绾圭悊閲囨牱鍣ㄧ殑鏁伴噺銆傚鏋滀竴涓綉鏍肩湡鐨勬湁锛堣繖涔堝锛夌汗鐞嗭紝鎴戜滑灏辩煡閬撳畠浠殑鍚嶅瓧搴旇鏄粈涔堛傝繖涓儻渚嬩篃浣挎垜浠兘澶熷鐞嗕竴涓綉鏍间笂鐨勪换浣曟暟閲忕殑绾圭悊锛岄氳繃瀹氫箟鍚堥傜殑閲囨牱鍣ㄥ紑鍙戣呭彲浠ヨ嚜鐢变娇鐢ㄥ笇鏈涗娇鐢ㄧ殑鏁伴噺锛堣櫧鐒跺畾涔夊皯鐨勮瘽灏变細鏈夌偣娴垂缁戝畾鍜寀niform璋冪敤浜嗭級銆 -像这样的问题有很多不同的解决方案,如果你不喜欢这个方案,你可以自己创造一个你自己的方案。 -最后的绘制代码: +鍍忚繖鏍风殑闂鏈夊緢澶氫笉鍚岀殑瑙e喅鏂规锛屽鏋滀綘涓嶅枩娆㈣繖涓柟妗堬紝浣犲彲浠ヨ嚜宸卞垱閫犱竴涓綘鑷繁鐨勬柟妗堛 +鏈鍚庣殑缁樺埗浠g爜锛 ```c++ @@ -185,9 +187,9 @@ void Draw(Shader shader) glBindVertexArray(0); } ``` -这不是最漂亮的代码,但是这主要归咎于C++转换类型时的丑陋,比如int转string时。我们首先计算N-元素每个纹理类型,把它链接到纹理类型字符串来获取合适的uniform名。然后查找合适的采样器位置,给它位置值对应当前激活纹理单元,绑定纹理。这也是我们需要在Draw方法是用shader的原因。我们添加”material.”到作为结果的uniform名,因为我们通常把纹理储存进材质结构体(对于每个实现也许会有不同)。 +杩欎笉鏄渶婕備寒鐨勪唬鐮侊紝浣嗘槸杩欎富瑕佸綊鍜庝簬C++杞崲绫诲瀷鏃剁殑涓戦檵锛屾瘮濡俰nt杞瑂tring鏃躲傛垜浠鍏堣绠桸-鍏冪礌姣忎釜绾圭悊绫诲瀷锛屾妸瀹冮摼鎺ュ埌绾圭悊绫诲瀷瀛楃涓叉潵鑾峰彇鍚堥傜殑uniform鍚嶃傜劧鍚庢煡鎵惧悎閫傜殑閲囨牱鍣ㄤ綅缃紝缁欏畠浣嶇疆鍊煎搴斿綋鍓嶆縺娲荤汗鐞嗗崟鍏冿紝缁戝畾绾圭悊銆傝繖涔熸槸鎴戜滑闇瑕佸湪Draw鏂规硶鏄敤shader鐨勫師鍥犮傛垜浠坊鍔犫漨aterial.鈥濆埌浣滀负缁撴灉鐨剈niform鍚嶏紝鍥犱负鎴戜滑閫氬父鎶婄汗鐞嗗偍瀛樿繘鏉愯川缁撴瀯浣擄紙瀵逛簬姣忎釜瀹炵幇涔熻浼氭湁涓嶅悓锛夈 -注意,当我们把diffuse和specular传递到字符串流(stringstream)的时候,计数器会增加,在C++自增叫做:变量++,它会先返回自身然后加1,而++变量,先加1再返回自身,我们的例子里,我们先传递原来的计数器值到字符串流,然后再加1,下一轮生效。 -你可以从这里得到Mesh类的源码。 +娉ㄦ剰锛屽綋鎴戜滑鎶奷iffuse鍜宻pecular浼犻掑埌瀛楃涓叉祦锛坰tringstream锛夌殑鏃跺欙紝璁℃暟鍣ㄤ細澧炲姞锛屽湪C++鑷鍙仛锛氬彉閲++锛屽畠浼氬厛杩斿洖鑷韩鐒跺悗鍔1锛岃++鍙橀噺锛屽厛鍔1鍐嶈繑鍥炶嚜韬紝鎴戜滑鐨勪緥瀛愰噷锛屾垜浠厛浼犻掑師鏉ョ殑璁℃暟鍣ㄥ煎埌瀛楃涓叉祦锛岀劧鍚庡啀鍔1锛屼笅涓杞敓鏁堛 +浣犲彲浠ヤ粠杩欓噷寰楀埌Mesh绫荤殑婧愮爜銆 -Mesh类是对我们前面的教程里讨论的很多话题的的简洁的抽象在下面的教程里,我们会创建一个模型,它用作乘放多个网格物体的容器,真正的实现Assimp的加载接口。 \ No newline at end of file +Mesh绫绘槸瀵规垜浠墠闈㈢殑鏁欑▼閲岃璁虹殑寰堝璇濋鐨勭殑绠娲佺殑鎶借薄鍦ㄤ笅闈㈢殑鏁欑▼閲岋紝鎴戜滑浼氬垱寤轰竴涓ā鍨嬶紝瀹冪敤浣滀箻鏀惧涓綉鏍肩墿浣撶殑瀹瑰櫒锛岀湡姝g殑瀹炵幇Assimp鐨勫姞杞芥帴鍙c \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 47cde67..c0fcccf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,7 +18,10 @@ pages: - Assimp: '03 Model Loading/01 Assimp.md' - 缃戞牸: '03 Model Loading/02 Mesh.md' - 妯″瀷: '03 Model Loading/03 Model.md' - + - 楂樼骇OpenGL(Advanced OpenGL): + - 娣卞害娴嬭瘯: '04 Advanced OpenGL/01 Depth testing.md' + - 楂樼骇鍏夌収(Advanced Lighting): + - HDR: '05 Advanced Lighting/06 HDR.md' site_name: LearnOpenGL-CN