Three.js入门 (参考胖达老师的视频) | 大帅老猿threejs特训

第一天 Three.js基础

空间坐标系

image.png

右手坐标系,threejs朝上是Y,朝右是X,朝屏幕外面是Z,Blender朝上是Z

Blender视图基本操作

A 全选
AA 取消全选(按两次A)
Shift +D 复制物体(右键或者ESC取消移动)
Shift+鼠标中键盘 移动视角
鼠标滚轮 推拉视角
鼠标中键按住 旋转视角
G grab 移动
S scale 缩放
R rotate 旋转
小键盘 . 聚焦选中物体
X 或 Delete 删除
Shift + A 添加模型
TAB 编辑模式/物体模式切换
TAB模式下 1 2 3 切换 点 线 面 几种选择模式
环选边 Alt+ 点击边(编辑模式--边模式下)
Ctrl + B 切角 (编辑模式-选中切角边,滚动鼠标中键可以增减分段数)
M 合并(焊接)顶点
/ 独立显示选中物体,恢复显示全部

编辑模式 tab
内插面 i
挤出 e
移动 g(再按 x、y、z 锁定到对应轴)
旋转 r
缩放 s
删除点线面 x 或 Del

如果是录视频,可以先安装Screencast Keys插件,这个插件从github下面去下载 https://github.com/nutti/Scre...

甜甜圈

打开Blender,导入原始没有处理过的模型 donuts_原始未处理.glb

Mac Book上面可以用 Shift + 右键 移动视角, Control + 右键 推拉视角
这时缩小的时候,远处有一部分看不到,是因为甜甜圈的尺寸太大,观察视图的远裁剪面裁剪造成的。

image.png

Shift + A 添加一个立方体(Cube),这个时候发现新加的立方体很小,是甜甜圈太大了。删除立方体,用A键选择所有,或者用Select Hierarchy选中甜甜圈
使用缩放工具Scale,可以同时选中X,Y,Z,然后输入0.001,缩小1000倍。如果要让甜甜圈回到视角中心,用小键盘的.这个键或者Shift + C

参考高两米的立方体,进一步用鼠标调整尺寸到合适的大小,调整好尺寸后,要应用一下,使用Control + A, 然后选Rotate and Scale把尺寸缩放应用到物体上。这个时候,视图缩放不会看到被裁剪了。

Screen Recording 2022-12-25 at 21.34.57.gif

然后要对甜甜圈进行拆分,因为每个甜甜圈单独掉落。

image.png

用tab键进入编辑模式,用1,2,3选不同的模式

Screen Recording 2022-12-26 at 21.29.06.gif

然后用左边栏的Cursor图标,从顶部选,然后点右键,选拆分

image.png
image.png
image.png

拆分完成后,可以在Blender里面直接做出下落的动画。如果已经有动画,可以先清一下,删除关键帧。

先给每个甜甜圈加上刚体Rigid Body,然后选托盘,托盘设为被动刚体。这样就可以放出动画了。
然后可以把这个动画烘焙到关键帧,选中所有甜甜圈,Layout --- Object --- Rigid Body --- Bake to Keyframes

导入threejs,创建三大件

先创建场景THREE.Scene
然后创建透视相机PerspectiveCamera,第一个参数fov是广角
接着是WebGLRenderer

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 10)
const renderer = new THREE.WebGLRenderer({ antialias: true})
renderer.setSize(window.innerWidth, window.innerHeight) // 场景大小可以超过屏幕,可以是div对应的大小
document.body.appendChild(renderer.domElement)

灯光

const ambientLight = new THREE.AmbientLight(0xffffff, 0.2)  // 0.4
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6)  // 1
scene.add(directionalLight)

加入物体 boxMesh 由boxGeometry和boxMaterial组成,例如

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)

这个时候用npm start运行出来是黑的,原因是相机的位置和角度不对

这个时候用threejs.org/editor来调试排查一下
先加一个box,然后再加一个环境光,饱和度Intensity设为0.5
然后加一个PerspectiveCamera,此时相机相当于扣到盒子里了

Screen Recording 2022-12-25 at 23.40.51.gif

