版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.19 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò)盐捷,最近也想學(xué)著使用這個(gè)圖形庫(kù)棘捣,感覺(jué)還是很有意思辜腺,也就自然想著好好的總結(jié)一下,希望對(duì)大家能有所幫助乍恐。下面內(nèi)容來(lái)自歡迎來(lái)到OpenGL的世界评疗。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式、對(duì)象茵烈、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器百匆、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫(kù)的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫(kù)的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫(kù)的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫(kù)的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫(kù)的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫(kù)的使用(二十一)—— 高級(jí)OpenGL之深度測(cè)試
22. OpenGL 圖形庫(kù)的使用(二十二)—— 高級(jí)OpenGL之模板測(cè)試Stencil testing
23. OpenGL 圖形庫(kù)的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫(kù)的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
25. OpenGL 圖形庫(kù)的使用(二十五)—— 高級(jí)OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫(kù)的使用(二十六)—— 高級(jí)OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫(kù)的使用(二十七)—— 高級(jí)OpenGL之高級(jí)數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫(kù)的使用(二十八)—— 高級(jí)OpenGL之高級(jí)GLSL Advanced GLSL
29. OpenGL 圖形庫(kù)的使用(二十九)—— 高級(jí)OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫(kù)的使用(三十)—— 高級(jí)OpenGL之實(shí)例化Instancing
31. OpenGL 圖形庫(kù)的使用(三十一)—— 高級(jí)OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫(kù)的使用(三十二)—— 高級(jí)光照之高級(jí)光照Advanced Lighting
33. OpenGL 圖形庫(kù)的使用(三十三)—— 高級(jí)光照之Gamma校正Gamma Correction
陰影映射基本
陰影是光線被阻擋的結(jié)果;當(dāng)一個(gè)光源的光線由于其他物體的阻擋不能夠達(dá)到一個(gè)物體的表面的時(shí)候呜投,那么這個(gè)物體就在陰影中了加匈。陰影能夠使場(chǎng)景看起來(lái)真實(shí)得多,并且可以讓觀察者獲得物體之間的空間位置關(guān)系仑荐。場(chǎng)景和物體的深度感因此能夠得到極大提升雕拼,下圖展示了有陰影和沒(méi)有陰影的情況下的不同:
你可以看到,有陰影的時(shí)候你能更容易地區(qū)分出物體之間的位置關(guān)系粘招,例如啥寇,當(dāng)使用陰影的時(shí)候浮在地板上的立方體的事實(shí)更加清晰。
陰影還是比較不好實(shí)現(xiàn)的洒扎,因?yàn)楫?dāng)前實(shí)時(shí)渲染領(lǐng)域還沒(méi)找到一種完美的陰影算法示姿。目前有幾種近似陰影技術(shù),但它們都有自己的弱點(diǎn)和不足逊笆,這點(diǎn)我們必須要考慮到栈戳。
視頻游戲中較多使用的一種技術(shù)是陰影貼圖(shadow mapping),效果不錯(cuò)难裆,而且相對(duì)容易實(shí)現(xiàn)子檀。陰影貼圖并不難以理解镊掖,性能也不會(huì)太低,而且非常容易擴(kuò)展成更高級(jí)的算法(比如 Omnidirectional Shadow Maps和 Cascaded Shadow Maps)褂痰。
陰影映射
陰影映射(Shadow Mapping)
背后的思路非常簡(jiǎn)單:我們以光的位置為視角進(jìn)行渲染亩进,我們能看到的東西都將被點(diǎn)亮,看不見(jiàn)的一定是在陰影之中了缩歪。假設(shè)有一個(gè)地板归薛,在光源和它之間有一個(gè)大盒子。由于光源處向光線方向看去匪蝙,可以看到這個(gè)盒子主籍,但看不到地板的一部分,這部分就應(yīng)該在陰影中了逛球。
這里的所有藍(lán)線代表光源可以看到的fragment千元。黑線代表被遮擋的fragment:它們應(yīng)該渲染為帶陰影的。如果我們繪制一條從光源出發(fā)颤绕,到達(dá)最右邊盒子上的一個(gè)片元上的線段或射線幸海,那么射線將先擊中懸浮的盒子,隨后才會(huì)到達(dá)最右側(cè)的盒子奥务。結(jié)果就是懸浮的盒子被照亮物独,而最右側(cè)的盒子將處于陰影之中。
我們希望得到射線第一次擊中的那個(gè)物體氯葬,然后用這個(gè)最近點(diǎn)和射線上其他點(diǎn)進(jìn)行對(duì)比议纯。然后我們將測(cè)試一下看看射線上的其他點(diǎn)是否比最近點(diǎn)更遠(yuǎn),如果是的話溢谤,這個(gè)點(diǎn)就在陰影中瞻凤。對(duì)從光源發(fā)出的射線上的成千上萬(wàn)個(gè)點(diǎn)進(jìn)行遍歷是個(gè)極端消耗性能的舉措,實(shí)時(shí)渲染上基本不可取世杀。我們可以采取相似舉措阀参,不用投射出光的射線。我們所使用的是非常熟悉的東西:深度緩沖瞻坝。
你可能記得在深度測(cè)試教程中蛛壳,在深度緩沖里的一個(gè)值是攝像機(jī)視角下,對(duì)應(yīng)于一個(gè)片元的一個(gè)0到1之間的深度值所刀。如果我們從光源的透視圖來(lái)渲染場(chǎng)景衙荐,并把深度值的結(jié)果儲(chǔ)存到紋理中會(huì)怎樣?通過(guò)這種方式浮创,我們就能對(duì)光源的透視圖所見(jiàn)的最近的深度值進(jìn)行采樣忧吟。最終,深度值就會(huì)顯示從光源的透視圖下見(jiàn)到的第一個(gè)片元了斩披。我們管儲(chǔ)存在紋理中的所有這些深度值溜族,叫做深度貼圖(depth map)或陰影貼圖讹俊。
左側(cè)的圖片展示了一個(gè)定向光源(所有光線都是平行的)在立方體下的表面投射的陰影。通過(guò)儲(chǔ)存到深度貼圖中的深度值煌抒,我們就能找到最近點(diǎn)仍劈,用以決定片元是否在陰影中。我們使用一個(gè)來(lái)自光源的視圖和投影矩陣來(lái)渲染場(chǎng)景就能創(chuàng)建一個(gè)深度貼圖寡壮。這個(gè)投影和視圖矩陣結(jié)合在一起成為一個(gè)T
變換贩疙,它可以將任何三維位置轉(zhuǎn)變到光源的可見(jiàn)坐標(biāo)空間。
定向光并沒(méi)有位置况既,因?yàn)樗灰?guī)定為無(wú)窮遠(yuǎn)这溅。然而,為了實(shí)現(xiàn)陰影貼圖坏挠,我們得從一個(gè)光的透視圖渲染場(chǎng)景,這樣就得在光的方向的某一點(diǎn)上渲染場(chǎng)景邪乍。
在右邊的圖中我們顯示出同樣的平行光和觀察者降狠。我們渲染一個(gè)點(diǎn)Pˉ處的片元,需要決定它是否在陰影中庇楞。我們先得使用T把Pˉ變換到光源的坐標(biāo)空間里榜配。既然點(diǎn)Pˉ是從光的透視圖中看到的,它的z坐標(biāo)就對(duì)應(yīng)于它的深度吕晌,例子中這個(gè)值是0.9蛋褥。使用點(diǎn)Pˉ在光源的坐標(biāo)空間的坐標(biāo),我們可以索引深度貼圖睛驳,來(lái)獲得從光的視角中最近的可見(jiàn)深度烙心,結(jié)果是點(diǎn)Cˉ,最近的深度是0.4乏沸。因?yàn)樗饕疃荣N圖的結(jié)果是一個(gè)小于點(diǎn)Pˉ的深度淫茵,我們可以斷定Pˉ被擋住了,它在陰影中了蹬跃。
深度映射由兩個(gè)步驟組成:首先匙瘪,我們渲染深度貼圖,然后我們像往常一樣渲染場(chǎng)景蝶缀,使用生成的深度貼圖來(lái)計(jì)算片元是否在陰影之中丹喻。聽(tīng)起來(lái)有點(diǎn)復(fù)雜,但隨著我們一步一步地講解這個(gè)技術(shù)翁都,就能理解了碍论。
深度貼圖
第一步我們需要生成一張深度貼圖(Depth Map)。深度貼圖是從光的透視圖里渲染的深度紋理柄慰,用它計(jì)算陰影骑冗。因?yàn)槲覀冃枰獙?chǎng)景的渲染結(jié)果儲(chǔ)存到一個(gè)紋理中赊瞬,我們將再次需要幀緩沖满粗。
首先豹障,我們要為渲染的深度貼圖創(chuàng)建一個(gè)幀緩沖對(duì)象:
GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
然后,創(chuàng)建一個(gè)2D紋理判哥,提供給幀緩沖的深度緩沖使用:
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
生成深度貼圖不太復(fù)雜遥倦。因?yàn)槲覀冎魂P(guān)心深度值谤绳,我們要把紋理格式指定為GL_DEPTH_COMPONENT
。我們還要把紋理的高寬設(shè)置為1024:這是深度貼圖的解析度袒哥。
把我們把生成的深度紋理作為幀緩沖的深度緩沖:
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
我們需要的只是在從光的透視圖下渲染場(chǎng)景的時(shí)候深度信息缩筛,所以顏色緩沖沒(méi)有用。然而幀緩沖對(duì)象不是完全不包含顏色緩沖的堡称,所以我們需要顯式告訴OpenGL我們不適用任何顏色數(shù)據(jù)進(jìn)行渲染瞎抛。我們通過(guò)將調(diào)用glDrawBuffer
和glReadBuffer
把讀和繪制緩沖設(shè)置為GL_NONE來(lái)做這件事。
合理配置將深度值渲染到紋理的幀緩沖后却紧,我們就可以開(kāi)始第一步了:生成深度貼圖桐臊。兩個(gè)步驟的完整的渲染階段,看起來(lái)有點(diǎn)像這樣:
// 1. 首選渲染深度貼圖
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. 像往常一樣渲染場(chǎng)景晓殊,但這次使用深度貼圖
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
glBindTexture(GL_TEXTURE_2D, depthMap);
RenderScene();
這段代碼隱去了一些細(xì)節(jié)断凶,但它表達(dá)了陰影映射的基本思路。這里一定要記得調(diào)用glViewport
巫俺。因?yàn)殛幱百N圖經(jīng)常和我們?cè)瓉?lái)渲染的場(chǎng)景(通常是窗口解析度)有著不同的解析度认烁,我們需要改變視口(viewport)的參數(shù)以適應(yīng)陰影貼圖的尺寸。如果我們忘了更新視口參數(shù)介汹,最后的深度貼圖要么太小要么就不完整却嗡。
1. 光源空間的變換
前面那段代碼中一個(gè)不清楚的函數(shù)是ConfigureShaderAndMatrices
。它是用來(lái)在第二個(gè)步驟確保為每個(gè)物體設(shè)置了合適的投影和視圖矩陣嘹承,以及相關(guān)的模型矩陣稽穆。然而,第一個(gè)步驟中赶撰,我們從光的位置的視野下使用了不同的投影和視圖矩陣來(lái)渲染的場(chǎng)景舌镶。
因?yàn)槲覀兪褂玫氖且粋€(gè)所有光線都平行的定向光。出于這個(gè)原因豪娜,我們將為光源使用正交投影矩陣餐胀,透視圖將沒(méi)有任何變形:
GLfloat near_plane = 1.0f, far_plane = 7.5f;
glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
這里有個(gè)本節(jié)教程的demo場(chǎng)景中使用的正交投影矩陣的例子。因?yàn)橥队熬仃囬g接決定可視區(qū)域的范圍瘤载,以及哪些東西不會(huì)被裁切否灾,你需要保證投影視錐(frustum)的大小,以包含打算在深度貼圖中包含的物體鸣奔。當(dāng)物體和片元不在深度貼圖中時(shí)墨技,它們就不會(huì)產(chǎn)生陰影惩阶。
為了創(chuàng)建一個(gè)視圖矩陣來(lái)變換每個(gè)物體,把它們變換到從光源視角可見(jiàn)的空間中扣汪,我們將使用glm::lookAt
函數(shù)断楷;這次從光源的位置看向場(chǎng)景中央。
glm::mat4 lightView = glm::lookAt(glm::vec(-2.0f, 4.0f, -1.0f), glm::vec3(0.0f), glm::vec3(1.0));
二者相結(jié)合為我們提供了一個(gè)光空間的變換矩陣崭别,它將每個(gè)世界空間坐標(biāo)變換到光源處所見(jiàn)到的那個(gè)空間冬筒;這正是我們渲染深度貼圖所需要的。
glm::mat4 lightSpaceMatrix = lightProjection * lightView;
這個(gè)lightSpaceMatrix
正是前面我們稱為T的那個(gè)變換矩陣茅主。有了lightSpaceMatrix
只要給shader提供光空間的投影和視圖矩陣舞痰,我們就能像往常那樣渲染場(chǎng)景了。然而诀姚,我們只關(guān)心深度值响牛,并非所有片元計(jì)算都在我們的著色器中進(jìn)行。為了提升性能赫段,我們將使用一個(gè)與之不同但更為簡(jiǎn)單的著色器來(lái)渲染出深度貼圖呀打。
2. 渲染至深度貼圖
當(dāng)我們以光的透視圖進(jìn)行場(chǎng)景渲染的時(shí)候,我們會(huì)用一個(gè)比較簡(jiǎn)單的著色器瑞佩,這個(gè)著色器除了把頂點(diǎn)變換到光空間以外聚磺,不會(huì)做得更多了坯台。這個(gè)簡(jiǎn)單的著色器叫做simpleDepthShader
炬丸,就是使用下面的這個(gè)著色器:
#version 330 core
layout (location = 0) in vec3 position;
uniform mat4 lightSpaceMatrix;
uniform mat4 model;
void main()
{
gl_Position = lightSpaceMatrix * model * vec4(position, 1.0f);
}
這個(gè)頂點(diǎn)著色器將一個(gè)單獨(dú)模型的一個(gè)頂點(diǎn),使用lightSpaceMatrix
變換到光空間中蜒蕾。
由于我們沒(méi)有顏色緩沖稠炬,最后的片元不需要任何處理,所以我們可以簡(jiǎn)單地使用一個(gè)空像素著色器:
#version 330 core
void main()
{
// gl_FragDepth = gl_FragCoord.z;
}
這個(gè)空像素著色器什么也不干咪啡,運(yùn)行完后首启,深度緩沖會(huì)被更新。我們可以取消那行的注釋撤摸,來(lái)顯式設(shè)置深度毅桃,但是這個(gè)(指注釋掉那行之后)是更有效率的,因?yàn)榈讓訜o(wú)論如何都會(huì)默認(rèn)去設(shè)置深度緩沖准夷。
渲染深度緩沖現(xiàn)在成了:
simpleDepthShader.Use();
glUniformMatrix4fv(lightSpaceMatrixLocation, 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
RenderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
這里的RenderScene
函數(shù)的參數(shù)是一個(gè)著色器程序(shader program)钥飞,它調(diào)用所有相關(guān)的繪制函數(shù),并在需要的地方設(shè)置相應(yīng)的模型矩陣衫嵌。
最后读宙,在光的透視圖視角下,很完美地用每個(gè)可見(jiàn)片元的最近深度填充了深度緩沖楔绞。通過(guò)將這個(gè)紋理投射到一個(gè)2D四邊形上(和我們?cè)趲彌_一節(jié)做的后處理過(guò)程類似)结闸,就能在屏幕上顯示出來(lái)唇兑,我們會(huì)獲得這樣的東西:
將深度貼圖渲染到四邊形上的像素著色器:
#version 330 core
out vec4 color;
in vec2 TexCoords;
uniform sampler2D depthMap;
void main()
{
float depthValue = texture(depthMap, TexCoords).r;
color = vec4(vec3(depthValue), 1.0);
}
要注意的是當(dāng)用透視投影矩陣取代正交投影矩陣來(lái)顯示深度時(shí),有一些輕微的改動(dòng)桦锄,因?yàn)槭褂猛敢曂队皶r(shí)扎附,深度是非線性的。本節(jié)教程的最后察纯,我們會(huì)討論這些不同之處帕棉。
你可以在這里獲得把場(chǎng)景渲染成深度貼圖的源碼。
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
// Properties
const GLuint SCR_WIDTH = 800, SCR_HEIGHT = 600;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar* path);
void RenderScene(Shader &shader);
void RenderCube();
void RenderQuad();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// Delta
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// Global variables
GLuint woodTexture;
GLuint planeVAO;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// Setup and compile our shaders
Shader simpleDepthShader("shadow_mapping_depth.vs", "shadow_mapping_depth.frag");
Shader debugDepthQuad("debug_quad.vs", "debug_quad_depth.frag");
GLfloat planeVertices[] = {
// Positions // Normals // Texture Coords
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
-25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f,
- 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f
};
// Setup plane VAO
GLuint planeVBO;
glGenVertexArrays(1, &planeVAO);
glGenBuffers(1, &planeVBO);
glBindVertexArray(planeVAO);
glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindVertexArray(0);
// Light source
glm::vec3 lightPos(-2.0f, 4.0f, -1.0f);
// Load textures
woodTexture = loadTexture("../../../resources/textures/wood.png");
// Configure depth map FBO
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
// - Create depth texture
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// 1. Render depth of scene to texture (from light's perspective)
// - Get light projection/view matrix.
glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;
GLfloat near_plane = 1.0f, far_plane = 7.5f;
lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));
lightSpaceMatrix = lightProjection * lightView;
// - render scene from light's point of view
simpleDepthShader.Use();
glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader.Program, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
RenderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Reset viewport
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render Depth map to quad
debugDepthQuad.Use();
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "near_plane"), near_plane);
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "far_plane"), far_plane);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthMap);
RenderQuad();
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
void RenderScene(Shader &shader)
{
// Floor
glm::mat4 model;
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(planeVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
// Cubes
model = glm::mat4();
model = glm::translate(model, glm::vec3(0.0f, 1.5f, 0.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 2.0));
model = glm::rotate(model, 60.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
model = glm::scale(model, glm::vec3(0.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
}
// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
GLfloat quadVertices[] = {
// Positions // Texture Coords
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
};
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
// RenderCube() Renders a 1x1 3D cube in NDC.
GLuint cubeVAO = 0;
GLuint cubeVBO = 0;
void RenderCube()
{
// Initialize (if necessary)
if (cubeVAO == 0)
{
GLfloat vertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
};
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
// Fill buffer
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Link vertex attributes
glBindVertexArray(cubeVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Render Cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar* path)
{
// Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
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_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
{
keys[key] = false;
keysPressed[key] = false;
}
}
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
渲染陰影
正確地生成深度貼圖以后我們就可以開(kāi)始生成陰影了饼记。這段代碼在像素著色器中執(zhí)行香伴,用來(lái)檢驗(yàn)一個(gè)片元是否在陰影之中,不過(guò)我們?cè)陧旤c(diǎn)著色器中進(jìn)行光空間的變換:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
out vec2 TexCoords;
out VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
vec4 FragPosLightSpace;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
vs_out.FragPos = vec3(model * vec4(position, 1.0));
vs_out.Normal = transpose(inverse(mat3(model))) * normal;
vs_out.TexCoords = texCoords;
vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
}
這兒的新的地方是FragPosLightSpace
這個(gè)輸出向量具则。我們用同一個(gè)lightSpaceMatrix
即纲,把世界空間頂點(diǎn)位置轉(zhuǎn)換為光空間。頂點(diǎn)著色器傳遞一個(gè)普通的經(jīng)變換的世界空間頂點(diǎn)位置vs_out.FragPos
和一個(gè)光空間的vs_out.FragPosLightSpace
給像素著色器博肋。
像素著色器使用Blinn-Phong
光照模型渲染場(chǎng)景低斋。我們接著計(jì)算出一個(gè)shadow值,當(dāng)fragment在陰影中時(shí)是1.0匪凡,在陰影外是0.0膊畴。然后,diffuse
和specular
顏色會(huì)乘以這個(gè)陰影元素病游。由于陰影不會(huì)是全黑的(由于散射)唇跨,我們把ambient
分量從乘法中剔除。
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
vec4 FragPosLightSpace;
} fs_in;
uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
float ShadowCalculation(vec4 fragPosLightSpace)
{
[...]
}
void main()
{
vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
vec3 normal = normalize(fs_in.Normal);
vec3 lightColor = vec3(1.0);
// Ambient
vec3 ambient = 0.15 * color;
// Diffuse
vec3 lightDir = normalize(lightPos - fs_in.FragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * lightColor;
// Specular
vec3 viewDir = normalize(viewPos - fs_in.FragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = 0.0;
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
vec3 specular = spec * lightColor;
// 計(jì)算陰影
float shadow = ShadowCalculation(fs_in.FragPosLightSpace);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
FragColor = vec4(lighting, 1.0f);
}
像素著色器大部分是從高級(jí)光照教程中復(fù)制過(guò)來(lái)衬衬,只不過(guò)加上了個(gè)陰影計(jì)算买猖。我們聲明一個(gè)shadowCalculation
函數(shù),用它計(jì)算陰影滋尉。像素著色器的最后玉控,我們我們把diffuse和specular乘以(1-陰影元素),這表示這個(gè)片元有多大成分不在陰影中狮惜。這個(gè)像素著色器還需要兩個(gè)額外輸入高诺,一個(gè)是光空間的片元位置和第一個(gè)渲染階段得到的深度貼圖。
首先要檢查一個(gè)片元是否在陰影中碾篡,把光空間片元位置轉(zhuǎn)換為裁切空間的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)虱而。當(dāng)我們?cè)陧旤c(diǎn)著色器輸出一個(gè)裁切空間頂點(diǎn)位置到gl_Position時(shí),OpenGL自動(dòng)進(jìn)行一個(gè)透視除法耽梅,將裁切空間坐標(biāo)的范圍-w到w轉(zhuǎn)為-1到1薛窥,這要將x、y、z元素除以向量的w元素來(lái)實(shí)現(xiàn)诅迷。由于裁切空間的FragPosLightSpace
并不會(huì)通過(guò)gl_Position
傳到像素著色器里佩番,我們必須自己做透視除法:
float ShadowCalculation(vec4 fragPosLightSpace)
{
// 執(zhí)行透視除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
[...]
}
返回了片元在光空間的-1到1的范圍。
當(dāng)使用正交投影矩陣罢杉,頂點(diǎn)w元素仍保持不變趟畏,所以這一步實(shí)際上毫無(wú)意義√沧猓可是赋秀,當(dāng)使用透視投影的時(shí)候就是必須的了,所以為了保證在兩種投影矩陣下都有效就得留著這行律想。
因?yàn)閬?lái)自深度貼圖的深度在0到1的范圍猎莲,我們也打算使用projCoords
從深度貼圖中去采樣,所以我們將NDC坐標(biāo)變換為0到1的范圍: (譯者注:這里的意思是技即,上面的projCoords
的xyz分量都是[-1,1](下面會(huì)指出這對(duì)于遠(yuǎn)平面之類的點(diǎn)才成立)著洼,而為了和深度貼圖的深度相比較,z分量需要變換到[0,1]而叼;為了作為從深度貼圖中采樣的坐標(biāo)身笤,xy分量也需要變換到[0,1]。所以整個(gè)projCoords
向量都需要變換到[0,1]范圍葵陵。)
projCoords = projCoords * 0.5 + 0.5;
有了這些投影坐標(biāo)液荸,我們就能從深度貼圖中采樣得到0到1的結(jié)果,從第一個(gè)渲染階段的projCoords
坐標(biāo)直接對(duì)應(yīng)于變換過(guò)的NDC坐標(biāo)脱篙。我們將得到光的位置視野下最近的深度:
float closestDepth = texture(shadowMap, projCoords.xy).r;
為了得到片元的當(dāng)前深度娇钱,我們簡(jiǎn)單獲取投影向量的z坐標(biāo),它等于來(lái)自光的透視視角的片元的深度涡尘。
float currentDepth = projCoords.z;
實(shí)際的對(duì)比就是簡(jiǎn)單檢查currentDepth
是否高于closetDepth
忍弛,如果是响迂,那么片元就在陰影中考抄。
float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
完整的shadowCalculation
函數(shù)是這樣的:
float ShadowCalculation(vec4 fragPosLightSpace)
{
// 執(zhí)行透視除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// 變換到[0,1]的范圍
projCoords = projCoords * 0.5 + 0.5;
// 取得最近點(diǎn)的深度(使用[0,1]范圍下的fragPosLight當(dāng)坐標(biāo))
float closestDepth = texture(shadowMap, projCoords.xy).r;
// 取得當(dāng)前片元在光源視角下的深度
float currentDepth = projCoords.z;
// 檢查當(dāng)前片元是否在陰影中
float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
return shadow;
}
激活這個(gè)著色器,綁定合適的紋理蔗彤,激活第二個(gè)渲染階段默認(rèn)的投影以及視圖矩陣川梅,結(jié)果如下圖所示:
如果你做對(duì)了,你會(huì)看到地板和上有立方體的陰影然遏。你可以從這里找到demo程序的源碼贫途。
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
// Properties
const GLuint SCR_WIDTH = 800, SCR_HEIGHT = 600;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar* path);
void RenderScene(Shader &shader);
void RenderCube();
void RenderQuad();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// Delta
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// Global variables
GLuint woodTexture;
GLuint planeVAO;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// Setup and compile our shaders
Shader shader("shadow_mapping.vs", "shadow_mapping.frag");
Shader simpleDepthShader("shadow_mapping_depth.vs", "shadow_mapping_depth.frag");
Shader debugDepthQuad("debug_quad.vs", "debug_quad_depth.frag");
// Set texture samples
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "diffuseTexture"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "shadowMap"), 1);
GLfloat planeVertices[] = {
// Positions // Normals // Texture Coords
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
-25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f,
- 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f
};
// Setup plane VAO
GLuint planeVBO;
glGenVertexArrays(1, &planeVAO);
glGenBuffers(1, &planeVBO);
glBindVertexArray(planeVAO);
glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindVertexArray(0);
// Light source
glm::vec3 lightPos(-2.0f, 4.0f, -1.0f);
// Load textures
woodTexture = loadTexture("../../../resources/textures/wood.png");
// Configure depth map FBO
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
// - Create depth texture
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// 1. Render depth of scene to texture (from ligth's perspective)
// - Get light projection/view matrix.
glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;
GLfloat near_plane = 1.0f, far_plane = 7.5f;
lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));
lightSpaceMatrix = lightProjection * lightView;
// - now render scene from light's point of view
simpleDepthShader.Use();
glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader.Program, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
RenderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. Render scene as normal
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.Use();
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
// Set light uniforms
glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthMap);
RenderScene(shader);
// 3. DEBUG: visualize depth map by rendering it to plane
debugDepthQuad.Use();
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "near_plane"), near_plane);
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "far_plane"), far_plane);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthMap);
//RenderQuad();
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
void RenderScene(Shader &shader)
{
// Floor
glm::mat4 model;
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(planeVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
// Cubes
model = glm::mat4();
model = glm::translate(model, glm::vec3(0.0f, 1.5f, 0.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 2.0));
model = glm::rotate(model, 60.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
model = glm::scale(model, glm::vec3(0.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
}
// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
GLfloat quadVertices[] = {
// Positions // Texture Coords
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
};
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
// RenderCube() Renders a 1x1 3D cube in NDC.
GLuint cubeVAO = 0;
GLuint cubeVBO = 0;
void RenderCube()
{
// Initialize (if necessary)
if (cubeVAO == 0)
{
GLfloat vertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
};
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
// Fill buffer
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Link vertex attributes
glBindVertexArray(cubeVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Render Cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar* path)
{
// Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
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_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
{
keys[key] = false;
keysPressed[key] = false;
}
}
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
改進(jìn)陰影貼圖
我們?cè)噲D讓陰影映射工作,但是你也看到了待侵,陰影映射還是有點(diǎn)不真實(shí)丢早,我們修復(fù)它才能獲得更好的效果,這是下面的部分所關(guān)注的焦點(diǎn)。
1. 陰影失真
前面的圖片中明顯有不對(duì)的地方怨酝。放大看會(huì)發(fā)現(xiàn)明顯的線條樣式:
我們可以看到地板四邊形渲染出很大一塊交替黑線傀缩。這種陰影貼圖的不真實(shí)感叫做陰影失真(Shadow Acne)
,下圖解釋了成因:
因?yàn)殛幱百N圖受限于解析度农猬,在距離光源比較遠(yuǎn)的情況下赡艰,多個(gè)片元可能從深度貼圖的同一個(gè)值中去采樣。圖片每個(gè)斜坡代表深度貼圖一個(gè)單獨(dú)的紋理像素斤葱。你可以看到慷垮,多個(gè)片元從同一個(gè)深度值進(jìn)行采樣。
雖然很多時(shí)候沒(méi)問(wèn)題揍堕,但是當(dāng)光源以一個(gè)角度朝向表面的時(shí)候就會(huì)出問(wèn)題料身,這種情況下深度貼圖也是從一個(gè)角度下進(jìn)行渲染的。多個(gè)片元就會(huì)從同一個(gè)斜坡的深度紋理像素中采樣衩茸,有些在地板上面惯驼,有些在地板下面;這樣我們所得到的陰影就有了差異递瑰。因?yàn)檫@個(gè)祟牲,有些片元被認(rèn)為是在陰影之中,有些不在抖部,由此產(chǎn)生了圖片中的條紋樣式说贝。
我們可以用一個(gè)叫做陰影偏移(shadow bias)
的技巧來(lái)解決這個(gè)問(wèn)題,我們簡(jiǎn)單的對(duì)表面的深度(或深度貼圖)應(yīng)用一個(gè)偏移量慎颗,這樣片元就不會(huì)被錯(cuò)誤地認(rèn)為在表面之下了乡恕。
使用了偏移量后,所有采樣點(diǎn)都獲得了比表面深度更小的深度值俯萎,這樣整個(gè)表面就正確地被照亮傲宜,沒(méi)有任何陰影。我們可以這樣實(shí)現(xiàn)這個(gè)偏移:
float bias = 0.005;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
一個(gè)0.005的偏移就能幫到很大的忙夫啊,但是有些表面坡度很大函卒,仍然會(huì)產(chǎn)生陰影失真。有一個(gè)更加可靠的辦法能夠根據(jù)表面朝向光線的角度更改偏移量:使用點(diǎn)乘:
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
這里我們有一個(gè)偏移量的最大值0.05撇眯,和一個(gè)最小值0.005报嵌,它們是基于表面法線和光照方向的。這樣像地板這樣的表面幾乎與光源垂直熊榛,得到的偏移就很小锚国,而比如立方體的側(cè)面這種表面得到的偏移就更大。下圖展示了同一個(gè)場(chǎng)景玄坦,但使用了陰影偏移血筑,效果的確更好:
選用正確的偏移數(shù)值,在不同的場(chǎng)景中需要一些像這樣的輕微調(diào)校,但大多情況下豺总,實(shí)際上就是增加偏移量直到所有失真都被移除的問(wèn)題梆砸。
2. 懸浮
使用陰影偏移的一個(gè)缺點(diǎn)是你對(duì)物體的實(shí)際深度應(yīng)用了平移。偏移有可能足夠大园欣,以至于可以看出陰影相對(duì)實(shí)際物體位置的偏移帖世,你可以從下圖看到這個(gè)現(xiàn)象(這是一個(gè)夸張的偏移值):
這個(gè)陰影失真叫做懸浮(Peter Panning)
,因?yàn)槲矬w看起來(lái)輕輕懸浮在表面之上(譯注Peter Pan就是童話彼得潘沸枯,而panning有平移日矫、懸浮之意,而且彼得潘是個(gè)會(huì)飛的男孩…)绑榴。我們可以使用一個(gè)叫技巧解決大部分的Peter panning問(wèn)題:當(dāng)渲染深度貼圖時(shí)候使用正面剔除(front face culling)
你也許記得在面剔除教程中OpenGL默認(rèn)是背面剔除哪轿。我們要告訴OpenGL我們要剔除正面。
因?yàn)槲覀冎恍枰疃荣N圖的深度值翔怎,對(duì)于實(shí)體物體無(wú)論我們用它們的正面還是背面都沒(méi)問(wèn)題窃诉。使用背面深度不會(huì)有錯(cuò)誤,因?yàn)殛幱霸谖矬w內(nèi)部有錯(cuò)誤我們也看不見(jiàn)赤套。
為了修復(fù)peter游移飘痛,我們要進(jìn)行正面剔除,先必須開(kāi)啟GL_CULL_FACE
:
glCullFace(GL_FRONT);
RenderSceneToDepthMap();
glCullFace(GL_BACK); // 不要忘記設(shè)回原先的culling face
這十分有效地解決了peter panning
的問(wèn)題容握,但只針對(duì)實(shí)體物體宣脉,內(nèi)部不會(huì)對(duì)外開(kāi)口。我們的場(chǎng)景中剔氏,在立方體上工作的很好塑猖,但在地板上無(wú)效,因?yàn)檎嫣蕹耆瞥说匕逄铬恕5孛媸且粋€(gè)單獨(dú)的平面羊苟,不會(huì)被完全剔除。如果有人打算使用這個(gè)技巧解決peter panning必須考慮到只有剔除物體的正面才有意義感憾。
另一個(gè)要考慮到的地方是接近陰影的物體仍然會(huì)出現(xiàn)不正確的效果蜡励。必須考慮到何時(shí)使用正面剔除對(duì)物體才有意義。不過(guò)使用普通的偏移值通常就能避免peter panning
吹菱。
3. 采樣過(guò)多
無(wú)論你喜不喜歡還有一個(gè)視覺(jué)差異巍虫,就是光的視錐不可見(jiàn)的區(qū)域一律被認(rèn)為是處于陰影中彭则,不管它真的處于陰影之中鳍刷。出現(xiàn)這個(gè)狀況是因?yàn)槌龉獾囊曞F的投影坐標(biāo)比1.0大,這樣采樣的深度紋理就會(huì)超出他默認(rèn)的0到1的范圍俯抖。根據(jù)紋理環(huán)繞方式输瓜,我們將會(huì)得到不正確的深度結(jié)果,它不是基于真實(shí)的來(lái)自光源的深度值。
你可以在圖中看到尤揣,光照有一個(gè)區(qū)域搔啊,超出該區(qū)域就成為了陰影;這個(gè)區(qū)域?qū)嶋H上代表著深度貼圖的大小北戏,這個(gè)貼圖投影到了地板上负芋。發(fā)生這種情況的原因是我們之前將深度貼圖的環(huán)繞方式設(shè)置成了GL_REPEAT。
我們寧可讓所有超出深度貼圖的坐標(biāo)的深度范圍是1.0嗜愈,這樣超出的坐標(biāo)將永遠(yuǎn)不在陰影之中旧蛾。我們可以儲(chǔ)存一個(gè)邊框顏色,然后把深度貼圖的紋理環(huán)繞選項(xiàng)設(shè)置為GL_CLAMP_TO_BORDER
:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
現(xiàn)在如果我們采樣深度貼圖0到1坐標(biāo)范圍以外的區(qū)域蠕嫁,紋理函數(shù)總會(huì)返回一個(gè)1.0的深度值锨天,陰影值為0.0。結(jié)果看起來(lái)會(huì)更真實(shí):
仍有一部分是黑暗區(qū)域剃毒。那里的坐標(biāo)超出了光的正交視錐的遠(yuǎn)平面病袄。你可以看到這片黑色區(qū)域總是出現(xiàn)在光源視錐的極遠(yuǎn)處。
當(dāng)一個(gè)點(diǎn)比光的遠(yuǎn)平面還要遠(yuǎn)時(shí)赘阀,它的投影坐標(biāo)的z坐標(biāo)大于1.0益缠。這種情況下,GL_CLAMP_TO_BORDER
環(huán)繞方式不起作用基公,因?yàn)槲覀儼炎鴺?biāo)的z元素和深度貼圖的值進(jìn)行了對(duì)比左刽;它總是為大于1.0的z返回true。
解決這個(gè)問(wèn)題也很簡(jiǎn)單酌媒,我們簡(jiǎn)單的強(qiáng)制把shadow的值設(shè)為0.0欠痴,不管投影向量的z坐標(biāo)是否大于1.0:
float ShadowCalculation(vec4 fragPosLightSpace)
{
[...]
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
檢查遠(yuǎn)平面,并將深度貼圖限制為一個(gè)手工指定的邊界顏色秒咨,就能解決深度貼圖采樣超出的問(wèn)題喇辽,我們最終會(huì)得到下面我們所追求的效果:
這些結(jié)果意味著,只有在深度貼圖范圍以內(nèi)的被投影的fragment坐標(biāo)才有陰影雨席,所以任何超出范圍的都將會(huì)沒(méi)有陰影菩咨。由于在游戲中通常這只發(fā)生在遠(yuǎn)處,就會(huì)比我們之前的那個(gè)明顯的黑色區(qū)域效果更真實(shí)陡厘。
PCF
陰影現(xiàn)在已經(jīng)附著到場(chǎng)景中了抽米,不過(guò)這仍不是我們想要的。如果你放大看陰影糙置,陰影映射對(duì)解析度的依賴很快變得很明顯云茸。
因?yàn)樯疃荣N圖有一個(gè)固定的解析度,多個(gè)片元對(duì)應(yīng)于一個(gè)紋理像素谤饭。結(jié)果就是多個(gè)片元會(huì)從深度貼圖的同一個(gè)深度值進(jìn)行采樣标捺,這幾個(gè)片元便得到的是同一個(gè)陰影懊纳,這就會(huì)產(chǎn)生鋸齒邊。
你可以通過(guò)增加深度貼圖解析度的方式來(lái)降低鋸齒塊亡容,也可以嘗試盡可能的讓光的視錐接近場(chǎng)景嗤疯。
另一個(gè)(并不完整的)解決方案叫做PCF(percentage-closer filtering)
,這是一種多個(gè)不同過(guò)濾方式的組合闺兢,它產(chǎn)生柔和陰影茂缚,使它們出現(xiàn)更少的鋸齒塊和硬邊。核心思想是從深度貼圖中多次采樣屋谭,每一次采樣的紋理坐標(biāo)都稍有不同阱佛。每個(gè)獨(dú)立的樣本可能在也可能不再陰影中。所有的次生結(jié)果接著結(jié)合在一起戴而,進(jìn)行平均化凑术,我們就得到了柔和陰影。
一個(gè)簡(jiǎn)單的PCF的實(shí)現(xiàn)是簡(jiǎn)單的從紋理像素四周對(duì)深度貼圖采樣所意,然后把結(jié)果平均起來(lái):
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
這個(gè)textureSize
返回一個(gè)給定采樣器紋理的0級(jí)mipmap的vec2類型的寬和高淮逊。用1除以它返回一個(gè)單獨(dú)紋理像素的大小,我們用以對(duì)紋理坐標(biāo)進(jìn)行偏移扶踊,確保每個(gè)新樣本泄鹏,來(lái)自不同的深度值。這里我們采樣得到9個(gè)值秧耗,它們?cè)谕队白鴺?biāo)的x和y值的周圍备籽,為陰影阻擋進(jìn)行測(cè)試,并最終通過(guò)樣本的總數(shù)目將結(jié)果平均化分井。
使用更多的樣本车猬,更改texelSize
變量,你就可以增加陰影的柔和程度尺锚。下面你可以看到應(yīng)用了PCF的陰影:
從稍微遠(yuǎn)一點(diǎn)的距離看去撞秋,陰影效果好多了谒麦,也不那么生硬了。如果你放大脑溢,仍會(huì)看到陰影貼圖解析度的不真實(shí)感把敞,但通常對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)效果已經(jīng)很好了郎仆。
你可以從這里找到這個(gè)例子的全部源碼和第二個(gè)階段的頂點(diǎn)和片段著色器旗国。
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
// Properties
const GLuint SCR_WIDTH = 800, SCR_HEIGHT = 600;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar* path);
void RenderScene(Shader &shader);
void RenderCube();
void RenderQuad();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// Delta
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// Options
GLboolean shadows = true;
// Global variables
GLuint woodTexture;
GLuint planeVAO;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// Setup and compile our shaders
Shader shader("shadow_mapping.vs", "shadow_mapping.frag");
Shader simpleDepthShader("shadow_mapping_depth.vs", "shadow_mapping_depth.frag");
Shader debugDepthQuad("debug_quad.vs", "debug_quad_depth.frag");
// Set texture samples
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "diffuseTexture"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "shadowMap"), 1);
GLfloat planeVertices[] = {
// Positions // Normals // Texture Coords
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
-25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f,
- 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f
};
// Setup plane VAO
GLuint planeVBO;
glGenVertexArrays(1, &planeVAO);
glGenBuffers(1, &planeVBO);
glBindVertexArray(planeVAO);
glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindVertexArray(0);
// Light source
glm::vec3 lightPos(-2.0f, 4.0f, -1.0f);
// Load textures
woodTexture = loadTexture("../../../resources/textures/wood.png");
// Configure depth map FBO
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
// - Create depth texture
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// Change light position over time
//lightPos.x = sin(glfwGetTime()) * 3.0f;
//lightPos.z = cos(glfwGetTime()) * 2.0f;
//lightPos.y = 5.0 + cos(glfwGetTime()) * 1.0f;
// 1. Render depth of scene to texture (from light's perspective)
// - Get light projection/view matrix.
glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;
GLfloat near_plane = 1.0f, far_plane = 7.5f;
lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
//lightProjection = glm::perspective(45.0f, (GLfloat)SHADOW_WIDTH / (GLfloat)SHADOW_HEIGHT, near_plane, far_plane); // Note that if you use a perspective projection matrix you'll have to change the light position as the current light position isn't enough to reflect the whole scene.
lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));
lightSpaceMatrix = lightProjection * lightView;
// - now render scene from light's point of view
simpleDepthShader.Use();
glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader.Program, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
RenderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. Render scene as normal
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.Use();
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
// Set light uniforms
glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
// Enable/Disable shadows by pressing 'SPACE'
glUniform1i(glGetUniformLocation(shader.Program, "shadows"), shadows);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthMap);
RenderScene(shader);
// 3. DEBUG: visualize depth map by rendering it to plane
debugDepthQuad.Use();
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "near_plane"), near_plane);
glUniform1f(glGetUniformLocation(debugDepthQuad.Program, "far_plane"), far_plane);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthMap);
//RenderQuad();
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
void RenderScene(Shader &shader)
{
// Floor
glm::mat4 model;
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(planeVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
// Cubes
model = glm::mat4();
model = glm::translate(model, glm::vec3(0.0f, 1.5f, 0.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 2.0));
model = glm::rotate(model, 60.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
model = glm::scale(model, glm::vec3(0.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
}
// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
GLfloat quadVertices[] = {
// Positions // Texture Coords
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
};
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
// RenderCube() Renders a 1x1 3D cube in NDC.
GLuint cubeVAO = 0;
GLuint cubeVBO = 0;
void RenderCube()
{
// Initialize (if necessary)
if (cubeVAO == 0)
{
GLfloat vertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
};
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
// Fill buffer
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Link vertex attributes
glBindVertexArray(cubeVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Render Cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar* path)
{
// Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
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_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
if (keys[GLFW_KEY_SPACE] && !keysPressed[GLFW_KEY_SPACE])
{
shadows = !shadows;
keysPressed[GLFW_KEY_SPACE] = true;
}
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
{
keys[key] = false;
keysPressed[key] = false;
}
}
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
實(shí)際上PCF還有更多的內(nèi)容褐望,以及很多技術(shù)要點(diǎn)需要考慮以提升柔和陰影的效果,但處于本章內(nèi)容長(zhǎng)度考慮挣轨,我們將留在以后討論军熏。
1. 正交 vs 投影
在渲染深度貼圖的時(shí)候,正交(Orthographic)
和投影(Projection)
矩陣之間有所不同刃唐。正交投影矩陣并不會(huì)將場(chǎng)景用透視圖進(jìn)行變形羞迷,所有視線/光線都是平行的界轩,這使它對(duì)于定向光來(lái)說(shuō)是個(gè)很好的投影矩陣画饥。然而透視投影矩陣衔瓮,會(huì)將所有頂點(diǎn)根據(jù)透視關(guān)系進(jìn)行變形,結(jié)果因此而不同抖甘。下圖展示了兩種投影方式所產(chǎn)生的不同陰影區(qū)域:
透視投影對(duì)于光源來(lái)說(shuō)更合理热鞍,不像定向光,它是有自己的位置的衔彻。透視投影因此更經(jīng)常用在點(diǎn)光源和聚光燈上薇宠,而正交投影經(jīng)常用在定向光上。
另一個(gè)細(xì)微差別是艰额,透視投影矩陣澄港,將深度緩沖視覺(jué)化經(jīng)常會(huì)得到一個(gè)幾乎全白的結(jié)果。發(fā)生這個(gè)是因?yàn)橥敢曂队跋卤冢疃茸兂闪朔蔷€性的深度值回梧,它的大多數(shù)可辨范圍接近于近平面。為了可以像使用正交投影一樣合適的觀察到深度值祖搓,你必須先講過(guò)非線性深度值轉(zhuǎn)變?yōu)榫€性的狱意,我們?cè)谏疃葴y(cè)試教程中已經(jīng)討論過(guò)。
#version 330 core
out vec4 color;
in vec2 TexCoords;
uniform sampler2D depthMap;
uniform float near_plane;
uniform float far_plane;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane));
}
void main()
{
float depthValue = texture(depthMap, TexCoords).r;
color = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective
// color = vec4(vec3(depthValue), 1.0); // orthographic
}
這個(gè)深度值與我們見(jiàn)到的用正交投影的很相似拯欧。需要注意的是详囤,這個(gè)只適用于調(diào)試;正交或投影矩陣的深度檢查仍然保持原樣镐作,因?yàn)橄嚓P(guān)的深度并沒(méi)有改變藏姐。
附加資源
- Tutorial 16 : Shadow mapping:提供的類似的陰影映射教程,里面有一些額外的解釋该贾。
- Shadow Mapping – Part 1:ogldev:提供的另一個(gè)陰影映射教程包各。
- How Shadow Mapping Works:的一個(gè)第三方Y(jié)ouTube視頻教程,里面解釋了陰影映射及其實(shí)現(xiàn)靶庙。
- Common Techniques to Improve Shadow Depth Maps:微軟的一篇好文章问畅,其中理出了很多提升陰影貼圖質(zhì)量的技術(shù)。
后記
這一篇已經(jīng)結(jié)束六荒,下一篇關(guān)于陰影中的點(diǎn)陰影护姆。