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图形编程技巧。

相关推荐
bug丸2 分钟前
v8引擎垃圾回收
前端·javascript·垃圾回收
安全小王子4 分钟前
攻防世界web第三题file_include
前端
&活在当下&5 分钟前
ref 和 reactive 的用法和区别
前端·javascript·vue.js
百事老饼干8 分钟前
VUE前端实现防抖节流 Lodash
前端
web Rookie12 分钟前
React 高阶组件(HOC)
前端·javascript·react.js
云白冰26 分钟前
hiprint结合vue2项目实现静默打印详细使用步骤
前端·javascript·vue.js
葡萄架子34 分钟前
Python中的logger作用(from loguru import logger)
java·前端·python
Hi_MrXiao42 分钟前
前端实现图片压缩插件(image-compressorionjs)
前端
阿智@111 小时前
Node.js 助力前端开发:自动化操作实战
运维·前端·node.js·自动化
m0_748251721 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·vue.js·ajax