在三维图形里面,平面都不是双面显示。加入一个平面,把这个平面的Y坐标设为3,与box分开。然后把环境光调亮到2.2,如果从正面可以看到这个面,反面看就是透明的。这个叫背面剔除,背面就不显示了。当然代码或者unity3d里面可以特殊处理,把平面设为双面显示。

当切换到透视相机,相机在盒子里面,从中心往外看,全是透明,所以一片漆黑。
现在把透视相机的Z坐标改为2,拉出盒子,然后从透视相机看,就可以看到盒子了。

右手坐标系,threejs朝上是Y,朝右是X,朝屏幕外面是Z,Blender朝上是Z

回到VS Code把相机位置改一下 camera.position.set(0, 0, 2), 就可以看到盒子的一个面,绿色的方形了。此时要注意数据类型,比如字符串或者undefined错误的传到数值参数,整个就是黑的,看不到。可以考虑用TypeScript

此时box不能动,需要加入控制相机,可以用现成例子里面的 OrbitControls, 这样可以鼠标拖着物体缩放和旋转。

Screen Recording 2022-12-26 at 00.10.22.gif

接着把box相关的代码去掉,加入甜甜圈模型

new GLTFLoader().load('../resources/models/donuts.glb', (gltf) => {
  scene.add(gltf.scene);
})

现在可以在页面里面看到甜甜圈的模型了,但是颜色和光线都不对,和实物差距太大

Screen Recording 2022-12-26 at 00.17.13.gif

可以用console.log(gltf)来看甜甜圈的js对象是什么结构,每个甜甜圈的名字和Blender里面是对应的,但是Blender里面名字不能超过20个字符
同时可以看到js对象里面还有一个名叫"柱体"的圆的底板,可以把visible设为false, gltf.scene.children.find(x => x.name === '柱体').visible = false

Screen Recording 2022-12-26 at 00.29.07.gif

也可以看到animations数组里面有6个动画AnimationClip
还有userData可以用来存放一些自定义的数据

可以用gltf.scene.traverse()来遍历整个对象树来对每个子对象加上阴影,receiveShadow, castShadow

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


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

  })

注意这里要吧mixer放进帧循环里面,调用update(),类似轨道控制器

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
  controls.update()
  if (mixer) {
    mixer.update(0.02)
  }
}

Screen Recording 2022-12-26 at 19.58.21.gif

加载环境光
先调整一下相机,把camera.position.set(0, 0, 2) 改为 camera.position.set(0.3, 0.3, 0.5),这样初始状态相机离物体近一点
然后调整一下环境光,比如 const ambientLight = new THREE.AmbientLight(0xffffff, 0.4), 环境光不用太亮
接着加上有向光

const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
scene.add(directionalLight)

此时可以看到甜甜圈已经被照亮,但是照不到的地方还是很黑,盘子也看不清楚。

Screen Recording 2022-12-26 at 20.12.32.gif

盘子黑是因为盘子的材质需要环境反光,现在先给场景加上背景色
scene.background = new THREE.Color(0.6, 0.6, 0.6)
这时盘子的整体能看到,但是还是黑的

Screen Recording 2022-12-26 at 20.18.44.gif

现在加入一个环境图来照亮盘子

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);
});

现在盘子可以看到是灰白色了
注意背景的贴图文件是高动态范围的HDR文件 (High Dynamic Range),有亮度超过普通RGB最大值的的特别亮的部分。

Screen Recording 2022-12-26 at 20.48.41.gif

这时由于光太亮,过曝了,需要把环境光和有向光调低一点,例如环境光调为0.2,有向光调为0.6。由于有环境贴图,环境光可以直接去掉。

盘子会被Blender自动转为PBR(Physicallly-Based Rendering)材质,是针对金属或者粗糙面的材质。建模的时候,已经把盘子在Blender这里的金属度设为满格。金属度,高光,粗糙度都会被带到threejs里面。高光很强的话就相当于镜子。

Screen Recording 2022-12-26 at 20.21.12.gif

可以动态给物体加上一个旋转。 donuts = gltf.scene
在animation()里面加入代码来旋转

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

Screen Recording 2022-12-26 at 20.50.11 (1).gif

底部的那个柱体也可以显示出来

Screen Recording 2022-12-26 at 21.02.58.gif

https://sketchfab.com/可以获取模型

