WebGL纹理与材质

目录


在WebGL中,纹理和材质是实现3D物体表面视觉效果的关键。纹理是图像数据,而材质则是如何应用这些纹理以模拟物体表面的属性。

理解纹理

纹理是二维图像,可以映射到3D物体的表面上,为物体增添细节和真实感。在WebGL中,我们通常使用TEXTURE_2D类型的纹理,它们可以是普通的RGB或RGBA图像,也可以是特殊的立方体贴图、深度贴图等。

创建纹理对象

javascript 复制代码
const texture = gl.createTexture();
加载纹理图像
javascript
const image = new Image();
image.onload = () => {
  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);
};
image.src = 'texture.jpg';

设置纹理参数

纹理参数会影响纹理的过滤方式(如何插值纹理坐标)和边缘处理:

javascript 复制代码
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // 水平方向重复
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); // 垂直方向重复
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); // 使用线性过滤和MIP贴图
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // 使用线性过滤

理解材质

材质是描述物体表面属性的参数集合,包括颜色、光泽、透明度、粗糙度等。在WebGL中,材质通常通过着色器程序实现,通过传入不同的 uniforms 来控制材质属性。

常见材质属性

  • 颜色(Color):物体的基本颜色,可以通过vec3或vec4传递。
  • 镜面反射系数(Shininess):影响镜面高光的强度。
  • 环境光(Ambient Light):环境对物体的全局照明。
  • 漫射光(Diffuse Light):模拟光源直接照射到物体的效果。
  • 镜面光(Specular Light):模拟镜面反射的效果。
  • 透明度(Opacity):物体的不透明程度,通常通过float值传递。

应用材质

在顶点着色器中,通常不需要材质属性,但在片段着色器中,你需要根据材质属性计算最终的颜色:

glsl 复制代码
precision mediump float;

uniform vec3 u_color;
uniform float u_opacity;
uniform vec3 u_ambientLight;
uniform vec3 u_diffuseLight;
uniform vec3 u_specularLight;
uniform vec3 u_shininess;
uniform vec3 u_eyePosition;

// ...其他输入...

void main() {
  vec3 ambient = u_ambientLight;
  vec3 diffuse = u_diffuseLight * max(dot(normal, lightDirection), 0.0);
  vec3 specular = u_specularLight * pow(max(dot(normal, reflect(-lightDirection, normal)), 0.0), u_shininess);

  vec3 litColor = ambient + diffuse + specular;
  vec4 finalColor = vec4(litColor * u_color, u_opacity);

  gl_FragColor = finalColor;
}

纹理映射

将纹理应用到3D物体上,需要在顶点着色器中设置纹理坐标属性,并在片段着色器中采样纹理:

glsl 复制代码
// 顶点着色器
attribute vec2 a_texCoord;
varying vec2 v_texCoord;

void main() {
  // ...其他计算...

  v_texCoord = a_texCoord;
}

// 片段着色器
uniform sampler2D u_texture;

void main() {
  vec4 texColor = texture2D(u_texture, v_texCoord);
  vec3 litColor = ... // 计算光照

  gl_FragColor = vec4(litColor * texColor.rgb, texColor.a);
}

在主程序中,设置纹理坐标属性和纹理单元:

