2.6 纹理 (Textures)


文档摘要

2.6 纹理 (Textures) WebGL 核心概念:2.6 纹理 (Textures) 详解与实践 在 WebGL 的奇妙世界中,纹理 (Textures) 扮演着至关重要的角色。它们如同画布上的颜料,为我们构建的 3D 模型赋予丰富的视觉细节和表面质感,使得原本单调的几何形状变得生动而富有表现力。纹理不仅仅是简单的图片,更是 WebGL 图形渲染管线中不可或缺的数据来源,它极大地提升了场景的真实感和沉浸感。 2.6.1 纹理的本质与作用 简单来说,纹理就是存储图像数据的内存区域。在 WebGL 中,这些图像数据可以被映射到 3D 模型的表面,从而改变模型的颜色、光照反应以及其他视觉属性。想象一下,如果所有的物体都只有单一的颜色和光滑的表面,世界将会多么单调乏味。

2.6 纹理 (Textures)

WebGL 核心概念:2.6 纹理 (Textures) 详解与实践

在 WebGL 的奇妙世界中,纹理 (Textures) 扮演着至关重要的角色。它们如同画布上的颜料,为我们构建的 3D 模型赋予丰富的视觉细节和表面质感,使得原本单调的几何形状变得生动而富有表现力。纹理不仅仅是简单的图片,更是 WebGL 图形渲染管线中不可或缺的数据来源,它极大地提升了场景的真实感和沉浸感。

2.6.1 纹理的本质与作用

简单来说,纹理就是存储图像数据的内存区域。在 WebGL 中,这些图像数据可以被映射到 3D 模型的表面,从而改变模型的颜色、光照反应以及其他视觉属性。想象一下,如果所有的物体都只有单一的颜色和光滑的表面,世界将会多么单调乏味。纹理的引入,让我们能够为模型添加木纹、砖墙、金属锈迹、人物皮肤等各种各样的表面细节,极大地丰富了场景的视觉表现力。

纹理的核心作用可以归纳为以下几点:

  • 增加表面细节: 纹理可以模拟物体表面的凹凸不平、颜色变化等细节,无需增加模型的几何复杂度,就能实现精细的视觉效果。例如,通过纹理贴图,我们可以让一个简单的立方体看起来像一个布满砖块的墙壁,而无需真的用复杂的几何体去构建每一块砖。

  • 赋予材质属性: 纹理可以存储材质信息,例如颜色、反射率、粗糙度等。通过结合光照模型,纹理可以影响物体表面的光照反应,模拟不同的材质效果,如金属的光泽、木头的纹理、布料的柔软等。

  • 实现特殊效果: 纹理不仅可以用于表面着色,还可以用于实现各种高级的图形效果,例如:

    • 环境贴图 (Environment Mapping): 模拟物体表面反射周围环境的效果,增加真实感。

    • 法线贴图 (Normal Mapping): 模拟物体表面的微小凹凸,增强光照细节。

    • 高度贴图 (Height Mapping): 用于地形渲染,模拟地面的起伏变化。

    • 阴影贴图 (Shadow Mapping): 用于实现阴影效果,增加场景的立体感。

总而言之,纹理是 WebGL 中至关重要的资源,它极大地拓展了图形渲染的可能性,是构建逼真、生动 WebGL 应用的基础。

2.6.2 WebGL 纹理类型

WebGL 支持多种纹理类型,以满足不同的渲染需求。最常用的纹理类型是 2D 纹理 (2D Textures),也是我们本篇文章的重点。除了 2D 纹理之外,WebGL 还支持以下纹理类型:

  • 立方体纹理 (Cube Map Textures): 由六个 2D 纹理组成,用于环境贴图,模拟天空盒或反射效果。

  • 3D 纹理 (3D Textures): 也称为体积纹理,用于存储三维数据,例如医学扫描数据或流体模拟结果。

  • 纹理数组 (Texture Arrays): 将多个 2D 纹理组合成一个数组,方便在着色器中进行索引访问。

  • 缓冲纹理 (Buffer Textures): 将 GPU 缓冲区的数据作为纹理使用,用于高级渲染技术。

在 WebGL 中,纹理对象是通过 gl.createTexture() 函数创建的。无论创建哪种类型的纹理,都需要先创建纹理对象,然后根据纹理类型进行相应的配置和数据上传。

2.6.3 2D 纹理的创建与配置

