OpenGL系列教程(三)---基础光照

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011283226/article/details/86715717

【写在前面】

本章主要内容:

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*,这里还需要注意的是,我使用了两个vao,m_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/

猜你喜欢

转载自blog.csdn.net/u011283226/article/details/86715717