LearnOpenGL Note - Lighting: Multiple lights

OpenGL光照系列6

Posted by Tao on Tuesday, March 8, 2022

0 Preface

本节笔记对应的内容为多光源,部分内容参考自傅老師/OpenGL教學 第二章

我们在前面的教程中已经学习了许多关于OpenGL中光照的知识,其中包括Phong Shading、Material、Lighting Map以及不同种类的投光物(Light Caster)。在这一节中,我们将结合之前学过的所有知识,创建一个包含六个光源的场景。我们将模拟一个类似太阳的定向光(Directional Light)光源,四个分散在场景中的点光源(Point Light),以及一个手电筒(Flashlight)。

为了在场景中使用多个光源,我们希望将光照计算封装到GLSL中。这样做的原因是,每一种光源都需要一种不同的计算方法,而一旦我们想对多个光源进行光照计算时,代码很快就会变得非常复杂。如果我们只在main函数中进行所有的这些计算,代码很快就会变得难以理解。

GLSL中的函数和C函数很相似,它有一个函数名、一个返回值类型,如果函数不是在main函数之前声明的,我们还必须在代码文件顶部声明一个原型。我们对每个光照类型都创建一个不同的函数:定向光、点光源和聚光。

当我们在场景中使用多个光源时,通常使用以下方法:我们需要有一个单独的颜色向量代表片段的输出颜色。对于每一个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上。所以场景中的每个光源都会计算它们各自对片段的影响,并结合为一个最终的输出颜色。大体的结构会像是这样:

out vec4 FragColor;

void main()
{
  // 定义一个输出颜色值
  vec3 output;
  // 将定向光的贡献加到输出中
  output += someFunctionToCalculateDirectionalLight();
  // 对所有的点光源也做相同的事情
  for(int i = 0; i < nr_of_point_lights; i++)
    output += someFunctionToCalculatePointLight();
  // 也加上其它的光源(比如聚光)
  output += someFunctionToCalculateSpotLight();

  FragColor = vec4(output, 1.0);
}  

实际的代码对每一种实现都可能不同,但大体的结构都是差不多的。我们定义了几个函数,用来计算每个光源的影响,并将最终的结果颜色加到输出颜色向量上。例如,如果两个光源都很靠近一个片段,那么它们所结合的贡献将会形成一个比单个光源照亮时更加明亮的片段。

1 Directional light

我们需要在片段着色器中封装一个函数CalcLightDirectional来计算定向光对相应片段的贡献:它接受一些参数并计算定向光照颜色。

#version 330 core
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoord;

struct Material {
  vec3 ambient;
  sampler2D diffuse;
  sampler2D specular;
  float shininess;
};

struct LightDirectional {
  vec3 pos;
  vec3 dirToLight;
  vec3 color;
};

struct LightPoint {
  float constant;
  float linear;
  float quadratic;
};

struct LightSpot {
  float cosPhyInner;
  float cosPhyOutter;
};

uniform Material material;
uniform LightDirectional lightD;
uniform LightPoint lightP;
uniform LightSpot lightS;

uniform vec3 objColor;
uniform vec3 ambientColor;
uniform vec3 cameraPos;

out vec4 FragColor;

vec3 CalcLightDirectional(LightDirectional light, vec3 uNormal,
                          vec3 dirToCamera) {
  // diffuse max(dot(L,N),0)
  float diffIntensity = max(dot(light.dirToLight, uNormal), 0);
  vec3 diffColor =
      diffIntensity * light.color * texture(material.diffuse, TexCoord).rgb;

  // specular pow(max(dot(R,cam),0))
  vec3 R = normalize(reflect(-light.dirToLight, uNormal));
  float specIntensity = pow(max(dot(R, dirToCamera), 0), material.shininess);
  vec3 specColor =
      specIntensity * light.color * texture(material.specular, TexCoord).rgb;

  vec3 result = diffColor + specColor;
  return result;
}

void main() {
  vec3 finalResult = vec3(0, 0, 0);
  vec3 uNormal = normalize(Normal);
  vec3 dirToCamera = normalize(cameraPos - FragPos);
  finalResult += CalcLightDirectional(lightD, uNormal, dirToCamera);

  FragColor = vec4(finalResult, 1.0f);
}

我们基本上只是从上一节中复制了代码,并使用函数参数的两个向量来计算定向光的贡献向量。最终环境光、漫反射和镜面光的贡献将会合并为单个颜色向量返回。

2 Point light

与平行光类似,在片元着色器中定义LightPoint结构及点光源函数CalcPointLight

...
struct LightPoint {
  vec3 pos;
  vec3 dirToLight;
  vec3 color;
  float constant;
  float linear;
  float quadratic;
};
...
uniform Material material;
uniform LightDirectional lightD;
uniform LightPoint lightP0;

