Skip to content

78. 实战:T恤印花设计

Published:

上节学了贴花几何体,可以给网格模型表面加一些装饰:

2025-06-06 22.00.56.gif

这节我们来实现 T 恤印花设计的功能:

2025-06-06 21.06.44.gif

我们要做上传图片、切换颜色、切换图片等交互,这里需要用前端框架。

所以我们是 React + Three.js 来写,当然,你用 vue 也可以,都差不多。

创建项目:

npx create-vite t-shirt-design

image.png

选择 react、js 来创建 vite 项目。

进入项目,安装依赖:

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

去掉 StrictMode 和 index.css

image.png

然后改一下 App.jsx

import { useEffect, useRef, useState } from 'react';
import { init } from './3d-init'
import './App.css'

function App() {

  useEffect(() => {
    const dom = document.getElementById('content');
    const { scene } = init(dom);
  
    return () => {
      dom.innerHTML = '';
    }
  }, []);

  return <div>
    <div id="main">
      <div id="content">
      </div>
    </div>
  </div>
}

export default App

在 App.css 写下样式:

body {
  margin: 0;
}

然后来初始化 3d 场景:

创建 3d-init.js

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

export function init(dom) {

    const scene = new THREE.Scene();
    scene.add(mesh);

    const axesHelper = new THREE.AxesHelper(500);
    scene.add(axesHelper);

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

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

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

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

    const renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    renderer.setSize(width, height);

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

    render();

    dom.append(renderer.domElement);

    window.onresize = function () {
        const width = window.innerWidth;
        const height = window.innerHeight;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    };
    
    const controls = new OrbitControls(camera, renderer.domElement);

    return {
        scene,
        renderer,
        controls
    }
}

创建 mesh.js

import * as THREE from 'three';

const group = new THREE.Group();

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

group.add(mesh);

export default group;

我们先跑起来看下:

npm run dev

image.png

2025-06-07 08.24.39.gif

没啥问题。

然后我们找个T恤模型:

你可以从这里下载:

https://github.com/QuarkGluonPlasma/threejs-course-code/blob/main/t-shirt-design/public/tshirt.glb

image.png

放到 public 目录下:

image.png

在 mesh.js 加载下:

import * as THREE from 'three';
import { DRACOLoader, GLTFLoader } from 'three/examples/jsm/Addons.js';

const group = new THREE.Group();

const gltfLoader = new GLTFLoader();

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/versioned/decoders/1.5.6/' );
gltfLoader.setDRACOLoader(dracoLoader);

gltfLoader.load('./tshirt.glb', (gltf) => {
    group.add(gltf.scene);
    gltf.scene.scale.setScalar(1000);
});

export default group;

这个模型做了 draco 压缩,所以我们要用 DracoLoader 来解压。

看下效果:

2025-06-07 08.31.35.gif

没啥问题。

我们要在白色T恤上印花,那得知道位置。

这个用 RayCaster 在点击的时候获取就行。

先准备下图片:

image.png

heart.png

然后加一下点击事件:

image.png

const loader = new THREE.TextureLoader();
const texture = loader.load('./heart.png');
texture.colorSpace = THREE.SRGBColorSpace;

renderer.domElement.addEventListener('click', (e) => {
    const y = -((e.offsetY / height) * 2 - 1);
    const x = (e.offsetX / width) * 2 - 1;

    const rayCaster = new THREE.Raycaster();
    rayCaster.setFromCamera(new THREE.Vector2(x, y), camera);

    const intersections = rayCaster.intersectObjects(mesh.children);

    if(intersections.length) {
      const position = intersections[0].point;

      const orientation = new THREE.Euler();
      const size = new THREE.Vector3(100, 100, 100);
      const geometry = new DecalGeometry(intersections[0].object, position, orientation, size);
      const material = new THREE.MeshPhongMaterial({
          polygonOffset: true,
          polygonOffsetFactor: -4,
          map: texture,
          transparent: true,
      });
      const mesh = new THREE.Mesh( geometry, material );
      scene.add(mesh);
    }
});

点击的时候,拿到交点位置 intersections[0].point,在那个位置创建一个贴花几何体 DecalGeometry,设置颜色贴图为 heart.png

看下效果:

2025-06-07 08.40.17.gif

这样,就可以在T恤上印花了。

案例代码上传了小册仓库

总结

这节我们实现了T恤印花的功能。

首先,创建了 react + three.js 的项目,因为后面会有一些前端的表单交互。

加载了 draco 压缩的T恤模型,处理点击事件,在交点位置添加 DecalGeometry 实现印花功能。

下节我们来加上更多图案的选择,以及用户自己上传图案的功能。

评论