Skip to content

39. 几何体材质共用和 clone、copy

Published:

前面做实战的时候,你可能会遇到一个问题:

改了一个 mesh 的几何体或者材质,另一个 mesh 也跟着变了。

因为它们共用一个 geometry、material。

我们先来复现一下:

npx create-vite material-share

image.png

进入项目,安装依赖:

npm install
npm install --save three
npm install --save-dev @types/three

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();

const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshLambertMaterial({
  color: 'orange',
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const directionLight = new THREE.DirectionalLight(0xffffff, 2);
directionLight.position.set(500, 400, 300);
scene.add(directionLight);

const ambientLight = new THREE.AmbientLight();
scene.add(ambientLight);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
camera.position.set(300, 300, 500);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

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

创建 Scene、Light、Camera、Renderer。

画了一个立方体。

改下 style.css

body {
  margin: 0;
}

跑一下:

npm run dev

image.png

2025-04-05 13.01.07.gif

然后我们复制一个立方体出来:

image.png

给它设置了一个绿色,并且改了下位置。

const mesh2 = mesh.clone();
mesh2.material.color.set('lightgreen');
mesh2.position.y = 200;
scene.add(mesh2);

image.png

你会发现之前的立方体的材质颜色也改了。

我们再改下顶点试试:

image.png

const positions = mesh2.geometry.attributes.position;
for(let i = 0; i< positions.count; i++) {
  positions.setX(i, positions.getX(i) * 2);
}

遍历顶点位置,给 x 坐标乘以 2

看一下:

image.png

也是 mesh 也跟着变了。

因为 mesh 和 mesh2 的 geometry 和 material 是共用的。

如何解决这个问题呢?

clone 一份就好了:

image.png

mesh2.geometry = mesh2.geometry.clone();
mesh2.material = mesh2.material.clone();

image.png

这样就好了。

但材质的共用也不完全是不好的,很多 mesh 共用一个材质的话,那我们是不是可以一起修改?

而且可以通过修改 material.visible 来达到批量隐藏一些 mesh 的效果。

我们试一下:

image.png

我们又 clone 了两个 mesh

const mesh3 = mesh.clone();
mesh3.position.x = 200;
const mesh4 = mesh.clone();
mesh4.position.x = -200;
scene.add(mesh3, mesh4);

image.png

这三个 mesh 是共用材质的,想让这三个 mesh 隐藏,就可以通过这点来做到:

image.png

mesh3.material.visible = false;

image.png

这样就都隐藏了。

除了 clone 外,还有个 copy 方法,那个是复制值用的。

比如我们让 mesh 旋转下:

image.png

创建一个 Clock 通过 getDelta 拿到两次渲染之间的间隔,作为每次 rotateY 的值。

const clock = new THREE.Clock();
function render() {
    const delta = clock.getDelta(); 
    mesh.rotateY(delta);

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

2025-04-05 13.16.18.gif

如果想让 mesh2、mesh3、mesh4 同步旋转呢?

就可以用 copy:

image.png

mesh2.rotation.copy(mesh.rotation);
mesh3.rotation.copy(mesh.rotation);
mesh4.rotation.copy(mesh.rotation);

2025-04-05 13.17.54.gif

再复杂的旋转也是一样:

image.png

mesh.rotateY(delta * Math.random());
mesh.rotateX(delta * Math.random());
mesh.rotateZ(delta * Math.random());

2025-04-05 13.19.23.gif

依然是完全同步的。

copy 是把传入的对象的值复制给当前对象,而 clone 是创建一个新的对象。

前面我们做林海雪原实战的时候,用过一个树的模型。

把它拿过来:

https://github.com/QuarkGluonPlasma/threejs-course-code/tree/main/snowy-forest/public/tree

image.png

点击下载按钮,下载这两个文件

放在 public 目录下:

image.png

在代码里引入。

创建 src/tree.js

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const tree = new THREE.Group();

const loader = new GLTFLoader();

function loadTree(callback) {
    loader.load('./tree/tree.gltf', gltf => {
        console.log(gltf);
    
        tree.add(gltf.scene);

        callback(tree);
    });
}

export default loadTree;

然后在 main.js 复制几棵树出来:

image.png

loadTree(tree => {
  const group = new THREE.Group();

  tree.scale.set(20, 20, 20);

  const tree2 = tree.clone();
  tree2.position.x = -200;
  const tree3 = tree.clone();
  tree3.position.x = 200;

  group.add(tree, tree2, tree3);
  group.position.z = 300;
  scene.add(group);
});

用一个 Group 来管理这三棵树,group 整体改变 z,内部树改变 x

2025-04-05 13.28.30.gif

然后我们改下材质颜色:

image.png

tree.getObjectByName('leaves001').material.color.set('green');
tree.getObjectByName('tree001').material.color.set('brown');

2025-04-05 13.31.49.gif

我们只改了一棵树的材质颜色,另外的也都跟着变了。

这就是材质共用的问题。

当然,这样可以批量修改所有树的颜色还是很方便的。

但如果你要给复制出来的两棵树不同的颜色呢?

image.png

tree2.traverse(obj => {
      if (obj.isMesh) {
          obj.material = obj.material.clone();
      }
  })
  tree2.getObjectByName('leaves001').material.color.set('orange');

批量 clone 一下所有 mesh 的材质就好了。

image.png

这就是常见的材质共用问题。

案例代码上传了小册仓库

总结

这节我们探讨了下 mesh 的 geometry 和 material 共用的问题。

它有的时候很方便,比如可以批量修改这些 mesh 的材质颜色,可以设置 visible 批量隐藏。

但有的时候会有相互影响的问题,这时候 clone 一份新的 geometry 或者 material 就好了。

此外,当你想复制值的时候,可以直接用 copy 方法,比如 rotation.copy、position.copy,它会复制目标对象的值到当前对象。

材质共用问题是非常常见的问题,clone、copy 也是很常用的方法,后面会大量用到。

评论