• 教你如何利用threejs对3D模型皮肤进行DIY


    一步一步教你如何利用threejs加载gltf模型来实现DIY换肤功能。

    模型准备

    • 模型制作

      模型可以通过网上下载,也可以自己通过c4d、maya、blender等模型制作软件得到。这里就不叙述有关模型制作的问题,本文中会在blender进行模型的有关设置。

    • 模型导出

      1、导出前设定

      为了在页面中方便后续的操作,在导出模型前,将模型的各个部件拆分好进行命名约定(本文以小车模型为例)具体如下图所示:
      图1
      2、导出模型格式选取

      threejs可以加载的模型有很多中,之前.ojb、.json、.FBX等格式都有讲过参我之前的文章从Maya中把模型搬运至网页的过程首个threejs-3D项目,所以我这里选取官方推荐现在使用的格式.gltf、.glb。

      gltf与glb的区别: gltf文件类似与json格式而glb是以二进制流进行存储。

      3、模型导出

      在blender中直接有gltf格式导出的选项,如果没有特别的要求,按照默认配置导出就可以了。导出界面如下图所示:
      图2

    场景建立

    • 使用threejs建立一个场景

    首先将需要的东西从threejs (r110) 中引入,然后进行建立场景四部曲:

      import {Scene, WebGLRenderer, PerspectiveCamera, Color} from 'three';
    

    1、Scene

      let scene = new Scene();
      scene.background = new Color(0xB3CEFB);
      scene.fog = new Fog(scene.background, 1, 100);
    

    2、Camera

      let camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
      camera.position.z = 15;
    

    3、Render

      let renderer = new WebGLRenderer({
        alpha: true,
        antialias: true
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
    

    4、Animate

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

    然后就会看到一个蓝蓝的场景(因为设置了背景颜色)

    加载模型

    • GLTFLoader加载模型至场景
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
    
    const loader = function () {
      let gltfLoader = new GLTFLoader();
      gltfLoader.load(
        'toycar.glb', // your .glb & .gltf
        gltf => {
          scene.add(gltf.scene);  // 添加至建立好的场景中
    
          // gltf.animations; // Array<THREE.AnimationClip>
          // gltf.scene; // THREE.Group
          // gltf.scenes; // Array<THREE.Group>
          // gltf.cameras; // Array<THREE.Camera>
          // gltf.asset; // Object
        },
        () => {
          // ..
        },
        (error) => {
          console.log(error);
        }
      )
    }
    

    GLTFLoader加载成功后会返回一个对象,其中scene.children中会包含所导出的所有部件。具体返回以及参数介绍可以查看参考文档

    添加至场景后可以看到一个漆黑的汽车,如下图所示:
    图3

    这是因为场景中没有光源的照射所以导致漆黑一片,只能看一个轮廓,所以我么需要在建立的场景中增加灯关。

    • 添加灯光

    threejs中灯光有很多中,这里我们添加DirectionalLight(平行光)HemisphereLight(半球光)

      import {
        DirectionalLight, 
        DirectionalLightHelper, 
        HemisphereLight, 
        HemisphereLightHelper
      } from 'three';
      
      // Helper为灯关的辅助线方便调试
    
      let directionalLight = new DirectionalLight(0xffffff, 0.5);
      directionalLight.position.set(-4, 8, 4);
      let dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000); 
    
      let hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4);
      hemisphereLight.position.set(0, 8, 0);
      let hHelper = new HemisphereLightHelper(hemisphereLight, 5);
    
      scene.add(directionalLight);
      scene.add(hemisphereLight);
    

    添加灯光后效果如图:
    图4

    • 阴影渲染

    接着我们将设置模型的阴影显示,阴影显示需要光源和投射地(阴影显示的地方),现在我们已经有了光源差投射地,所以我们创建一个地板,让阴影投射至地板上,来达到想要的效果:

      // 制作一个地板
      import {PlaneGeometry, MeshPhongMaterial, Mesh} from 'three';
      
      let floorGeometry = new PlaneGeometry(5000, 5000, 1);
      let floorMaterial = new MeshPhongMaterial({
        color: 0x77F28F,
        shininess: 0,
        // wireframe: true
      });
      let floor = new Mesh(floorGeometry, floorMaterial);
      floor.rotation.x = -0.5 * Math.PI;
      floor.position.y = -2.1;
    
      scene.add(floor);
    

    现在我们可以看到一个绿色的地板出现在场景中,但是还是不见车子的阴影,这是因为我们还要对光源、渲染器、模型、地板进行设置才能显示阴影:

      
      // 首先渲染器开启阴影
      renderer.shadowMap.enabled = true;
    
      // 光源开启阴影
      directionalLight.castShadow = true;
      directionalLight.shadow.mapSize = new Vector2(1024, 1024);
    
      // 地板接受阴影开启
      floor.receiveShadow = true;
    
      // 模型Mesh开启阴影
      gltf.scene.traverse(obj => {
        if(obj.isMesh) {
        obj.castShadow = true;
        obj.receiveShadow = true;
        }
      })
    

    这时候就可以看到小车的阴影渲染到绘制的地板上了,然后你可能会看到车身上有很多条纹状的黑线,这是因为在渲染阴影中产生了伪影,然后我们可以调节light.shadow.bias来解决

      directionalLight.shadow.bias = -0.001;  // value 自行调节
    

    阴影渲染对比如下图所示:
    图5

    换肤功能

    想要实现各个部件换肤功能,我们需要选中部件,修改选中部件材质来达到我们换肤的功能。

    • 部件选中

    选取部件其实比较简单,在场景中加入射线检测就可以了,实现如下:

      import {Vector2, Vector3, Raycaster} from 'three';
      
      let raycaster = new Raycaster();
    
      let mouse = new Vector2();
    
      document.body.addEventListener('click', selectHandler, false);
    
      const selectHandler = function (ev) {
        mouse.x = (ev.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1;
        
        raycaster.setFromCamera(mouse, camera);
    
        // 这里我们只检测模型的选中情况
        let intersects = raycaster.intersectObjects(gltf.scene.children, true);
    
        if (intersects.length > 0) {
          let selectedObjects = intersects[0].object;
        }
      }
    
    • 设置颜色或者纹理

    现在我们得到部件,现在只需要修改材质颜色即可。

    核心code如下:

    let newMaterial = selectedObjects.material.clone();
    newMaterial.color = new Color('#D3C542'); //重新修改颜色
    selectedObjects.material = newMaterial;
    

    如果需要用图片,那么更改方式更修改图片类似,先用TextureLoader()加载纹理图,然后设置material.map,最后更新material就可以了。

    • 添加选中效果

    这里想增加一个选中部件后,那个部件进行外发光的效果,让用户觉得自己是选中了这个部件。这个外发光的效果可以自己用ShaderMaterial()去实现,我这边用的threejs在postprocessing提供的效果,具体实现如下所示:

      import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
      
      import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
      
      import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
    
      let composer = new EffectComposer(renderer);
      let renderPass = new RenderPass(scene, camera);
      composer.addPass(renderPass);
    
      let outlinePass = new OutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera);
      composer.addPass(outlinePass);
    
      outlinePass.visibleEdgeColor.set('#130AF2'); // 选中颜色
      outlinePass.edgeStrength = 5;
      outlinePass.edgeGlow = 1.5;
      
      const render = function () {
        requestAnimationFrame(render);
        composer.render();
      }
    

    功能展示(gif图的效果可能没那么好):

    图6

    至此使用threejs给3d模型的基本操作就是这样的了,剩下的发挥靠自己想象吧。

    其它功能效果

    • camera-controls

      模型的旋转控制,这里直接使用threejs提供的控件OrbitControls()

      import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
      
      let controls = new OrbitControls(camera, renderer.domElement);
      controls.enablePan = false;
      controls.maxPolarAngle = Math.PI / 2;
      controls.minPolarAngle = Math.PI / 3;
      controls.enableZoom = false;
      
      controls.update();
      
    • 部件分离动画

      分离动画:简单点解释就是给各个部件的位置设置一个开始的点,一个结束点,然后利用TweenLite、TweenMax进行补间动画, 下面以车声为例子:

      // 车身上移动画
      let component = gltf.scene.getObjectByName('car_body');
      TweenLite.to(component.position, 1.5, {
        y: 5,
        ease: Power4.easeOut
      });
      
    • 重影动画

      利用threejs的提供的postprocessing来实现部件移动的时候会产生重影,实现如下:

      import { AfterimagePass } from 'three/examples/jsm/postprocessing/AfterimagePass.js';
      
      let afterimagePass = new AfterimagePass();
        
      composer.addPass(afterimagePass);
      

      功能展示(gif图的效果可能没那么好):

    图7

    最后的最后

    如果以上有啥错误或者有啥要交流的欢迎骚扰:(吃口饭不容易!)

    wechat: flowers1225

    gmail: asdflowers1225@gmail.com

    github: https://github.com/flowers1225

    个人网站:https://flowers1225.com/

  • 相关阅读:
    常见排序算法的实现和比较
    关于数据库索引的原理和应用
    数据库查询优化的几种方式
    进程间通信的几种方式
    TCP的三次握手和四次分手
    【面试题35】第一个只出现一次的字符
    【面试题34】丑数
    【面试题33】把数组排成最小的数
    【面试题32】从1到n整数中1出现的次数
    简单工厂模式
  • 原文地址:https://www.cnblogs.com/pursues/p/12598402.html
Copyright © 2020-2023  润新知