GVKun编程网logo

three.js的问题(three.js例子)

85

本文将带您了解关于three.js的问题的新内容,同时我们还将为您解释three.js例子的相关知识,另外,我们还将为您提供关于blender建模&threejs开发初体验|大帅老猿threejs特训

本文将带您了解关于three.js的问题的新内容,同时我们还将为您解释three.js例子的相关知识,另外,我们还将为您提供关于blender建模 & threejs开发初体验 | 大帅老猿threejs特训、javascript – Three.js – 未捕获的ReferenceError:未定义THREE、Lanuch threejs with 甜甜圈| 大帅老猿threejs特训、three 元宇宙开发 | 大帅老猿threejs特训的实用信息。

本文目录一览:

three.js的问题(three.js例子)

three.js的问题(three.js例子)

@抢小孩糖吃 你好,想跟你请教个问题:我们现在需要运用web3d。实现用户DIY家居产品。实现沙发的拼装和沙发布料的选择。请问three.js能够实现吗?有没有相关的文章?谢谢

blender建模 & threejs开发初体验 | 大帅老猿threejs特训

blender建模 & threejs开发初体验 | 大帅老猿threejs特训

之前看到过threejs做的各种炫酷的案例,甚是有趣,一直打算自己也能写个demo尝试下,这次就跟着教程来学习下,小小的入个门。

建模

这次是用blender建的模,当然也可以用其他软件。

建模这个其实没啥好说的,各种细节非常琐碎,就是一个熟能生巧的过程。教程对新手还是比较友好的,每一步的具体操作,快捷键等,教程都有讲到。多看几遍视频,入门blender建模不是问题。

也是心有点大,不想完全复制课上讲的场馆,就按自己想法搭个稍微不一样点的。那就来个大话西游(大爱紫霞)里的场景吧。

image.png

对于新手来说,建模完全就是一个缓慢而纠结的过程,遇到各种各样的问题自不必说,跟着教程一步步来就好,其中细节此处略过不表。

插一句

新手可能会遇到无法插入中文文本的问题,稍微提一下:
由于默认不支持中文,所以添加文本的时候选择下支持中文的字体(比如`Microsoft YaHei`)
然后把需要的文字从别的地方复制过来,在编辑模式下粘贴就OK了

hanzi.png

人物

有了大概的模型后,当然想让场景能动起来,像游戏里一样,加入一个人物,并控制着四处走动,参观下场景^_^。

人物模型可以在网上白嫖找一个,比如sketchfab,此处我们先使用教程提供的模型。

model.png

一般模型荡下来时都是静态的,要想有走动的动作,还要进行骨骼绑定,之后我们可以获取到走动-walk静止-idle两种动作。

walk.gif

绑定动作

通过threejs中的动画混合器,可以剪辑(clip)出上面的两种动作

code1.png

然后判断键盘按下的是否是行走(w)键来对应的播放走动-walk静止-idle的模型动画

code2.png

此处crossPlay是做了一个动画切换的优化。

视角控制

像游戏里一样,视角跟随鼠标进行转动,其实是相机的视角切换,此处我们可以获取到鼠标的移动时的位置,然后计算出clientX的差值,给予一定权重后让人物转动(rotateY)相应角度即可。

先要把相机cameraadd到任务模型

code3.png

射线检测

此处依据Raycaster进行了射线碰撞检测,简单来讲,就是以人物中心为起点,向各个方向发出射线,然后检测射线是否与其他物体相交,若相交再根据两个中心点距离来判断是否会发生碰撞。

结尾

blender建模真的是细活,一点点抠,很需要耐心啊。。

以上只是一点浅见,这次课程真的是收获蛮多,希望多来点这种课程。

javascript – Three.js – 未捕获的ReferenceError:未定义THREE

javascript – Three.js – 未捕获的ReferenceError:未定义THREE

当我运行我的 JavaScript代码.我收到以下错误“Uncaught ReferenceError:THREE未定义”.
提到的路线是:

var renderer = new THREE.Webglrenderer();

// I have attached the three.js library in the script tag. I don't kNow what seems to be problem.

var scene = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.set = (0,10);
camera.lookAt(camera.position);
scene.add(camera);

