Skip to content

纹理贴图

🕒 Published at:

通过纹理贴图加载器TextureLoader的load()方法加载一张图片可以返回一个纹理对象Texture,纹理对象Texture可以作为模型材质颜色贴图.map属性的值。

基本贴图

js
const geometry = new THREE.PlaneGeometry(200, 100); 
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./earth.jpg');
const material = new THREE.MeshLambertMaterial({
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture,//map表示材质的颜色贴图属性
});

也可以通过颜色贴图属性.map直接设置纹理贴图,和材质的参数设置一样。

js
material.map = texture;

注意最新版本,webgl渲染器默认编码方式已经改变,为了避免色差,纹理对象编码方式要修改为THREE.SRGBColorSpace

js
texture.colorSpace  = THREE.SRGBColorSpace;//设置为SRGB颜色空间

通过上述关键代码可以实现一个🌍的效果

代码

js
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 创建一个模型
const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
//类型数组创建顶点数据
const vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    80, 0, 0, //顶点2坐标
    80, 80, 0, //顶点3坐标
    0, 80, 0, //顶点4坐标
]);

// 创建属性缓冲区对象
const attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,表示一个顶点的xyz坐标
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;

// Uint16Array类型数组创建顶点索引数据
const indexes = new Uint16Array([
    0, 1, 2, 0, 2, 3,
])

// BufferAttribute表示顶点索引属性的值
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组

const material = new THREE.MeshBasicMaterial({
    color: 0x00ffff, //材质颜色
    // side: THREE.FrontSide, //默认只有正面可见
    // side: THREE.BackSide, //设置只有背面可见
    side: THREE.DoubleSide, //两面可见
});
// 网格模型本质:一个一个三角形(面)构成
const mesh = new THREE.Mesh(geometry, material); 


//场景
const scene = new THREE.Scene();
scene.add(mesh); //模型对象添加到场景中

//辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);

//光源设置
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(100, 60, 50);
scene.add(directionalLight);
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

//渲染器和相机
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(292, 223, 185);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

// 渲染循环
function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
render();

const controls = new OrbitControls(camera, renderer.domElement);

// 画布跟随窗口变化
window.onresize = function () {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
};

顶点UV坐标的作用

顶点UV坐标的作用是从纹理贴图上提取像素映射到网格模型Mesh的几何体表面上。

顶点UV坐标可以在0~1.0之间任意取值,纹理贴图左下角对应的UV坐标是(0,0),右上角对应的坐标(1,1)。

自定义顶点UVgeometry.attributes.uv

顶点UV坐标geometry.attributes.uv和顶点位置坐标geometry.attributes.position是一一对应的,

UV顶点坐标你可以根据需要在0~1之间任意设置,具体怎么设置,要看你想把图片的哪部分映射到Mesh的几何体表面上。

代码

js
import * as THREE from 'three';

const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
//类型数组创建顶点数据
const vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    160, 0, 0, //顶点2坐标
    160, 80, 0, //顶点3坐标
    0, 80, 0, //顶点4坐标
]);
// 创建属性缓冲区对象
const attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,表示一个顶点的xyz坐标
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;

// Uint16Array类型数组创建顶点索引数据
const indexes = new Uint16Array([
    0, 1, 2, 0, 2, 3,
])
// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组

/**纹理坐标0~1之间随意定义*/
const uvs = new Float32Array([
    0, 0, //图片左下角
    1, 0, //图片右下角
    1, 1, //图片右上角
    0, 1, //图片左上角
]);
// 获取纹理贴图左下角四分之一部分的像素值
// const uvs = new Float32Array([
//     0, 0, 
//     0.5, 0, 
//     0.5, 0.5, 
//     0, 0.5, 
// ]);
// 设置几何体attributes属性的uv属性
geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标


//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./earth.jpg');
const material = new THREE.MeshBasicMaterial({
    map: texture, //map表示材质的颜色贴图属性
});
const mesh = new THREE.Mesh(geometry, material);

圆形图片

如何把一个矩形图片剪裁为圆形渲染呢

其实很简单,可以通过圆形几何体CircleGeometry创建一个网格模型Mesh,把一张图片作为圆形Mesh材质的颜色贴图,这样就可以把一张方形图片剪裁渲染为圆形效果。

js
//CircleGeometry的顶点UV坐标是按照圆形采样纹理贴图
const geometry = new THREE.CircleGeometry(60, 100);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./texture.jpg');
const material = new THREE.MeshBasicMaterial({
    map: texture,//map表示材质的颜色贴图属性
    side:THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);

CircleGeometry的UV坐标会对颜色纹理贴图.map进行提取,CircleGeometry的UV坐标默认提取的就是一个圆形轮廓。

纹理对象Texture阵列

使用threejs纹理对象Texture的阵列功能+矩形平面几何体PlaneGeometry实现一个地面瓷砖效果。

矩形平面设置颜色贴图

js
const geometry = new THREE.PlaneGeometry(2000, 2000);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');
const material = new THREE.MeshLambertMaterial({
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture,//map表示材质的颜色贴图属性
});
const mesh = new THREE.Mesh(geometry, material);

纹理对象Texture的阵列功能

js
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(12,12);//注意选择合适的阵列数量

旋转矩形平面

注意旋转方向影响矩形平面背面还是正面朝上,threejs默认渲染正面,不渲染背面。

js
// 旋转矩形平面
mesh.rotateX(-Math.PI/2);

矩形Mesh+背景透明png贴图

three.js项目开发中,把一个背景透明的.png图像作为平面矩形网格模型Mesh的颜色贴图是一个非常有用的功能,通过这样一个功能,可以对three.js三维场景进行标注。

整体思路:创建一个矩形平面,设置颜色贴图.map,注意选择背景透明的.png图像作为颜色贴图,同时材质设置transparent: true,这样png图片背景完全透明的部分不显示。

js
// 矩形平面网格模型设置背景透明的png贴图
const geometry = new THREE.PlaneGeometry(60, 60); //默认在XOY平面上
const textureLoader = new THREE.TextureLoader();
const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load('./指南针.png'),        
    transparent: true, //使用背景透明的png贴图,注意开启透明计算
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotateX(-Math.PI / 2);

网格地面辅助观察GridHelper

js
// 添加一个辅助网格地面
const gridHelper = new THREE.GridHelper(300, 25, 0x004444, 0x004444);

矩形平面PlaneGeometry设置颜色贴图

js
const geometry = new THREE.PlaneGeometry(60, 60);
const textureLoader = new THREE.TextureLoader();
const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load('./指南针.png'),
});
const mesh = new THREE.Mesh(geometry, material);

