胖达老师带着three自由 | 大帅老猿threejs特训

第一课
Honeyview_9716.png

了解 three 安装和基础应用 做了一个 甜甜圈 案例

提到 Three.js,不得不先提 OpenGL 和 WebGL,OpenGL 是一个跨平台的3D/2D的绘图标准(规范),WebGL(Web Graphics Library)是一种3D绘图协议。

WebGL允许把JavaScript和OpenGL 结合在一起运用,但使用WebGL原生的API来写3D程序非常的复杂,同时需要相对较多的数学知识,对于开发者来说学习成本非常高。

Three.js是基于webGL的封装的一个易于使用且轻量级的3D库,Three.js对WebGL提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本,极大地提高了性能,功能也非常强大。

使用 npm 安装threejs

npm i three

然后,引入three

import * as THREE from 'three';

整体代码


<!--  -->
<template>
  <button @click="glbPlay">aaa</button>
  <div id="three" ref="three">
    <div ref="le" ></div>
  </div>
  
</template>

<script setup lang="ts">
  import {ref,reactive,onMounted,onUnmounted} from 'vue';
  import * as dat from 'dat.gui';
  import { 
    AmbientLight,
    AnimationMixer,
    AxesHelper,
    CameraHelper,
    Color,
    DirectionalLight,
    DirectionalLightHelper,
    EquirectangularReflectionMapping,
    HemisphereLight,
    LoopOnce,
    Mesh,
    MeshLambertMaterial,
    PerspectiveCamera,
    PlaneGeometry,
    PointLight,
    Scene, 
    SpotLight, 
    sRGBEncoding, 
    WebGLRenderer,
  } from 'three';
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
  import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';

  /**
   * 配置
   */
  // MARK: 配置
  const le = ref();
  const three = ref();
  const mixer = ref();
  const donuts = ref();
  const action = ref();
  const clips = ref();

  const scene = new Scene();  // 场景对象Scene
  const axes = new AxesHelper(50); // 轴长度
  
  const th = reactive({ 
    ctrl: new dat.GUI(),
    renderer:new WebGLRenderer({
      antialias: true
    }), // 渲染器对象
    ambienLight : new AmbientLight(0xcccccc), // 自然光
    planeGeometry:new PlaneGeometry(100,100),// 地面
    spotLight:new SpotLight(0xFFFFFF),// 聚光灯
    pointlight:new PointLight(0xFFFFFF,6,60),  // 点光源
    directionalLight:new DirectionalLight(0xFFFFFF,4), // 平行光源
    hemisphereLight : new HemisphereLight(0xffffff,0x00ff00,1)// 半球光光源
  });

  scene.background =new Color(0x333333);
  scene.add(axes); // 添加轴

  new RGBELoader()
    .load('./HDR/sky.hdr', function (texture) {
        scene.background = texture;
        texture.mapping = EquirectangularReflectionMapping;
        scene.environment = texture;
        th.renderer.outputEncoding = sRGBEncoding;
        th.renderer.render(scene, camera);
});


  let render=()=>{
    th.renderer.render(scene,camera);
  }

  /**
   * 影相机
   */
  // MARK: 影相机
  // let camera = ref<PerspectiveCamera|OrthographicCamera>(new PerspectiveCamera(75,window.innerWidth/window.innerHeight,5,10000)); //透视相机
  let camera = new PerspectiveCamera(75,window.innerWidth/window.innerHeight,10,1000); //透视相机
  camera.position.set(-20, 20, 30);
  camera.lookAt(scene.position);

  /**
   * 灯光
   */
  // MARK: 灯光
  th.spotLight.position.set(0, 30, 60);
  th.spotLight.castShadow = true;
  th.spotLight.intensity = 0.6;  // 光照强度
  // th.spotLight.shadow.mapSize = new Vector2(1024, 1024);  // shadow 与计算此光照的阴影
  // th.spotLight.shadow.camera.far = 130;  // far:远面
  // th.spotLight.shadow.camera.near = 40;  // near:近面
  scene.add(th.spotLight);

  scene.add(th.ambienLight); // 自然光

  // const pointlight = new PointLight(0xFFFFFF,6,60);  // 点光源
  th.pointlight.position.x = 0;
  th.pointlight.position.y = 0;
  th.pointlight.position.z = 5;
  // scene.add(th.pointlight);  // 点光源

  new DirectionalLight(0xFFFFFF,4) // 平行光源
  th.directionalLight.castShadow=true;
  th.directionalLight.shadow.mapSize.width = 2048;
  th.directionalLight.shadow.mapSize.height = 2048;
  th.directionalLight.position.set(20, 20, 20);
  // scene.add(th.directionalLight);

  const helper =new DirectionalLightHelper(th.directionalLight,5);
  // scene.add(helper);

  const dirCameraHelper = new CameraHelper(th.directionalLight.shadow.camera)
  // scene.add(dirCameraHelper);

  const hemisphereLight = new HemisphereLight(0xffffff,0x00ff00,1);
  hemisphereLight.position.set(100,50,100);
  // scene.add(hemisphereLight);// 半球光光源



  /**
   * 渲染 场景
   */
  // MARK: 渲染 场景
  th.renderer.shadowMap.enabled = true;
  th.renderer.setSize(window.innerWidth,window.innerHeight);

  
  /**
   * 地面
   */
  // MARK: 地面
  const lambertMaterial = new MeshLambertMaterial({//材质对象Lambert
    color:0xcccccc
  });
  const plane = new Mesh(th.planeGeometry,lambertMaterial); // 网格模型
  plane.rotation.x=-0.5*Math.PI;
  plane.position.set(15, 0, 0);
  plane.receiveShadow = true;
  // scene.add(plane);

  /**
   * OrbitControls 轨道控制器
   */
  // MARK: OrbitControls 轨道控制器
  let controls = new OrbitControls(camera,th.renderer.domElement);
  controls.update();

  /**
   * 导入模型
   */
  // MARK: 导入模型

  new GLTFLoader().setPath('./model/test/').load('test.glb',gltf=>{
    
    console.log(gltf);
    scene.add(gltf.scene);
    donuts.value = gltf.scene;

    // gltf.scene.traverse((child)=>{
    //     console.log(child.name);
    // })
    
    clips.value = gltf.animations; // 播放所有动画
    
  });

  let glbPlay=()=>{
    mixer.value = new AnimationMixer(donuts.value);
    clips.value.forEach( (clip: any)=> {
      action.value = mixer.value.clipAction(clip);
      action.value.loop = LoopOnce;
      action.value.timeScale=1.5;
      // 停在最后一帧
      action.value.clampWhenFinished = true;
      action.value.play();
    });
  }

  //============================================场景搭建end==================================

  onMounted(()=>{
    
    le.value.appendChild(th.renderer.domElement);
    
    window.addEventListener('resize',()=>{
      camera.aspect = window.innerWidth/window.innerHeight;
      camera.updateProjectionMatrix();
      th.renderer.setSize(window.innerWidth,window.innerHeight);
      render();
    },false);
  });

  

  //============================================构建场景 start==================================
  // MARK: 构建场景


  //============================================构建场景 end==================================

  let renderScene=():void=>{
    controls.update();
    requestAnimationFrame(renderScene);
    render();
    if (donuts.value){
        donuts.value.rotation.y += 0.01;
    }

    if (mixer.value) {
        mixer.value.update(0.02);
    }
  }
  renderScene()

  onUnmounted(()=>{
    th.ctrl.destroy()  // 销毁dat.GUI
  });