javascript 复制代码
const texCoordAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_texCoord');
gl.enableVertexAttribArray(texCoordAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(shaderProgram.uniforms.u_texture, 0);

高级纹理技术

  • 立方体贴图(Cubemaps):用于模拟环境反射。
  • 法线贴图(Normal Maps):模拟物体表面的凹凸感。
  • 环境光遮罩(Environment Masking):用于控制光照的边缘效果。
  • 位移贴图(Displacement Maps):通过改变表面的高度来模拟物体的细节。
  • 颜色空间转换:例如从sRGB转换到线性空间,以正确处理颜色表现。

实现材质系统

为了简化材质的管理和使用,可以创建一个材质类,包含材质属性和方法,用于设置和应用材质:

javascript 复制代码
class Material {
  constructor(color, shininess) {
    this.color = color;
    this.shininess = shininess;
    // ...其他材质属性...
  }

  applyMaterial(shaderProgram) {
    gl.uniform3fv(shaderProgram.uniforms.u_color, this.color);
    gl.uniform1f(shaderProgram.uniforms.u_shininess, this.shininess);
    // ...应用其他材质属性...
  }
}

const myMaterial = new Material([1, 0, 0], 100);

在绘制物体时,先调用applyMaterial方法:

javascript 复制代码
myMaterial.applyMaterial(shaderProgram);
drawObject();

纹理坐标和纹理坐标的生成

纹理坐标是将纹理映射到3D物体表面的关键。通常,每个顶点都有一个对应的纹理坐标,用于在片段着色器中采样纹理。纹理坐标范围通常在[0, 1]之间,(0, 0)对应纹理左下角,(1, 1)对应右上角。

手动设置纹理坐标

如果你知道每个顶点应该映射到纹理的哪个部分,可以直接在顶点数据中指定纹理坐标:

javascript 复制代码
const vertices = [
  // 顶点位置和纹理坐标
  -1, -1, 0, 0, 0,
   1, -1, 1, 0,
   1,  1, 1, 1,
  -1,  1, 0, 1
];

// ...创建顶点缓冲对象和纹理坐标缓冲对象...

自动生成纹理坐标

有时,你可能希望纹理按照特定方式拉伸或重复。在这种情况下,可以编写一个函数自动生成纹理坐标:

javascript 复制代码
function generateTextureCoordinates(numVertices) {
  const textureCoordinates = new Float32Array(numVertices * 2);

  for (let i = 0; i < numVertices; i++) {
    const u = i / (numVertices - 1);
    const v = i % 2 === 0 ? 0 : 1;
    textureCoordinates[i * 2] = u;
    textureCoordinates[i * 2 + 1] = v;
  }

  return textureCoordinates;
}

纹理映射模式

WebGL提供了多种纹理映射模式,通过TEXTURE_WRAP_S和TEXTURE_WRAP_T纹理参数控制水平和垂直方向的纹理重复:

  • CLAMP_TO_EDGE:边缘处的纹理坐标被固定在0或1,不会重复。
  • REPEAT:纹理会水平和垂直方向重复。
  • MIRRORED_REPEAT:纹理会水平和垂直方向重复,每次翻转一次。

纹理过滤

纹理过滤决定了当纹理坐标在[0, 1]范围内之外时,如何插值纹理颜色。通过TEXTURE_MIN_FILTER和TEXTURE_MAG_FILTER纹理参数控制:

  • NEAREST:使用最近的颜色像素,结果可能会有明显的像素化。
  • LINEAR:线性插值,更平滑,但可能会模糊细节。
  • NEAREST_MIPMAP_NEARESTLINEAR_MIPMAP_NEAREST:在MIP贴图中选择最近的级别进行插值。
  • NEAREST_MIPMAP_LINEARLINEAR_MIPMAP_LINEAR:在MIP贴图中线性插值。

纹理压缩

纹理压缩可以减少内存占用和带宽需求,特别是在移动设备上。WebGL支持多种纹理压缩格式,如S3TC (DXTn)、ETC1、PVRTC等。使用压缩纹理时,需要检查浏览器/设备是否支持,并使用相应的扩展。

javascript 复制代码
const compressedTexImage2D = gl.compressedTexImage2D;
const COMPRESSED_RGB_S3TC_DXT1_EXT = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)[0];

compressedTexImage2D(gl.TEXTURE_2D, 0, COMPRESSED_RGB_S3TC_DXT1_EXT, width, height, 0, data);

纹理单元和纹理数组

WebGL支持多个纹理单元,允许同时应用多个纹理。通过gl.activeTexture()切换纹理单元,gl.uniform1i()设置着色器中的纹理单元位置。

javascript 复制代码
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture2);
gl.uniform1i(shader.uniforms.u_texture2, 1);

纹理数组允许在一个纹理对象中存储多个独立的2D纹理,通过纹理坐标的一个额外维度访问。

动态纹理和视频纹理

WebGL允许动态更新纹理内容,这对于实时渲染或使用视频作为纹理特别有用:

javascript 复制代码
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, videoElement);

在视频播放时,定期更新纹理:

javascript 复制代码
function updateTexture() {
  if (videoElement.readyState >= videoElement.HAVE_CURRENT_DATA) {
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, videoElement);
  }
  requestAnimationFrame(updateTexture);
}
updateTexture();

纹理映射的变形