开启透明transparent: true

js
const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load('./指南针.png'),   
    //transparent: true:使用背景透明的png贴图,注意允许透明   
    transparent: true, 
});

旋转平移矩形平面

PlaneGeometry矩形平面默认是在XOY平面上,如果你想平行于XOZ平面,就需要手动旋转。

js
mesh.rotateX(-Math.PI/2);//平行地面:矩形Mesh默认单面可见,注意旋转-Math.PI / 2

如果你不想矩形平面Mesh与地面网格线重合,可以通过位置属性.position偏移。

js
mesh.position.y = 1;//适当偏移,不与地面重合

UV动画

通过改变uv坐标可以实现一个简单的uv动画

纹理对象Texture的.offset的功能是偏移贴图在Mesh上位置,本质上相当于修改了UV顶点坐标。

js
texture.offset.x +=0.5;//纹理U方向偏移
js
texture.offset.y +=0.5;//纹理V方向偏移

一般还需要结合纹理对象.wrapS或.wrapT与.offset组合使用

js
texture.offset.x +=0.5;//纹理U方向偏移
// 设置.wrapS也就是U方向,纹理映射模式(包裹模式)
texture.wrapS = THREE.RepeatWrapping;//对应offste.x偏移
js
texture.offset.y +=0.5;//纹理V方向偏移
// 设置.wrapT也就是V方向,纹理映射模式
texture.wrapT = THREE.RepeatWrapping;//对应offste.y偏移

怎么使用呢

纹理对象Texture的.offset的功能是偏移贴图在Mesh上位置。

js
// 渲染循环
function render() {
    texture.offset.x +=0.001;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

纹理贴图阵列 + UV动画

js
// 设置U方向阵列模式
texture.wrapS = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.x=50;//注意选择合适的阵列数量
js
// 渲染循环
function render() {
    texture.offset.x +=0.1;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

通过上述所说 我们可以实现一个简单的传送带功能

完整代码

js
import * as THREE from 'three';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
import earth from '../assets/纹理3.jpg';
const route = useRoute();

onMounted(() => {
  // 创建场景
  const scene = new THREE.Scene();
  const geometry = new THREE.PlaneGeometry(200, 20);
  //纹理贴图加载器TextureLoader
  const texLoader = new THREE.TextureLoader();

  // .load()方法加载图像,返回一个纹理对象Texture
  const texture = texLoader.load(earth);
  texture.colorSpace = THREE.SRGBColorSpace; //设置为SRGB颜色空间
  // 设置阵列
  texture.wrapS = THREE.RepeatWrapping;

  // uv两个方向纹理重复数量
  texture.repeat.x = 50; //注意选择合适的阵列数量

  const material = new THREE.MeshLambertMaterial({
    // color: 0x00ffff,
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture //map表示材质的颜色贴图属性
  });
  // 添加一个辅助网格地面
  //   const gridHelper = new THREE.GridHelper(300, 25, 0x004444, 0x004444);

  //   scene.add(gridHelper); //网格模型添加到场景中

  // material.map = texture;//也可以通过map属性直接设置,材质的参数设置一样

  const mesh = new THREE.Mesh(geometry, material);
  mesh.rotateX(-Math.PI / 2);
  mesh.position.y = 1;

  scene.add(mesh); //网格模型添加到场景中

  // AxesHelper:辅助观察的坐标系
  const axesHelper = new THREE.AxesHelper(100);
  scene.add(axesHelper);

  // tip: 环境光
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  directionalLight.position.set(100, 60, 50);
  scene.add(directionalLight);
  const ambient = new THREE.AmbientLight(0xffffff, 0.4);
  scene.add(ambient);

  //   const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
  //   scene.add(dirLightHelper);

  // width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
  const width = window.innerWidth; //宽度
  const height = window.innerHeight; //高度

  /**
   * 透视投影相机设置
   */
  // 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
  const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
  camera.position.set(292, 223, 185); //相机在Three.js三维坐标系中的位置
  camera.lookAt(0, 0, 0); //相机观察目标指向Three.js坐标系原点

  /**
   * 创建渲染器对象
   */
  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
  renderer.render(scene, camera); //执行渲染操作
  document.getElementById('canvas').appendChild(renderer.domElement);

  // 设置相机控件轨道控制器OrbitControls
  const controls = new OrbitControls(camera, renderer.domElement);

  function render() {
    texture.offset.x += 0.1; //设置纹理动画
    renderer.render(scene, camera); //执行渲染操作
    requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
  }
  render();

  window.onresize = () => {
    if (route.query.type === '1') {
      // 重置渲染器输出画布canvas尺寸
      renderer.setSize(window.innerWidth, window.innerHeight);
      return;
    } else if (route.query.type === '2') {
      // 重置渲染器输出画布canvas尺寸
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
      // 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
      // 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
      camera.updateProjectionMatrix();
    }
  };
});