vec3 CalcLightPoint(LightPoint light, vec3 uNormal, vec3 dirToCamera) {
  // attenuation
  float dist = length(light.pos - FragPos);
  float attenuation = 1 / (light.constant + light.linear * dist +
                           light.quadratic * dist * dist);

  // diffuse
  float diffIntensity =
      max(dot(normalize(light.pos - FragPos), uNormal), 0) * attenuation;
  vec3 diffColor =
      diffIntensity * light.color * texture(material.diffuse, TexCoord).rgb;

  // specular
  vec3 R = normalize(reflect(-normalize(light.pos - FragPos), uNormal));
  float specIntensity =
      pow(max(dot(R, dirToCamera), 0), material.shininess) * attenuation;
  vec3 specColor =
      specIntensity * light.color * texture(material.specular, TexCoord).rgb;

  vec3 result = diffColor + specColor;
  return result;
}

void main() {
  vec3 finalResult = vec3(0, 0, 0);
  vec3 uNormal = normalize(Normal);
  vec3 dirToCamera = normalize(cameraPos - FragPos);
  // finalResult += CalcLightDirectional(lightD, uNormal, dirToCamera);
  finalResult += CalcLightPoint(lightP0, uNormal, dirToCamera);
  // finalResult += CalcLightDirectional(lightP1, uNormal, dirToCamera);
  // finalResult += CalcLightDirectional(lightP2, uNormal, dirToCamera);
  // finalResult += CalcLightDirectional(lightP3, uNormal, dirToCamera);

  FragColor = vec4(finalResult, 1.0f);
}

新建LightPoint类继承于LightDirectional:

LightPoint::LightPoint(glm::vec3 _position, glm::vec3 _angles, glm::vec3 _color)
    : LightDirectional(_position,_angles,_color)
{
    constant = 1.0f;
    linear = 0.09f;
    quadratic = 0.032f;
}

再复制3个点光源P1,P2,P3,并在main中传入数值,效果如下:

result

其中ambient决定了图中的最暗值。

3 Spot Light

新建LightSpot类继承LightPoint: LightSpot.h

#ifndef _LIGHTSPOT_H_
#define _LIGHTSPOT_H_

#include <glm/glm.hpp>
#include <glm/gtx/rotate_vector.hpp>

#include "LightPoint.h"

class LightSpot : public LightPoint
{
public:
    LightSpot(glm::vec3 _position, glm::vec3 _angles, glm::vec3 _color = glm::vec3(1.0f, 1.0f, 1.0f));
    ~LightSpot();

    float cosPhyInner = 0.9f;
    float cosPhyOutter = 0.85f;
};

#endif

fragmentSource:

...
struct LightSpot {
  vec3 pos;
  vec3 dirToLight;
  vec3 color;
  float constant;
  float linear;
  float quadratic;
  float cosPhyInner;
  float cosPhyOutter;
};

uniform LightSpot lightS;

vec3 CalcLightSpot(LightSpot light, vec3 uNormal, vec3 dirToCamera) {
  // attenuation
  float dist = length(light.pos - FragPos);
  float attenuation = 1 / (light.constant + light.linear * dist +
                           light.quadratic * dist * dist);
  float spotRatio;
  float cosTheta = dot(normalize(FragPos - light.pos), -light.dirToLight);

  if (cosTheta > light.cosPhyInner) {
    spotRatio = 1.0;
  } else if (cosTheta > light.cosPhyOutter) {
    spotRatio = (cosTheta - light.cosPhyOutter) /
                (light.cosPhyInner - light.cosPhyOutter);
  } else {
    spotRatio = 0;
  }

  // diffuse
  float diffIntensity = max(dot(normalize(light.pos - FragPos), uNormal), 0) *
                        attenuation * spotRatio;
  vec3 diffColor =
      diffIntensity * light.color * texture(material.diffuse, TexCoord).rgb;

  // specular
  vec3 R = normalize(reflect(-normalize(light.pos - FragPos), uNormal));
  float specIntensity = pow(max(dot(R, dirToCamera), 0), material.shininess) *
                        attenuation * spotRatio;
  vec3 specColor =
      specIntensity * light.color * texture(material.specular, TexCoord).rgb;

  vec3 result = diffColor + specColor;
  return result;
}

void main() {
  vec3 finalResult = vec3(0, 0, 0);
  vec3 uNormal = normalize(Normal);
  vec3 dirToCamera = normalize(cameraPos - FragPos);
  //finalResult += CalcLightDirectional(lightD, uNormal, dirToCamera);
  //finalResult += CalcLightPoint(lightP0, uNormal, dirToCamera);
  //finalResult += CalcLightPoint(lightP1, uNormal, dirToCamera);
  //finalResult += CalcLightPoint(lightP2, uNormal, dirToCamera);
  //finalResult += CalcLightPoint(lightP3, uNormal, dirToCamera);
  finalResult += CalcLightSpot(lightS, uNormal, dirToCamera);

  FragColor = vec4(finalResult, 1.0f);
}

效果如下;

spot_result

4 Putting it all together

最后,我们在片元着色器中打开所有灯光,得到以下效果:

final_result

完整代码(待补充)

「如果这篇文章对你有用,请随意打赏」

Heisenberg Blog

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付


comments powered by Disqus