在计算机图形学领域,光照是创建逼真虚拟场景的核心要素。它能够模拟现实世界中光线与物体表面的交互,赋予图形立体感和真实感。本文将深入探讨计算机图形学中光照的原理,并结合 JavaScript 代码进行实践演示。
一、光照基础概念
1.1 光源类型
- 环境光:模拟现实世界中光线的间接传播,如房间内墙壁反射的光线。环境光均匀地照射在场景中的所有物体上,不会产生明显的阴影和方向感。
- 点光源:从一个固定点向各个方向均匀发射光线,类似灯泡。随着距离增加,光线强度会逐渐减弱,点光源会在物体上产生明显的阴影和明暗对比。
- 平行光:光线以平行的方式传播,不考虑距离衰减,类似太阳光。所有光线的方向和强度都相同,常用于模拟远距离光源。
- 聚光灯:光线从一个点出发,沿着特定的圆锥体方向照射,有明显的照射范围,如手电筒的光线。
1.2 材质属性
物体的材质决定了它如何反射和吸收光线,主要属性包括:
- 漫反射颜色:物体表面在各个方向上均匀反射光线的颜色,决定了物体在光照下的基本颜色。
- 镜面反射颜色:物体表面反射光线呈现高光的颜色,用于模拟光滑表面的反光效果。
- 环境反射颜色:物体对环境光的反射颜色。
- 光泽度:控制镜面反射高光的大小和锐利程度,数值越高,高光越集中、越小。
二、光照计算的数学原理(非公式表达)
光照计算的核心是确定光线与物体表面的交互方式,主要涉及三个重要向量的计算:
- 光线方向向量:从物体表面上的点指向光源的方向。
- 法线向量:垂直于物体表面的向量,用于确定光线入射角。
- 视角方向向量:从物体表面上的点指向观察者眼睛的方向。
光照强度的计算通过这些向量之间的夹角和相应的数学运算来实现。例如,漫反射光照强度取决于光线方向向量和法线向量之间的夹角,夹角越小,漫反射强度越大;镜面反射光照强度则与光线方向向量、法线向量和视角方向向量都有关系,通过特定的运算模拟高光效果。
三、使用 JavaScript 实现光照效果
在 JavaScript 中,我们可以借助 WebGL 库来实现光照效果。以下是一个简单的使用点光源的示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Point Light Example</title>
</head>
<body>
<canvas id="glCanvas" width="600" height="600"></canvas>
<script type="module">
import { mat4, vec3 } from 'gl-matrix';
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL is not supported in your browser');
return;
}
// 顶点着色器
const vertexShaderSource = `
attribute vec4 a_position;
attribute vec3 a_normal;
uniform mat4 u_modelViewMatrix;
uniform mat4 u_projectionMatrix;
uniform mat3 u_normalMatrix;
uniform vec3 u_lightPosition;
varying vec3 v_normal;
varying vec3 v_lightDir;
void main() {
v_normal = normalize(u_normalMatrix * a_normal);
v_lightDir = normalize(u_lightPosition - vec3(u_modelViewMatrix * a_position));
gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
}
`;
// 片段着色器
const fragmentShaderSource = `
precision mediump float;
varying vec3 v_normal;
varying vec3 v_lightDir;
uniform vec3 u_lightColor;
uniform vec3 u_ambientColor;
uniform vec3 u_diffuseColor;
void main() {
float diff = max(dot(v_normal, v_lightDir), 0.0);
vec3 diffuse = u_diffuseColor * u_lightColor * diff;
vec3 ambient = u_ambientColor * u_diffuseColor;
gl_FragColor = vec4(ambient + diffuse, 1.0);
}
`;
// 创建着色器程序
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
// 设置顶点数据
const vertices = new Float32Array([
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
0.5, 0.5, 0.0, 0.0, 0.0, 1.0,
-0.5, 0.5, 0.0, 0.0, 0.0, 1.0
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 6 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray(positionAttribLocation);
const normalAttribLocation = gl.getAttribLocation(shaderProgram, 'a_normal');
gl.vertexAttribPointer(normalAttribLocation, 3, gl.FLOAT, false, 6 * Float32Array.BYTES_PER_ELEMENT, 3 * Float32Array.BYTES_PER_ELEMENT);
gl.enableVertexAttribArray(normalAttribLocation);
// 设置矩阵
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, (45 * Math.PI) / 180, canvas.width / canvas.height, 0.1, 100.0);
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -5]);
const normalMatrix = mat3.create();
mat3.fromMat4(normalMatrix, modelViewMatrix);
const u_projectionMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_projectionMatrix');
gl.uniformMatrix4fv(u_projectionMatrixLocation, false, projectionMatrix);
const u_modelViewMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_modelViewMatrix');
gl.uniformMatrix4fv(u_modelViewMatrixLocation, false, modelViewMatrix);
const u_normalMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_normalMatrix');
gl.uniformMatrix3fv(u_normalMatrixLocation, false, normalMatrix);
// 设置光照参数
const u_lightPosition = vec3.fromValues(0, 1, 2);
const u_lightColor = vec3.fromValues(1, 1, 1);
const u_ambientColor = vec3.fromValues(0.2, 0.2, 0.2);
const u_diffuseColor = vec3.fromValues(1, 0, 0);
const u_lightPositionLocation = gl.getUniformLocation(shaderProgram, 'u_lightPosition');
gl.uniform3fv(u_lightPositionLocation, u_lightPosition);
const u_lightColorLocation = gl.getUniformLocation(shaderProgram, 'u_lightColor');
gl.uniform3fv(u_lightColorLocation, u_lightColor);
const u_ambientColorLocation = gl.getUniformLocation(shaderProgram, 'u_ambientColor');
gl.uniform3fv(u_ambientColorLocation, u_ambientColor);
const u_diffuseColorLocation = gl.getUniformLocation(shaderProgram, 'u_diffuseColor');
gl.uniform3fv(u_diffuseColorLocation, u_diffuseColor);
// 渲染循环
function render() {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
在上述代码中:
- 首先创建了 WebGL 上下文,并定义了顶点着色器和片段着色器。顶点着色器用于计算顶点的位置、法线和光线方向,并传递给片段着色器;片段着色器根据这些信息计算最终的像素颜色。
- 接着设置了顶点数据,包括物体的顶点位置和法线。
- 然后定义了投影矩阵、模型视图矩阵和法线矩阵,用于对物体进行变换和投影。
- 最后设置了光照相关的参数,如点光源的位置、颜色,环境光颜色、物体的漫反射颜色等,并在渲染循环中不断绘制场景。
四、光照效果的优化与拓展
- 阴影计算:通过阴影映射等技术,可以为场景添加更真实的阴影效果,增强场景的立体感。
- 全局光照模拟:模拟光线在场景中的多次反射,使场景更加逼真,但计算复杂度较高。
- 实时动态光照:实现光源位置、颜色等属性的实时变化,创造出更丰富的视觉效果。
通过不断学习和实践,结合更多先进的算法和技术,你可以在计算机图形学中创造出令人惊叹的光照效果。