版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.01.18 |
前言
OpenGL 圖形庫項目中一直也沒用過,最近也想學著使用這個圖形庫,感覺還是很有意思,也就自然想著好好的總結一下,希望對大家能有所幫助。下面內容來自歡迎來到OpenGL的世界。
1. OpenGL 圖形庫使用(一) —— 概念基礎
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象、擴展和狀態機
3. OpenGL 圖形庫使用(三) —— 著色器、數據類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標系統之五種不同的坐標系統(一)
8. OpenGL 圖形庫的使用(八)—— 坐標系統之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復習總結
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網格
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之高級數據Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
高級光照
在光照教程中,我們簡單的介紹了Phong光照模型,它給我們的場景帶來的基本的現實感。Phong模型看起來還不錯,但本章我們把重點放在一些細微差別上。
Blinn-Phong
Phong光照很棒,而且性能較高,但是它的鏡面反射在某些條件下會失效,特別是當發光值屬性低的時候,對應一個非常大的粗糙的鏡面區域。下面的圖片展示了,當我們使用鏡面的發光值為1.0時,一個帶紋理地板的效果:
你可以看到,鏡面區域邊緣迅速減弱并截止。出現這個問題的原因是在視線向量和反射向量的角度不允許大于90度。如果大于90度的話,點乘的結果就會是負數,鏡面的貢獻成分就會變成0。你可能會想,這不是一個問題,因為大于90度時我們不應看到任何光,對吧?
錯了,這只適用于漫散射部分,當法線和光源之間的角度大于90度時意味著光源在被照亮表面的下方,這樣光的散射成分就會是0.0。然而,對于鏡面光照,我們不會測量光源和法線之間的角度,而是測量視線和反射方向向量之間的。看看下面的兩幅圖:
現在看來問題就很明顯了。左側圖片顯示Phong反射的θ小于90度的情況。我們可以看到右側圖片視線和反射之間的角θ大于90度,這樣鏡面反射成分將會被消除。通常這也不是問題,因為視線方向距離反射方向很遠,但如果我們使用一個數值較低的發光值參數的話,鏡面半徑就會足夠大,以至于能夠貢獻一些鏡面反射的成份了。在例子中,我們在角度大于90度時消除了這個貢獻(如第一個圖片所示)。
1977年James F. Blinn引入了Blinn-Phong
著色,它擴展了我們目前所使用的Phong著色。Blinn-Phong模型很大程度上和Phong是相似的,不過它稍微改進了Phong模型,使之能夠克服我們所討論到的問題。它放棄使用反射向量,而是基于我們現在所說的一個叫做半程向量(halfway vector)
的向量,這是個單位向量,它在視線方向和光線方向的中間。半程向量和表面法線向量越接近,鏡面反射成份就越大。
當視線方向恰好與(想象中的)反射向量對齊時,半程向量就與法線向量重合。這樣觀察者的視線越接近原本的反射方向,鏡面反射的高光就會越強。
這里,你可以看到無論觀察者往哪里看,半程向量和表面法線之間的夾角永遠都不會超過90度(當然除了光源遠遠低于表面的情況)。這樣會產生和Phong反射稍稍不同的結果,但這時看起來會更加可信,特別是發光值參數比較低的時候。Blinn-Phong著色模型也正是早期OpenGL固定函數輸送管道(fixed function pipeline)
所使用的著色模型。
得到半程向量很容易,我們將光的方向向量和視線向量相加,然后將結果歸一化(normalize)
;
翻譯成GLSL代碼如下:
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
實際的鏡面反射的計算,就成為計算表面法線和半程向量的點乘,并對其結果進行約束(大于或等于0),然后獲取它們之間角度的余弦,再添加上發光值參數:
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;
除了我們剛剛討論的,Blinn-Phong沒有更多的內容了。Blinn-Phong和Phong的鏡面反射唯一不同之處在于,現在我們要測量法線和半程向量之間的角度,而半程向量是視線方向和反射向量之間的夾角。
Blinn-Phong著色的一個附加好處是,它比Phong著色性能更高,因為我們不必計算更加復雜的反射向量了。
引入了半程向量來計算鏡面反射后,我們再也不會遇到Phong著色的驟然截止問題了。下圖展示了兩種不同方式下發光值指數為0.5時鏡面區域的不同效果:
Phong和Blinn-Phong著色之間另一個細微差別是,半程向量和表面法線之間的角度經常會比視線和反射向量之間的夾角更小。結果就是,為了獲得和Phong著色相似的效果,必須把發光值參數設置的大一點。通常的經驗是將其設置為Phong著色的發光值參數的2至4倍。
下圖是Phong指數為8.0和Blinn-Phong指數為32的時候,兩種specular反射模型的對比:
你可以看到Blinn-Phong的鏡面反射成分要比Phong銳利一些。這通常需要使用一點小技巧才能獲得之前你所看到的Phong著色的效果,但Blinn-Phong著色的效果比默認的Phong著色通常更加真實一些。
這里我們用到了一個簡單像素著色器,它可以在普通Phong反射和Blinn-Phong反射之間進行切換:
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);
}
后記
本篇已經結束,下一篇是Gamma校正。