2D 纹理是 WebGL 中最常用的纹理类型,用于存储图像数据。创建和使用 2D 纹理通常包括以下步骤:

  1. 创建纹理对象: 使用 gl.createTexture() 函数创建一个空的纹理对象。

  2. 绑定纹理: 使用 gl.bindTexture(target, texture) 函数将纹理对象绑定到 WebGL 上下文。对于 2D 纹理,target 参数通常设置为 gl.TEXTURE_2D

  3. 配置纹理参数: 使用 gl.texParameteri(target, pname, param) 函数配置纹理的各种参数,例如纹理的环绕方式 (wrapping) 和过滤方式 (filtering)。

  4. 上传纹理数据: 使用 gl.texImage2D(target, level, internalformat, width, height, border, format, type, pixels) 函数将图像数据上传到纹理对象。

  5. 解绑纹理 (可选): 使用 gl.bindTexture(target, null) 解绑纹理对象。虽然不是必须的,但解绑纹理是一个良好的编程习惯,可以避免意外地修改其他纹理。

下面,我们将逐一详细讲解每个步骤。

2.6.3.1 创建纹理对象 (gl.createTexture())

gl.createTexture() 函数非常简单,它不接受任何参数,直接返回一个新的纹理对象。

const texture = gl.createTexture(); if (!texture) { console.error("Failed to create texture object!"); return; }

2.6.3.2 绑定纹理 (gl.bindTexture())

gl.bindTexture(target, texture) 函数用于将纹理对象绑定到 WebGL 上下文的纹理单元 (Texture Unit) 上。纹理单元是 GPU 中用于存储和访问纹理的硬件资源。WebGL 至少支持 8 个纹理单元 (从 gl.TEXTURE0gl.TEXTURE7),甚至更多。

对于 2D 纹理,target 参数必须设置为 gl.TEXTURE_2Dtexture 参数则是通过 gl.createTexture() 创建的纹理对象。

gl.bindTexture(gl.TEXTURE_2D, texture);

Graph TD 图示:纹理绑定

2.6.3.3 配置纹理参数 (gl.texParameteri())

gl.texParameteri(target, pname, param) 函数用于配置纹理的各种参数,控制纹理在渲染过程中的行为。常用的参数包括:

  • 纹理环绕方式 (Texture Wrapping): 当纹理坐标超出 [0, 1] 范围时,纹理如何重复或裁剪。

    • gl.TEXTURE_WRAP_S: 水平方向 (s 坐标) 的环绕方式。

    • gl.TEXTURE_WRAP_T: 垂直方向 (t 坐标) 的环绕方式。

    • 可选值:

      • gl.REPEAT: 重复纹理。

      • gl.CLAMP_TO_EDGE: 将纹理边缘拉伸到超出范围的区域。

      • gl.MIRRORED_REPEAT: 镜像重复纹理。

Graph TD 图示:纹理环绕方式

  • 纹理过滤方式 (Texture Filtering): 当纹理被放大或缩小时,如何采样纹理颜色。

    • gl.TEXTURE_MIN_FILTER: 纹理缩小时的过滤方式 (缩小过滤)。

    • gl.TEXTURE_MAG_FILTER: 纹理放大时的过滤方式 (放大过滤)。

    • 可选值:

      • gl.NEAREST: 最近邻过滤,选择最近的纹理像素颜色。速度快,但可能出现锯齿状效果。

      • gl.LINEAR: 线性过滤 (双线性过滤),对周围 2x2 纹理像素颜色进行线性插值。效果平滑,但速度稍慢。

      • gl.NEAREST_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR, gl.LINEAR_MIPMAP_LINEAR: Mipmap 过滤,使用预先生成的不同尺寸的纹理 (mipmap) 进行过滤,可以提高缩小纹理的渲染质量,并减少性能开销。

Graph TD 图示:纹理过滤方式

代码示例:配置纹理参数

// 设置纹理环绕方式为重复 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); // 设置纹理缩小过滤为线性 Mipmap 线性 (三线性过滤) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); // 设置纹理放大过滤为线性过滤 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

注意: 当使用 Mipmap 过滤时,需要在上传纹理数据后调用 gl.generateMipmap(gl.TEXTURE_2D) 函数生成 Mipmap。

2.6.3.4 上传纹理数据 (gl.texImage2D())

gl.texImage2D(target, level, internalformat, width, height, border, format, type, pixels) 函数用于将图像数据上传到纹理对象。这个函数参数较多,我们逐一解释:

  • target: 必须设置为 gl.TEXTURE_2D

  • level: Mipmap 层级,通常设置为 0,表示基本纹理层级。对于 Mipmap,可以从 0 开始逐层上传不同尺寸的纹理数据。

  • internalformat: WebGL 内部存储纹理数据的格式。常用的格式包括:

    • gl.RGBA: 红绿蓝透明度,每个分量 8 位,共 32 位。

    • gl.RGB: 红绿蓝,每个分量 8 位,共 24 位。

    • gl.LUMINANCE: 亮度,单通道灰度纹理。

    • gl.ALPHA: 透明度,单通道透明度纹理。

    • 更多格式请参考 WebGL 文档。

  • width: 纹理宽度,像素单位。

  • height: 纹理高度,像素单位。

  • border: 必须设置为 0。

  • format: 输入像素数据的格式,必须与 internalformat 兼容。常用的格式与 internalformat 相同,例如 gl.RGBA, gl.RGB, gl.LUMINANCE, gl.ALPHA

  • type: 输入像素数据的数据类型。常用的类型包括:

    • gl.UNSIGNED_BYTE: 无符号字节,每个颜色分量用 0-255 表示。

    • gl.FLOAT: 浮点数,每个颜色分量用 0.0-1.0 的浮点数表示。

    • 更多类型请参考 WebGL 文档。

  • pixels: 包含图像数据的 TypedArray (例如 Uint8Array, Float32Array),或者 HTML <img> 元素,或者 HTML <canvas> 元素。