http://threejs.com/editor 可以帮助排查错误

第二天 Blender基本操作 创建展馆模型

练习挤出一个方块小人

这里先练习一下,简单用多边形挤出一个方块小人
tab键切换到Edit Mode,然后用选择面的模式Face Selection Mode选择顶面

image.png

接着用Shift + E 用 Extrude Region来挤出顶面

image.png
image.png

然后选底面,用Loop Cut切出竖直的一个面

image.png
image.png

把切开的两个底面选中,用挤出分别的各个面向下挤出

image.png
image.png

这样分别再选底面,然后用G键,就可以把两条腿分开

image.png

然后再从中间Loop Cut,选左右两个小面,然后向两边挤出,就捏出手臂了

image.png

把脖子用Loop Cut,接着用Shrink/Flatten把脖子变细

image.png

image.png

然后选顶面把头再挤出来

image.png

清除所有物体,开始建模场馆

如果加入一个圆环 Add --- Mesh --- Torus,会有很多面,看起来不光滑,这个时候可以选择平滑着色Shade Smooth,看起来光滑一些

image.png

加入圆柱,把圆柱的边数选为120,并且选左边的Transform,把圆柱往上提一下,高度再压一下。圆柱只能在创建的时候选边数,创建之后就改不了边数了。

image.png

现在准备把圆柱挖空

image.png

切换到选面的模式,做一个内插面。接着调整尺寸,把场馆改为30m x 30m,高度3m

image.png
image.png

然后在x轴方向开一个门,先吧坐标轴调一下,俯视往下看,切换到线框模式Shift + Z,把门都选中,然后删除门的部分。

image.png
image.png

墙需要封口,就好像是纸糊的一个墙,需要把切口再拿一块粘上去。可以用选中一条边,然后Extrude Region(Shift + E)挤出面
也可以先用E键挤出一个边,然后选中一个点,shift再选中一个active的点,Snap to Active吸附到活动点。这样就把门框给封口了。

image.png

image.png

然后增加一个六棱柱,可以用G键来移动,同时可以按X或者Y,Z把坐标轴锁住,便于移动。缩放用S(Scale)。
但是缩放之后,Z的位置还要调整,所以可以切换到选择顶面的模式来调整高度。此时有轴心偏移的问题,物体的轴心不在中心,某些方向的旋转就有问题。
这时候需要重新设一下轴心点。选Transform --- Set Origin

image.png

把六棱柱环切并往外拉伸成一个基座

image.png

加入一个立方体,然后横向和纵向拉伸顶面,成为上大下小的柱子

image.png

对这个上大下小的柱子靠上部进行环切,然后向4个方向拉伸,之后把拉伸出来的末端变窄。整体再调整尺寸。

image.png

接着输入文字。这里容易出现的问题是找不到修改文字的面板。直接按tab键可以切换到编辑模式。但是这时候可能出现看不到文字,需要在右上角选择Toggle X-Ray,可以用透明的显示。

Screen Recording 2022-12-29 at 11.09.39.gif

然后修改一下字体,在Mac上面字体需要从System/Library/Fonts里面去找

image.png

然后用快捷键R旋转,锁定X轴,然后直接键盘输入90度立起来

image.png

用Set Origin to Geometry把2023的字的中心设为几何中心,这样可以按照中心旋转

现在可以把文字转为网格再挤出,也可以用修改器,实体化(Add Modifier --- Solidify),这样字就有厚度了

image.png

image.png

然后G移动到靠近门,R旋转-90度锁定Z轴,2023要朝着里面

image.png

image.png

给2023加一个底座

image.png

屏幕有后处理技术,在场景里面加光线。Blender里面场景渲染引擎(Scene -- Render Engine)有环境光遮蔽和辉光设置

先加一个平的薄屏幕,命名为Screen0,尺寸4.27m x 2.4m (16:9)
image.png

然后加弧形的屏幕,可以从弧形的墙(Shift + D)复制10个面,

image.png

复制出来之后需要对这个弧形进行分离(Separate Selection),并且设定原点到几何中心(Set Origin to Geometry)

image.png

然后用修改器,实体化(Add Modifier --- Solidify)加上厚度

image.png

