计算机图形学中的光照:从基础到实践

170 阅读5分钟

在计算机图形学领域,光照是创建逼真虚拟场景的核心要素。它能够模拟现实世界中光线与物体表面的交互,赋予图形立体感和真实感。本文将深入探讨计算机图形学中光照的原理,并结合 JavaScript 代码进行实践演示。

一、光照基础概念

1.1 光源类型

  • 环境光:模拟现实世界中光线的间接传播,如房间内墙壁反射的光线。环境光均匀地照射在场景中的所有物体上,不会产生明显的阴影和方向感。
  • 点光源:从一个固定点向各个方向均匀发射光线,类似灯泡。随着距离增加,光线强度会逐渐减弱,点光源会在物体上产生明显的阴影和明暗对比。
  • 平行光:光线以平行的方式传播,不考虑距离衰减,类似太阳光。所有光线的方向和强度都相同,常用于模拟远距离光源。
  • 聚光灯:光线从一个点出发,沿着特定的圆锥体方向照射,有明显的照射范围,如手电筒的光线。

1.2 材质属性

物体的材质决定了它如何反射和吸收光线,主要属性包括:

  • 漫反射颜色:物体表面在各个方向上均匀反射光线的颜色,决定了物体在光照下的基本颜色。
  • 镜面反射颜色:物体表面反射光线呈现高光的颜色,用于模拟光滑表面的反光效果。
  • 环境反射颜色:物体对环境光的反射颜色。
  • 光泽度:控制镜面反射高光的大小和锐利程度,数值越高,高光越集中、越小。

二、光照计算的数学原理(非公式表达)

光照计算的核心是确定光线与物体表面的交互方式,主要涉及三个重要向量的计算:

  1. 光线方向向量:从物体表面上的点指向光源的方向。
  1. 法线向量:垂直于物体表面的向量,用于确定光线入射角。
  1. 视角方向向量:从物体表面上的点指向观察者眼睛的方向。

光照强度的计算通过这些向量之间的夹角和相应的数学运算来实现。例如,漫反射光照强度取决于光线方向向量和法线向量之间的夹角,夹角越小,漫反射强度越大;镜面反射光照强度则与光线方向向量、法线向量和视角方向向量都有关系,通过特定的运算模拟高光效果。

三、使用 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>

在上述代码中:

  1. 首先创建了 WebGL 上下文,并定义了顶点着色器和片段着色器。顶点着色器用于计算顶点的位置、法线和光线方向,并传递给片段着色器;片段着色器根据这些信息计算最终的像素颜色。
  1. 接着设置了顶点数据,包括物体的顶点位置和法线。
  1. 然后定义了投影矩阵、模型视图矩阵和法线矩阵,用于对物体进行变换和投影。
  1. 最后设置了光照相关的参数,如点光源的位置、颜色,环境光颜色、物体的漫反射颜色等,并在渲染循环中不断绘制场景。

四、光照效果的优化与拓展

  1. 阴影计算:通过阴影映射等技术,可以为场景添加更真实的阴影效果,增强场景的立体感。
  1. 全局光照模拟:模拟光线在场景中的多次反射,使场景更加逼真,但计算复杂度较高。
  1. 实时动态光照:实现光源位置、颜色等属性的实时变化,创造出更丰富的视觉效果。

通过不断学习和实践,结合更多先进的算法和技术,你可以在计算机图形学中创造出令人惊叹的光照效果。