版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.01.20 |
前言
OpenGL 圖形庫項目中一直也沒用過尺锚,最近也想學(xué)著使用這個圖形庫席楚,感覺還是很有意思趣惠,也就自然想著好好的總結(jié)一下,希望對大家能有所幫助鉴扫。下面內(nèi)容來自歡迎來到OpenGL的世界赞枕。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象坪创、擴展和狀態(tài)機
3. OpenGL 圖形庫使用(三) —— 著色器炕婶、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級OpenGL之高級數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫的使用(三十二)—— 高級光照之高級光照Advanced Lighting
33. OpenGL 圖形庫的使用(三十三)—— 高級光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫的使用(三十四)—— 高級光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫的使用(三十五)—— 高級光照之陰影 - 點陰影Point Shadows
36. OpenGL 圖形庫的使用(三十六)—— 高級光照之法線貼圖Normal Mapping
37. OpenGL 圖形庫的使用(三十七)—— 高級光照之視差貼圖Parallax Mapping
38. OpenGL 圖形庫的使用(三十八)—— 高級光照之HDR
39. OpenGL 圖形庫的使用(三十九)—— 高級光照之泛光
40. OpenGL 圖形庫的使用(四十)—— 高級光照之延遲著色法Deferred Shading
41. OpenGL 圖形庫的使用(四十一)—— 高級光照之SSAO
42. OpenGL 圖形庫的使用(四十二)—— PBR之理論Theory
43. OpenGL 圖形庫的使用(四十三)—— PBR之光照Lighting
44. OpenGL 圖形庫的使用(四十四)—— PBR之幾篇沒有翻譯的英文原稿
調(diào)試
圖形編程可以帶來很多的樂趣,然而如果什么東西渲染錯誤莱预,或者甚至根本就沒有渲染柠掂,它同樣可以給你帶來大量的沮喪感!由于我們大部分時間都在與像素打交道依沮,當(dāng)出現(xiàn)錯誤的時候?qū)ふ义e誤的源頭可能會非常困難涯贞。調(diào)試(Debug)這樣的視覺錯誤與往常熟悉的CPU調(diào)試不同枪狂。我們沒有一個可以用來輸出文本的控制臺,在GLSL代碼中也不能設(shè)置斷點宋渔,更沒有方法檢測GPU的運行狀態(tài)州疾。
在這篇教程中,我們將來見識幾個調(diào)試OpenGL程序的技巧皇拣。OpenGL中的調(diào)試并不是很難严蓖,掌握住這些技巧對之后的學(xué)習(xí)會有非常大的幫助。
glGetError()
當(dāng)你不正確使用OpenGL的時候(比如說在綁定之前配置一個緩沖)审磁,它會檢測到,并在幕后生成一個或多個用戶錯誤標(biāo)記岂座。我們可以使用一個叫做glGetError的函數(shù)查詢這些錯誤標(biāo)記态蒂,他會檢測錯誤標(biāo)記集,并且在OpenGL確實出錯的時候返回一個錯誤值费什。
GLenum glGetError();
當(dāng)glGetError
被調(diào)用時钾恢,它要么會返回錯誤標(biāo)記之一,要么返回?zé)o錯誤鸳址。glGetError
會返回的錯誤值如下:
在OpenGL的函數(shù)文檔中你可以找到函數(shù)在錯誤時生成的錯誤代碼瘩蚪。比如說,如果你看一下glBindTexture函數(shù)的文檔稿黍,在 Errors 部分中你可以看到它可能生成的所有用戶錯誤代碼疹瘦。
當(dāng)一個錯誤標(biāo)記被返回的時候,將不會報告其它的錯誤標(biāo)記巡球。換句話說言沐,當(dāng)glGetError
被調(diào)用的時候,它會清除所有的錯誤標(biāo)記(在分布式系統(tǒng)上只會清除一個酣栈,見下面的注釋)险胰。這也就是說如果你在每一幀之后調(diào)用glGetError
一次,它返回一個錯誤矿筝,但你不能確定這就是唯一的錯誤起便,并且錯誤的來源可能在這一幀的任意地方。
注意當(dāng)OpenGL是分布式
(Distributely)
運行的時候窖维,如在X11系統(tǒng)上榆综,其它的用戶錯誤代碼仍然可以被生成,只要它們有著不同的錯誤代碼铸史。調(diào)用glGetError
只會重置一個錯誤代碼標(biāo)記奖年,而不是所有。由于這一點沛贪,我們通常會建議在一個循環(huán)中調(diào)用glGetError
陋守。
glBindTexture(GL_TEXTURE_2D, tex);
std::cout << glGetError() << std::endl; // 返回 0 (無錯誤)
glTexImage2D(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
std::cout << glGetError() << std::endl; // 返回 1280 (非法枚舉)
glGenTextures(-5, textures);
std::cout << glGetError() << std::endl; // 返回 1281 (非法值)
std::cout << glGetError() << std::endl; // 返回 0 (無錯誤)
glGetError
非常棒的一點就是它能夠非常簡單地定位錯誤可能的來源震贵,并且驗證OpenGL使用的正確性。比如說你獲得了一個黑屏的結(jié)果但是不知道什么造成了它:是不是幀緩沖設(shè)置錯誤水评?是不是我忘記綁定紋理了猩系?通過在代碼中各處調(diào)用glGetError,你可以非持性铮快速地查明OpenGL錯誤開始出現(xiàn)的位置寇甸,這也就意味著這次調(diào)用之前的代碼中哪里出錯了。
默認情況下glGetError只會打印錯誤數(shù)字疗涉,如果你不去記憶的話會非常難以理解拿霉。通常我們會寫一個助手函數(shù)來簡便地打印出錯誤字符串以及錯誤檢測函數(shù)調(diào)用的位置。
GLenum glCheckError_(const char *file, int line)
{
GLenum errorCode;
while ((errorCode = glGetError()) != GL_NO_ERROR)
{
std::string error;
switch (errorCode)
{
case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break;
case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break;
case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
}
std::cout << error << " | " << file << " (" << line << ")" << std::endl;
}
return errorCode;
}
#define glCheckError() glCheckError_(__FILE__, __LINE__)
防止你不知道__FILE__
和__LINE__
這兩個預(yù)處理指令(Preprocessor Directive)
是什么咱扣,它們會在編譯的時候被替換成編譯時對應(yīng)的文件與行號绽淘。如果我們堅持在代碼中使用大量glGetError
的調(diào)用,這就會讓我們更加準(zhǔn)確地知道哪個glGetError調(diào)用返回了錯誤(譯注:記得glGetError顯示的錯誤會發(fā)生在該次調(diào)用與上次調(diào)用之間闹伪,如果間隔太大的話需要檢查的地方就太多了)沪铭。
glBindBuffer(GL_VERTEX_ARRAY, vbo);
glCheckError();
這會給我們以下的輸出:
還有一個重要的事情需要知道,GLEW
有一個歷史悠久的bug偏瓤,調(diào)用glewInit()
會設(shè)置一個GL_INVALID_ENUM
的錯誤標(biāo)記杀怠,所以第一次調(diào)用的glGetError永遠會猝不及防地給你返回一個錯誤代碼。如果要修復(fù)這個bug厅克,我們建議您在調(diào)用glewInit之后立即調(diào)用glGetError消除這個標(biāo)記:
glewInit();
glGetError();
glGetError
并不能幫助你很多赔退,因為它返回的信息非常簡單,但不可否認它經(jīng)常能幫助你檢查筆誤或者快速定位錯誤來源证舟±攵郏總而言之,是一個非常簡單但有效的工具褪储。
調(diào)試輸出
雖然沒有glGetError
普遍卵渴,但一個叫做調(diào)試輸出(Debug Output)
的OpenGL拓展是一個非常有用的工具,它在4.3版本之后變?yōu)榱撕诵腛penGL的一部分鲤竹。通過使用調(diào)試輸出拓展浪读,OpenGL自身會直接發(fā)送一個比起glGetError更為完善的錯誤或警告信息給用戶。它不僅提供了更多的信息辛藻,也能夠幫助你使用調(diào)試器(Debugger)捕捉錯誤源頭碘橘。
調(diào)試輸出自4.3版本變?yōu)楹诵腛penGL的一部分,這也就是說你可以在任意運行OpenGL 4.3及以上版本的機器中找到這一功能吱肌。如果OpenGL低于這一版本痘拆,你可以可以查詢
ARB_debug_output
或者AMD_debug_output
拓展來獲取它的功能。注意OS X好像不支持調(diào)試輸出功能(從網(wǎng)上看到的氮墨,我暫時還沒有測試纺蛆。如果我錯了請告訴我一下)
要想開始使用調(diào)試輸出吐葵,我們先要在初始化進程中從OpenGL請求一個調(diào)試輸出上下文。這個進程根據(jù)你的窗口系統(tǒng)會有所不同桥氏,這里我們只會討論在GLFW
中配置温峭,但你可以在教程最后的附加資源處找到其它系統(tǒng)的相關(guān)資料。
1. GLFW中的調(diào)試輸出
在GLFW中請求一個調(diào)試輸出非常簡單字支,我們只需要傳遞一個提醒到GLFW中凤藏,告訴它我們需要一個調(diào)試輸出上下文即可。我們需要在調(diào)用glfwCreateWindow
之前完成這一請求堕伪。
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
一旦GLFW初始化完成揖庄,如果我們使用的OpenGL 版本為4.3或以上的話我們就有一個調(diào)試上下文了,如果不是的話則祈禱系統(tǒng)仍然能夠請求一個調(diào)試上下文吧欠雌。如果還是不行的話我們必須使用它的OpenGL拓展來請求調(diào)試輸出蹄梢。
在調(diào)試上下文中使用OpenGL會明顯更緩慢一點,所以當(dāng)你在優(yōu)化或者發(fā)布程序之前請將這一GLFW調(diào)試請求給注釋掉桨昙。
要檢查我們是否成功地初始化了調(diào)試上下文检号,我們可以對OpenGL進行查詢:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
// 初始化調(diào)試輸出
}
調(diào)試輸出工作的方式是這樣的腌歉,我們首先將一個錯誤記錄函數(shù)的回調(diào)(類似于GLFW輸入的回調(diào))傳遞給OpenGL蛙酪,在這個回調(diào)函數(shù)中我們可以自由地處理OpenGL錯誤數(shù)據(jù),在這里我們將輸出一些有用的錯誤數(shù)據(jù)到控制臺中翘盖。下面是這個就是OpenGL對調(diào)試輸出所期待的回調(diào)函數(shù)的原型:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, void *userParam);
注意在OpenGL的某些實現(xiàn)中最后一個參數(shù)為const void*
而不是void*
桂塞。
有了這一大堆的數(shù)據(jù),我們可以創(chuàng)建一個非常有用的錯誤打印工具:
void APIENTRY glDebugOutput(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
void *userParam)
{
// 忽略一些不重要的錯誤/警告代碼
if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
std::cout << "---------------" << std::endl;
std::cout << "Debug message (" << id << "): " << message << std::endl;
switch (source)
{
case GL_DEBUG_SOURCE_API: std::cout << "Source: API"; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Source: Window System"; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break;
case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Source: Third Party"; break;
case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Source: Application"; break;
case GL_DEBUG_SOURCE_OTHER: std::cout << "Source: Other"; break;
} std::cout << std::endl;
switch (type)
{
case GL_DEBUG_TYPE_ERROR: std::cout << "Type: Error"; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Type: Undefined Behaviour"; break;
case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Type: Portability"; break;
case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Type: Performance"; break;
case GL_DEBUG_TYPE_MARKER: std::cout << "Type: Marker"; break;
case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Type: Push Group"; break;
case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Type: Pop Group"; break;
case GL_DEBUG_TYPE_OTHER: std::cout << "Type: Other"; break;
} std::cout << std::endl;
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: std::cout << "Severity: high"; break;
case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "Severity: medium"; break;
case GL_DEBUG_SEVERITY_LOW: std::cout << "Severity: low"; break;
case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break;
} std::cout << std::endl;
std::cout << std::endl;
}
當(dāng)調(diào)試輸出檢測到了一個OpenGL錯誤馍驯,它會調(diào)用這個回調(diào)函數(shù)阁危,我們將可以打印出非常多的OpenGL錯誤信息。注意我們忽略掉了一些錯誤代碼汰瘫,這些錯誤代碼一般不能給我們?nèi)魏斡杏玫男畔ⅲū热鏝Vidia驅(qū)動中的131185僅告訴我們緩沖成功創(chuàng)建了)狂打。
2. 過濾調(diào)試輸出
有了glDebugMessageControl
,你可以潛在地過濾出你需要的錯誤類型混弥。在這里我們不打算過濾任何來源趴乡,類型或者嚴(yán)重等級。如果我們僅希望顯示OpenGL API的高嚴(yán)重等級錯誤消息蝗拿,你可以設(shè)置為以下這樣:
glDebugMessageControl(GL_DEBUG_SOURCE_API,
GL_DEBUG_TYPE_ERROR,
GL_DEBUG_SEVERITY_HIGH,
0, nullptr, GL_TRUE);
有了我們的配置晾捏,如果你的上下文支持調(diào)試輸出的話,每個不正確的OpenGL指令都會打印出一大堆的有用數(shù)據(jù)哀托。
3. 回溯調(diào)試錯誤源
使用調(diào)試輸出另一個很棒的技巧就是你可以很容易找出錯誤發(fā)生的準(zhǔn)確行號或者調(diào)用惦辛。通過在DebugOutput
中特定的錯誤類型上(或者在函數(shù)的頂部,如果你不關(guān)心類型的話)設(shè)置一個斷點仓手,調(diào)試器將會捕捉到拋出的錯誤胖齐,你可以往上查找調(diào)用棧直到找到消息發(fā)出的源頭玻淑。
這需要一些手動操作,但如果你大致知道你在尋找什么市怎,這會非常有用岁忘,能夠幫助你快速定位錯誤。
4. 自定義錯誤輸出
除了僅僅是閱讀信息区匠,我們也可以使用glDebugMessageInsert
將信息推送到調(diào)試輸出系統(tǒng):
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0,
GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here");
如果你正在利用其它使用調(diào)試輸出上下文的程序或OpenGL代碼進行開發(fā)干像,這會非常有用。其它的開發(fā)者能快速了解你自定義OpenGL代碼中任何報告出來的Bug驰弄。
總而言之麻汰,調(diào)試輸出(如果你能使用它)對與快速捕捉錯誤是非常有用的,完全值得你花一點時間來配置戚篙,它能夠省下你非常多的開發(fā)時間五鲫。你可以在這里找到源碼,里面glGetError
和調(diào)試輸出上下文都有配置岔擂;看看你是否能夠修復(fù)所有的錯誤位喂。
調(diào)試著色器輸出
對于GLSL來說,我們不能訪問像是glGetError
這樣的函數(shù)乱灵,也不能通過步進的方式運行著色器代碼塑崖。如果你得到一個黑屏或者完全錯誤的視覺效果,通常想要知道著色器代碼是否有誤會非常困難痛倚。是的规婆,我們是有編譯錯誤報告來報告語法錯誤,但是捕捉語義錯誤又是一大難題蝉稳。
一個經(jīng)常使用的技巧就是將著色器程序中所有相關(guān)的變量直接發(fā)送到片段著色器的輸出通道抒蚜,以評估它們。通過直接輸出著色器變量到輸出顏色通道耘戚,我們通澄怂瑁可以通過觀察視覺結(jié)果來獲取有用的信息。比如說收津,如果我們想要檢查一個模型的法向量是否正確饿这,我們可以把它們(可以是變換過的也可以是沒有變換過的)從頂點著色器傳遞到片段著色器中,在片段著色器中我們會用以下這種方式輸出法向量:
#version 330 core
out vec4 FragColor;
in vec3 Normal;
[...]
void main()
{
[...]
FragColor.rgb = Normal;
FragColor.a = 1.0f;
}
通過輸出一個(非顏色)變量到這樣子的輸出顏色通道中朋截,我們可以快速審查變量是否顯示著正確的值蛹稍。舉例來說,如果最后的視覺效果完全是黑色的部服,則很清楚表明法向量沒有正確地傳遞至著色器中唆姐。當(dāng)它們都顯示出來的時候,檢查它們(大概)正確與否就會變得非常簡單廓八。
從視覺效果來看奉芦,我們可以看見法向量應(yīng)該是正確的赵抢,因為納米裝的右側(cè)大部分都是紅色的(這表明法線大概(正確地)指向正x軸),并且類似的納米裝的前方大部分都為藍色声功,即正z軸方向烦却。
這一方法可以很容易拓展到你想要測試的任何變量。一旦你卡住了或者懷疑你的著色器有問題先巴,嘗試顯示多個變量和/或中間結(jié)果其爵,看看哪部分算法什么的沒加上或者有錯誤。
OpenGL GLSL參考編譯器
每一個驅(qū)動都有它自己的怪癖伸蚯。比如說NVIDIA驅(qū)動會更寬容一點摩渺,通常會忽略一些限制或者規(guī)范,而ATI/AMD驅(qū)動則通常會嚴(yán)格執(zhí)行OpenGL規(guī)范(在我看來會更好一點)剂邮。問題是在一臺機器上的著色器到另一臺機器上可能就由于驅(qū)動差異不能正常工作了摇幻。
通過多年的經(jīng)驗?zāi)銜罱K能夠知道不同GPU供應(yīng)商之間的細微差別,但如果你想要保證你的著色器代碼在所有的機器上都能運行挥萌,你可以直接對著官方的標(biāo)準(zhǔn)使用OpenGL的GLSL參考編譯器(Reference Compiler)來檢查绰姻。你可以從這里下載所謂的GLSL語言校驗器(GLSL Lang Validator)
的可執(zhí)行版本,或者從這里找到完整的源碼引瀑。
有了這個GLSL語言校驗器狂芋,你可以很方便的檢查你的著色器代碼,只需要把著色器文件作為程序的第一個參數(shù)即可伤疙。注意GLSL語言校驗器是通過下列固定的后綴名來決定著色器的類型的:
-
.vert
:頂點著色器(Vertex Shader)
-
.frag:片段著色器
(Fragment Shader)` -
.geom:幾何著色器
(Geometry Shader)` -
.tesc:細分控制著色器
(Tessellation Control Shader)` -
.tese:細分計算著色器
(Tessellation Evaluation Shader)` -
.comp
:計算著色器(Compute Shader)
運行GLSL參考編譯器非常簡單:
glsllangvalidator shaderFile.vert
注意如果沒有檢測到錯誤的話則沒有輸出银酗。對一個不正確的頂點著色器使用GLSL參考編譯器進行測試會輸出以下結(jié)果:
它不會顯示AMD辆影,NVidia徒像,以及Intel的GLSL編譯器之間的細微差別,也不能保證你的著色器完全沒有Bug蛙讥,但它至少能夠幫你對著直接的GLSL規(guī)范進行檢查锯蛀。
幀緩沖輸出
你的調(diào)試工具箱中另外一個技巧就是在OpenGL程序中一塊特定區(qū)域顯示幀緩沖的內(nèi)容。你可能會比較頻繁地使用幀緩沖次慢,但由于幀緩沖的魔法通常在幕后進行旁涤,有時候想要知道出什么問題會非常困難。在你的程序中顯示幀緩沖的內(nèi)容是一個很有用的技巧迫像,幫助你快速檢查錯誤劈愚。
注意,這里討論的幀緩沖顯示內(nèi)容(附件)僅能在紋理附件上使用闻妓,而不能應(yīng)用于渲染緩沖對象菌羽。
通過使用一個非常簡單,只顯示紋理的著色器由缆,我們可以寫一個助手函數(shù)快速在屏幕右上角顯示任何紋理注祖。
// 頂點著色器
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position, 0.0f, 1.0f);
TexCoords = texCoords;
}
// 片段著色器
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D fboAttachment;
void main()
{
FragColor = texture(fboAttachment, TexCoords);
}
void DisplayFramebufferTexture(GLuint textureID)
{
if(!notInitialized)
{
// 在屏幕右上角猾蒂,使用NDC頂點坐標(biāo)初始化著色器和VAO
[...]
}
glActiveTexture(GL_TEXTURE0);
glUseProgram(shaderDisplayFBOOutput);
glBindTexture(GL_TEXTURE_2D, textureID);
glBindVertexArray(vaoDebugTexturedRect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glUseProgram(0);
}
int main()
{
[...]
while (!glfwWindowShouldClose(window))
{
[...]
DisplayFramebufferTexture(fboAttachment0);
glfwSwapBuffers(window);
}
}
這將在屏幕右上角給你一個小窗口,用來調(diào)試幀緩沖的輸出是晨。比如你想要檢查延遲渲染器的幾何渲染階段中的法向量是否正確肚菠,使用這個會非常方便:
你當(dāng)然可以拓展這個函數(shù)以支持渲染更多的紋理。這個方法能夠非痴纸桑快速地讓你對幀緩沖內(nèi)容有著持續(xù)的反饋蚊逢。
外部調(diào)試軟件
當(dāng)上面所有介紹到的技巧都不能使用的時候,我們?nèi)钥梢允褂靡粋€第三方的工具來幫助我們調(diào)試箫章。第三方應(yīng)用通常將它們自己注入到OpenGL驅(qū)動中时捌,并且能夠攔截各種OpenGL調(diào)用,給你大量有用的數(shù)據(jù)炉抒。這些工具可以在很多方面幫助到你:對OpenGL函數(shù)使用進行性能測試奢讨,尋找瓶頸,檢查緩沖內(nèi)存焰薄,顯示紋理和幀緩沖附件拿诸。如果你正在寫(大規(guī)模)產(chǎn)品代碼,這類的工具在開發(fā)過程中是非常有用的塞茅。
我在下面列出了一些流行的調(diào)試工具亩码,選幾個嘗試一下,看看哪個最適合你野瘦。
1. gDebugger
gDebugger
是一個非常易用的跨平臺OpenGL程序調(diào)試工具描沟。gDebugger會在你運行的OpenGL程序邊上,提供OpenGL狀態(tài)的詳細概況鞭光。你可以隨時暫停程序來檢查當(dāng)前狀態(tài)吏廉,紋理內(nèi)容以及緩沖使用。你可以在這里下載gDebugger惰许。
運行g(shù)Debugger只需要打開程序席覆,創(chuàng)建一個工程,給它你OpenGL程序的位置于工作目錄即可汹买。
2. RenderDoc
RenderDoc
是另外一個很棒的(完全開源的)獨立調(diào)試工具佩伤。和gDebugger類似,你只需要設(shè)置捕捉的程序以及工作目錄就行了晦毙。你的程序會正常運行生巡,當(dāng)你想要檢查一個特定的幀的時候,你只需要讓RenderDoc在程序當(dāng)前狀態(tài)下捕捉一個或多個幀即可见妒。在捕捉的幀當(dāng)中孤荣,你可以觀察管線狀態(tài),所有OpenGL指令,緩沖儲存垃环,以及使用的紋理邀层。
3. CodeXL
CodeXL是由AMD開發(fā)的一款GPU調(diào)試工具,它有獨立版本也有Visual Studio
插件版本遂庄。CodeXL可以給你非常多的信息寥院,對于圖形程序的性能測試也非常有用。CodeXL在NVidia與Intel的顯卡上也能運行涛目,不過會不支持OpenCL調(diào)試秸谢。
我沒有太多的CodeXL使用經(jīng)驗,我個人覺得gDebugger
和RenderDoc
會更容易使用一點霹肝,但我仍把它列在這里估蹄,因為它仍是一個非常可靠的工具沫换,并且主要是由最大的GPU制造商之一AMD開發(fā)的臭蚁。
4. NVIDIA Nsight
NVIDIA流行的Nsight GPU調(diào)試工具并不是一個獨立程序,而是一個Visual Studio IDE或者Eclipse IDE的插件讯赏。Nsight插件對圖形開發(fā)者來說非常容易使用垮兑,因為它給出了GPU用量,逐幀GPU狀態(tài)大量運行時的統(tǒng)計數(shù)據(jù)漱挎。
當(dāng)你在Visual Studio
(或Eclipse)使用Nsight的調(diào)試或者性能測試指令啟動程序的時候系枪,Nsight將會在程序自身中運行。Nsight非常棒的一點就是它在你的程序中渲染了一套GUI系統(tǒng)磕谅,你可以使用它獲取你程序各種各樣有用的信息私爷,可以是運行時也可以是逐幀分析。
Nsight
是一款非常有用的工具膊夹,在我看來比上述其它工具都有更好的表現(xiàn)衬浑,但它仍有一個非常重要的缺點,它只能在NVIDIA的顯卡上工作割疾。如果你正在使用一款NVIDIA的顯卡(并且使用Visual Studio)嚎卫,Nsight是非常值得一試的嘉栓。
我知道我可能遺漏了一些其它的調(diào)試工具(比如我還能想到有Valve的VOGL和APItrace)宏榕,但我覺得這個列表已經(jīng)給你足夠多的工具來實驗了。我并不是之前提到的任何一個工具的專家侵佃,所以如果我在哪里講錯了請在評論區(qū)留言麻昼,我會很樂意改正。
附加資源
- 為什么你的代碼會產(chǎn)生一個黑色窗口:Reto Koradi列舉了你的屏幕沒有產(chǎn)生任何輸出的可能原因馋辈。
- 調(diào)試輸出:Vallentin Source寫的一份非常詳細的調(diào)試輸出教程抚芦,里面有在多個窗口系統(tǒng)中配置調(diào)試上下文的詳細信息。
后記
本篇結(jié)束,后面更精彩~~~