日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

Learn OpenGL (九):基础光照

發(fā)布時(shí)間:2023/11/27 生活经验 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Learn OpenGL (九):基础光照 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  • 環(huán)境光照(Ambient Lighting):即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠(yuǎn)處的光),所以物體幾乎永遠(yuǎn)不會(huì)是完全黑暗的。為了模擬這個(gè),我們會(huì)使用一個(gè)環(huán)境光照常量,它永遠(yuǎn)會(huì)給物體一些顏色。
  • 漫反射光照(Diffuse Lighting):模擬光源對(duì)物體的方向性影響(Directional Impact)。它是馮氏光照模型中視覺上最顯著的分量。物體的某一部分越是正對(duì)著光源,它就會(huì)越亮。
  • 鏡面光照(Specular Lighting):模擬有光澤物體上面出現(xiàn)的亮點(diǎn)。鏡面光照的顏色相比于物體的顏色會(huì)更傾向于光的顏色。
  • 環(huán)境光照

    光通常都不是來自于同一個(gè)光源,而是來自于我們周圍分散的很多光源,即使它們可能并不是那么顯而易見。光的一個(gè)屬性是,它可以向很多方向發(fā)散并反彈,從而能夠到達(dá)不是非常直接臨近的點(diǎn)。所以,光能夠在其它的表面上反射,對(duì)一個(gè)物體產(chǎn)生間接的影響。考慮到這種情況的算法叫做全局照明(Global Illumination)算法,但是這種算法既開銷高昂又極其復(fù)雜。

    由于我們現(xiàn)在對(duì)那種又復(fù)雜又開銷高昂的算法不是很感興趣,所以我們將會(huì)先使用一個(gè)簡(jiǎn)化的全局照明模型,即環(huán)境光照。正如你在上一節(jié)所學(xué)到的,我們使用一個(gè)很小的常量(光照)顏色,添加到物體片段的最終顏色中,這樣子的話即便場(chǎng)景中沒有直接的光源也能看起來存在有一些發(fā)散的光。

    把環(huán)境光照添加到場(chǎng)景里非常簡(jiǎn)單。我們用光的顏色乘以一個(gè)很小的常量環(huán)境因子,再乘以物體的顏色,然后將最終結(jié)果作為片段的顏色:

    void main()
    {float ambientStrength = 0.1;vec3 ambient = ambientStrength * lightColor;vec3 result = ambient * objectColor;FragColor = vec4(result, 1.0);
    }
    

    如果你現(xiàn)在運(yùn)行你的程序,你會(huì)注意到馮氏光照的第一個(gè)階段已經(jīng)應(yīng)用到你的物體上了。這個(gè)物體非常暗,但由于應(yīng)用了環(huán)境光照(注意光源立方體沒受影響是因?yàn)槲覀儗?duì)它使用了另一個(gè)著色器),也不是完全黑的。它看起來應(yīng)該像這樣:

    漫反射光照

    環(huán)境光照本身不能提供最有趣的結(jié)果,但是漫反射光照就能開始對(duì)物體產(chǎn)生顯著的視覺影響了。漫反射光照使物體上與光線方向越接近的片段能從光源處獲得更多的亮度。為了能夠更好的理解漫反射光照,請(qǐng)看下圖:

    圖左上方有一個(gè)光源,它所發(fā)出的光線落在物體的一個(gè)片段上。我們需要測(cè)量這個(gè)光線是以什么角度接觸到這個(gè)片段的。如果光線垂直于物體表面,這束光對(duì)物體的影響會(huì)最大化(譯注:更亮)。為了測(cè)量光線和片段的角度,我們使用一個(gè)叫做法向量(Normal Vector)的東西,它是垂直于片段表面的一個(gè)向量(這里以黃色箭頭表示),我們?cè)诤竺嬖僦v這個(gè)東西。這兩個(gè)向量之間的角度很容易就能夠通過點(diǎn)乘計(jì)算出來。

    你可能記得在變換那一節(jié)教程里,我們知道兩個(gè)單位向量的夾角越小,它們點(diǎn)乘的結(jié)果越傾向于1。當(dāng)兩個(gè)向量的夾角為90度的時(shí)候,點(diǎn)乘會(huì)變?yōu)?。這同樣適用于θθ,θθ越大,光對(duì)片段顏色的影響就應(yīng)該越小。

    注意,為了(只)得到兩個(gè)向量夾角的余弦值,我們使用的是單位向量(長(zhǎng)度為1的向量),所以我們需要確保所有的向量都是標(biāo)準(zhǔn)化的,否則點(diǎn)乘返回的就不僅僅是余弦值了(見變換)。

    點(diǎn)乘返回一個(gè)標(biāo)量,我們可以用它計(jì)算光線對(duì)片段顏色的影響。不同片段朝向光源的方向的不同,這些片段被照亮的情況也不同。

    所以,計(jì)算漫反射光照需要什么?

  • 法向量:一個(gè)垂直于頂點(diǎn)表面的向量。
  • 定向的光線:作為光源的位置與片段的位置之間向量差的方向向量。為了計(jì)算這個(gè)光線,我們需要光的位置向量和片段的位置向量。
  • 法向量

    法向量是一個(gè)垂直于頂點(diǎn)表面的(單位)向量。由于頂點(diǎn)本身并沒有表面(它只是空間中一個(gè)獨(dú)立的點(diǎn)),我們利用它周圍的頂點(diǎn)來計(jì)算出這個(gè)頂點(diǎn)的表面。我們能夠使用一個(gè)小技巧,使用叉乘對(duì)立方體所有的頂點(diǎn)計(jì)算法向量,但是由于3D立方體不是一個(gè)復(fù)雜的形狀,所以我們可以簡(jiǎn)單地把法線數(shù)據(jù)手工添加到頂點(diǎn)數(shù)據(jù)中。更新后的頂點(diǎn)數(shù)據(jù)數(shù)組可以在這里找到。試著去想象一下,這些法向量真的是垂直于立方體各個(gè)平面的表面的(一個(gè)立方體由6個(gè)平面組成)。

    由于我們向頂點(diǎn)數(shù)組添加了額外的數(shù)據(jù),所以我們應(yīng)該更新光照的頂點(diǎn)著色器:

    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aNormal;
    ...
    

    現(xiàn)在我們已經(jīng)向每個(gè)頂點(diǎn)添加了一個(gè)法向量并更新了頂點(diǎn)著色器,我們還要更新頂點(diǎn)屬性指針。注意,燈使用同樣的頂點(diǎn)數(shù)組作為它的頂點(diǎn)數(shù)據(jù),然而燈的著色器并沒有使用新添加的法向量。我們不需要更新燈的著色器或者是屬性的配置,但是我們必須至少修改一下頂點(diǎn)屬性指針來適應(yīng)新的頂點(diǎn)數(shù)組的大小:

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    

    我們只想使用每個(gè)頂點(diǎn)的前三個(gè)float,并且忽略后三個(gè)float,所以我們只需要把步長(zhǎng)參數(shù)改成float大小的6倍就行了。

    雖然對(duì)燈的著色器使用不能完全利用的頂點(diǎn)數(shù)據(jù)看起來不是那么高效,但這些頂點(diǎn)數(shù)據(jù)已經(jīng)從箱子對(duì)象載入后開始就儲(chǔ)存在GPU的內(nèi)存里了,所以我們并不需要儲(chǔ)存新數(shù)據(jù)到GPU內(nèi)存中。這實(shí)際上比給燈專門分配一個(gè)新的VBO更高效了。

    所有光照的計(jì)算都是在片段著色器里進(jìn)行,所以我們需要將法向量由頂點(diǎn)著色器傳遞到片段著色器。我們這么做:

    out vec3 Normal;void main()
    {gl_Position = projection * view * model * vec4(aPos, 1.0);Normal = aNormal;
    }
    

    接下來,在片段著色器中定義相應(yīng)的輸入變量:

    in vec3 Normal;
    

    計(jì)算漫反射光照

    我們現(xiàn)在對(duì)每個(gè)頂點(diǎn)都有了法向量,但是我們?nèi)匀恍枰庠吹奈恢孟蛄亢推蔚奈恢孟蛄俊S捎诠庠吹奈恢檬且粋€(gè)靜態(tài)變量,我們可以簡(jiǎn)單地在片段著色器中把它聲明為uniform:

    uniform vec3 lightPos;
    

    然后在渲染循環(huán)中(渲染循環(huán)的外面也可以,因?yàn)樗粫?huì)改變)更新uniform。我們使用在前面聲明的lightPos向量作為光源位置:

    lightingShader.setVec3("lightPos", lightPos);
    

    最后,我們還需要片段的位置。我們會(huì)在世界空間中進(jìn)行所有的光照計(jì)算,因此我們需要一個(gè)在世界空間中的頂點(diǎn)位置。我們可以通過把頂點(diǎn)位置屬性乘以模型矩陣(不是觀察和投影矩陣)來把它變換到世界空間坐標(biāo)。這個(gè)在頂點(diǎn)著色器中很容易完成,所以我們聲明一個(gè)輸出變量,并計(jì)算它的世界空間坐標(biāo):

    out vec3 FragPos;  
    out vec3 Normal;void main()
    {gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));Normal = aNormal;
    }
    

    最后,在片段著色器中添加相應(yīng)的輸入變量。

    in vec3 FragPos;
    

    現(xiàn)在,所有需要的變量都設(shè)置好了,我們可以在片段著色器中添加光照計(jì)算了。

    我們需要做的第一件事是計(jì)算光源和片段位置之間的方向向量。前面提到,光的方向向量是光源位置向量與片段位置向量之間的向量差。你可能記得在變換教程中,我們能夠簡(jiǎn)單地通過讓兩個(gè)向量相減的方式計(jì)算向量差。我們同樣希望確保所有相關(guān)向量最后都轉(zhuǎn)換為單位向量,所以我們把法線和最終的方向向量都進(jìn)行標(biāo)準(zhǔn)化:

    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    

    當(dāng)計(jì)算光照時(shí)我們通常不關(guān)心一個(gè)向量的模長(zhǎng)或它的位置,我們只關(guān)心它們的方向。所以,幾乎所有的計(jì)算都使用單位向量完成,因?yàn)檫@簡(jiǎn)化了大部分的計(jì)算(比如點(diǎn)乘)。所以當(dāng)進(jìn)行光照計(jì)算時(shí),確保你總是對(duì)相關(guān)向量進(jìn)行標(biāo)準(zhǔn)化,來保證它們是真正地單位向量。忘記對(duì)向量進(jìn)行標(biāo)準(zhǔn)化是一個(gè)十分常見的錯(cuò)誤。

    下一步,我們對(duì)norm和lightDir向量進(jìn)行點(diǎn)乘,計(jì)算光源對(duì)當(dāng)前片段實(shí)際的漫發(fā)射影響。結(jié)果值再乘以光的顏色,得到漫反射分量。兩個(gè)向量之間的角度越大,漫反射分量就會(huì)越小:

    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    

    如果兩個(gè)向量之間的角度大于90度,點(diǎn)乘的結(jié)果就會(huì)變成負(fù)數(shù),這樣會(huì)導(dǎo)致漫反射分量變?yōu)樨?fù)數(shù)。為此,我們使用max函數(shù)返回兩個(gè)參數(shù)之間較大的參數(shù),從而保證漫反射分量不會(huì)變成負(fù)數(shù)。負(fù)數(shù)顏色的光照是沒有定義的,所以最好避免它,除非你是那種古怪的藝術(shù)家。

    現(xiàn)在我們有了環(huán)境光分量和漫反射分量,我們把它們相加,然后把結(jié)果乘以物體的顏色,來獲得片段最后的輸出顏色。

    vec3 result = (ambient + diffuse) * objectColor;
    FragColor = vec4(result, 1.0);
    

    如果你的應(yīng)用(和著色器)編譯成功了,你可能看到類似的輸出:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <learnopengl/filesystem.h>