可以试着给场馆加上一个顶部,可以先加球体,然后选前视图,选删除面切掉一半,然后选择网格可以挤出一些格子。可以选择顶部在Blender里面不渲染。由于在Three.js里面不使用这个顶,直接就在模型删除。

image.png

材质

由于墙体和地板是一体的,先要做个分离。有时候美术会把这两个做来是一体的,这样材质就容易出问题。
选中底面,把地面分离出来。

然后新建一个材质,把PBR漫反射材质的图拖拽到材质里面,把Color连到Base Color,这样地板就是大理石材质了。但是现在放的有点大,还需要后续调整。

image.png

然后把rouphness粗糙度的图也加进来,连之前是一个0.5的固定的值,连之后粗糙度就是和图片关联了。

image.png

法向(normal)贴图来模拟表遍的凹凸

image.png

UV

接着解决贴图大小的问题,牵涉到UV Editing的概念,比如贴图怎么贴,斜着铺还是平铺,贴多大,类似铺地砖。

先把右上显示辅助物体,网格这些关掉。到UV Editing,把对应地板的圆放大,让材质的图变小,这样纹理就出来了,也可以看到一点反光。

image.png

添加一个日光,可以看的更清楚。可以在透明模式下,把日光光源位置进行调整,往上面放,然后R转一个角度,可以看到柱子的阴影。

image.png

看起来阴影有点模糊,切换到Shading菜单下面清楚一些。

image.png

然后可以把紫色的粗糙的法向贴图换成光滑的。大理石的格子可以用ps加一个边界,边界填充黑色。

Modeling菜单有时候找不到,可以从右边重新添加
给墙加上材质,然后选中墙体,用tab进入编辑模式,要用Select --- All选中所有的要贴的墙体,这时再调整位置,缩放。

image.png

image.png

加3个200多W的点光源,这些灯光就是在Blender里面看效果,threejs导入模型的时候都没有这些灯光。

image.png

对2023投一个视频
先把2023的字转换为网格,tab键进入编辑模式,然后在UV菜单下Select All (快捷方式按a),这时发现映射的方式是不对的。

image.png

选UV菜单,展开方式选择块状投影 UV -- Unwrap -- Cube Projection

image.png

image.png

加载模型到three.js

把模型放进threejs,把远裁剪面调到100, 调整相机位置camera.position.set(10, 5, 10),就可以看到展馆了

在gltf里面递归遍历查找到名字是Text2023的Mesh,并且赋值到happyNewYear2023

 gltf.scene.traverse((child) => {
    if (child.name === 'Text2023') {
      happyNewYear2023 = child;
    }
    child.receiveShadow = true
    child.castShadow = true
  })

然后把视频加载进来,注意要把hayypNewYear2023.material.map赋上材质

  const video = document.createElement('video');
  video.src = "./resources/yanhua.mp4";
  video.muted = true;
  video.autoplay = "autoplay";
  video.loop = true;
  const videoTexture = new THREE.VideoTexture(video);
  happyNewYear2023.material.map = videoTexture
  
  video.play();

可能有点浏览器不能自动播放,需要交互或者muted加延迟。

Screen Recording 2022-12-30 at 10.28.42.gif

此时可以看到柱子这些也有视频播放,是因为柱子缺少材质,用了缺省的,一起关联到2023这个材质了
可以创建一个新的材质,并且赋值到happyNewYear.material,这样柱子就没有贴图了

  const videoMaterial = new THREE.MeshBasicMaterial({
     map: videoTexture
  });
  happyNewYear2023.material = videoMaterial

Screen Recording 2022-12-30 at 10.38.50.gif

有点过曝,在Blender里面对材质调整一下,比如去掉法向的图片,避免反光过强

从Sketchfab下载一些模型,可以加载到threejs里面。但是尺寸和位置可能不对。最好在模型里面把尺寸调整好,这里就直接scale一下

let item0
new GLTFLoader().load('../resources/models/the_sonic_wing.glb', (gltf) => {
item0 = gltf.scene
item0.scale.set(0.3, 0.3, 0.3)
item0.position.set(0,8,0)
item0.rotateY(Math.PI / 4)

scene.add(item0)
})

Screen Recording 2022-12-30 at 12.18.55.gif
Screen Recording 2022-12-30 at 12.36.12.gif