</script>

<style scoped lang="less">
  @import '~@/common/less/index.less';

</style>

以上代码可以直接运行,运行的效果如下

QQ截图20230107112323.png

因为自己会建模,所以没有用到老师的模形

第二课

Blender 的建模基础

aaa.png

微信图片_20230107120347.png

主要自己会建模这一课没怎么认真听

第三课

元宇宙开发

角色引入

// 角色引入

new GLTFLoader().setPath("./model/test/").load("player.glb", gltf => {
  
  playerMesh.value = gltf.scene;
  gltf.scene.position.set(0, -1.43, 14.5);
  gltf.scene.rotateY(Math.PI);

  gltf.scene.traverse((child)=>{
    child.receiveShadow = true;
    child.castShadow = true;
  })

  camera.position.set(0, 2.5, -4);
  camera.lookAt(lookTarget);

  const pointLight: PointLight = new PointLight(0xffffff, 1);
  pointLight.position.set(0, 1.8, -1);


  playerMixer.value = new AnimationMixer(gltf.scene);

  const clipWalk = AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 30);  // 走路动画
  actionWalk.value = playerMixer.value.clipAction(clipWalk);

  const clipIdle = AnimationUtils.subclip(gltf.animations[0], 'idle', 31, 281);  // 待机动画
  actionIdle.value = playerMixer.value.clipAction(clipIdle);
  actionIdle.value.play();


  gltf.scene.add(pointLight);
  gltf.scene.add(camera);
  scene.add(gltf.scene);
});

场景搭建


// 场景的整体搭建 
// 给场景上贴图 和 贴视频