#include <learnopengl/shader_m.h>
#include <learnopengl/camera.h>
#include <iostream>// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;// timing
float deltaTime = 0.0f;	
float lastFrame = 0.0f;// lighting
glm::vec3 lightPos(9.2f, 1.0f, 2.0f);// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime);
}// glfw: whenever the window size changed (by OS or user resize) this callback function executes
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height);
}// glfw: whenever the mouse moves, this callback is called
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}// glfw: whenever the mouse scroll wheel scrolls, this callback is called
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{camera.ProcessMouseScroll(yoffset);
}int main()
{// glfw: initialize and configureglfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creationGLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);glfwMakeContextCurrent(window);// tell GLFW to capture our mouseglfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);// glad: load all OpenGL function pointersif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// configure global opengl stateglEnable(GL_DEPTH_TEST);// build and compile our shader zprogramShader lightingShader("2.1.basic_lighting.vs", "2.1.basic_lighting.fs");Shader lampShader("2.1.lamp.vs", "2.1.lamp.fs");// set up vertex data (and buffer(s)) and configure vertex attributesfloat vertices[] = {-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,-0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,-0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,-0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,-0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,-0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,-0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f};// first, configure the cube's VAO (and VBO)unsigned int VBO, cubeVAO;glGenVertexArrays(1, &cubeVAO);glGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glBindVertexArray(cubeVAO);// position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// normal attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube)unsigned int lightVAO;glGenVertexArrays(1, &lightVAO);glBindVertexArray(lightVAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// note that we update the lamp's position attribute's stride to reflect the updated buffer dataglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// render loopwhile (!glfwWindowShouldClose(window)){// per-frame time logicfloat currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;// inputprocessInput(window);// renderglClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); lightingShader.use();lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);lightingShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);lightingShader.setVec3("lightPos", lightPos);// view/projection transformationsglm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);glm::mat4 view = camera.GetViewMatrix();lightingShader.setMat4("projection", projection);lightingShader.setMat4("view", view);// world transformationglm::mat4 model;lightingShader.setMat4("model", model);// render the cubeglBindVertexArray(cubeVAO);glDrawArrays(GL_TRIANGLES, 0, 36);// also draw the lamp objectlampShader.use();lampShader.setMat4("projection", projection);lampShader.setMat4("view", view);model = glm::mat4();model = glm::translate(model, lightPos);model = glm::scale(model, glm::vec3(0.2f)); // a smaller cubelampShader.setMat4("model", model);glBindVertexArray(lightVAO);glDrawArrays(GL_TRIANGLES, 0, 36);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)glfwSwapBuffers(window);glfwPollEvents();	
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);}// optional: de-allocate all resources once they've outlived their purpose:glDeleteVertexArrays(1, &cubeVAO);glDeleteVertexArrays(1, &lightVAO);glDeleteBuffers(1, &VBO);// glfw: terminate, clearing all previously allocated GLFW resources.glfwTerminate();return 0;
}