除了简单的平面映射,WebGL还支持多种纹理映射技术,以实现更复杂的表面效果:

  • 偏移和缩放(Offset and Scale):通过设置纹理坐标,可以改变纹理在物体表面的位置和大小。
javascript 复制代码
vec2 offset = vec2(0.1, 0.2);
vec2 scale = vec2(1.5, 0.8);
v_texCoord = (a_texCoord - offset) * scale;
  • 扭曲(Tiling and Wrapping):使用纹理坐标和数学函数(如sin、cos)来创建扭曲效果。
javascript 复制代码
v_texCoord = vec2(
  a_texCoord.x + sin(a_texCoord.y * 10.0) * 0.1,
  a_texCoord.y + cos(a_texCoord.x * 10.0) * 0.1
);
  • 偏移映射(Parallax Mapping):根据法线和视角,模拟物体表面的深度感。
  • 立方体贴图环境映射(Cube Map Environment Mapping):使用6个面的立方体贴图,模拟物体对周围环境的反射。

材质属性的扩展

除了基本的颜色、光泽和透明度,材质还可以包含更多属性,以模拟更复杂的表面效果:

  • 金属度(Metallic):表示物体是否像金属一样反射光线。
  • 粗糙度(Roughness):影响镜面高光的扩散程度。
  • AO(Ambient Occlusion):模拟物体表面的阴影,增加深度感。
  • 法线(Normal):用于法线映射,改变物体表面的光照效果。
  • 环境光遮罩(Environment Masking):控制光照在物体边缘的衰减。

在着色器中,可以根据这些属性计算最终的颜色:

glsl 复制代码
// ...其他输入...

vec3 baseColor = u_baseColor.rgb;
float metallic = u_metallic;
float roughness = u_roughness;
vec3 ao = u_ao;
vec3 normal = normalize(u_normal);
vec3 reflectedColor = ... // 使用立方体贴图计算反射色

vec3 ambient = u_ambientLight * baseColor * ao;
vec3 diffuse = u_diffuseLight * max(dot(normal, lightDirection), 0.0);
vec3 specular = ... // 根据metallic、normal和viewDirection计算

vec3 litColor = ambient + diffuse + specular + reflectedColor;
vec4 finalColor = vec4(litColor, u_opacity);

gl_FragColor = finalColor;

材质库

为了更好地组织和管理材质,可以创建一个材质库,存储各种预设的材质,便于在场景中复用:

javascript 复制代码
class MaterialLibrary {
  constructor() {
    this.materials = {};
  }

  add(name, material) {
    this.materials[name] = material;
  }

  get(name) {
    return this.materials[name];
  }
}

const materialLibrary = new MaterialLibrary();
materialLibrary.add('red', new Material([1, 0, 0], 100));

在绘制物体时,从库中获取并应用材质:

javascript 复制代码
const myMaterial = materialLibrary.get('red');
myMaterial.applyMaterial(shaderProgram);
drawObject();

纹理混合

通过在片段着色器中混合多个纹理,可以实现复杂的表面效果,如混合不同的纹理层、实现渐变和过渡效果:

glsl 复制代码
uniform sampler2D u_texture1;
uniform sampler2D u_texture2;

void main() {
  vec4 texColor1 = texture2D(u_texture1, v_texCoord);
  vec4 texColor2 = texture2D(u_texture2, v_texCoord);
  vec4 finalColor = mix(texColor1, texColor2, u_mixFactor);

  gl_FragColor = finalColor;
}

性能优化

  • 纹理压缩:减少纹理的内存占用和加载时间。
  • 纹理合并:将多个小纹理合并为一个大纹理,减少纹理切换的开销。
  • MIP贴图:提高纹理采样速度,尤其是在物体远离相机时。
  • 纹理过滤:选择合适的纹理过滤策略,平衡质量与性能。
  • 纹理单元复用:尽量减少纹理单元的切换。

总结

WebGL中的纹理和材质是实现3D图形真实感的关键。通过理解它们的工作原理,以及如何创建和应用,你将能够创建出丰富的3D场景。结合高级纹理技术,如法线映射、环境光遮罩等,可以进一步提升视觉效果。在实践中不断学习和探索,你将能够掌握更多的3D图形编程技巧。

相关推荐
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte5 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc