计算机图形学进阶知识教学

368 阅读8分钟

19192213-ae56-414f-b27e-d46245032e61_1746673096173248998_origin~tplv-a9rns2rl98-image-qvalue.jpeg

一、引言

计算机图形学是一门研究如何利用计算机生成、处理和显示图形的学科。在之前的学习中,我们已经掌握了计算机图形学的一些基础知识,如基本图形的绘制、图形变换等。本教程将带领大家深入探索计算机图形学的进阶知识,包括纹理映射、抗锯齿、渲染优化等内容,同时会结合 JavaScript 代码示例来帮助大家更好地理解和实践。

二、纹理映射

(一)纹理映射基础

纹理映射是一种为图形对象表面添加细节和真实感的技术。简单来说,就是将一张 2D 的纹理图像 “贴” 到 3D 模型的表面。比如我们要创建一个木质的箱子,通过纹理映射,就可以把一张真实的木纹图片映射到箱子模型的各个面上,使其看起来像真实的木头。

(二)JavaScript 实现纹理映射示例

以下是一个使用 JavaScript 和 WebGL 实现简单纹理映射的代码示例:

// 获取WebGL上下文
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
// 初始化着色器程序
const program = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource);
gl.useProgram(program);
// 设置顶点数据
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = new Float32Array([
    -1.0, -1.0, 0.0,
    1.0, -1.0, 0.0,
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
// 设置纹理坐标数据
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
const texCoords = new Float32Array([
    0.0, 0.0,
    1.0, 0.0,
    0.0, 1.0,
    1.0, 1.0
]);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
// 加载纹理图像
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
const img = new Image();
img.src = 'texture.jpg';
img.onload = function () {
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
    gl.generateMipmap(gl.TEXTURE_2D);
};
// 设置顶点属性指针
const positionAttribLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionAttribLocation);
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
const texCoordAttribLocation = gl.getAttribLocation(program, 'a_texCoord');
gl.enableVertexAttribArray(texCoordAttribLocation);
gl.vertexAttribPointer(texCoordAttribLocation, 2, gl.FLOAT, false, 0, 0);
// 绘制图形
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 顶点着色器源代码
const vertexShaderSource = `
    attribute vec3 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main() {
        gl_Position = vec4(a_position, 1.0);
        v_texCoord = a_texCoord;
    }
`;
// 片元着色器源代码
const fragmentShaderSource = `
    precision mediump float;
    varying vec2 v_texCoord;
    uniform sampler2D u_texture;
    void main() {
        gl_FragColor = texture2D(u_texture, v_texCoord);
    }
`;

在上述代码中,我们首先获取 WebGL 上下文,然后初始化着色器程序。接着设置顶点数据和纹理坐标数据,加载纹理图像。通过顶点着色器将顶点位置和纹理坐标传递给片元着色器,片元着色器根据纹理坐标从纹理图像中采样颜色值,最终实现纹理映射效果。

三、抗锯齿

(一)锯齿问题产生原因

在计算机图形渲染中,由于屏幕是由离散的像素组成,当绘制斜线、曲线或具有复杂边界的图形时,就会出现锯齿现象。这是因为像素只能以整数坐标进行显示,无法精确地表示图形的真实边界,导致图形边缘呈现出锯齿状。

(二)抗锯齿算法介绍

  1. 多重采样抗锯齿(MSAA) :MSAA 是一种常见的抗锯齿方法。它在每个像素内进行多次采样,对采样点的颜色进行平均计算,从而得到更平滑的边缘。例如,一个像素原本只显示一种颜色,采用 MSAA 后,在这个像素区域内会对多个点进行颜色采样,然后综合这些采样点的颜色来确定该像素最终显示的颜色,使得图形边缘看起来更平滑。
  1. 快速近似抗锯齿(FXAA) :FXAA 是一种基于图像后处理的抗锯齿算法。它通过分析图像中相邻像素的颜色和亮度变化,来识别可能存在锯齿的边缘区域,然后对这些区域进行处理,通过混合相邻像素的颜色来平滑锯齿。与 MSAA 不同,FXAA 不需要额外的采样,而是在已经渲染好的图像上进行操作,因此相对来说性能消耗较低。
  1. 时间性抗锯齿(TAA) :TAA 利用了时间上的连贯性。它会参考前几帧的渲染结果,对当前帧进行抗锯齿处理。例如,它会根据前几帧中对应像素的颜色和位置信息,结合当前帧的渲染数据,对当前像素的颜色进行优化,从而减少锯齿现象。TAA 在处理动态场景时效果较好,能够有效避免画面闪烁等问题。

(三)JavaScript 实现抗锯齿示例(以简单的超级采样抗锯齿为例)

// 获取WebGL上下文
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
// 初始化着色器程序
const program = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource);
gl.useProgram(program);
// 设置顶点数据
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = new Float32Array([
    -1.0, -1.0, 0.0,
    1.0, -1.0, 0.0,
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
// 超级采样抗锯齿相关设置
const sampleCount = 4;
const superSampledCanvas = document.createElement('canvas');
superSampledCanvas.width = canvas.width * sampleCount;
superSampledCanvas.height = canvas.height * sampleCount;
const superSampledGl = superSampledCanvas.getContext('webgl');
const superSampledProgram = initShaderProgram(superSampledGl, vertexShaderSource, fragmentShaderSource);
superSampledGl.useProgram(superSampledProgram);
// 在超级采样画布上绘制图形
superSampledGl.bindBuffer(superSampledGl.ARRAY_BUFFER, positionBuffer);
superSampledGl.vertexAttribPointer(superSampledGl.getAttribLocation(superSampledProgram, 'a_position'), 3, superSampledGl.FLOAT, false, 0, 0);
superSampledGl.enableVertexAttribArray(superSampledGl.getAttribLocation(superSampledProgram, 'a_position'));
superSampledGl.drawArrays(superSampledGl.TRIANGLE_STRIP, 0, 4);
// 对超级采样画布的结果进行下采样,得到最终抗锯齿结果
const superSampledTexture = superSampledGl.createTexture();
superSampledGl.bindTexture(superSampledGl.TEXTURE_2D, superSampledTexture);
superSampledGl.texImage2D(superSampledGl.TEXTURE_2D, 0, superSampledGl.RGBA, superSampledGl.RGBA, superSampledGl.UNSIGNED_BYTE, superSampledCanvas);
const finalTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, finalTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, canvas.width, canvas.height);
// 设置顶点属性指针
const positionAttribLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionAttribLocation);
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// 绘制最终抗锯齿后的图形
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 顶点着色器源代码
const vertexShaderSource = `
    attribute vec3 a_position;
    void main() {
        gl_Position = vec4(a_position, 1.0);
    }
`;
// 片元着色器源代码
const fragmentShaderSource = `
    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 示例颜色
    }
`;