cube片元著色器:

#version 330 core
out vec4 FragColor;in vec3 Normal;  
in vec3 FragPos;  uniform vec3 lightPos; 
uniform vec3 lightColor;
uniform vec3 objectColor;void main()
{// ambientfloat ambientStrength = 0.1;vec3 ambient = ambientStrength * lightColor;// diffuse vec3 norm = normalize(Normal);vec3 lightDir = normalize(lightPos - FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = diff * lightColor;vec3 result = (ambient + diffuse) * objectColor;FragColor = vec4(result, 1.0);
} 

如何鏡面反射?

現(xiàn)在我們已經(jīng)把法向量從頂點(diǎn)著色器傳到了片段著色器。可是,目前片段著色器里的計(jì)算都是在世界空間坐標(biāo)中進(jìn)行的。所以,我們是不是應(yīng)該把法向量也轉(zhuǎn)換為世界空間坐標(biāo)?基本正確,但是這不是簡(jiǎn)單地把它乘以一個(gè)模型矩陣就能搞定的。

首先,法向量只是一個(gè)方向向量,不能表達(dá)空間中的特定位置。同時(shí),法向量沒有齊次坐標(biāo)(頂點(diǎn)位置中的w分量)。這意味著,位移不應(yīng)該影響到法向量。因此,如果我們打算把法向量乘以一個(gè)模型矩陣,我們就要從矩陣中移除位移部分,只選用模型矩陣左上角3×3的矩陣(注意,我們也可以把法向量的w分量設(shè)置為0,再乘以4×4矩陣;這同樣可以移除位移)。對(duì)于法向量,我們只希望對(duì)它實(shí)施縮放和旋轉(zhuǎn)變換。

