OpenGL 图形库的使用(三十二)—— 高级光照之高级光照Advanced Lighting

版本记录

版本号 时间
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校正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容