版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2017.12.29 |
前言
OpenGL ES圖形庫(kù)項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫(kù),感覺還是很有意思,也就自然想著好好的總結(jié)一下,希望對(duì)大家能有所幫助。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
多光源
我們?cè)谇懊娴慕坛讨幸呀?jīng)學(xué)習(xí)了許多關(guān)于OpenGL中光照的知識(shí),其中包括馮氏著色(Phong Shading)
、材質(zhì)(Material)
、光照貼圖(Lighting Map)
以及不同種類的投光物(Light Caster)
。在這一節(jié)中,我們將結(jié)合之前學(xué)過的所有知識(shí),創(chuàng)建一個(gè)包含六個(gè)光源的場(chǎng)景。我們將模擬一個(gè)類似太陽的定向光(Directional Light)光源
,四個(gè)分散在場(chǎng)景中的點(diǎn)光源(Point Light)
,以及一個(gè)手電筒(Flashlight)
。
為了在場(chǎng)景中使用多個(gè)光源,我們希望將光照計(jì)算封裝到GLSL函數(shù)中。這樣做的原因是,每一種光源都需要一種不同的計(jì)算方法,而一旦我們想對(duì)多個(gè)光源進(jìn)行光照計(jì)算時(shí),代碼很快就會(huì)變得非常復(fù)雜。如果我們只在main函數(shù)中進(jìn)行所有的這些計(jì)算,代碼很快就會(huì)變得難以理解。
GLSL中的函數(shù)和C函數(shù)很相似,它有一個(gè)函數(shù)名、一個(gè)返回值類型,如果函數(shù)不是在main函數(shù)之前聲明的,我們還必須在代碼文件頂部聲明一個(gè)原型。我們對(duì)每個(gè)光照類型都創(chuàng)建一個(gè)不同的函數(shù):定向光、點(diǎn)光源和聚光。
當(dāng)我們?cè)趫?chǎng)景中使用多個(gè)光源時(shí),通常使用以下方法:我們需要有一個(gè)單獨(dú)的顏色向量代表片段的輸出顏色。對(duì)于每一個(gè)光源,它對(duì)片段的貢獻(xiàn)顏色將會(huì)加到片段的輸出顏色向量上。所以場(chǎng)景中的每個(gè)光源都會(huì)計(jì)算它們各自對(duì)片段的影響,并結(jié)合為一個(gè)最終的輸出顏色。大體的結(jié)構(gòu)會(huì)像是這樣:
out vec4 FragColor;
void main()
{
// 定義一個(gè)輸出顏色值
vec3 output;
// 將定向光的貢獻(xiàn)加到輸出中
output += someFunctionToCalculateDirectionalLight();
// 對(duì)所有的點(diǎn)光源也做相同的事情
for(int i = 0; i < nr_of_point_lights; i++)
output += someFunctionToCalculatePointLight();
// 也加上其它的光源(比如聚光)
output += someFunctionToCalculateSpotLight();
FragColor = vec4(output, 1.0);
}
實(shí)際的代碼對(duì)每一種實(shí)現(xiàn)都可能不同,但大體的結(jié)構(gòu)都是差不多的。我們定義了幾個(gè)函數(shù),用來計(jì)算每個(gè)光源的影響,并將最終的結(jié)果顏色加到輸出顏色向量上。例如,如果兩個(gè)光源都很靠近一個(gè)片段,那么它們所結(jié)合的貢獻(xiàn)將會(huì)形成一個(gè)比單個(gè)光源照亮?xí)r更加明亮的片段。
定向光
我么需要在片段著色器中定義一個(gè)函數(shù)來計(jì)算定向光對(duì)相應(yīng)片段的貢獻(xiàn):它接受一些參數(shù)并計(jì)算一個(gè)定向光照顏色。
首先,我們需要定義一個(gè)定向光源最少所需要的變量。我們可以將這些變量?jī)?chǔ)存在一個(gè)叫做DirLight
的結(jié)構(gòu)體中,并將它定義為一個(gè)uniform
。需要的變量在上一節(jié)中都介紹過:
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
接下來我們可以將dirLight
傳入一個(gè)有著一下原型的函數(shù)。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
和C/C++一樣,如果我們想調(diào)用一個(gè)函數(shù)(這里是在main函數(shù)中調(diào)用),這個(gè)函數(shù)需要在調(diào)用者的行數(shù)之前被定義過。在這個(gè)例子中我們更喜歡在main函數(shù)以下定義函數(shù),所以上面要求就不滿足了。所以,我們需要在main函數(shù)之上定義函數(shù)的原型,這和C語言中是一樣的。
你可以看到,這個(gè)函數(shù)需要一個(gè)DirLight
結(jié)構(gòu)體和其它兩個(gè)向量來進(jìn)行計(jì)算。如果你認(rèn)真完成了上一節(jié)的話,這個(gè)函數(shù)的內(nèi)容應(yīng)該理解起來很容易:
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// 漫反射著色
float diff = max(dot(normal, lightDir), 0.0);
// 鏡面光著色
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// 合并結(jié)果
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
我們基本上只是從上一節(jié)中復(fù)制了代碼,并使用函數(shù)參數(shù)的兩個(gè)向量來計(jì)算定向光的貢獻(xiàn)向量。最終環(huán)境光、漫反射和鏡面光的貢獻(xiàn)將會(huì)合并為單個(gè)顏色向量返回。
點(diǎn)光源
和定向光一樣,我們也希望定義一個(gè)用于計(jì)算點(diǎn)光源對(duì)相應(yīng)片段貢獻(xiàn),以及衰減的函數(shù)。同樣,我們定義一個(gè)包含了點(diǎn)光源所需所有變量的結(jié)構(gòu)體:
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
你可以看到,我們?cè)贕LSL中使用了預(yù)處理指令來定義了我們場(chǎng)景中點(diǎn)光源的數(shù)量。接著我們使用了這個(gè)NR_POINT_LIGHTS
常量來創(chuàng)建了一個(gè)PointLight
結(jié)構(gòu)體的數(shù)組。GLSL中的數(shù)組和C數(shù)組一樣,可以使用一對(duì)方括號(hào)來創(chuàng)建。現(xiàn)在我們有四個(gè)待填充數(shù)據(jù)的PointLight結(jié)構(gòu)體。
我們也可以定義一個(gè)大的結(jié)構(gòu)體(而不是為每種類型的光源定義不同的結(jié)構(gòu)體),包含所有不同種光照類型所需的變量,并將這個(gè)結(jié)構(gòu)體用到所有的函數(shù)中,只需要忽略用不到的變量就行了。然而,我個(gè)人覺得當(dāng)前的方法會(huì)更直觀一點(diǎn),不僅能夠節(jié)省一些代碼,而且由于不是所有光照類型都需要所有的變量,這樣也能節(jié)省一些內(nèi)存。
點(diǎn)光源函數(shù)的原型如下:
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
這個(gè)函數(shù)從參數(shù)中獲取所需的所有數(shù)據(jù),并返回一個(gè)代表該點(diǎn)光源對(duì)片段的顏色貢獻(xiàn)的vec3
。我們?cè)僖淮温斆鞯貜闹暗慕坛讨袕?fù)制粘貼代碼,完成了下面這樣的函數(shù):
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射著色
float diff = max(dot(normal, lightDir), 0.0);
// 鏡面光著色
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// 衰減
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
// 合并結(jié)果
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
將這些功能抽象到這樣一個(gè)函數(shù)中的優(yōu)點(diǎn)是,我們能夠不用重復(fù)的代碼而很容易地計(jì)算多個(gè)點(diǎn)光源的光照了。在main函數(shù)中,我們只需要?jiǎng)?chuàng)建一個(gè)循環(huán),遍歷整個(gè)點(diǎn)光源數(shù)組,對(duì)每個(gè)點(diǎn)光源調(diào)用CalcPointLight
就可以了。
合并結(jié)果
現(xiàn)在我們已經(jīng)定義了一個(gè)計(jì)算定向光的函數(shù)和一個(gè)計(jì)算點(diǎn)光源的函數(shù)了,我們可以將它們合并放到main函數(shù)中。
void main()
{
// 屬性
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// 第一階段:定向光照
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// 第二階段:點(diǎn)光源
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
// 第三階段:聚光
//result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
每個(gè)光源類型都將它們的貢獻(xiàn)加到了最終的輸出顏色上,直到所有的光源都處理完了。最終的顏色包含了場(chǎng)景中所有光源的顏色影響所合并的結(jié)果。如果你想的話,你也可以實(shí)現(xiàn)一個(gè)聚光,并將它的效果加到輸出顏色中。我們會(huì)將CalcSpotLight函數(shù)留給讀者作為練習(xí)。
設(shè)置定向光結(jié)構(gòu)體的uniform
應(yīng)該非常熟悉了,但是你可能會(huì)在想我們?cè)撊绾卧O(shè)置點(diǎn)光源的uniform值,因?yàn)辄c(diǎn)光源的uniform現(xiàn)在是一個(gè)PointLight的數(shù)組了。這并不是我們以前討論過的話題。
很幸運(yùn)的是,這并不是很復(fù)雜,設(shè)置一個(gè)結(jié)構(gòu)體數(shù)組的uniform和設(shè)置一個(gè)結(jié)構(gòu)體的uniform是很相似的,但是這一次在訪問uniform位置的時(shí)候,我們需要定義對(duì)應(yīng)的數(shù)組下標(biāo)值:
lightingShader.setFloat("pointLights[0].constant", 1.0f);
在這里我們索引了pointLights
數(shù)組中的第一個(gè)PointLight
,并獲取了constant變量的位置。但這也意味著不幸的是我們必須對(duì)這四個(gè)點(diǎn)光源手動(dòng)設(shè)置uniform值,這讓點(diǎn)光源本身就產(chǎn)生了28個(gè)uniform調(diào)用,非常冗長(zhǎng)。你也可以嘗試將這些抽象出去一點(diǎn),定義一個(gè)點(diǎn)光源類,讓它來為你設(shè)置uniform值,但最后你仍然要用這種方式設(shè)置所有光源的uniform值。
別忘了,我們還需要為每個(gè)點(diǎn)光源定義一個(gè)位置向量,所以我們讓它們?cè)趫?chǎng)景中分散一點(diǎn)。我們會(huì)定義另一個(gè)glm::vec3數(shù)組來包含點(diǎn)光源的位置:
glm::vec3 pointLightPositions[] = {
glm::vec3( 0.7f, 0.2f, 2.0f),
glm::vec3( 2.3f, -3.3f, -4.0f),
glm::vec3(-4.0f, 2.0f, -12.0f),
glm::vec3( 0.0f, 0.0f, -3.0f)
};
接下來我們從pointLights數(shù)組中索引對(duì)應(yīng)的PointLight,將它的position值設(shè)置為剛剛定義的位置值數(shù)組中的其中一個(gè)。同時(shí)我們還要保證現(xiàn)在繪制的是四個(gè)燈立方體而不是僅僅一個(gè)。只要對(duì)每個(gè)燈物體創(chuàng)建一個(gè)不同的模型矩陣就可以了,和我們之前對(duì)箱子的處理類似。
如果你還使用了手電筒的話,所有光源組合的效果將看起來和下圖差不多:
你可以看到,很顯然天空中有一個(gè)全局照明(像一個(gè)太陽),我們有四個(gè)光源分散在場(chǎng)景中,以及玩家視角的手電筒。看起來是不是非常不錯(cuò)?
你可以在這里找到最終程序的源代碼。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <learnopengl/shader_m.h>
#include <learnopengl/camera.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);
// 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(1.2f, 1.0f, 2.0f);
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// tell GLFW to capture our mouse
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// configure global opengl state
// -----------------------------
glEnable(GL_DEPTH_TEST);
// build and compile our shader zprogram
// ------------------------------------
Shader lightingShader("6.multiple_lights.vs", "6.multiple_lights.fs");
Shader lampShader("6.lamp.vs", "6.lamp.fs");
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
// positions all containers
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
// positions of the point lights
glm::vec3 pointLightPositions[] = {
glm::vec3( 0.7f, 0.2f, 2.0f),
glm::vec3( 2.3f, -3.3f, -4.0f),
glm::vec3(-4.0f, 2.0f, -12.0f),
glm::vec3( 0.0f, 0.0f, -3.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);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
// 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 data
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// load textures (we now use a utility function to keep the code more organized)
// -----------------------------------------------------------------------------
unsigned int diffuseMap = loadTexture(FileSystem::getPath("resources/textures/container2.png").c_str());
unsigned int specularMap = loadTexture(FileSystem::getPath("resources/textures/container2_specular.png").c_str());
// shader configuration
// --------------------
lightingShader.use();
lightingShader.setInt("material.diffuse", 0);
lightingShader.setInt("material.specular", 1);
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// per-frame time logic
// --------------------
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// be sure to activate shader when setting uniforms/drawing objects
lightingShader.use();
lightingShader.setVec3("viewPos", camera.Position);
lightingShader.setFloat("material.shininess", 32.0f);
/*
Here we set all the uniforms for the 5/6 types of lights we have. We have to set them manually and index
the proper PointLight struct in the array to set each uniform variable. This can be done more code-friendly
by defining light types as classes and set their values in there, or by using a more efficient uniform approach
by using 'Uniform buffer objects', but that is something we'll discuss in the 'Advanced GLSL' tutorial.
*/
// directional light
lightingShader.setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f);
lightingShader.setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f);
lightingShader.setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f);
// point light 1
lightingShader.setVec3("pointLights[0].position", pointLightPositions[0]);
lightingShader.setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[0].constant", 1.0f);
lightingShader.setFloat("pointLights[0].linear", 0.09);
lightingShader.setFloat("pointLights[0].quadratic", 0.032);
// point light 2
lightingShader.setVec3("pointLights[1].position", pointLightPositions[1]);
lightingShader.setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[1].constant", 1.0f);
lightingShader.setFloat("pointLights[1].linear", 0.09);
lightingShader.setFloat("pointLights[1].quadratic", 0.032);
// point light 3
lightingShader.setVec3("pointLights[2].position", pointLightPositions[2]);
lightingShader.setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[2].constant", 1.0f);
lightingShader.setFloat("pointLights[2].linear", 0.09);
lightingShader.setFloat("pointLights[2].quadratic", 0.032);
// point light 4
lightingShader.setVec3("pointLights[3].position", pointLightPositions[3]);
lightingShader.setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[3].constant", 1.0f);
lightingShader.setFloat("pointLights[3].linear", 0.09);
lightingShader.setFloat("pointLights[3].quadratic", 0.032);
// spotLight
lightingShader.setVec3("spotLight.position", camera.Position);
lightingShader.setVec3("spotLight.direction", camera.Front);
lightingShader.setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f);
lightingShader.setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("spotLight.constant", 1.0f);
lightingShader.setFloat("spotLight.linear", 0.09);
lightingShader.setFloat("spotLight.quadratic", 0.032);
lightingShader.setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f)));
lightingShader.setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));
// view/projection transformations
glm::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 transformation
glm::mat4 model;
lightingShader.setMat4("model", model);
// bind diffuse map
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
// bind specular map
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
// render containers
glBindVertexArray(cubeVAO);
for (unsigned int i = 0; i < 10; i++)
{
// calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightingShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// also draw the lamp object(s)
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
// we now draw as many light bulbs as we have point lights.
glBindVertexArray(lightVAO);
for (unsigned int i = 0; i < 4; i++)
{
model = glm::mat4();
model = glm::translate(model, pointLightPositions[i]);
model = glm::scale(model, glm::vec3(0.2f)); // Make it a smaller cube
lampShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// 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;
}
// 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 top
lastX = 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);
}
// utility function for loading a 2D texture from file
// ---------------------------------------------------
unsigned int loadTexture(char const * path)
{
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
if (data)
{
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
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);
stbi_image_free(data);
}
else
{
std::cout << "Texture failed to load at path: " << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
上面圖片中的所有光源都是使用上一節(jié)中所使用的默認(rèn)屬性,但如果你愿意實(shí)驗(yàn)這些數(shù)值的話,你能夠得到很多有意思的結(jié)果。藝術(shù)家和關(guān)卡設(shè)計(jì)師通常都在編輯器中不斷的調(diào)整這些光照參數(shù),保證光照與環(huán)境相匹配。在我們剛剛創(chuàng)建的簡(jiǎn)單光照環(huán)境中,你可以簡(jiǎn)單地調(diào)整一下光源的屬性,創(chuàng)建很多有意思的視覺效果:
我們也改變了清屏的顏色來更好地反應(yīng)光照。你可以看到,只需要簡(jiǎn)單地調(diào)整一些光照參數(shù),你就能創(chuàng)建完全不同的氛圍。
相信你現(xiàn)在已經(jīng)對(duì)OpenGL
的光照有很好的理解了。有了目前所學(xué)的這些知識(shí),我們已經(jīng)可以創(chuàng)建出豐富有趣的環(huán)境和氛圍了。嘗試實(shí)驗(yàn)一下不同的值,創(chuàng)建出你自己的氛圍吧。
后記
未完,待續(xù)~~~