其次,如果模型矩陣執(zhí)行了不等比縮放,頂點(diǎn)的改變會(huì)導(dǎo)致法向量不再垂直于表面了。因此,我們不能用這樣的模型矩陣來變換法向量。下面的圖展示了應(yīng)用了不等比縮放的模型矩陣對(duì)法向量的影響:

每當(dāng)我們應(yīng)用一個(gè)不等比縮放時(shí)(注意:等比縮放不會(huì)破壞法線,因?yàn)榉ň€的方向沒被改變,僅僅改變了法線的長(zhǎng)度,而這很容易通過標(biāo)準(zhǔn)化來修復(fù)),法向量就不會(huì)再垂直于對(duì)應(yīng)的表面了,這樣光照就會(huì)被破壞。

修復(fù)這個(gè)行為的訣竅是使用一個(gè)為法向量專門定制的模型矩陣。這個(gè)矩陣稱之為法線矩陣(Normal Matrix),它使用了一些線性代數(shù)的操作來移除對(duì)法向量錯(cuò)誤縮放的影響。如果你想知道這個(gè)矩陣是如何計(jì)算出來的,建議去閱讀這個(gè)文章。

法線矩陣被定義為「模型矩陣左上角的逆矩陣的轉(zhuǎn)置矩陣」。真是拗口,如果你不明白這是什么意思,別擔(dān)心,我們還沒有討論逆矩陣(Inverse Matrix)和轉(zhuǎn)置矩陣(Transpose Matrix)。注意,大部分的資源都會(huì)將法線矩陣定義為應(yīng)用到模型-觀察矩陣(Model-view Matrix)上的操作,但是由于我們只在世界空間中進(jìn)行操作(不是在觀察空間),我們只使用模型矩陣。