var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0.0,1.0,0.0));
geometry.vertices.push(new THREE.Vector3(-1.0,-1.0,0.0));
geometry.vertices.push(new THREE.Vector3(1.0,0.0));
geometry.faces.push(new THREE.Face3(0,1,2));

var material = new THREE.BasicmeshMaterial({
    color: 0xFFFFFF,side: THREE.DoubleSide
});

var mesh = new THREE.Mesh(geometry,material);
mesh.position.set(-1.5,0.0,4.0);
scene.add(mesh);

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

render();

解决方法

你需要先包含three.js

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>

Lanuch threejs with 甜甜圈| 大帅老猿threejs特训

Lanuch threejs with 甜甜圈| 大帅老猿threejs特训

WebGL简介

WebGL(全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。
WebGL使用需要图形学知识,对WebGL编程可以通过js和glsl两种语言。如果想直接使用WebGL,使用者可以采用着色器(Shader)用来实现图像渲染的,但对于新手来说,Shader还是困难的。这时我们可以使用Three.js来简化我们对底层图形学的开发知识,更快的上手3D开发过程。

用Threejs实现一个甜甜圈demo

image.png

  • github地址

安转依赖

npm ci

import * as THREE from ''three'';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { RGBELoader } from ''three/examples/jsm/loaders/RGBELoader'';

let mixer;

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

camera.position.set(5, 10, 25);

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

scene.background = new THREE.Color(0.2, 0.2, 0.2);

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

const directionLight = new THREE.DirectionalLight(0xffffff, 0.4);
scene.add(directionLight);

// const boxGeometry = new THREE.BoxGeometry(1,1,1);
// const boxMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00});
// const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
// scene.add(boxMesh);

new GLTFLoader().load(''../resources/models/zhanguan.glb'', (gltf) => {

    // console.log(gltf);
    scene.add(gltf.scene);

    gltf.scene.traverse((child)=>{
        // console.log(child.name);
        if (child.name === ''2023'') {
            const video = document.createElement(''video'');
            video.src = "./resources/yanhua.mp4";
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
        
            child.material = videoMaterial;
        }
        if (child.name === ''大屏幕01'') {
            const video = document.createElement(''video'');
            video.src = "./resources/video01.mp4";
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
        
            child.material = videoMaterial;
        }
        if (child.name === ''大屏幕02'' || child.name === ''操作台屏幕'') {
            const video = document.createElement(''video'');
            video.src = "./resources/video01.mp4";
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
        
            child.material = videoMaterial;
        }
        if (child.name === ''环形屏幕'') {
            const video = document.createElement(''video'');
            video.src = "./resources/video02.mp4";
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
        
            child.material = videoMaterial;
        }
        if (child.name === ''柱子屏幕'') {
            const video = document.createElement(''video'');
            video.src = "./resources/yanhua.mp4";
            video.muted = true;
            video.autoplay = "autoplay";
            video.loop = true;
            video.play();
            
            const videoTexture = new THREE.VideoTexture(video);
            const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
        
            child.material = videoMaterial;
        }
    })



    mixer = new THREE.AnimationMixer(gltf.scene);
    const clips = gltf.animations; // 播放所有动画
    clips.forEach(function (clip) {
        const action = mixer.clipAction(clip);
        action.loop = THREE.LoopOnce;
        // 停在最后一帧
        action.clampWhenFinished = true;
        action.play();
    });

})

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

function animate() {
    requestAnimationFrame(animate);

    renderer.render(scene, camera);

    controls.update();

    // if (donuts){
    //     donuts.rotation.y += 0.01;
    // }

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

animate();

最后

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

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

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

不管做什么还是要坚持,坚持才会迎来胜利。如果你有兴趣的话,也可以加入猿创营 (v:dashuailaoyuan),一起交流学习

关于three.js的问题three.js例子的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于blender建模 & threejs开发初体验 | 大帅老猿threejs特训、javascript – Three.js – 未捕获的ReferenceError:未定义THREE、Lanuch threejs with 甜甜圈| 大帅老猿threejs特训、three 元宇宙开发 | 大帅老猿threejs特训等相关内容,可以在本站寻找。

本文标签: