現代OpenGL系列教程(三)---基礎光照

【寫在前面】

本章主要內容:

1、基礎光照類型( Ambient,Diffuse,Specular )

2、在GLSL中進行光照計算


【正文開始】

在前面的文章中,我們已經學會了如何在glsl中使用一般的頂點數據,並通過使用紋理讓它更加的真實,但這還遠遠不夠,在現實世界中,我們所看到一個物體的顏色並不是它本來的顏色,而是它不能吸收的顏色,或者說,它所反射出來的顏色。

如下圖所示:

我們所看下方的矩形塊顏色,是它反射了太陽光(白色光)中的紅色(主要顏色,還有一些其他顏色)所綜合起來的顏色。

當給定物體一個顏色 color,它能從光源light中反射出來顏色的計算方法很簡單,進行相乘:result = color * light

例如:color = vec3(1.0f, 0.0f, 0.0f) 紅色,light = vec3(1.0f, 1.0f, 1.0f) 白色,result = vec3(1.0f, 0.0f, 0.0f) 紅色,即:我們看到物體的顏色爲紅色。

事實上,現實世界中的光照是非常複雜的,它需要考慮非常多的因素,所以直接模擬是不現實的,因此,在 OpenGL 中使用的是簡化了的光照模型,這個模型被稱爲馮氏光照模型( Phong Lighting Model )。

馮氏光照模型的主要結構由 3 個分量組成:環境光( Ambient )、漫反射光( Diffuse )和鏡面高光( Specular )。

環境光】即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠處的光),所以物體幾乎永遠不會是完全黑暗的。爲了模擬這個,我們會使用一個環境光照常量,它永遠會給物體一些顏色。

漫反射光】模擬光源對物體的方向性影響。它是馮氏光照模型中視覺上最顯著的分量。物體的某一部分越是正對着光源,它就會越亮,反之,它就會越亮。

鏡面高光】模擬有光澤物體上面出現的亮點。鏡面光照的顏色相比於物體的顏色會更傾向於光的顏色。

那麼,我們要如何在 glsl 中進行計算呢?

1、進行環境光的計算。

因爲場景中存在環境光,我們的物體應該不是完全黑暗的,所以這裏使用一個小的光照常量來進行相乘。

    vec3 ambient = lightColor * vec3(0.2f, 0.2f, 0.2f);

2、進行漫反射光的計算。

要進行漫反射光的計算,我們必須要知道兩個向量:光源的方向向量片元的法線向量

法向量】一個垂直於頂點表面的(單位)向量。由於頂點本身並沒有表面(它只是空間中一個獨立的點)。

實際上,法向量是可以通過相鄰的頂點計算出來的,在高數中可以知道:兩個向量進行叉乘可以得到一個垂直的向量,方向可以由右手法則確定。

