版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.19 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒用過(guò)仲闽,最近也想學(xué)著使用這個(gè)圖形庫(kù)腋腮,感覺還是很有意思窍侧,也就自然想著好好的總結(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
34. OpenGL 圖形庫(kù)的使用(三十四)—— 高級(jí)光照之陰影 - 陰影映射Shadow Mapping
點(diǎn)光源陰影
上個(gè)教程我們學(xué)到了如何使用陰影映射技術(shù)創(chuàng)建動(dòng)態(tài)陰影。效果不錯(cuò)冠桃,但它只適合定向光命贴,因?yàn)殛幱爸皇窃趩我欢ㄏ蚬庠聪律傻摹K运步卸ㄏ蜿幱坝成涫程疃龋幱埃┵N圖生成自定向光的視角胸蛛。
本節(jié)我們的焦點(diǎn)是在各種方向生成動(dòng)態(tài)陰影。這個(gè)技術(shù)可以適用于點(diǎn)光源樱报,生成所有方向上的陰影葬项。
這個(gè)技術(shù)叫做點(diǎn)光陰影,過(guò)去的名字是萬(wàn)向陰影貼圖(omnidirectional shadow maps)
技術(shù)肃弟。
本節(jié)代碼基于前面的陰影映射教程玷室,所以如果你對(duì)傳統(tǒng)陰影映射不熟悉,還是建議先讀一讀陰影映射教程笤受。 算法和定向陰影映射差不多:我們從光的透視圖生成一個(gè)深度貼圖穷缤,基于當(dāng)前fragment
位置來(lái)對(duì)深度貼圖采樣,然后用儲(chǔ)存的深度值和每個(gè)fragment進(jìn)行對(duì)比箩兽,看看它是否在陰影中津肛。定向陰影映射和萬(wàn)向陰影映射的主要不同在于深度貼圖的使用上。
對(duì)于深度貼圖汗贫,我們需要從一個(gè)點(diǎn)光源的所有渲染場(chǎng)景身坐,普通2D深度貼圖不能工作;如果我們使用立方體貼圖會(huì)怎樣落包?因?yàn)榱⒎襟w貼圖可以儲(chǔ)存6個(gè)面的環(huán)境數(shù)據(jù)部蛇,它可以將整個(gè)場(chǎng)景渲染到立方體貼圖的每個(gè)面上,把它們當(dāng)作點(diǎn)光源四周的深度值來(lái)采樣咐蝇。
生成后的深度立方體貼圖被傳遞到光照像素著色器涯鲁,它會(huì)用一個(gè)方向向量來(lái)采樣立方體貼圖,從而得到當(dāng)前的fragment的深度(從光的透視圖)有序。大部分復(fù)雜的事情已經(jīng)在陰影映射教程中討論過(guò)了抹腿。算法只是在深度立方體貼圖生成上稍微復(fù)雜一點(diǎn)。
生成深度立方體貼圖
為創(chuàng)建一個(gè)光周圍的深度值的立方體貼圖旭寿,我們必須渲染場(chǎng)景6次:每次一個(gè)面警绩。顯然渲染場(chǎng)景6次需要6個(gè)不同的視圖矩陣,每次把一個(gè)不同的立方體貼圖面附加到幀緩沖對(duì)象上盅称。這看起來(lái)是這樣的:
for(int i = 0; i < 6; i++)
{
GLuint face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, face, depthCubemap, 0);
BindViewMatrix(lightViewMatrices[i]);
RenderScene();
}
這會(huì)很耗費(fèi)性能因?yàn)橐粋€(gè)深度貼圖下需要進(jìn)行很多渲染調(diào)用肩祥。這個(gè)教程中我們將轉(zhuǎn)而使用另外的一個(gè)小技巧來(lái)做這件事,幾何著色器允許我們使用一次渲染過(guò)程來(lái)建立深度立方體貼圖缩膝。
首先搭幻,我們需要?jiǎng)?chuàng)建一個(gè)立方體貼圖:
GLuint depthCubemap;
glGenTextures(1, &depthCubemap);
然后生成立方體貼圖的每個(gè)面,將它們作為2D深度值紋理圖像:
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
for (GLuint i = 0; i < 6; ++i)
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,
SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
不要忘記設(shè)置合適的紋理參數(shù):
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
正常情況下逞盆,我們把立方體貼圖紋理的一個(gè)面附加到幀緩沖對(duì)象上檀蹋,渲染場(chǎng)景6次,每次將幀緩沖的深度緩沖目標(biāo)改成不同立方體貼圖面云芦。由于我們將使用一個(gè)幾何著色器俯逾,它允許我們把所有面在一個(gè)過(guò)程渲染,我們可以使用glFramebufferTexture
直接把立方體貼圖附加成幀緩沖的深度附件:
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
還要記得調(diào)用glDrawBuffer
和glReadBuffer
:當(dāng)生成一個(gè)深度立方體貼圖時(shí)我們只關(guān)心深度值舅逸,所以我們必須顯式告訴OpenGL這個(gè)幀緩沖對(duì)象不會(huì)渲染到一個(gè)顏色緩沖里桌肴。
萬(wàn)向陰影貼圖有兩個(gè)渲染階段:首先我們生成深度貼圖,然后我們正常使用深度貼圖渲染琉历,在場(chǎng)景中創(chuàng)建陰影坠七。幀緩沖對(duì)象和立方體貼圖的處理看起是這樣的:
// 1. first render to depth cubemap
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 2. then render scene as normal with shadow mapping (using depth cubemap)
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
RenderScene();
這個(gè)過(guò)程和默認(rèn)的陰影映射一樣水醋,盡管這次我們渲染和使用的是一個(gè)立方體貼圖深度紋理,而不是2D深度紋理彪置。在我們實(shí)際開始從光的視角的所有方向渲染場(chǎng)景之前拄踪,我們先得計(jì)算出合適的變換矩陣。
1. 光空間的變換
設(shè)置了幀緩沖和立方體貼圖拳魁,我們需要一些方法來(lái)講場(chǎng)景的所有幾何體變換到6個(gè)光的方向中相應(yīng)的光空間惶桐。與陰影映射教程類似,我們將需要一個(gè)光空間的變換矩陣T潘懊,但是這次是每個(gè)面都有一個(gè)姚糊。
每個(gè)光空間的變換矩陣包含了投影和視圖矩陣。對(duì)于投影矩陣來(lái)說(shuō)授舟,我們將使用一個(gè)透視投影矩陣救恨;光源代表一個(gè)空間中的點(diǎn),所以透視投影矩陣更有意義释树。每個(gè)光空間變換矩陣使用同樣的投影矩陣:
GLfloat aspect = (GLfloat)SHADOW_WIDTH/(GLfloat)SHADOW_HEIGHT;
GLfloat near = 1.0f;
GLfloat far = 25.0f;
glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), aspect, near, far);
非常重要的一點(diǎn)是忿薇,這里glm::perspective的視野參數(shù),設(shè)置為90度躏哩。90度我們才能保證視野足夠大到可以合適地填滿立方體貼圖的一個(gè)面署浩,立方體貼圖的所有面都能與其他面在邊緣對(duì)齊。
因?yàn)橥队熬仃囋诿總€(gè)方向上并不會(huì)改變扫尺,我們可以在6個(gè)變換矩陣中重復(fù)使用筋栋。我們要為每個(gè)方向提供一個(gè)不同的視圖矩陣。用glm::lookAt
創(chuàng)建6個(gè)觀察方向正驻,每個(gè)都按順序注視著立方體貼圖的的一個(gè)方向:右弊攘、左、上姑曙、下襟交、近、遠(yuǎn):
std::vector<glm::mat4> shadowTransforms;
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(1.0,0.0,0.0), glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(-1.0,0.0,0.0), glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(0.0,1.0,0.0), glm::vec3(0.0,0.0,1.0));
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(0.0,-1.0,0.0), glm::vec3(0.0,0.0,-1.0));
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(0.0,0.0,1.0), glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj *
glm::lookAt(lightPos, lightPos + glm::vec3(0.0,0.0,-1.0), glm::vec3(0.0,-1.0,0.0));
這里我們創(chuàng)建了6個(gè)視圖矩陣伤靠,把它們乘以投影矩陣捣域,來(lái)得到6個(gè)不同的光空間變換矩陣。glm::lookAt
的target參數(shù)是它注視的立方體貼圖的面的一個(gè)方向宴合。
這些變換矩陣發(fā)送到著色器渲染到立方體貼圖里焕梅。
2. 深度著色器
為了把值渲染到深度立方體貼圖,我們將需要3個(gè)著色器:頂點(diǎn)和像素著色器卦洽,以及一個(gè)它們之間的幾何著色器贞言。
幾何著色器是負(fù)責(zé)將所有世界空間的頂點(diǎn)變換到6個(gè)不同的光空間的著色器。因此頂點(diǎn)著色器簡(jiǎn)單地將頂點(diǎn)變換到世界空間阀蒂,然后直接發(fā)送到幾何著色器:
#version 330 core
layout (location = 0) in vec3 position;
uniform mat4 model;
void main()
{
gl_Position = model * vec4(position, 1.0);
}
緊接著幾何著色器以3個(gè)三角形的頂點(diǎn)作為輸入该窗,它還有一個(gè)光空間變換矩陣的uniform數(shù)組弟蚀。幾何著色器接下來(lái)會(huì)負(fù)責(zé)將頂點(diǎn)變換到光空間;這里它開始變得有趣了酗失。
幾何著色器有一個(gè)內(nèi)建變量叫做gl_Layer
义钉,它指定發(fā)散出基本圖形送到立方體貼圖的哪個(gè)面。當(dāng)不管它時(shí)级零,幾何著色器就會(huì)像往常一樣把它的基本圖形發(fā)送到輸送管道的下一階段断医,但當(dāng)我們更新這個(gè)變量就能控制每個(gè)基本圖形將渲染到立方體貼圖的哪一個(gè)面滞乙。當(dāng)然這只有當(dāng)我們有了一個(gè)附加到激活的幀緩沖的立方體貼圖紋理才有效:
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;
uniform mat4 shadowMatrices[6];
out vec4 FragPos; // FragPos from GS (output per emitvertex)
void main()
{
for(int face = 0; face < 6; ++face)
{
gl_Layer = face; // built-in variable that specifies to which face we render.
for(int i = 0; i < 3; ++i) // for each triangle's vertices
{
FragPos = gl_in[i].gl_Position;
gl_Position = shadowMatrices[face] * FragPos;
EmitVertex();
}
EndPrimitive();
}
}
幾何著色器相對(duì)簡(jiǎn)單奏纪。我們輸入一個(gè)三角形,輸出總共6個(gè)三角形(6*3頂點(diǎn)斩启,所以總共18個(gè)頂點(diǎn))序调。在main函數(shù)中,我們遍歷立方體貼圖的6個(gè)面兔簇,我們每個(gè)面指定為一個(gè)輸出面发绢,把這個(gè)面的interger(整數(shù))存到gl_Layer。然后垄琐,我們通過(guò)把面的光空間變換矩陣乘以FragPos边酒,將每個(gè)世界空間頂點(diǎn)變換到相關(guān)的光空間,生成每個(gè)三角形狸窘。注意墩朦,我們還要將最后的FragPos變量發(fā)送給像素著色器,我們需要計(jì)算一個(gè)深度值翻擒。
上個(gè)教程氓涣,我們使用的是一個(gè)空的像素著色器,讓OpenGL配置深度貼圖的深度值陋气。這次我們將計(jì)算自己的深度劳吠,這個(gè)深度就是每個(gè)fragment位置和光源位置之間的線性距離。計(jì)算自己的深度值使得之后的陰影計(jì)算更加直觀巩趁。
#version 330 core
in vec4 FragPos;
uniform vec3 lightPos;
uniform float far_plane;
void main()
{
// get distance between fragment and light source
float lightDistance = length(FragPos.xyz - lightPos);
// map to [0;1] range by dividing by far_plane
lightDistance = lightDistance / far_plane;
// Write this as modified depth
gl_FragDepth = lightDistance;
}
像素著色器將來(lái)自幾何著色器的FragPos痒玩、光的位置向量和視錐的遠(yuǎn)平面值作為輸入。這里我們把fragment
和光源之間的距離议慰,映射到0到1的范圍凰荚,把它寫入為fragment的深度值。
使用這些著色器渲染場(chǎng)景褒脯,立方體貼圖附加的幀緩沖對(duì)象激活以后便瑟,你會(huì)得到一個(gè)完全填充的深度立方體貼圖,以便于進(jìn)行第二階段的陰影計(jì)算番川。
萬(wàn)向陰影貼圖
所有事情都做好了到涂,是時(shí)候來(lái)渲染萬(wàn)向陰影(Omnidirectional Shadow)
了脊框。這個(gè)過(guò)程和定向陰影映射教程相似,盡管這次我們綁定的深度貼圖是一個(gè)立方體貼圖践啄,而不是2D紋理浇雹,并且將光的投影的遠(yuǎn)平面發(fā)送給了著色器。
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.Use();
// ... send uniforms to shader (including light's far_plane value)
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
// ... bind other textures
RenderScene();
這里的renderScene
函數(shù)在一個(gè)大立方體房間中渲染一些立方體屿讽,它們散落在大立方體各處昭灵,光源在場(chǎng)景中央。
頂點(diǎn)著色器和像素著色器和原來(lái)的陰影映射著色器大部分都一樣:不同之處是在光空間中像素著色器不再需要一個(gè)fragment位置伐谈,現(xiàn)在我們可以使用一個(gè)方向向量采樣深度值烂完。
因?yàn)檫@個(gè)頂點(diǎn)著色器不再需要將他的位置向量變換到光空間,所以我們可以去掉FragPosLightSpace
變量:
#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;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
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;
}
片段著色器的Blinn-Phong
光照代碼和我們之前陰影相乘的結(jié)尾部分一樣:
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
} fs_in;
uniform sampler2D diffuseTexture;
uniform samplerCube depthMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform float far_plane;
float ShadowCalculation(vec3 fragPos)
{
[...]
}
void main()
{
vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
vec3 normal = normalize(fs_in.Normal);
vec3 lightColor = vec3(0.3);
// Ambient
vec3 ambient = 0.3 * 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;
// Calculate shadow
float shadow = ShadowCalculation(fs_in.FragPos);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
FragColor = vec4(lighting, 1.0f);
}
有一些細(xì)微的不同:光照代碼一樣诵棵,但我們現(xiàn)在有了一個(gè)uniform變量samplerCube
抠蚣,shadowCalculation
函數(shù)用fragment的位置作為它的參數(shù),取代了光空間的fragment位置履澳。我們現(xiàn)在還要引入光的視錐的遠(yuǎn)平面值嘶窄,后面我們會(huì)需要它。像素著色器的最后距贷,我們計(jì)算出陰影元素柄冲,當(dāng)fragment在陰影中時(shí)它是1.0,不在陰影中時(shí)是0.0忠蝗。我們使用計(jì)算出來(lái)的陰影元素去影響光照的diffuse和specular元素现横。
在ShadowCalculation
函數(shù)中有很多不同之處,現(xiàn)在是從立方體貼圖中進(jìn)行采樣什湘,不再使用2D紋理了长赞。我們來(lái)一步一步的討論一下的它的內(nèi)容。
我們需要做的第一件事是獲取立方體貼圖的森都闽撤。你可能已經(jīng)從教程的立方體貼圖部分想到得哆,我們已經(jīng)將深度儲(chǔ)存為fragment和光位置之間的距離了;我們這里采用相似的處理方式:
float ShadowCalculation(vec3 fragPos)
{
vec3 fragToLight = fragPos - lightPos;
float closestDepth = texture(depthMap, fragToLight).r;
}
在這里哟旗,我們得到了fragment的位置與光的位置之間的不同的向量贩据,使用這個(gè)向量作為一個(gè)方向向量去對(duì)立方體貼圖進(jìn)行采樣。方向向量不需要是單位向量闸餐,所以無(wú)需對(duì)它進(jìn)行標(biāo)準(zhǔn)化饱亮。最后的closestDepth
是光源和它最接近的可見fragment之間的標(biāo)準(zhǔn)化的深度值。
closestDepth
值現(xiàn)在在0到1的范圍內(nèi)了舍沙,所以我們先將其轉(zhuǎn)換會(huì)0到far_plane的范圍近上,這需要把他乘以far_plane:
closestDepth *= far_plane;
下一步我們獲取當(dāng)前fragment和光源之間的深度值,我們可以簡(jiǎn)單的使用fragToLight
的長(zhǎng)度來(lái)獲取它拂铡,這取決于我們?nèi)绾斡?jì)算立方體貼圖中的深度值:
float currentDepth = length(fragToLight);
返回的是和closestDepth
范圍相同的深度值壹无。
現(xiàn)在我們可以將兩個(gè)深度值對(duì)比一下葱绒,看看哪一個(gè)更接近,以此決定當(dāng)前的fragment是否在陰影當(dāng)中斗锭。我們還要包含一個(gè)陰影偏移地淀,所以才能避免陰影失真,這在前面教程中已經(jīng)討論過(guò)了岖是。
float bias = 0.05;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
完整的ShadowCalculation
現(xiàn)在變成了這樣:
float ShadowCalculation(vec3 fragPos)
{
// Get vector between fragment position and light position
vec3 fragToLight = fragPos - lightPos;
// Use the light to fragment vector to sample from the depth map
float closestDepth = texture(depthMap, fragToLight).r;
// It is currently in linear range between [0,1]. Re-transform back to original value
closestDepth *= far_plane;
// Now get current linear depth as the length between the fragment and light position
float currentDepth = length(fragToLight);
// Now test for shadows
float bias = 0.05;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
return shadow;
}
有了這些著色器帮毁,我們已經(jīng)能得到非常好的陰影效果了,這次從一個(gè)點(diǎn)光源所有周圍方向上都有陰影豺撑。有一個(gè)位于場(chǎng)景中心的點(diǎn)光源烈疚,看起來(lái)會(huì)像這樣:
你可以從這里找到這個(gè)demo的源碼、頂點(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);
glEnable(GL_CULL_FACE);
// Setup and compile our shaders
Shader shader("point_shadows.vs", "point_shadows.frag");
Shader simpleDepthShader("point_shadows_depth.vs", "point_shadows_depth.frag", "point_shadows_depth.gs");
// Set texture samples
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "diffuseTexture"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "depthMap"), 1);
// Light source
glm::vec3 lightPos(0.0f, 0.0f, 0.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 cubemap texture
GLuint depthCubemap;
glGenTextures(1, &depthCubemap);
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
for (GLuint i = 0; i < 6; ++i)
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// Attach cubemap as depth map FBO's color buffer
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
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();
// Move light position over time
//lightPos.z = sin(glfwGetTime() * 0.5) * 3.0;
// 0. Create depth cubemap transformation matrices
GLfloat aspect = (GLfloat)SHADOW_WIDTH / (GLfloat)SHADOW_HEIGHT;
GLfloat near = 1.0f;
GLfloat far = 25.0f;
glm::mat4 shadowProj = glm::perspective(90.0f, aspect, near, far);
std::vector<glm::mat4> shadowTransforms;
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 1.0, 0.0, 0.0), glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(-1.0, 0.0, 0.0), glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 1.0, 0.0), glm::vec3(0.0, 0.0, 1.0)));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, -1.0, 0.0), glm::vec3(0.0, 0.0, -1.0)));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 0.0, 1.0), glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 0.0, -1.0), glm::vec3(0.0, -1.0, 0.0)));
// 1. Render scene to depth cubemap
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
simpleDepthShader.Use();
for (GLuint i = 0; i < 6; ++i)
glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader.Program, ("shadowMatrices[" + std::to_string(i) + "]").c_str()), 1, GL_FALSE, glm::value_ptr(shadowTransforms[i]));
glUniform1f(glGetUniformLocation(simpleDepthShader.Program, "far_plane"), far);
glUniform3fv(glGetUniformLocation(simpleDepthShader.Program, "lightPos"), 1, &lightPos[0]);
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]);
// Enable/Disable shadows by pressing 'SPACE'
glUniform1i(glGetUniformLocation(shader.Program, "shadows"), shadows);
glUniform1f(glGetUniformLocation(shader.Program, "far_plane"), far);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
RenderScene(shader);
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
void RenderScene(Shader &shader)
{
// Room cube
glm::mat4 model;
model = glm::scale(model, glm::vec3(10.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glDisable(GL_CULL_FACE); // Note that we disable culling here since we render 'inside' the cube instead of the usual 'outside' which throws off the normal culling methods.
glUniform1i(glGetUniformLocation(shader.Program, "reverse_normals"), 1); // A small little hack to invert normals when drawing cube from the inside so lighting still works.
RenderCube();
glUniform1i(glGetUniformLocation(shader.Program, "reverse_normals"), 0); // And of course disable it
glEnable(GL_CULL_FACE);
// Cubes
model = glm::mat4();
model = glm::translate(model, glm::vec3(4.0f, -3.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, 3.0f, 1.0));
model = glm::scale(model, glm::vec3(1.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-3.0f, -1.0f, 0.0));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-1.5f, 1.0f, 1.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
model = glm::mat4();
model = glm::translate(model, glm::vec3(-1.5f, 2.0f, -3.0));
model = glm::rotate(model, 60.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
model = glm::scale(model, glm::vec3(1.5));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderCube();
}
// 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);
}
1. 顯示立方體貼圖深度緩沖
如果你想我一樣第一次并沒有做對(duì)胞得,那么就要進(jìn)行調(diào)試排錯(cuò)荧止,將深度貼圖顯示出來(lái)以檢查其是否正確屹电。因?yàn)槲覀儾辉儆?D深度貼圖紋理,深度貼圖的顯示不會(huì)那么顯而易見跃巡。
一個(gè)簡(jiǎn)單的把深度緩沖顯示出來(lái)的技巧是危号,在ShadowCalculation
函數(shù)中計(jì)算標(biāo)準(zhǔn)化的closestDepth
變量,把變量顯示為:
FragColor = vec4(vec3(closestDepth / far_plane), 1.0);
結(jié)果是一個(gè)灰度場(chǎng)景素邪,每個(gè)顏色代表著場(chǎng)景的線性深度值:
你可能也注意到了帶陰影部分在墻外外莲。如果看起來(lái)和這個(gè)差不多,你就知道深度立方體貼圖生成的沒錯(cuò)兔朦。否則你可能做錯(cuò)了什么偷线,也許是closestDepth仍然還在0到far_plane的范圍。
PCF
由于萬(wàn)向陰影貼圖基于傳統(tǒng)陰影映射的原則沽甥,它便也繼承了由解析度產(chǎn)生的非真實(shí)感声邦。如果你放大就會(huì)看到鋸齒邊了。PCF
或稱Percentage-closer filtering
允許我們通過(guò)對(duì)fragment位置周圍過(guò)濾多個(gè)樣本摆舟,并對(duì)結(jié)果平均化亥曹。
如果我們用和前面教程同樣的那個(gè)簡(jiǎn)單的PCF過(guò)濾器,并加入第三個(gè)維度恨诱,就是這樣的:
float shadow = 0.0;
float bias = 0.05;
float samples = 4.0;
float offset = 0.1;
for(float x = -offset; x < offset; x += offset / (samples * 0.5))
{
for(float y = -offset; y < offset; y += offset / (samples * 0.5))
{
for(float z = -offset; z < offset; z += offset / (samples * 0.5))
{
float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r;
closestDepth *= far_plane; // Undo mapping [0;1]
if(currentDepth - bias > closestDepth)
shadow += 1.0;
}
}
}
shadow /= (samples * samples * samples);
這段代碼和我們傳統(tǒng)的陰影映射沒有多少不同媳瞪。這里我們根據(jù)樣本的數(shù)量動(dòng)態(tài)計(jì)算了紋理偏移量,我們?cè)谌齻€(gè)軸向采樣三次照宝,最后對(duì)子樣本進(jìn)行平均化蛇受。
現(xiàn)在陰影看起來(lái)更加柔和平滑了,由此得到更加真實(shí)的效果:
然而厕鹃,samples設(shè)置為4.0兢仰,每個(gè)fragment我們會(huì)得到總共64個(gè)樣本笼呆,這太多了!
大多數(shù)這些樣本都是多余的旨别,它們?cè)谠挤较蛳蛄拷幉蓸邮模蝗缭诓蓸臃较蛳蛄康拇怪狈较蜻M(jìn)行采樣更有意義〗粘冢可是铭若,沒有(簡(jiǎn)單的)方式能夠指出哪一個(gè)子方向是多余的,這就難了递览。有個(gè)技巧可以使用叼屠,用一個(gè)偏移量方向數(shù)組,它們差不多都是分開的绞铃,每一個(gè)指向完全不同的方向镜雨,剔除彼此接近的那些子方向。下面就是一個(gè)有著20個(gè)偏移方向的數(shù)組:
vec3 sampleOffsetDirections[20] = vec3[]
(
vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1),
vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0),
vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1),
vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1)
);
然后我們把PCF算法與從sampleOffsetDirections
得到的樣本數(shù)量進(jìn)行適配儿捧,使用它們從立方體貼圖里采樣荚坞。這么做的好處是與之前的PCF算法相比,我們需要的樣本數(shù)量變少了菲盾。
float shadow = 0.0;
float bias = 0.15;
int samples = 20;
float viewDistance = length(viewPos - fragPos);
float diskRadius = 0.05;
for(int i = 0; i < samples; ++i)
{
float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;
closestDepth *= far_plane; // Undo mapping [0;1]
if(currentDepth - bias > closestDepth)
shadow += 1.0;
}
shadow /= float(samples);
這里我們把一個(gè)偏移量添加到指定的diskRadius
中颓影,它在fragToLight方向向量周圍從立方體貼圖里采樣。
另一個(gè)在這里可以應(yīng)用的有意思的技巧是懒鉴,我們可以基于觀察者里一個(gè)fragment的距離來(lái)改變diskRadius
诡挂;這樣我們就能根據(jù)觀察者的距離來(lái)增加偏移半徑了,當(dāng)距離更遠(yuǎn)的時(shí)候陰影更柔和临谱,更近了就更銳利璃俗。
float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;
PCF算法的結(jié)果如果沒有變得更好,也是非常不錯(cuò)的悉默,這是柔和的陰影效果:
當(dāng)然了城豁,我們添加到每個(gè)樣本的bias(偏移)高度依賴于上下文,總是要根據(jù)場(chǎng)景進(jìn)行微調(diào)的麦牺。試試這些值钮蛛,看看怎樣影響了場(chǎng)景。 這里是最終版本的頂點(diǎn)和像素著色器剖膳。
我還要提醒一下使用幾何著色器來(lái)生成深度貼圖不會(huì)一定比每個(gè)面渲染場(chǎng)景6次更快魏颓。使用幾何著色器有它自己的性能局限,在第一個(gè)階段使用它可能獲得更好的性能表現(xiàn)吱晒。這取決于環(huán)境的類型甸饱,以及特定的顯卡驅(qū)動(dòng)等等,所以如果你很關(guān)心性能,就要確保對(duì)兩種方法有大致了解叹话,然后選擇對(duì)你場(chǎng)景來(lái)說(shuō)更高效的那個(gè)偷遗。我個(gè)人還是喜歡使用幾何著色器來(lái)進(jìn)行陰影映射,原因很簡(jiǎn)單驼壶,因?yàn)樗鼈兪褂闷饋?lái)更簡(jiǎn)單氏豌。
附加資源
- Shadow Mapping for point light sources in OpenGL:sunandblackcat的萬(wàn)向陰影映射教程。
- Multipass Shadow Mapping With Point Lights:ogldev的萬(wàn)向陰影映射教程热凹。
- Omni-directional Shadows:Peter Houska的關(guān)于萬(wàn)向陰影映射的一組很好的ppt泵喘。
后記
本篇已結(jié)束,下一篇關(guān)于高級(jí)光照的法線貼圖般妙。