在这个示例中,我们创建了一个比原始画布大若干倍(这里是 4 倍)的超级采样画布,在超级采样画布上进行图形绘制,由于画布分辨率更高,图形边缘的锯齿在更多的像素点上表现得更平滑。然后通过下采样操作,将超级采样画布的结果复制到原始分辨率的画布上,从而实现抗锯齿效果。

四、渲染优化

(一)渲染优化的重要性

在计算机图形学中,渲染场景可能涉及大量的图形数据和复杂的计算。如果不进行优化,渲染过程可能会变得非常缓慢,导致帧率下降,用户体验变差。尤其是在实时渲染场景,如游戏、虚拟现实等应用中,渲染优化对于保证流畅的画面至关重要。

(二)渲染优化技术

  1. 层次细节(LOD)技术:LOD 技术根据物体与相机的距离来选择不同细节程度的模型进行渲染。当物体离相机较远时,使用低细节模型,这样可以减少绘制的三角形数量,降低计算量。例如在一个大型游戏场景中,远处的山脉可能使用一个简单的低多边形模型来表示,而当玩家靠近山脉时,再切换到高细节模型,既能保证远处物体的大致形状,又能提高渲染效率。
  1. 光照贴图:光照贴图是将预先计算好的光照信息存储在一张纹理图中。在渲染时,直接使用这张光照贴图来照亮物体,而不需要实时计算光照效果。比如一个室内场景,通过预先计算好的光照贴图,可以快速确定每个物体表面的光照强度和颜色,大大减少了实时光照计算的开销,提升渲染速度。

(三)JavaScript 实现简单渲染优化示例(以 LOD 技术为例)

// 获取WebGL上下文
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
// 初始化着色器程序
const program = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource);
gl.useProgram(program);
// 定义不同细节程度的模型顶点数据
const highDetailPositions = new Float32Array([
    // 高细节模型顶点数据,包含更多顶点以呈现更精细形状
]);
const mediumDetailPositions = new Float32Array([
    // 中等细节模型顶点数据,顶点数量适中
]);
const lowDetailPositions = new Float32Array([
    // 低细节模型顶点数据,顶点数量较少
]);
// 创建不同细节程度模型的顶点缓冲
const highDetailBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, highDetailBuffer);
gl.bufferData(gl.ARRAY_BUFFER, highDetailPositions, gl.STATIC_DRAW);
const mediumDetailBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, mediumDetailBuffer);
gl.bufferData(gl.ARRAY_BUFFER, mediumDetailPositions, gl.STATIC_DRAW);
const lowDetailBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, lowDetailBuffer);
gl.bufferData(gl.ARRAY_BUFFER, lowDetailPositions, gl.STATIC_DRAW);
// 根据相机与物体距离选择合适的模型进行绘制
function drawObject(cameraDistance) {
    let currentBuffer;
    if (cameraDistance < 50) {
        currentBuffer = highDetailBuffer;
    } else if (cameraDistance < 100) {
        currentBuffer = mediumDetailBuffer;
    } else {
        currentBuffer = lowDetailBuffer;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, currentBuffer);
    const positionAttribLocation = gl.getAttribLocation(program, 'a_position');
    gl.enableVertexAttribArray(positionAttribLocation);
    gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
    gl.drawArrays(gl.TRIANGLES, 0, currentBuffer.numVertices);
}
// 顶点着色器源代码
const vertexShaderSource = `
    attribute vec3 a_position;
    void main() {
        gl_Position = vec4(a_position, 1.0);
    }
`;
// 片元着色器源代码
const fragmentShaderSource = `
    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); // 示例颜色
    }
`;

在这个示例中,我们定义了高、中、低三种细节程度的模型顶点数据,并创建了相应的顶点缓冲。在drawObject函数中,根据相机与物体的距离来选择合适的模型顶点缓冲进行绘制,从而实现 LOD 技术,优化渲染性能。

五、总结

通过本教程,我们深入学习了计算机图形学的纹理映射、抗锯齿和渲染优化等进阶知识,并通过 JavaScript 代码示例进行了实践。希望大家能够掌握这些知识和技能,在今后的计算机图形学项目中,创造出更加真实、流畅和高效的图形效果。