在頂點(diǎn)著色器中,我們可以使用inverse和transpose函數(shù)自己生成這個(gè)法線矩陣,這兩個(gè)函數(shù)對(duì)所有類型矩陣都有效。注意我們還要把被處理過的矩陣強(qiáng)制轉(zhuǎn)換為3×3矩陣,來保證它失去了位移屬性以及能夠乘以vec3的法向量。

Normal = mat3(transpose(inverse(model))) * aNormal;

在漫反射光照部分,光照表現(xiàn)并沒有問題,這是因?yàn)槲覀儧]有對(duì)物體本身執(zhí)行任何縮放操作,所以并不是必須要使用一個(gè)法線矩陣,僅僅讓模型矩陣乘以法線也可以。可是,如果你進(jìn)行了不等比縮放,使用法線矩陣去乘以法向量就是必不可少的了。

即使是對(duì)于著色器來說,逆矩陣也是一個(gè)開銷比較大的運(yùn)算,因此,只要可能就應(yīng)該避免在著色器中進(jìn)行逆矩陣運(yùn)算,它們必須為你場(chǎng)景中的每個(gè)頂點(diǎn)都進(jìn)行這樣的處理。用作學(xué)習(xí)目這樣做是可以的,但是對(duì)于一個(gè)對(duì)效率有要求的應(yīng)用來說,在繪制之前你最好用CPU計(jì)算出法線矩陣,然后通過uniform把值傳遞給著色器(像模型矩陣一樣)。

我們通過反射法向量周圍光的方向來計(jì)算反射向量。然后我們計(jì)算反射向量和視線方向的角度差,如果夾角越小,那么鏡面光的影響就會(huì)越大。它的作用效果就是,當(dāng)我們?nèi)タ垂獗晃矬w所反射的那個(gè)方向的時(shí)候,我們會(huì)看到一個(gè)高光。

觀察向量是鏡面光照附加的一個(gè)變量,我們可以使用觀察者世界空間位置和片段的位置來計(jì)算它。之后,我們計(jì)算鏡面光強(qiáng)度,用它乘以光源的顏色,再將它加上環(huán)境光和漫反射分量。