用www.mixamo.com,上传人物模型 可以自动绑定到骨骼,生成一些动作比如走路,跑步等等。

练习做软包沙发

加一个立方体,tab键进入编辑模式,选中4个边,然后用Control + B 把一个棱倒角,然后太尖锐了可以用鼠标滚轮切平滑一些。如果Mac可以用Command + B,单指切棱,双指平滑。

Screen Recording 2022-12-30 at 20.35.10.gif

第三天 灯光阴影和角色控制

resize window

有一个固定写法,resize window后自动调节宽高

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}, false)

hbr加载的环境光过亮,可以先去掉,threejs没有办法调hbr的亮度,因为这个是美术的事情,而且比较耗费性能。直接用PS打开调亮度就可以了。

把柱子,桌子和墙都投上视频

Screen Recording 2022-12-30 at 21.26.27.gif

加入人物

把人物加入到场景,先要放一个缺省位置,可以用threejs editor先加载场景确定一下Z轴朝外面。

由于太黑了,可以把方向光调到0.8

由于要从人物视角来看场景,先前可以旋转缩放的相机控制就先不用了。
可以临时试着加入camera.lookAt(0, 0, 0)先从一个固定视角看, 但是这是相机就动不了了。

所以要去掉前面的lookAt,给人物加一个自拍杆,让摄像机在人的头顶背后一点看着人,从身后自拍整个场景和人物。
用一个父子物体的概念来实现这个跟拍。

  playerMesh.add(camera)
  camera.position.set(0, 2, -3)  // 角色1.6m高,所以Y设两米

但是这个时候焦点的方向不对,可能看到另一处去了。
所以要加入 camera.lookAt(playerMesh.position)
这个时候就可看到角色了,正对着角色的脚,因为人物模型的中心一般都设在脚那里。
为了看到角色全身,需要做一个偏移

  camera.position.set(0, 2, -6) 
  camera.lookAt(new THREE.Vector3(0, 0, 1))

Screen Recording 2022-12-30 at 22.23.03.gif

角色光线太暗,需要加点光源,主角必须自带光环。每个人都应该给自己加一盏灯,照亮自己,也照亮别人,心情也会好起来。一路走,一路的场景也就亮了。

前进和转弯

然后加入按键检测w前进,s后退

  window.addEventListener('keydown', (e) => {
    if (e.key === 'w') {
      playerMesh.translateZ(0.1)
    } else if (e.key === 's') {
      playerMesh.translateZ(-0.1)
    }
  })

用e.clientX的差分值乘上系数0.003来控制左右专项 playerMesh.rotateY(delta * 0.003)

  let previousX
  window.addEventListener('mousemove', (e) => {
    const delta = previousX === undefined ? 0 : e.clientX - previousX
    previousX = e.clientX
    playerMesh.rotateY(delta * 0.003)
  })

Screen Recording 2022-12-30 at 23.00.35.gif

给角色加上动作

模型里面已经有一个动画合并了走路和等待状态,需要进行裁剪

先准备好playerMixer

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

等待的状态从31帧开始到结束

  const clipIdle = THREE.AnimationUtils.subclip(gltf.animations[0], 'idle', 31, 281);
  actionIdle = playerMixer.clipAction(clipIdle);
  actionIdle.play(); // 初始待机状态

注意playerMixer要在animate()里面 update才可以生效,这样角色就处于idle状态了

然后加入walk的裁剪

  const clipWalk = THREE.AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 30);
  actionWalk = playerMixer.clipAction(clipWalk);
  // actionWalk.play(); // 这行只是测试使用

加入过渡函数,旧的动作和新的动作有淡入淡出的效果

function crossPlay(curAction, newAction) {
  curAction.fadeOut(0.3);
  newAction.reset(); // 走路和休息两个动作交替,新的动作因为之前播放过,可能停留在任意位置
  newAction.setEffectiveWeight(1);
  newAction.play();
  newAction.fadeIn(0.3);
}

优化流畅度

let deltaTime;
const clock = new THREE.Clock();
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    deltaTime = clock.getDelta();
};

用这个deltaTime到translateZ()那里 playerMesh.translateZ(3 * deltaTime);

