版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.01.18 |
前言
OpenGL 圖形庫項目中一直也沒用過稠腊,最近也想學(xué)著使用這個圖形庫躁染,感覺還是很有意思,也就自然想著好好的總結(jié)一下架忌,希望對大家能有所幫助吞彤。下面內(nèi)容來自歡迎來到OpenGL的世界。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式叹放、對象饰恕、擴展和狀態(tài)機
3. OpenGL 圖形庫使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級OpenGL之高級數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
高級光照
在光照教程中井仰,我們簡單的介紹了Phong光照模型埋嵌,它給我們的場景帶來的基本的現(xiàn)實感。Phong模型看起來還不錯糕档,但本章我們把重點放在一些細(xì)微差別上莉恼。
Blinn-Phong
Phong光照很棒拌喉,而且性能較高速那,但是它的鏡面反射在某些條件下會失效,特別是當(dāng)發(fā)光值屬性低的時候尿背,對應(yīng)一個非常大的粗糙的鏡面區(qū)域端仰。下面的圖片展示了,當(dāng)我們使用鏡面的發(fā)光值為1.0時田藐,一個帶紋理地板的效果:
你可以看到荔烧,鏡面區(qū)域邊緣迅速減弱并截止。出現(xiàn)這個問題的原因是在視線向量和反射向量的角度不允許大于90度汽久。如果大于90度的話鹤竭,點乘的結(jié)果就會是負(fù)數(shù),鏡面的貢獻(xiàn)成分就會變成0景醇。你可能會想臀稚,這不是一個問題,因為大于90度時我們不應(yīng)看到任何光三痰,對吧吧寺?
錯了窜管,這只適用于漫散射部分,當(dāng)法線和光源之間的角度大于90度時意味著光源在被照亮表面的下方稚机,這樣光的散射成分就會是0.0幕帆。然而,對于鏡面光照赖条,我們不會測量光源和法線之間的角度失乾,而是測量視線和反射方向向量之間的∥痴В看看下面的兩幅圖:
現(xiàn)在看來問題就很明顯了仗扬。左側(cè)圖片顯示Phong反射的θ小于90度的情況。我們可以看到右側(cè)圖片視線和反射之間的角θ大于90度蕾额,這樣鏡面反射成分將會被消除早芭。通常這也不是問題,因為視線方向距離反射方向很遠(yuǎn)诅蝶,但如果我們使用一個數(shù)值較低的發(fā)光值參數(shù)的話退个,鏡面半徑就會足夠大,以至于能夠貢獻(xiàn)一些鏡面反射的成份了调炬。在例子中语盈,我們在角度大于90度時消除了這個貢獻(xiàn)(如第一個圖片所示)。
1977年James F. Blinn引入了Blinn-Phong
著色缰泡,它擴展了我們目前所使用的Phong著色刀荒。Blinn-Phong模型很大程度上和Phong是相似的,不過它稍微改進(jìn)了Phong模型棘钞,使之能夠克服我們所討論到的問題缠借。它放棄使用反射向量,而是基于我們現(xiàn)在所說的一個叫做半程向量(halfway vector)
的向量宜猜,這是個單位向量泼返,它在視線方向和光線方向的中間。半程向量和表面法線向量越接近姨拥,鏡面反射成份就越大绅喉。
當(dāng)視線方向恰好與(想象中的)反射向量對齊時,半程向量就與法線向量重合叫乌。這樣觀察者的視線越接近原本的反射方向柴罐,鏡面反射的高光就會越強。
這里憨奸,你可以看到無論觀察者往哪里看革屠,半程向量和表面法線之間的夾角永遠(yuǎn)都不會超過90度(當(dāng)然除了光源遠(yuǎn)遠(yuǎn)低于表面的情況)。這樣會產(chǎn)生和Phong反射稍稍不同的結(jié)果,但這時看起來會更加可信屠阻,特別是發(fā)光值參數(shù)比較低的時候红省。Blinn-Phong著色模型也正是早期OpenGL固定函數(shù)輸送管道(fixed function pipeline)
所使用的著色模型。
得到半程向量很容易国觉,我們將光的方向向量和視線向量相加吧恃,然后將結(jié)果歸一化(normalize)
;
翻譯成GLSL代碼如下:
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
實際的鏡面反射的計算麻诀,就成為計算表面法線和半程向量的點乘痕寓,并對其結(jié)果進(jìn)行約束(大于或等于0),然后獲取它們之間角度的余弦蝇闭,再添加上發(fā)光值參數(shù):
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;
除了我們剛剛討論的呻率,Blinn-Phong沒有更多的內(nèi)容了。Blinn-Phong和Phong的鏡面反射唯一不同之處在于呻引,現(xiàn)在我們要測量法線和半程向量之間的角度礼仗,而半程向量是視線方向和反射向量之間的夾角。
Blinn-Phong著色的一個附加好處是逻悠,它比Phong著色性能更高元践,因為我們不必計算更加復(fù)雜的反射向量了。
引入了半程向量來計算鏡面反射后童谒,我們再也不會遇到Phong著色的驟然截止問題了单旁。下圖展示了兩種不同方式下發(fā)光值指數(shù)為0.5時鏡面區(qū)域的不同效果:
Phong和Blinn-Phong著色之間另一個細(xì)微差別是,半程向量和表面法線之間的角度經(jīng)常會比視線和反射向量之間的夾角更小饥伊。結(jié)果就是象浑,為了獲得和Phong著色相似的效果,必須把發(fā)光值參數(shù)設(shè)置的大一點琅豆。通常的經(jīng)驗是將其設(shè)置為Phong著色的發(fā)光值參數(shù)的2至4倍愉豺。
下圖是Phong指數(shù)為8.0和Blinn-Phong指數(shù)為32的時候,兩種specular反射模型的對比:
你可以看到Blinn-Phong的鏡面反射成分要比Phong銳利一些趋距。這通常需要使用一點小技巧才能獲得之前你所看到的Phong著色的效果粒氧,但Blinn-Phong著色的效果比默認(rèn)的Phong著色通常更加真實一些。
這里我們用到了一個簡單像素著色器节腐,它可以在普通Phong反射和Blinn-Phong反射之間進(jìn)行切換:
void main()
{
[...]
float spec = 0.0;
if(blinn)
{
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0), 16.0);
}
else
{
vec3 reflectDir = reflect(-lightDir, normal);
spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0);
}
你可以在這里找到這個簡單的demo的源碼以及頂點和片段著色器。按下b鍵摘盆,這個demo就會從Phong切換到Blinn-Phong光照翼雀,反之亦然。
// 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>
// 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);
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// Delta
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// Options
GLboolean blinn = false;
// 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);
// glDepthFunc(GL_ALWAYS); // Set to always pass the depth test (same effect as glDisable(GL_DEPTH_TEST))
// Setup and compile our shaders
Shader shader("advanced_lighting.vs", "advanced_lighting.frag");
GLfloat planeVertices[] = {
// Positions // Normals // Texture Coords
8.0f, -0.5f, 8.0f, 0.0f, 1.0f, 0.0f, 5.0f, 0.0f,
-8.0f, -0.5f, 8.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-8.0f, -0.5f, -8.0f, 0.0f, 1.0f, 0.0f, 0.0f, 5.0f,
8.0f, -0.5f, 8.0f, 0.0f, 1.0f, 0.0f, 5.0f, 0.0f,
-8.0f, -0.5f, -8.0f, 0.0f, 1.0f, 0.0f, 0.0f, 5.0f,
8.0f, -0.5f, -8.0f, 0.0f, 1.0f, 0.0f, 5.0f, 5.0f
};
// Setup plane VAO
GLuint planeVAO, 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(0.0f, 0.0f, 0.0f);
// Load textures
GLuint floorTexture = loadTexture("../../../resources/textures/wood.png");
// 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);
// Draw objects
shader.Use();
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)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));
// Set light uniforms
glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
glUniform1i(glGetUniformLocation(shader.Program, "blinn"), blinn);
// Floor
glBindVertexArray(planeVAO);
glBindTexture(GL_TEXTURE_2D, floorTexture);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
std::cout << (blinn ? "true" : "false") << std::endl;
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 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_B] && !keysPressed[GLFW_KEY_B])
{
blinn = !blinn;
keysPressed[GLFW_KEY_B] = 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);
}
后記
本篇已經(jīng)結(jié)束孩擂,下一篇是Gamma校正狼渊。