常见的纹理数据来源:

  • HTML <img> 元素: 从图片文件加载纹理。

    const image = new Image(); image.onload = function() { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D); // 生成 Mipmap }; image.src = "path/to/image.png";
  • HTML <canvas> 元素: 动态绘制纹理。

    const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; const ctx = canvas.getContext('2d'); // 在 canvas 上绘制图像 ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 256, 256); // ... 更多绘制操作 ... gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); gl.generateMipmap(gl.TEXTURE_2D); // 生成 Mipmap
  • TypedArray (例如 Uint8Array): 手动创建纹理数据。

    const width = 256; const height = 256; const pixels = new Uint8Array(width * height * 4); // RGBA 格式 // 填充像素数据,例如生成棋盘格纹理 for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const index = (y * width + x) * 4; const color = ((x ^ y) & 8) ? 255 : 0; // 棋盘格颜色 pixels[index] = color; // R pixels[index + 1] = color; // G pixels[index + 2] = color; // B pixels[index + 3] = 255; // A } } gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels); gl.generateMipmap(gl.TEXTURE_2D); // 生成 Mipmap

注意: 当使用 HTMLImageElementHTMLCanvasElement 作为 pixels 参数时,widthheight 参数可以设置为 0,WebGL 会自动从图像或 canvas 中获取尺寸。

2.6.3.5 解绑纹理 (gl.bindTexture(target, null))

完成纹理配置和数据上传后,可以选择解绑纹理对象。解绑纹理可以防止后续的纹理操作意外地影响到当前纹理。

gl.bindTexture(gl.TEXTURE_2D, null);

2.6.4 在着色器中使用纹理

纹理数据上传到 GPU 后,需要在着色器中才能使用。在着色器中,我们需要以下步骤:

  1. 声明 sampler2D 类型的 uniform 变量: sampler2D 是着色器中用于访问 2D 纹理的特殊类型。

  2. 传递纹理单元索引: 将纹理单元的索引 (例如 0, 1, 2, ...) 传递给 sampler2D uniform 变量。

  3. 使用 texture2D(sampler, vec2 uv) 函数采样纹理颜色: texture2D() 函数根据纹理坐标 uv 从指定的 sampler2D 纹理中采样颜色。

顶点着色器 (Vertex Shader):

顶点着色器主要负责传递纹理坐标到片元着色器。纹理坐标通常是一个 vec2 类型的属性 (attribute) 或计算得出,并作为 varying 变量传递给片元着色器。

attribute vec4 a_position; attribute vec2 a_texCoord; // 纹理坐标属性 varying vec2 v_texCoord; // 传递给片元着色器的纹理坐标 void main() { gl_Position = a_position; v_texCoord = a_texCoord; // 将纹理坐标传递给片元着色器 }

片元着色器 (Fragment Shader):

片元着色器中使用 sampler2D uniform 变量接收纹理,并使用 texture2D() 函数采样纹理颜色。

precision mediump float; uniform sampler2D u_texture; // 纹理 uniform 变量 varying vec2 v_texCoord; // 从顶点着色器接收的纹理坐标 void main() { // 使用 texture2D() 函数采样纹理颜色 vec4 textureColor = texture2D(u_texture, v_texCoord); gl_FragColor = textureColor; // 输出纹理颜色 }

JavaScript 代码:设置纹理 uniform 变量

在 JavaScript 代码中,需要先获取 sampler2D uniform 变量的位置,然后使用 gl.uniform1i() 函数将纹理单元索引传递给 uniform 变量。

const u_textureLocation = gl.getUniformLocation(program, 'u_texture'); // ... (纹理创建和上传代码) ... // 在渲染之前,激活纹理单元 0 gl.activeTexture(gl.TEXTURE0); // 将纹理绑定到纹理单元 0 gl.bindTexture(gl.TEXTURE_2D, texture); // 将纹理单元 0 的索引 (0) 传递给 u_texture uniform 变量 gl.uniform1i(u_textureLocation, 0); // ... (绘制代码) ...