Screen Recording 2022-12-31 at 00.21.18.gif

碰撞检测

思路是手里拿一根棍去检测前方是否有碰撞,用末尾的值减去起始的值得到向量的值 a = v2 - v1

const curPos = playerMesh.position.clone(); // 保存当前位置
playerMesh.translateZ(1); // 相当于试探性把角色前进1m, Z是角色正前方
const frontPos = playerMesh.position.clone(); // 得到如果前进一米的位置
playerMesh.translateZ(-1); // 角色又退回保存的当前位置

const frontVector3 = frontPos.sub(curPos).normalize(); // 得到向量并且归一化
const raycasterFront = new THREE.Raycaster(playerMesh.position.clone().add(playerHalfHeight), frontVector3); // 从脚底加身高的一半,就是角色中心发出射线,方向矢量
const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children); // 检测所有射线碰撞到的物体,无论远近,会得到每个物体离射线起点的距离
console.log(collisionResultsFrontObjs);
if (collisionResultsFrontObjs.length === 0 || collisionResultsFrontObjs[0].distance > 1) { // 没有物体或者离物体距离大于1米就前进
playerMesh.translateZ(3 * deltaTime);
}

阴影4步走

1 打开renderer阴影

    const renderer = new THREE.WebGLRenderer();
    renderer.shadowMap.enabled = true;

2 打开灯光阴影;设置灯光阴影大小

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 1, 0);
light.castShadow = true; // 默认值false
scene.add(light);

light.shadow.mapSize.width = 512; //默认值
light.shadow.mapSize.height = 512; //默认值

3 设置灯光阴影体相机远近大小

light.shadow.camera.near = 0.5; //默认值
light.shadow.camera.far = 500; //默认值

const shadowDistance = 50
light.shadow.camera.left = -shadowDistance;
light.shadow.camera.right = shadowDistance;
light.shadow.camera.top = shadowDistance;
light.shadow.camera.bottom = -shadowDistance;

4 设置Mesh 投射阴影 接收阴影

gltf.scene.traverse((child) => {
    child.castShadow = true;
        child.receiveShadow = true;
})
// 阴影调试辅助工具
const helper = new THREE.CameraHelper( light.shadow.camera );
scene.add( helper );

这个时候出来很多格子, 注意要再设shadow bias

light.shadow.bias = 0.0001

也可试一下其他消除阴影网格的办法

将四方块设置为 不接收阴影 obj.receiveShadow = false; 可避免条纹,但四方块上就没有投影效果了。
将材质的 material.side = 0; side属性设置为0也可以正常。
或者将材质的投影面设置为背面也可解决 material.shadowSide = THREE.BackSide;

经过尝试发现在travase里面用 shadowSide = 0可行

if (child.material) {
  child.material.shadowSide = 0
}

Screen Recording 2022-12-31 at 13.19.47.gif

给人物加上阴影,在加载人物模型的地方调用traverse()

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

image.png

可以从skechfab下载一个机器人加载进去

let robotMesh
new GLTFLoader().load('../resources/models/robot.glb', (gltf) => {
  gltf.scene.traverse((child) => {
    child.castShadow = true
    child.receiveShadow = true
  })

  robotMesh = gltf.scene
  robotMesh.position.set(5, 0.4, -5)
  robotMesh.rotateY(Math.PI / 2)
  scene.add(robotMesh)

})

image.png

如果导入的模型尺寸不对,可以在Blender里面调整,Scale之后用Control + A来Apply Rotation and Scale一下

(如果需要烘焙到关键帧,选中所有,Layout --- Object --- Rigid Body --- Bake to Keyframes)

可以播放动画,也可以对动画裁剪

  robotMixer = new THREE.AnimationMixer(gltf.scene);
  const clips = gltf.animations; // 播放所有动画
  clips.forEach(function (clip) {
    const action = robotMixer.clipAction(clip);
    action.loop = THREE.LoopRepeat // THREE.LoopRepeat THREE.LoopOnce 
    action.play();
  });

注意不要忘了在animate()里面调用update()

  if (robotMixer) {
    robotMixer.update(0.02)
  }

Untitled.gif

感谢大帅老师和胖达老师提供学习three.js的机会


DisplayName2023
1 声望0 粉丝