new GLTFLoader().setPath("./model/test/").load("caab1.glb", (gltf) => {
  // console.log(gltf);
  scene.add(gltf.scene);
  donuts.value = gltf.scene;

  

  gltf.scene.traverse((child) => {
    // console.log(child.name);
    child.castShadow = true;
    child.receiveShadow = true;

    console.log(child.name);
    
    if (child.name == "2023") {
      happy.value = child;
      const video = document.createElement("video");
      video.src = "./img/yanhua.mp4";
      video.muted = true;
      video.autoplay = true;
      video.loop = true;
      video.play();

      // console.log(document.body);
      // document.body.append(video);

      const videoTexture = new VideoTexture(video);
      const videoMaterial = new MeshBasicMaterial({ map: videoTexture });

      (child as any).material = videoMaterial;
    }

    if (child.name === "video1") {
      const video = document.createElement("video");
      video.src = "./img/video1.mp4";
      video.muted = true;
      video.autoplay = true;
      video.loop = true;
      video.play();

      const videoTexture = new VideoTexture(video);
      const videoMaterial = new MeshBasicMaterial({ map: videoTexture });

      (child as any).material = videoMaterial;
    }
    if (child.name === "video2") {
      const video = document.createElement("video");
      video.src = "./img/video2.mp4";
      video.muted = true;
      video.autoplay = true;
      video.loop = true;
      video.play();

      const videoTexture = new VideoTexture(video);
      const videoMaterial = new MeshBasicMaterial({ map: videoTexture });

      (child as any).material = videoMaterial;
    }

    if (child.name === "ccc1") {
      const video = document.createElement("video");
      video.src = "./img/ccc1.mp4";
      video.muted = true;
      video.autoplay = true;
      video.loop = true;
      video.play();

      const videoTexture = new VideoTexture(video);
      const videoMaterial = new MeshBasicMaterial({ map: videoTexture });

      (child as any).material = videoMaterial;
    }

    if (child.name === "bbb1" || child.name === "bbb2" || child.name === "bbb3") {
      const video = document.createElement("video");
      video.src = "./img/bbb1.mp4";
      video.muted = true;
      video.autoplay = true;
      video.loop = true;
      video.play();

      const videoTexture = new VideoTexture(video);
      const videoMaterial = new MeshBasicMaterial({ map: videoTexture });

      (child as any).material = videoMaterial;
    }

    if (child.name == "image1") {
      let imageTexture = new TextureLoader().load('./img/Cam1.jpg');
      let imageMaterial = new MeshBasicMaterial({
        map:imageTexture,
        side:BackSide
      });

      (child as any).material = imageMaterial;
    }

    if (child.name == "image2") {
      let imageTexture = new TextureLoader().load('./img/Cam2.jpg');
      let imageMaterial = new MeshBasicMaterial({
        map:imageTexture,
        side:BackSide
      });

      (child as any).material = imageMaterial;
    }

    if (child.name == "image3") {
      let imageTexture = new TextureLoader().load('./img/Came3.jpg');
      let imageMaterial = new MeshBasicMaterial({
        map:imageTexture,
        side:BackSide
      });

      (child as any).material = imageMaterial;
    }
    if (child.name == "image4") {
      let imageTexture = new TextureLoader().load('./img/Came4.jpg');
      let imageMaterial = new MeshBasicMaterial({
        map:imageTexture,
        side:BackSide
      });

      (child as any).material = imageMaterial;
    }

  });
});

场景搭建


// 碰撞检测

window.addEventListener("keydown", (e) => {
  if (e.key == "w") {
    // if(playerMesh.value){
    //   playerMesh.value.translateZ(0.1);
    // }

    if(playerMesh.value){
      const curPos = playerMesh.value.position.clone();
      playerMesh.value.translateZ(1);
      const frontPos = playerMesh.value.position.clone();
      playerMesh.value.translateZ(-1);
      
      const frontVector3 = frontPos.sub(curPos).normalize()

      const raycasterFront = new Raycaster(playerMesh.value.position.clone().add(playerHalfHeight), frontVector3);
      const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children);

      console.log(collisionResultsFrontObjs);


      if ((collisionResultsFrontObjs  && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1)  || collisionResultsFrontObjs.length==0) {
        console.log('AAAAAAAAAAAAAAAAAAAAAAw');
        playerMesh.value.translateZ(0.1);
      }
    }

    if (!isWalk.value) {
      if( actionIdle.value && actionWalk.value){
        crossPlay(actionIdle.value,actionWalk.value);
      }
      isWalk.value = true;
    }
    
  }

微信图片_20230107120353.png

three元宇宙开发 | 大帅老猿threejs特训

结束语

可以加入猿创营 (v:dashuailaoyuan),一起交流学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值