光照

在上一节我们实现了材质.

材质用于表征一个表面的各种属性.

但是相同的材质在不同光照的情况下显现出的结果不同.例如:一件白衣服在不同灯光下显现的颜色不同.

所以最终像素点上的颜色应该由光照和材质共同决定.

光照模型有很多种,常见的有Phong光照模型,Blinn-Phong光照模型.
我们在这一节实现一个Blinn-Phong光照模型.

Light类

首先我们定义一个Light类,用于描述光源.


class Light
{
public:
glm::vec3 position;
glm::vec3 color;
float radiant;

public:
Light(){
position = glm::vec3(0.0f, 0.0f, 0.0f);
color = glm::vec3(1.0f, 1.0f, 1.0f);
radiant = 10.0f;
}
Light(glm::vec3 pos, glm::vec3 col, float rad) {
position = pos;
color = col;
radiant = rad;
}
~Light() {}
};

现实生活中的光源很多,最常见的就是太阳光,灯光.

其中太阳光是一种平行光,我们定义一个平行光

#pragma once

#include "Light/Light.hpp"

class DirectionalLight : public Light
{
public:
glm::vec3 direction;

public:
DirectionalLight()
: Light()
{
direction = glm::vec3(0.0f, -1.0f, 0.0f);
}

DirectionalLight(glm::vec3 direction)
: Light()
{
this->direction = glm::normalize(direction);
}

DirectionalLight(glm::vec3 direction,glm::vec3 position, glm::vec3 color, float radiant)
:Light(position, color, radiant)
{
this->direction = glm::normalize(direction);
}

~DirectionalLight(){}
};

另外我们还可以定义点光源,但这里先按下不表.

Fragment Shader

我们在Fragment Shader中定义光源

struct DirLight
{
vec3 dir;
vec3 color;
vec3 radiant;
};
struct PointLight
{
vec3 pos;
vec3 color;
vec3 radint;

float constant;
float linear;
float quadratic;
};

#define NR_POINT_LIGHTS 4
uniform DirLight dirLight; // 光源位置
uniform PointLight pointLight[NR_POINT_LIGHTS]; // 点光源位置

传递数据

我们定义了一个场景类用于管理所有物体,光源等.

在具体Material类中,我们传递光源数据给Shader

unsigned short directionalLightDirLoc = glGetUniformLocation(shader->shaderProgramID, "dirLight.dir");
unsigned short directionalLightColorLoc = glGetUniformLocation(shader->shaderProgramID, "dirLight.color");
unsigned short directionalLightRadiantLoc = glGetUniformLocation(shader->shaderProgramID, "dirLight.radiant");
glm::vec3 directionalLightDir = Singleton<Scene>::Instance().directionalLights.at(0)->direction;
glm::vec3 directionalLightColor = Singleton<Scene>::Instance().directionalLights.at(0)->color;
float directionalLightRadiant = Singleton<Scene>::Instance().directionalLights.at(0)->radiant;
glUniform3fv(directionalLightDirLoc, 1, glm::value_ptr(directionalLightDir));
glUniform3fv(directionalLightColorLoc, 1, glm::value_ptr(directionalLightColor));
glUniform1f(directionalLightRadiantLoc, directionalLightRadiant);

Blinn-Phong光照模型

Blinn-Phong光照模型是Phong光照模型的改进版本.

Phong光照模型认为光源是平行于视线的,而Blinn-Phong光照模型认为光源是平行于表面的.

Blinn-Phong光照模型的计算公式如下:

vec3 N = normalize(Normal); // 法向量
vec3 L = normalize(lightDir); // 光线方向
vec3 V = normalize(viewDir); // 视线方向
vec3 R = reflect(-L, N); // 反射向量

float diffuse = max(dot(N, L), 0.0); // 漫反射
float specular = pow(max(dot(R, V), 0.0), 32.0); // 镜面反射

vec3 ambient = vec3(0.1); // 环境光
vec3 diffuseColor = vec3(1.0, 1.0, 1.0); // 漫反射颜色
vec3 specularColor = vec3(1.0, 1.0, 1.0); // 镜面反射颜色

vec3 result = (ambient + diffuse * diffuseColor + specular * specularColor) * objectColor;

最终Shader

我们可以得出最终的Shader如下:

#version 330 core
struct DirLight
{
vec3 dir;
vec3 color;
vec3 radiant;
};
struct PointLight
{
vec3 pos;
vec3 color;
vec3 radint;

float constant;
float linear;
float quadratic;
};

#define NR_POINT_LIGHTS 4

in vec3 modelNormal;
in vec3 worldPos; // 世界坐标
in vec3 worldNormal; // 法线
in vec2 uv; // 纹理坐标

uniform DirLight dirLight; // 光源位置
uniform PointLight pointLight[NR_POINT_LIGHTS]; // 点光源位置

uniform sampler2D albedo; // 颜色纹理
uniform sampler2D metallic; // 金属度纹理
uniform sampler2D roughness; // 粗糙度纹理
uniform sampler2D emission; // 发光纹理
uniform sampler2D ao; // 环境光遮蔽纹理

uniform vec3 viewPos; // 观察者位置

out vec4 FragColor; // 输出颜色

vec3 computeDirLight(DirLight light, vec3 norm, vec3 viewDir) {
vec3 lightDir = normalize(-light.dir);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * light.color;

vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfDir), 0.0), 32.0);
vec3 specular = spec * light.color;

return diffuse + specular;
}

vec3 computePointLight(PointLight light, vec3 norm, vec3 fragPos, vec3 viewDir) {
vec3 lightDir = normalize(light.pos - fragPos);
float distance = length(light.pos - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * light.color * attenuation;

vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfDir), 0.0), 32.0);
vec3 specular = spec * light.color * attenuation;

return diffuse + specular;
}

void main() {
vec3 objectColor = texture(albedo, uv).rgb;
vec3 norm = normalize(modelNormal);
vec3 viewDir = normalize(viewPos - worldPos);

// 环境光
vec3 result = 0.1 * objectColor;

// 计算方向光源
result += computeDirLight(dirLight, norm, viewDir);

// 计算点光源
// for (int i = 0; i < NR_POINT_LIGHTS; i++) {
// result += computePointLight(pointLight[i], norm, worldPos, viewDir);
// }

FragColor = vec4(result * objectColor, 1.0);
}

结果

运行,可以看到我们的角色在光照下的表现了.