我們選擇在世界空間進(jìn)行光照計(jì)算,但是大多數(shù)人趨向于在觀察空間進(jìn)行光照計(jì)算。在觀察空間計(jì)算的好處是,觀察者的位置總是(0, 0, 0),所以這樣你直接就獲得了觀察者位置。可是我發(fā)現(xiàn)在學(xué)習(xí)的時(shí)候在世界空間中計(jì)算光照更符合直覺。如果你仍然希望在觀察空間計(jì)算光照的話,你需要將所有相關(guān)的向量都用觀察矩陣進(jìn)行變換(記得也要改變法線矩陣)。

為了得到觀察者的世界空間坐標(biāo),我們簡(jiǎn)單地使用攝像機(jī)對(duì)象的位置坐標(biāo)代替(它當(dāng)然就是觀察者)。所以我們把另一個(gè)uniform添加到片段著色器,把相應(yīng)的攝像機(jī)位置坐標(biāo)傳給片段著色器:

uniform vec3 viewPos;
lightingShader.setVec3("viewPos", camera.Position);

現(xiàn)在我們已經(jīng)獲得所有需要的變量,可以計(jì)算高光強(qiáng)度了。首先,我們定義一個(gè)鏡面強(qiáng)度(Specular Intensity)變量,給鏡面高光一個(gè)中等亮度顏色,讓它不要產(chǎn)生過度的影響。

float specularStrength = 0.5;

如果我們把它設(shè)置為1.0f,我們會(huì)得到一個(gè)非常亮的鏡面光分量,這對(duì)于一個(gè)珊瑚色的立方體來說有點(diǎn)太多了。下一節(jié)教程中我們會(huì)討論如何合理設(shè)置這些光照強(qiáng)度,以及它們是如何影響物體的。下一步,我們計(jì)算視線方向向量,和對(duì)應(yīng)的沿著法線軸的反射向量:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

需要注意的是我們對(duì)lightDir向量進(jìn)行了取反。reflect函數(shù)要求第一個(gè)向量是光源指向片段位置的向量,但是lightDir當(dāng)前正好相反,是從片段指向光源(由先前我們計(jì)算lightDir向量時(shí),減法的順序決定)。為了保證我們得到正確的reflect向量,我們通過對(duì)lightDir向量取反來獲得相反的方向。第二個(gè)參數(shù)要求是一個(gè)法向量,所以我們提供的是已標(biāo)準(zhǔn)化的norm向量。

剩下要做的是計(jì)算鏡面分量。下面的代碼完成了這件事:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

我們先計(jì)算視線方向與反射方向的點(diǎn)乘(并確保它不是負(fù)值),然后取它的32次冪。這個(gè)32是高光的反光度(Shininess)。一個(gè)物體的反光度越高,反射光的能力越強(qiáng),散射得越少,高光點(diǎn)就會(huì)越小。在下面的圖片里,你會(huì)看到不同反光度的視覺效果影響:

我們不希望鏡面成分過于顯眼,所以我們把指數(shù)保持為32。剩下的最后一件事情是把它加到環(huán)境光分量和漫反射分量里,再用結(jié)果乘以物體的顏色:

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);

我們現(xiàn)在為馮氏光照計(jì)算了全部的光照分量。根據(jù)你的視角,你可以看到類似下面的畫面:

片元著色器

#version 330 core
out vec4 FragColor;in vec3 Normal;  
in vec3 FragPos;  uniform vec3 lightPos; 
uniform vec3 viewPos; 
uniform vec3 lightColor;
uniform vec3 objectColor;void main()
{// ambientfloat ambientStrength = 0.1;vec3 ambient = ambientStrength * lightColor;// diffuse vec3 norm = normalize(Normal);vec3 lightDir = normalize(lightPos - FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = diff * lightColor;// specularfloat specularStrength = 0.5;vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, norm);  float spec = pow(max(dot(viewDir, reflectDir), 0.0), 512);vec3 specular = specularStrength * spec * lightColor;  vec3 result = (ambient + diffuse + specular) * objectColor;FragColor = vec4(result, 1.0);
} 

?

總結(jié)

以上是生活随笔為你收集整理的Learn OpenGL (九):基础光照的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。