void MyRender::initializeTriangle()
{
	glm::vec3 normal1 = glm::cross(glm::vec3(0.75f, -0.75f, 0.75f) - glm::vec3(-0.75f, -0.75f, 0.75f),
		glm::vec3(-0.375f, 0.75f, 0.375f) - glm::vec3(-0.75f, -0.75f, 0.75f));
	glm::vec3 normal2 = glm::cross(glm::vec3(0.75f, -0.75f, -0.75f) - glm::vec3(0.75f, -0.75f, 0.75f),
		glm::vec3(0.375f, 0.75f, 0.375f) - glm::vec3(0.75f, -0.75f, 0.75f));
	glm::vec3 normal3 = glm::cross(glm::vec3(-0.75f, -0.75f, -0.75f) - glm::vec3(0.75f, -0.75f, -0.75f),
		glm::vec3(0.375f, 0.75f, -0.375f) - glm::vec3(0.75f, -0.75f, -0.75f));
	glm::vec3 normal4 = glm::cross(glm::vec3(-0.75f, -0.75f, 0.75f) - glm::vec3(-0.75f, -0.75f, -0.75f),
		glm::vec3(-0.375f, 0.75f, -0.375f) - glm::vec3(-0.75f, -0.75f, -0.75f));
	glm::vec3 normal5 = glm::cross(glm::vec3(0.375f, 0.75f, 0.375f) - glm::vec3(-0.375f, 0.75f, 0.375f),
		glm::vec3(-0.375f, 0.75f, -0.375f) - glm::vec3(-0.375f, 0.75f, 0.375f));
	glm::vec3 normal6 = glm::cross(glm::vec3(-0.75f, -0.75f, 0.75f) - glm::vec3(0.75f, -0.75f, 0.75f),
		glm::vec3(0.75f, -0.75f, -0.75f) - glm::vec3(0.75f, -0.75f, 0.75f));

	VertexData vertices[] =
	{
		{ glm::vec3( -0.75f, -0.75f,  0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal1 },
		{ glm::vec3(  0.75f, -0.75f,  0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal1 },
		{ glm::vec3(-0.375f,  0.75f, 0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal1 },
		{ glm::vec3( 0.375f,  0.75f, 0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal1 },

		{ glm::vec3( 0.75f, -0.75f,   0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal2 },
		{ glm::vec3( 0.75f, -0.75f,  -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal2 },
		{ glm::vec3(0.375f,  0.75f,  0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal2 },
		{ glm::vec3(0.375f,  0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal2 },

		{ glm::vec3(  0.75f, -0.75f,  -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal3 },
		{ glm::vec3( -0.75f, -0.75f,  -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal3 },
		{ glm::vec3( 0.375f,  0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal3 },
		{ glm::vec3(-0.375f,  0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal3 },

		{ glm::vec3( -0.75f, -0.75f,  -0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal4 },
		{ glm::vec3( -0.75f, -0.75f,   0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal4 },
		{ glm::vec3(-0.375f,  0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal4 },
		{ glm::vec3(-0.375f,  0.75f,  0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal4 },

		{ glm::vec3(-0.375f, 0.75f,  0.375f), glm::vec3(0.8f, 0.8f, 0.0f), normal5 },
		{ glm::vec3( 0.375f, 0.75f,  0.375f), glm::vec3(0.8f, 0.8f, 0.0f), normal5 },
		{ glm::vec3(-0.375f, 0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal5 },
		{ glm::vec3( 0.375f, 0.75f, -0.375f), glm::vec3(0.0f, 0.8f, 0.8f), normal5 },

		{ glm::vec3( 0.75f, -0.75f,  0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal6 },
		{ glm::vec3(-0.75f, -0.75f,  0.75f), glm::vec3(0.8f, 0.8f, 0.0f), normal6 },
		{ glm::vec3( 0.75f, -0.75f, -0.75f), glm::vec3(0.0f, 0.8f, 0.8f), normal6 },
		{ glm::vec3(-0.75f, -0.75f, -0.75f), glm::vec3(0.0f, 0.8f, 0.8f), normal6 }
	};

	GLushort indices[] =
	{
		 0,  1,  2,  3,  3,
		 4,  4,  5,  6,  7, 7,
		 8,  8,  9, 10, 11, 11,
		12, 12, 13, 14, 15, 15,
		16, 16, 17, 18, 19, 19,
		20, 20, 21, 22, 23
	};

	glGenBuffers(1, &m_cubeVao);
	glBindVertexArray(m_cubeVao);

	glGenBuffers(1, &m_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	glGenBuffers(1, &m_ebo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	int location = 0;
	glVertexAttribPointer(location, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)0);
	glEnableVertexAttribArray(location);
	glVertexAttribPointer(location + 1, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)(sizeof(glm::vec3)));
	glEnableVertexAttribArray(location + 1);
	glVertexAttribPointer(location + 2, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)(sizeof(glm::vec3) * 2));
	glEnableVertexAttribArray(location + 2);

	glGenBuffers(1, &m_lampVao);
	glBindVertexArray(m_lampVao);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

	glVertexAttribPointer(location, 3, GL_FLOAT, GL_TRUE, sizeof(VertexData), (void *)0);
	glEnableVertexAttribArray(location);
}

因爲立方體有六個面,所以要計算六個面的法線 normal*,這裏還需要注意的是,我使用了兩個 vaom_cubeVao 用於繪製我們的物體,因爲它接受光照,使用的是光照着色器,m_lampVao 則是用於我們的燈光的可視化(這裏不是必要的),它不需要進行光照計算,所以使用一般的着色器。

glm::cross】返回兩個向量進行叉乘的結果。

現在,法向量有了,我們還需要一個光源的方向向量,它是一個 uniform 變量。

//這裏省略一些,具體見源碼
        .
        .   
        .
static glm::vec3 lightColor = glm::vec3(1.0f, 1.0f, 1.0f);
static glm::vec4 lightPosition = glm::vec4(0.0f, 0.0f, 2.0f, 1.0f);
static float angle = 0.0f;
        .
        .
        .
//將光源的進行旋轉
glm::mat4 lightMatrix(1.0f);
lightMatrix = glm::rotate(lightMatrix, 0.04f, glm::vec3(1.0f, 1.0f, 1.0f));
lightPosition = lightMatrix * lightPosition;
        .
        .
        .
//計算法線矩陣
glm::mat3 normalMatrix = glm::transpose(glm::inverse(modelMatrix));
GLuint normalLocation = glGetUniformLocation(m_cubeProgram, "normalMatrix");
glUniformMatrix3fv(normalLocation, 1, GL_FALSE, glm::value_ptr(normalMatrix));
        .
        .
        .
//將光源的顏色和位置傳入着色器中
GLuint lightColorLocation = glGetUniformLocation(m_cubeProgram, "lightColor");
glUniform3fv(lightColorLocation, 1, glm::value_ptr(lightColor));
GLuint lightPositionLocation = glGetUniformLocation(m_cubeProgram, "lightPosition");
glUniform3fv(lightPositionLocation, 1, glm::value_ptr(lightPosition));

片段着色器中: 

in vec4 fragPos;
in vec3 normal;

uniform vec3 viewPosition;
uniform vec3 lightColor;
uniform vec3 lightPosition;

 頂點着色器中:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color0;
layout (location = 2) in vec3 normal0;

out vec3 color;
out vec4 fragPos;	
out vec3 normal;

uniform mat3 normalMatrix;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(position, 1.0f);
	color = color0;
	fragPos = model * vec4(position, 1.0f);
	normal = normalMatrix * normal0;
}

fragPos 即頂點在世界空間中的座標(只左乘了 model 矩陣)。

normal 即法線,這裏左乘了一個法線矩陣

法線矩陣

每當我們應用一個不等比縮放時(注意:等比縮放不會破壞法線,因爲法線的方向沒被改變,僅僅改變了法線的長度,而這很容易通過標準化來修復),法向量就不會再垂直於對應的表面了,這樣光照就會被破壞。

修復這個行爲的訣竅是使用一個爲法向量專門定製的模型矩陣。這個矩陣稱之爲法線矩陣( Normal Matrix ),它使用了一些線性代數的操作來移除對法向量錯誤縮放的影響。

法線矩陣被定義爲「模型矩陣左上角的逆矩陣的轉置矩陣」。

現在開始計算漫反射光:

如圖,光源的方向向量爲 = lightPosition - fragPos

而光源對頂點的影響可以通過他們的夾角反映,他們的餘弦值越大,夾角越大,對於單位向量,餘弦值可以通過點乘得到( glsl 中使用 dot() 函數),使用 max() 函數保證一個大於0的值。

    vec3 lightDir = normalize(lightPosition - vec3(fragPos));
    float diff = max(dot(normalize(normal), lightDir), 0.0f);
    vec3 diffuse = diff * lightColor;

3、進行鏡面高光的計算。

鏡面光照是基於光的反射特性。如果我們想象物體表面像一面鏡子一樣,那麼,無論我們從哪裏去看那個表面所反射的光,鏡面光照都會達到最大化。

如圖所示,鏡面高光還依賴於我們( 觀察者 )的位置,即所謂的攝像機的位置,在上面的着色器中,它是 viewPosition 

前面講過觀察矩陣,它是由三個向量進行計算得到的一個矩陣,這裏我將它簡單的封裝到了一個 Camera 中:

Camera.h:

#ifndef CAMERA_H
#define CAMERA_H
#include <glm/matrix.hpp>

class Camera
{
public:
	enum MoveDirection
	{
		Front = 0,
		Back,
		Left,
		Right
	};

	enum RotateDirection
	{
		Vertical = 0,
		Horizontal
	};

public:
	Camera() { }
	Camera(const glm::vec3 &cameraPos, const glm::vec3 &cameraFront, const glm::vec3 &cameraUp);
	~Camera();

	void setCameraPos(const glm::vec3 &cameraPos) { m_cameraPos = cameraPos; }
	glm::vec3 getCameraPos() const { return m_cameraPos; }
	void setCameraFront(const glm::vec3 &cameraFront) { m_cameraFront = cameraFront; }
	glm::vec3 getCameraFront() const { return m_cameraFront; }
	glm::mat4 getViewMatrix() const;

	//在Front, Back, Left, Right四個方向上移動攝像機, 距離爲step
	void move(MoveDirection direction, float step);
	//在Vertical, Horizontal兩個方向上旋轉攝像機, 角度爲angle
	void rotate(RotateDirection direction, float angle);

private:
	glm::vec3 m_cameraPos = glm::vec3(0.0f, 0.0f, 5.0f);
	glm::vec3 m_cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
	glm::vec3 m_cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
};

#endif

Camera.cpp:

#include "Camera.h"
#include <glm/gtc/matrix_transform.hpp>

Camera::Camera(const glm::vec3 &cameraPos, const glm::vec3 &cameraFront, const glm::vec3 &cameraUp)
	: m_cameraPos(cameraPos),
	  m_cameraFront(cameraFront),
	  m_cameraUp(cameraUp)
{

}

Camera::~Camera()
{

}

glm::mat4 Camera::getViewMatrix() const
{
	return glm::lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_cameraUp);
}

void Camera::move(MoveDirection direction, float step)
{
	switch (direction)
	{
	case Front:
		m_cameraPos += step * m_cameraFront;
		break;

	case Back:
		m_cameraPos -= step * m_cameraFront;
		break;

	case Left:
		m_cameraPos -= step * glm::normalize(glm::cross(m_cameraFront, m_cameraUp));
		break;

	case Right:
		m_cameraPos += step * glm::normalize(glm::cross(m_cameraFront, m_cameraUp));
		break;
	default:
		break;
	}
}

void Camera::rotate(RotateDirection direction, float angle)
{
	static float pitch = 0.0f;
	static float yaw = -90.0f;

	if (direction == Vertical)
	{
		pitch += angle;

		if (pitch > 89.9f)
			pitch = 89.9f;
		if (pitch < -89.9f)
			pitch = -89.9f;
	}
	else if (direction == Horizontal)
		yaw += angle;

	float x = cos(glm::radians(yaw));
	float y = sin(glm::radians(pitch));
	float z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
	m_cameraFront = glm::normalize(glm::vec3(x, y, z));
}

關於這個 class 有一些數學知識,我就不多說了,在這裏,我們使用 getCameraPos() 即可得到攝像機的位置,然後將它傳入着色器( viewPosition )中即可。

現在開始計算鏡面高光:

首先計算 lightDir 的反射向量,在 GLSL 中使用 reflect() 函數,前面我們知道 lightDir 的方向是頂點指向光源,但是 reflect() 函數要求光源指向頂點,所以這裏需要取反

然後計算視線方向與反射方向的點乘,然後取它的 32 次冪。這個 32 是高光的反光度( Shininess )。一個物體的反光度越高,反射光的能力越強,散射得越少,高光點就會越小。

    vec3 viewDir = normalize(viewPosition - vec3(fragPos));
    vec3 reflectDir = reflect(-lightDir, normalize(normal));
    float spec = pow(max(dot(reflectDir, viewDir), 0.0f), 32);
    vec3 specular = 0.8f * spec * lightColor;

4、進行最終顏色的計算。

最後一步是最簡單的:

    vec3 resultColor = (diffuse + ambient + specular) * color;
    fragColor = vec4(resultColor, 1.0f);

我們只需要將三種光照的影響相加,再乘以物體的顏色,就是它最終的顏色。

效果圖如下:

 


 【結語】

本篇主要講了馮氏光照模型的簡單 GLSL 實現,其中難點就是有一點點物理和數學的知識,不過都是些基礎的知識,而且我應該講得很清楚了,然後這個光照模型實際上是有一些問題的,這在後面將會進行改進的。

最後系列代碼地址:https://github.com/mengps/OpenGL-Totural/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章