跳到主要内容

使用着色器实现环境光、方向光、点光源

· 阅读需 7 分钟
Yangbingrui
Front End Engineer @ grg

in real life, if an object is perfectly blue and the only light source is red, you won't see the object because it will absorb the light and not reflect it back to your eyes.

在现实生活中,如果一个物体是纯蓝色的,而唯一的光源是红色的,你将看不到这个物体,因为它会吸收红光而不会反射任何光线回到你的眼睛。这是因为:

  1. 实验示例

    • 在完全黑暗的房间里
    • 准备一个纯蓝色的物体(比如蓝色的纸张)
    • 使用一个红色LED手电筒照射
    • 结果:蓝色物体会显得几乎全黑
  2. 科学解释

    • 蓝色物体之所以呈现蓝色,是因为它反射蓝光,吸收其他波长的光
    • 红色光源只发射红光
    • 当红光照射到蓝色物体上时,所有红光都被吸收
    • 没有光线被反射回来,因此我们看到的是黑色

这个原理在舞台灯光设计中经常被应用,通过改变光源的颜色可以让某些颜色的道具"消失"或改变外观。

if the face faces the light, it will receive the full power if the face is at a perfect 90° angle, it won't receive any values in between will be interpolated

about normal in the vertex shader, we get the normal in world space normal

instead of providing the direction straight to the function, we are going to provide the position of the light and calculate the orientation from it

alt text

alt text

环境光

ambientLight.glsl
vec3 ambientLight(vec3 lightColor, float lightIntensity)
{
return lightColor * lightIntensity;
}

方向光

directionalLight.glsl
vec3 directionalLight(vec3 lightColor, float lightIntensity, vec3 normal, vec3 lightPosition, vec3 viewDirection, float specularPower)
{
vec3 lightDirection = normalize(lightPosition);
vec3 lightReflection = reflect(- lightDirection, normal);

// Shading
float shading = dot(normal, lightDirection);
shading = max(0.0, shading);

// Specular
float specular = - dot(lightReflection, viewDirection);
specular = max(0.0, specular);
specular = pow(specular, specularPower);

return lightColor * lightIntensity * (shading + specular);
}

计算漫反射强度

法向量和光源向量的点积:如果方向一致时,即夹角为0度,漫反射强度最大为1,如果方向垂直,即夹角为90度,则漫反射强度为0,如果 方向相反,夹角为180度,漫反射强度为-1,但实际中,漫反射强度最小为0,所以需要max(0.0, shading)

要点:

光源向量,法向量在计算前要归一化 如果对象在移动,向量也要同步移动

vec3 modelNormal = modelMatrix * vec4(normal, 0.0);

vec4(normal, 0.0) 将法向量转换为4维向量,然后与模型矩阵相乘,得到模型空间下的法向量,0.0表示忽略位移,因为 法向量是方向向量,与位移无关

计算镜面反射强度

先通过reflect函数计算出光反射向量

vec3 lightReflection = reflect(- lightDirection, normal);

注意是 - lightPosition,因为光线方向是负的光源向量

然后计算出视线向量:位置向量 - 摄像机位置向量,并且要归一化

接着几时计算光反射向量与视线向量的点积:实际的表现,跟两个向量点积的计算结果相反,实际表现是,当视线向量与反射方向相反 时,即夹角为pi时,其反射强度应该是最强的,当它们的方向一致,即夹角为0时,其反射强度应该是最弱的。所以实际表现跟点积的 计算结果相反,所以 - dot(lightReflection, viewDirection)

最后调整镜面反射强度:pow(specular, specularPower)specularPower 是高光指数,控制光斑大小

要点:

视线向量要归一化

vec3 viewDirection = normalize(vPosition - cameraPosition);

vPosition 是顶点位置向量,通过模型矩阵转换到世界坐标系下

vec3 vPosition = (modelMatrix * vec4(position, 1.0)).xyz;

cameraPosition 是摄像机位置向量,由three.js提供

点光源

pointLight.glsl
vec3 pointLight(vec3 lightColor, float lightIntensity, vec3 normal, vec3 lightPosition, vec3 viewDirection, float specularPower, vec3 position, float lightDecay)
{
vec3 lightDelta = lightPosition - position;
float lightDistance = length(lightDelta);
vec3 lightDirection = normalize(lightDelta);
vec3 lightReflection = reflect(- lightDirection, normal);

// Shading
float shading = dot(normal, lightDirection);
shading = max(0.0, shading);

// Specular
float specular = - dot(lightReflection, viewDirection);
specular = max(0.0, specular);
specular = pow(specular, specularPower);

// Decay
float decay = 1.0 - lightDistance * lightDecay;
decay = max(0.0, decay);

return lightColor * lightIntensity * decay * (shading + specular);
}

计算漫反射强度

对于不同的顶点来说,光源向量是不同的,所以需要计算光源向量

vec3 lightDelta = lightPosition - position;

lightPosition 是光源位置向量,position 是顶点位置向量

计算镜面反射强度

因为不同的顶点,光源向量是不同的,所以光反射向量也要随着光源向量做计算

vec3 lightReflection = reflect(- lightDirection, normal);

vertex shader

vertex.glsl
varying vec3 vNormal;
varying vec3 vPosition;

void main()
{
// Position
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;

// Model normal
vec4 modelNormal = modelMatrix * vec4(normal, 0.0);

// Varying
vNormal = modelNormal.xyz;
vPosition = modelPosition.xyz;
}

fragment shader

fragment.glsl
uniform vec3 uColor;

varying vec3 vNormal;
varying vec3 vPosition;

#include ../includes/ambientLight.glsl
#include ../includes/directionalLight.glsl
#include ../includes/pointLight.glsl

void main()
{
vec3 normal = normalize(vNormal);
vec3 viewDirection = normalize(vPosition - cameraPosition);
vec3 color = uColor;

// Lights
vec3 light = vec3(0.0);

light += ambientLight(
vec3(1.0), // Light color
0.03 // Light intensity
);

light += directionalLight(
vec3(0.1, 0.1, 1.0), // Light color
1.0, // Light intensity,
normal, // Normal
vec3(0.0, 0.0, 3.0), // Light position
viewDirection, // View direction
20.0 // Specular power
);

light += pointLight(
vec3(1.0, 0.1, 0.1), // Light color
1.0, // Light intensity,
normal, // Normal
vec3(0.0, 2.5, 0.0), // Light position
viewDirection, // View direction
20.0, // Specular power
vPosition, // Position
0.25 // Light decay
);


color *= light;

// Final color
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}