版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.01.19 |
前言
OpenGL 圖形庫項目中一直也沒用過掷空,最近也想學(xué)著使用這個圖形庫闯狱,感覺還是很有意思,也就自然想著好好的總結(jié)一下扼睬,希望對大家能有所幫助。下面內(nèi)容來自歡迎來到OpenGL的世界悴势。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式窗宇、對象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫使用(三) —— 著色器特纤、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級OpenGL之高級數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫的使用(三十二)—— 高級光照之高級光照Advanced Lighting
33. OpenGL 圖形庫的使用(三十三)—— 高級光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫的使用(三十四)—— 高級光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫的使用(三十五)—— 高級光照之陰影 - 點陰影Point Shadows
36. OpenGL 圖形庫的使用(三十六)—— 高級光照之法線貼圖Normal Mapping
視差貼圖基本
視差貼圖(Parallax Mapping)
技術(shù)和法線貼圖差不多军俊,但它有著不同的原則。和法線貼圖一樣視差貼圖能夠極大提升表面細(xì)節(jié)捧存,使之具有深度感粪躬。它也是利用了視錯覺担败,然而對深度有著更好的表達(dá),與法線貼圖一起用能夠產(chǎn)生難以置信的效果镰官。視差貼圖和光照無關(guān)提前,我在這里是作為法線貼圖的技術(shù)延續(xù)來討論它的。需要注意的是在開始學(xué)習(xí)視差貼圖之前強(qiáng)烈建議先對法線貼圖泳唠,特別是切線空間有較好的理解狈网。
視差貼圖屬于位移貼圖(Displacement Mapping)
技術(shù)的一種,它對根據(jù)儲存在紋理中的幾何信息對頂點進(jìn)行位移或偏移笨腥。一種實現(xiàn)的方式是比如有1000個頂點拓哺,更具紋理中的數(shù)據(jù)對平面特定區(qū)域的頂點的高度進(jìn)行位移。這樣的每個紋理像素包含了高度值紋理叫做高度貼圖脖母。一張簡單的磚塊表面的高度貼圖如下所示:
整個平面上的每個頂點都根據(jù)從高度貼圖采樣出來的高度值進(jìn)行位移士鸥,根據(jù)材質(zhì)的幾何屬性平坦的平面變換成凹凸不平的表面。例如一個平坦的平面利用上面的高度貼圖進(jìn)行置換能得到以下結(jié)果:
置換頂點有一個問題就是平面必須由很多頂點組成才能獲得具有真實感的效果镶奉,否則看起來效果并不會很好础淤。一個平坦的表面上有1000個頂點計算量太大了。我們能否不用這么多的頂點就能取得相似的效果呢哨苛?事實上鸽凶,上面的表面就是用6個頂點渲染出來的(兩個三角形)。上面的那個表面使用視差貼圖技術(shù)渲染建峭,位移貼圖技術(shù)不需要額外的頂點數(shù)據(jù)來表達(dá)深度玻侥,它像法線貼圖一樣采用一種聰明的手段欺騙用戶的眼睛。
視差貼圖背后的思想是修改紋理坐標(biāo)使一個fragment的表面看起來比實際的更高或者更低亿蒸,所有這些都根據(jù)觀察方向和高度貼圖凑兰。為了理解它如何工作,看看下面磚塊表面的圖片:
這里粗糙的紅線代表高度貼圖中的數(shù)值的立體表達(dá)边锁,向量Vˉ代表觀察方向姑食。如果平面進(jìn)行實際位移,觀察者會在點B看到表面茅坛。然而我們的平面沒有實際上進(jìn)行位移音半,觀察方向?qū)⒃邳cA與平面接觸。視差貼圖的目的是贡蓖,在A位置上的fragment不再使用點A的紋理坐標(biāo)而是使用點B的曹鸠。隨后我們用點B的紋理坐標(biāo)采樣,觀察者就像看到了點B一樣斥铺。
這個技巧就是描述如何從點A得到點B的紋理坐標(biāo)彻桃。視差貼圖嘗試通過對從fragment到觀察者的方向向量Vˉ進(jìn)行縮放的方式解決這個問題,縮放的大小是A處fragment的高度晾蜘。所以我們將Vˉ的長度縮放為高度貼圖在點A處H(A)采樣得來的值邻眷。下圖展示了經(jīng)縮放得到的向量Pˉ:
我們隨后選出Pˉ以及這個向量與平面對齊的坐標(biāo)作為紋理坐標(biāo)的偏移量眠屎。這能工作是因為向量Pˉ是使用從高度貼圖得到的高度值計算出來的,所以一個fragment的高度越高位移的量越大肆饶。
這個技巧在大多數(shù)時候都沒問題组力,但點B是粗略估算得到的。當(dāng)表面的高度變化很快的時候抖拴,看起來就不會真實,因為向量Pˉ最終不會和B接近腥椒,就像下圖這樣:
視差貼圖的另一個問題是阿宅,當(dāng)表面被任意旋轉(zhuǎn)以后很難指出從Pˉ
獲取哪一個坐標(biāo)。我們在視差貼圖中使用了另一個坐標(biāo)空間笼蛛,這個空間Pˉ
向量的x和y元素總是與紋理表面對齊洒放。如果你看了法線貼圖教程,你也許猜到了滨砍,我們實現(xiàn)它的方法往湿,是的,我們還是在切線空間中實現(xiàn)視差貼圖惋戏。
將fragment到觀察者的向量Vˉ轉(zhuǎn)換到切線空間中领追,經(jīng)變換的Pˉ向量的x和y元素將于表面的切線和副切線向量對齊。由于切線和副切線向量與表面紋理坐標(biāo)的方向相同响逢,我們可以用Pˉ的x和y元素作為紋理坐標(biāo)的偏移量绒窑,這樣就不用考慮表面的方向了。
理論都有了舔亭,下面我們來動手實現(xiàn)視差貼圖些膨。
視差貼圖
我們將使用一個簡單的2D平面,在把它發(fā)送給GPU之前我們先計算它的切線和副切線向量钦铺;和法線貼圖教程做的差不多订雾。我們將向平面貼diffuse紋理、法線貼圖以及一個位移貼圖矛洞,你可以點擊鏈接下載洼哎。這個例子中我們將視差貼圖和法線貼圖連用。因為視差貼圖生成表面位移了的幻覺缚甩,當(dāng)光照不匹配時這種幻覺就被破壞了谱净。法線貼圖通常根據(jù)高度貼圖生成,法線貼圖和高度貼圖一起用能保證光照能和位移想匹配擅威。
你可能已經(jīng)注意到壕探,上面鏈接上的那個位移貼圖和教程一開始的那個高度貼圖相比是顏色是相反的。這是因為使用反色高度貼圖(也叫深度貼圖)去模擬深度比模擬高度更容易郊丛。下圖反映了這個輕微的改變:
我們再次獲得A和B李请,但是這次我們用向量Vˉ減去點A的紋理坐標(biāo)得到Pˉ瞧筛。我們通過在著色器中用1.0減去采樣得到的高度貼圖中的值來取得深度值,而不再是高度值导盅,或者簡單地在圖片編輯軟件中把這個紋理進(jìn)行反色操作较幌,就像我們對連接中的那個深度貼圖所做的一樣。
位移貼圖是在像素著色器中實現(xiàn)的白翻,因為三角形表面的所有位移效果都不同乍炉。在像素著色器中我們將需要計算fragment
到觀察者到方向向量Vˉ所以我們需要觀察者位置和在切線空間中的fragment位置。法線貼圖教程中我們已經(jīng)有了一個頂點著色器滤馍,它把這些向量發(fā)送到切線空間岛琼,所以我們可以復(fù)制那個頂點著色器:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 tangent;
layout (location = 4) in vec3 bitangent;
out VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
vs_out.FragPos = vec3(model * vec4(position, 1.0));
vs_out.TexCoords = texCoords;
vec3 T = normalize(mat3(model) * tangent);
vec3 B = normalize(mat3(model) * bitangent);
vec3 N = normalize(mat3(model) * normal);
mat3 TBN = transpose(mat3(T, B, N));
vs_out.TangentLightPos = TBN * lightPos;
vs_out.TangentViewPos = TBN * viewPos;
vs_out.TangentFragPos = TBN * vs_out.FragPos;
}
在這里有件事很重要,我們需要把position和在切線空間中的觀察者的位置viewPos
發(fā)送給像素著色器巢株。
在像素著色器中槐瑞,我們實現(xiàn)視差貼圖的邏輯。像素著色器看起來會是這樣的:
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in;
uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;
uniform float height_scale;
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);
void main()
{
// Offset texture coordinates with Parallax Mapping
vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
vec2 texCoords = ParallaxMapping(fs_in.TexCoords, viewDir);
// then sample textures with new texture coords
vec3 diffuse = texture(diffuseMap, texCoords);
vec3 normal = texture(normalMap, texCoords);
normal = normalize(normal * 2.0 - 1.0);
// proceed with lighting code
[...]
}
我們定義了一個叫做ParallaxMapping
的函數(shù)阁苞,它把fragment
的紋理坐標(biāo)作和切線空間中的fragment到觀察者的方向向量為輸入困檩。這個函數(shù)返回經(jīng)位移的紋理坐標(biāo)。然后我們使用這些經(jīng)位移的紋理坐標(biāo)進(jìn)行diffuse和法線貼圖的采樣那槽。最后fragment的diffuse顏色和法線向量就正確的對應(yīng)于表面的經(jīng)位移的位置上了悼沿。
我們來看看ParallaxMapping
函數(shù)的內(nèi)部:
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
float height = texture(depthMap, texCoords).r;
vec3 p = viewDir.xy / viewDir.z * (height * height_scale);
return texCoords - p;
}
這個相對簡單的函數(shù)是我們所討論過的內(nèi)容的直接表述。我們用本來的紋理坐標(biāo)texCoords從高度貼圖中來采樣出當(dāng)前fragment高度H(A)骚灸。然后計算出Pˉ显沈,x和y元素在切線空間中,viewDir向量除以它的z元素逢唤,用fragment的高度對它進(jìn)行縮放拉讯。我們同時引入額一個height_scale的uniform,來進(jìn)行一些額外的控制鳖藕,因為視差效果如果沒有一個縮放參數(shù)通常會過于強(qiáng)烈魔慷。然后我們用Pˉ減去紋理坐標(biāo)來獲得最終的經(jīng)過位移紋理坐標(biāo)。
有一個地方需要注意著恩,就是viewDir.xy除以viewDir.z那里院尔。因為viewDir向量是經(jīng)過了標(biāo)準(zhǔn)化的,viewDir.z會在0.0到1.0之間的某處喉誊。當(dāng)viewDir大致平行于表面時邀摆,它的z元素接近于0.0,除法會返回比viewDir垂直于表面的時候更大的Pˉ向量伍茄。所以基本上我們增加了Pˉ的大小栋盹,當(dāng)以一個角度朝向一個表面相比朝向頂部時它對紋理坐標(biāo)會進(jìn)行更大程度的縮放;這回在角上獲得更大的真實度敷矫。
有些人更喜歡在等式中不使用viewDir.z
例获,因為普通的視差貼圖會在角上產(chǎn)生不想要的結(jié)果汉额;這個技術(shù)叫做有偏移量限制的視差貼圖(Parallax Mapping with Offset Limiting)
。選擇哪一個技術(shù)是個人偏好問題榨汤,但我傾向于普通的視差貼圖蠕搜。
最后的紋理坐標(biāo)隨后被用來進(jìn)行采樣(diffuse和法線)貼圖,下圖所展示的位移效果中height_scale
等于1:
這里你會看到只用法線貼圖和與視差貼圖相結(jié)合的法線貼圖的不同之處收壕。因為視差貼圖嘗試模擬深度妓灌,它實際上能夠根據(jù)你觀察它們的方向使磚塊疊加到其他磚塊上。
在視差貼圖的那個平面里你仍然能看到在邊上有古怪的失真蜜宪。原因是在平面的邊緣上旬渠,紋理坐標(biāo)超出了0到1的范圍進(jìn)行采樣,根據(jù)紋理的環(huán)繞方式導(dǎo)致了不真實的結(jié)果端壳。解決的方法是當(dāng)它超出默認(rèn)紋理坐標(biāo)范圍進(jìn)行采樣的時候就丟棄這個fragment:
texCoords = ParallaxMapping(fs_in.TexCoords, viewDir);
if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
discard;
丟棄了超出默認(rèn)范圍的紋理坐標(biāo)的所有fragment,視差貼圖的表面邊緣給出了正確的結(jié)果枪蘑。注意损谦,這個技巧不能在所有類型的表面上都能工作,但是應(yīng)用于平面上它還是能夠是平面看起來真的進(jìn)行位移了:
// Std. Includes
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.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 RenderQuad();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
GLboolean parallax_mapping = true;
GLfloat height_scale = 0.1;
// 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("parallax_mapping.vs", "parallax_mapping.frag");
// Load textures
GLuint diffuseMap = loadTexture("../../../resources/textures/bricks2.jpg");
GLuint normalMap = loadTexture("../../../resources/textures/bricks2_normal.jpg");
GLuint heightMap = loadTexture("../../../resources/textures/bricks2_disp.jpg");
/* GLuint diffuseMap = loadTexture("../../../resources/textures/toy_box_diffuse.png");
GLuint normalMap = loadTexture("../../../resources/textures/toy_box_normal.png");
GLuint heightMap = loadTexture("../../../resources/textures/toy_box_disp.png");*/
// Set texture units
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "diffuseMap"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "normalMap"), 1);
glUniform1i(glGetUniformLocation(shader.Program, "depthMap"), 2);
// Light position
glm::vec3 lightPos(0.5f, 1.0f, 0.3f);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// Clear the colorbuffer
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Configure view/projection matrices
shader.Use();
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)SCR_WIDTH / (GLfloat)SCR_HEIGHT, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// Render normal-mapped quad
glm::mat4 model;
model = glm::rotate(model, (GLfloat)glfwGetTime() * -10, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // Rotates the quad to show parallax mapping works in all directions
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
glUniform1f(glGetUniformLocation(shader.Program, "height_scale"), height_scale);
glUniform1i(glGetUniformLocation(shader.Program, "parallax"), parallax_mapping);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normalMap);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, heightMap);
RenderQuad();
// render light source (simply renders a smaller plane at the light's position for debugging/visualization)
model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.1f));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderQuad();
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
// RenderQuad() Renders a 1x1 quad in NDC
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
// positions
glm::vec3 pos1(-1.0, 1.0, 0.0);
glm::vec3 pos2(-1.0, -1.0, 0.0);
glm::vec3 pos3(1.0, -1.0, 0.0);
glm::vec3 pos4(1.0, 1.0, 0.0);
// texture coordinates
glm::vec2 uv1(0.0, 1.0);
glm::vec2 uv2(0.0, 0.0);
glm::vec2 uv3(1.0, 0.0);
glm::vec2 uv4(1.0, 1.0);
// normal vector
glm::vec3 nm(0.0, 0.0, 1.0);
// calculate tangent/bitangent vectors of both triangles
glm::vec3 tangent1, bitangent1;
glm::vec3 tangent2, bitangent2;
// - triangle 1
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;
GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent1 = glm::normalize(tangent1);
bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent1 = glm::normalize(bitangent1);
// - triangle 2
edge1 = pos3 - pos1;
edge2 = pos4 - pos1;
deltaUV1 = uv3 - uv1;
deltaUV2 = uv4 - uv1;
f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent2 = glm::normalize(tangent2);
bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent2 = glm::normalize(bitangent2);
GLfloat quadVertices[] = {
// Positions // normal // TexCoords // Tangent // Bitangent
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
};
// 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, 14 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
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;
}
#pragma region "User input"
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);
// Change parallax height scale
if (keys[GLFW_KEY_Q])
height_scale -= 0.001;
else if (keys[GLFW_KEY_E])
height_scale += 0.001;
// Enable/disable parallax mapping
if (keys[GLFW_KEY_SPACE] && !keysPressed[GLFW_KEY_SPACE])
{
parallax_mapping = !parallax_mapping;
keysPressed[GLFW_KEY_SPACE] = 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;
}
}
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Moves/alters the camera positions based on user input
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);
}
#pragma endregion
看起來不錯,運行起來也很快话侧,因為我們只要給視差貼圖提供一個額外的紋理樣本就能工作栗精。當(dāng)從一個角度看過去的時候,會有一些問題產(chǎn)生(和法線貼圖相似)瞻鹏,陡峭的地方會產(chǎn)生不正確的結(jié)果悲立,從下圖你可以看到:
問題的原因是這只是一個大致近似的視差映射。還有一些技巧讓我們在陡峭的高度上能夠獲得幾乎完美的結(jié)果新博,即使當(dāng)以一定角度觀看的時候薪夕。例如,我們不再使用單一樣本赫悄,取而代之使用多樣本來找到最近點B會得到怎樣的結(jié)果原献?
陡峭視差映射
陡峭視差映射(Steep Parallax Mapping)
是視差映射的擴(kuò)展,原則是一樣的埂淮,但不是使用一個樣本而是多個樣本來確定向量Pˉ到B姑隅。它能得到更好的結(jié)果,它將總深度范圍分布到同一個深度/高度的多個層中倔撞。從每個層中我們沿著Pˉ方向移動采樣紋理坐標(biāo)讲仰,直到我們找到了一個采樣得到的低于當(dāng)前層的深度值的深度值』居看看下面的圖片:
我們從上到下遍歷深度層叮盘,我們把每個深度層和儲存在深度貼圖中的它的深度值進(jìn)行對比秩贰。如果這個層的深度值小于深度貼圖的值,就意味著這一層的Pˉ向量部分在表面之下柔吼。我們繼續(xù)這個處理過程直到有一層的深度高于儲存在深度貼圖中的值:這個點就在(經(jīng)過位移的)表面下方毒费。
這個例子中我們可以看到第二層(D(2) = 0.73)的深度貼圖的值仍低于第二層的深度值0.4,所以我們繼續(xù)愈魏。下一次迭代觅玻,這一層的深度值0.6大于深度貼圖中采樣的深度值(D(3) = 0.37)。我們便可以假設(shè)第三層向量Pˉ是可用的位移幾何位置培漏。我們可以用從向量P3ˉ的紋理坐標(biāo)偏移T3來對fragment的紋理坐標(biāo)進(jìn)行位移溪厘。你可以看到隨著深度曾的增加精確度也在提高。
為實現(xiàn)這個技術(shù)牌柄,我們只需要改變ParallaxMapping
函數(shù)畸悬,因為所有需要的變量都有了:
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
// number of depth layers
const float numLayers = 10;
// calculate the size of each layer
float layerDepth = 1.0 / numLayers;
// depth of current layer
float currentLayerDepth = 0.0;
// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy * height_scale;
float deltaTexCoords = P / numLayers;
[...]
}
我們先定義層的數(shù)量,計算每一層的深度珊佣,最后計算紋理坐標(biāo)偏移蹋宦,每一層我們必須沿著Pˉ的方向進(jìn)行移動。
然后我們遍歷所有層咒锻,從上開始冷冗,知道找到小于這一層的深度值的深度貼圖值:
// get initial values
vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
while(currentLayerDepth < currentDepthMapValue)
{
// shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords;
// get depthmap value at current texture coordinates
currentDepthMapValue = texture(depthMap, currentTexCoords).r;
// get depth of next layer
currentLayerDepth += layerDepth;
}
return texCoords - currentTexCoords;
這里我們循環(huán)每一層深度,直到沿著Pˉ向量找到第一個返回低于(位移)表面的深度的紋理坐標(biāo)偏移量惑艇。從fragment的紋理坐標(biāo)減去最后的偏移量蒿辙,來得到最終的經(jīng)過位移的紋理坐標(biāo)向量,這次就比傳統(tǒng)的視差映射更精確了滨巴。
有10個樣本磚墻從一個角度看上去就已經(jīng)很好了思灌,但是當(dāng)有一個強(qiáng)前面展示的木制表面一樣陡峭的表面時,陡峭的視差映射的威力就顯示出來了:
我們可以通過對視差貼圖的一個屬性的利用恭取,對算法進(jìn)行一點提升习瑰。當(dāng)垂直看一個表面的時候紋理時位移比以一定角度看時的小。我們可以在垂直看時使用更少的樣本秽荤,以一定角度看時增加樣本數(shù)量:
const float minLayers = 8;
const float maxLayers = 32;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
這里我們得到viewDir
和正z
方向的點乘甜奄,使用它的結(jié)果根據(jù)我們看向表面的角度調(diào)整樣本數(shù)量(注意正z方向等于切線空間中的表面的法線)。如果我們所看的方向平行于表面窃款,我們就是用32層课兄。
你可以在這里找到最新的像素著色器代碼。這里也提供木制玩具箱的表面貼圖:diffuse晨继、法線烟阐、深度。
陡峭視差貼圖同樣有自己的問題。因為這個技術(shù)是基于有限的樣本數(shù)量的蜒茄,我們會遇到鋸齒效果以及圖層之間有明顯的斷層:
我們可以通過增加樣本的方式減少這個問題唉擂,但是很快就會花費很多性能。有些旨在修復(fù)這個問題的方法:不適用低于表面的第一個位置檀葛,而是在兩個接近的深度層進(jìn)行插值找出更匹配B的玩祟。
兩種最流行的解決方法叫做Relief Parallax Mapping
和Parallax Occlusion Mapping
,Relief Parallax Mapping
更精確一些屿聋,但是比Parallax Occlusion Mapping
性能開銷更多空扎。因為Parallax Occlusion Mapping
的效果和前者差不多但是效率更高,因此這種方式更經(jīng)常使用润讥,所以我們將在下面討論一下转锈。
視差遮蔽映射
視差遮蔽映射(Parallax Occlusion Mapping)
和陡峭視差映射的原則相同,但不是用觸碰的第一個深度層的紋理坐標(biāo)楚殿,而是在觸碰之前和之后撮慨,在深度層之間進(jìn)行線性插值。我們根據(jù)表面的高度距離啷個深度層的深度層值的距離來確定線性插值的大小脆粥∑瞿纾看看下面的圖片就能了解它是如何工作的:
你可以看到大部分和陡峭視差映射一樣,不一樣的地方是有個額外的步驟冠绢,兩個深度層的紋理坐標(biāo)圍繞著交叉點的線性插值。這也是近似的常潮,但是比陡峭視差映射更精確弟胀。
視差遮蔽映射的代碼基于陡峭視差映射,所以并不難:
[...] // steep parallax mapping code here
// get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
// get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
// interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
在對(位移的)表面幾何進(jìn)行交叉喊式,找到深度層之后孵户,我們獲取交叉前的紋理坐標(biāo)。然后我們計算來自相應(yīng)深度層的幾何之間的深度之間的距離岔留,并在兩個值之間進(jìn)行插值夏哭。線性插值的方式是在兩個層的紋理坐標(biāo)之間進(jìn)行的基礎(chǔ)插值。函數(shù)最后返回最終的經(jīng)過插值的紋理坐標(biāo)献联。
視差遮蔽映射的效果非常好竖配,盡管有一些可以看到的輕微的不真實和鋸齒的問題,這仍是一個好交易里逆,因為除非是放得非常大或者觀察角度特別陡进胯,否則也看不到。
// Std. Includes
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.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 RenderQuad();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
GLboolean parallax_mapping = true;
GLfloat height_scale = 0.1;
// 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("parallax_mapping.vs", "parallax_mapping.frag");
// Load textures
GLuint diffuseMap = loadTexture("../../../resources/textures/bricks2.jpg");
GLuint normalMap = loadTexture("../../../resources/textures/bricks2_normal.jpg");
GLuint heightMap = loadTexture("../../../resources/textures/bricks2_disp.jpg");
/* GLuint diffuseMap = loadTexture("../../../resources/textures/toy_box_diffuse.png");
GLuint normalMap = loadTexture("../../../resources/textures/toy_box_normal.png");
GLuint heightMap = loadTexture("../../../resources/textures/toy_box_disp.png");*/
// Set texture units
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "diffuseMap"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "normalMap"), 1);
glUniform1i(glGetUniformLocation(shader.Program, "depthMap"), 2);
// Light position
glm::vec3 lightPos(0.5f, 1.0f, 0.3f);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// Clear the colorbuffer
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Configure view/projection matrices
shader.Use();
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)SCR_WIDTH / (GLfloat)SCR_HEIGHT, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// Render normal-mapped quad
glm::mat4 model;
model = glm::rotate(model, (GLfloat)glfwGetTime() * -10, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // Rotates the quad to show parallax mapping works in all directions
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
glUniform1f(glGetUniformLocation(shader.Program, "height_scale"), height_scale);
glUniform1i(glGetUniformLocation(shader.Program, "parallax"), parallax_mapping);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normalMap);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, heightMap);
RenderQuad();
// render light source (simply renders a smaller plane at the light's position for debugging/visualization)
model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.1f));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
RenderQuad();
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
// RenderQuad() Renders a 1x1 quad in NDC
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
// positions
glm::vec3 pos1(-1.0, 1.0, 0.0);
glm::vec3 pos2(-1.0, -1.0, 0.0);
glm::vec3 pos3(1.0, -1.0, 0.0);
glm::vec3 pos4(1.0, 1.0, 0.0);
// texture coordinates
glm::vec2 uv1(0.0, 1.0);
glm::vec2 uv2(0.0, 0.0);
glm::vec2 uv3(1.0, 0.0);
glm::vec2 uv4(1.0, 1.0);
// normal vector
glm::vec3 nm(0.0, 0.0, 1.0);
// calculate tangent/bitangent vectors of both triangles
glm::vec3 tangent1, bitangent1;
glm::vec3 tangent2, bitangent2;
// - triangle 1
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;
GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent1 = glm::normalize(tangent1);
bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent1 = glm::normalize(bitangent1);
// - triangle 2
edge1 = pos3 - pos1;
edge2 = pos4 - pos1;
deltaUV1 = uv3 - uv1;
deltaUV2 = uv4 - uv1;
f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent2 = glm::normalize(tangent2);
bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent2 = glm::normalize(bitangent2);
GLfloat quadVertices[] = {
// Positions // normal // TexCoords // Tangent // Bitangent
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
};
// 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, 14 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
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;
}
#pragma region "User input"
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);
// Change parallax height scale
if (keys[GLFW_KEY_Q])
height_scale -= 0.001;
else if (keys[GLFW_KEY_E])
height_scale += 0.001;
// Enable/disable parallax mapping
if (keys[GLFW_KEY_SPACE] && !keysPressed[GLFW_KEY_SPACE])
{
parallax_mapping = !parallax_mapping;
keysPressed[GLFW_KEY_SPACE] = 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;
}
}
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Moves/alters the camera positions based on user input
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);
}
#pragma endregion
視差貼圖是提升場景細(xì)節(jié)非常好的技術(shù),但是使用的時候還是要考慮到它會帶來一點不自然。大多數(shù)時候視差貼圖用在地面和墻壁表面盯漂,這種情況下查明表面的輪廓并不容易颇玷,同時觀察角度往往趨向于垂直于表面。這樣視差貼圖的不自然也就很難能被注意到了就缆,對于提升物體的細(xì)節(jié)可以祈禱難以置信的效果帖渠。
附加資源
- Parallax Occlusion Mapping in GLSL:sunandblackcat.com上的視差貼圖教程。
- How Parallax Displacement Mapping Works:TheBennyBox的關(guān)于視差貼圖原理的視頻教程违崇。
后記
本篇已結(jié)束阿弃,下一篇是關(guān)于HDR。