Graph TD 图示:纹理在着色器中的使用

2.6.5 代码实践:加载图片纹理并渲染

下面是一个简单的代码示例,演示如何加载图片纹理并渲染到一个矩形上。

HTML 代码 (index.html):

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebGL Texture Example</title> </head> <body> <canvas id="webglCanvas" width="512" height="512"></canvas> <script src="main.js"></script> </body> </html>

JavaScript 代码 (main.js):

const canvas = document.getElementById('webglCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { console.error("WebGL is not supported!"); } // 顶点着色器代码 const vertexShaderSource = ` attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = a_position; v_texCoord = a_texCoord; } `; // 片元着色器代码 const fragmentShaderSource = ` precision mediump float; uniform sampler2D u_texture; varying vec2 v_texCoord; void main() { gl_FragColor = texture2D(u_texture, v_texCoord); } `; // 创建着色器程序 const program = createProgram(gl, vertexShaderSource, fragmentShaderSource); gl.useProgram(program); // 获取属性和 uniform 变量位置 const a_positionLocation = gl.getAttribLocation(program, 'a_position'); const a_texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); const u_textureLocation = gl.getUniformLocation(program, 'u_texture'); // 顶点数据 const vertices = new Float32Array([ // 顶点坐标 纹理坐标 -0.5, 0.5, 0.0, 1.0, // 左上 -0.5, -0.5, 0.0, 0.0, // 左下 0.5, 0.5, 1.0, 1.0, // 右上 0.5, -0.5, 1.0, 0.0 // 右下 ]); // 创建顶点缓冲区 const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 设置顶点属性指针 gl.vertexAttribPointer(a_positionLocation, 2, gl.FLOAT, false, 4 * 4, 0); gl.enableVertexAttribArray(a_positionLocation); gl.vertexAttribPointer(a_texCoordLocation, 2, gl.FLOAT, false, 4 * 4, 2 * 4); gl.enableVertexAttribArray(a_texCoordLocation); // 创建纹理对象 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // 加载图片 const image = new Image(); image.onload = function() { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); render(); // 图片加载完成后渲染 }; image.src = "texture.png"; // 替换为你的图片路径 function render() { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 激活纹理单元 0 gl.activeTexture(gl.TEXTURE0); // 将纹理绑定到纹理单元 0 gl.bindTexture(gl.TEXTURE_2D, texture); // 将纹理单元 0 的索引 (0) 传递给 u_texture uniform 变量 gl.uniform1i(u_textureLocation, 0); // 绘制矩形 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } // 创建着色器程序辅助函数 (简化代码) function createShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (success) { return shader; } console.error(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); } function createProgram(gl, vertexShaderSource, fragmentShaderSource) { const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); const success = gl.getProgramParameter(program, gl.LINK_STATUS); if (success) { return program; } console.error(gl.getProgramInfoLog(program)); gl.deleteProgram(program); }

texture.png (示例图片,你可以替换为你自己的图片):

你可以创建一个简单的 PNG 图片,例如一个彩色方块或者简单的图案,并将其命名为 texture.png,放置在与 main.js 文件相同的目录下。

运行代码:

在浏览器中打开 index.html 文件,如果一切正常,你将在 canvas 上看到加载的图片纹理渲染在矩形上。

2.6.6 总结与展望

纹理是 WebGL 中构建丰富视觉效果的关键技术之一。本文详细介绍了 2D 纹理的创建、配置和使用方法,并通过代码示例演示了如何加载图片纹理并渲染。

总结关键要点:

  • 纹理是存储图像数据的内存区域,用于为 3D 模型添加表面细节和材质属性。

  • 2D 纹理是 WebGL 中最常用的纹理类型,用于存储 2D 图像数据。

  • 创建 2D 纹理的步骤包括:创建纹理对象、绑定纹理、配置纹理参数、上传纹理数据、解绑纹理 (可选)。

  • 纹理参数包括纹理环绕方式 (wrapping) 和纹理过滤方式 (filtering),用于控制纹理在渲染过程中的行为。

  • 在着色器中使用 sampler2D uniform 变量和 texture2D() 函数采样纹理颜色。

  • 可以从 HTML <img> 元素、HTML <canvas> 元素或 TypedArray 加载纹理数据。

展望未来:

随着 WebGL 技术的不断发展,纹理的应用也越来越广泛和深入。未来的发展方向可能包括:

  • 更高效的纹理压缩技术: 例如 ASTC、ETC 等纹理压缩格式,可以减少纹理数据的大小,提高加载速度和渲染效率。

  • 更强大的纹理生成和处理工具: 例如程序化纹理生成、纹理合成、纹理烘焙等技术,可以更方便地创建和管理复杂的纹理资源。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U