• 作者:老汪软件技巧
  • 发表时间:2024-10-14 00:02
  • 浏览量:

目录

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

理解纹理

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

创建纹理对象

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';

设置纹理参数

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

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 来控制材质属性。

常见材质属性应用材质

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

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物体上,需要在顶点着色器中设置纹理坐标属性,并在片段着色器中采样纹理:

// 顶点着色器
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);
}

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

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);

高级纹理技术实现材质系统

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

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方法:

myMaterial.applyMaterial(shaderProgram);
drawObject();

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

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

手动设置纹理坐标

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

const vertices = [
  // 顶点位置和纹理坐标
  -1, -1, 0, 0, 0,
   1, -1, 1, 0,
   1,  1, 1, 1,
  -1,  1, 0, 1
];
// ...创建顶点缓冲对象和纹理坐标缓冲对象...

自动生成纹理坐标

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

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纹理参数控制水平和垂直方向的纹理重复:

纹理过滤

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

纹理压缩

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

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()设置着色器中的纹理单元位置。

gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture2);
gl.uniform1i(shader.uniforms.u_texture2, 1);

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

动态纹理和视频纹理

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

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, videoElement);

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

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还支持多种纹理映射技术,以实现更复杂的表面效果:

vec2 offset = vec2(0.1, 0.2);
vec2 scale = vec2(1.5, 0.8);
v_texCoord = (a_texCoord - offset) * scale;

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
);

材质属性的扩展

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

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

// ...其他输入...
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;

材质库

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

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));

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

const myMaterial = materialLibrary.get('red');
myMaterial.applyMaterial(shaderProgram);
drawObject();

纹理混合

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

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;
}

性能优化总结

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