Renderer init scene
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const canvas = document .createElement('canvas' )const sizes = {width: this .$refs.container.clientWidth, height: this .$refs.container.clientHeight } canvas.width = sizes.width; canvas.height = sizes.height; this .$refs.container.appendChild(canvas);const scene = new THREE.Scene();console .log(scene); const geometry = new THREE.SphereGeometry(5 , 32 , 32 );const material = new THREE.MeshBasicMaterial({color : '#aaa' , wireframe :true })const mesh = new THREE.Mesh(geometry, material)mesh.position.set(0 , 0 , -10 ); scene.add(mesh) const camera = new THREE.PerspectiveCamera(75 , sizes.width/sizes.height, 0.1 , 1000 );scene.add(camera); const renderer = new THREE.WebGLRenderer({canvas : canvas});renderer.render(scene, camera);
Caution! 虽设置canvas尺寸 但canvas初始化renderer时 其尺寸会受到影响 其结果仍使canvas超出父容器 出现滚动条 应使用renderer.setSize
1 2 3 const renderer = new THREE.WebGLRenderer()renderer.setSize(container.current.clientWidth, container.current.clientHeight) container.current.appendChild(renderer.domElement)
另外onResize要加防抖 overflow hidden该加还是得加
Controls 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { OrbitControls } from 'three/addons/controls/OrbitControls.js' ;... const controls = new OrbitControls(camera, renderer.domElement)camera.position.set( 0 , 20 , 100 ); controls.update(); function animate ( ) { requestAnimationFrame( animate ); controls.update(); renderer.render( scene, camera ); }
Mesh和Geometry 两者都可以rotate, Geometry是”是什么(what)” Mesh是”如何(how)”
Animation 1 2 3 4 5 tick ( ) { mesh.rotaion.y += 0.05 console .log("tick tack" ) requestAnimationFrame(tick) }
tick 调用频率是帧速率相关的 可以用Date或者THREE.Clock
1 2 3 4 5 6 7 8 9 const clock = new THREE.Clock()const animate = () => { const t = clock.getElapsedTime() group.rotation.y=0.5 *Math .PI*t requestAnimationFrame(animate); renderer.render(scene, camera); }; animate();
Issue: 单纯调用gsap无效 需要配合requestAnimationFrame和render方法才能使模型运动生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 gsap.to(group.position, { duration:1 , delay:1 , x:20 }) gsap.to(group.position, { duration:1 , delay:2 , x:0 }) const animate = () => { requestAnimationFrame(animate); renderer.render(scene, camera); }; animate();
Camera 正交相机
1 2 3 4 5 6 7 8 const camera = new THREE.OrthographicCamera( -20 *container.current.clientWidth/container.current.clientHeight, 20 *container.current.clientWidth/container.current.clientHeight, 20 , -20 , 0.1 , 1000 )
相机环绕
1 2 3 4 5 6 7 8 9 10 11 12 document .addEventListener('mousemove' , (e )=> { if (!container.current) return const x = (e.x - container.current.offsetLeft)/container.current.clientWidth - 0.5 ; const y = -(e.x - container.current.offsetTop)/container.current.clientHeight + 0.5 ; camera.position.z = Math .cos(x*Math .PI*10 )*20 camera.position.x = Math .sin(x*Math .PI*10 )*20 camera.lookAt(scene.position) renderer.render(scene, camera) })
camera controls
OrbitControl
DeviceOrientationControls
FirstPersionControls
FlyControls
PointLockControls
TrackballControls
debug tool: dat.gui (out of date) -> lil-guicontrol panel controlkit Guify Oui
纹理映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const loader = new THREE.TextureLoader()const texture = loader.load('./assets/images/cover/cover-6.webp' , (txt)=>{ console .log(txt) }, ()=>{ console .log('loading' ) }, (e)=>{ console .error(e) } ) texture.repeat.x = 2 texture.repeat.y = 2 texture.wrapS = THREE.MirroredRepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping texture.offset.x = 0.5 texture.rotation = Math .PI * 0.5 texture.center.x = 1 texture.center.y = 1 texture.magFilter = THREE.LinearFilter texture.minFilter = THREE.LinearMipmapLinearFilter const cubeMaterial = new THREE.MeshBasicMaterial({ map: texture }) const sphereGeometry = new THREE.SphereGeometry(5 , 32 , 32 )const ball = new THREE.Mesh(sphereGeometry, cubeMaterial)
material properties:
transparent true/false
opacity 透明度0~1
wireframe 显示线框
side 材质应用到外侧(THREE.FrontSide)或者内侧(THREE.BackSide)
flatshading
matcap
environmentMap and HDRI(High Dynamic Range Imaging 高动态范围成像)
在宏大场景中,计算周围环境在物体上的倒影是巨大的运算负担,于是聪明的图形工作者想到了将环境贴图直接贴在反光物体上的想法
在Three.js中支持将立方体环境的图像映射到物体上 常用HDRI生成立方环境贴图的免费网站:https://polyhaven.com/hdris 原HDRIHeaven
或使用blender工具
3D Text TextBufferGeometry
facetype.js 转换.ttf字体成json QQs: 包含中文的字体转换后出现问题,json中以‘字-坐标’作key-value,中文(甚至字母和数字)转换失败在json中显示为方框,于是输入的字无法找到映射。loader导入这样的字体会报 character “xxx” does not exists in font family XXX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js' ;import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry' ;const fontLoader = new FontLoader()fontLoader.load('./assets/font/Kristen ITC_Regular.json' , (font ) => { const textGeometry = new TextGeometry( 'Hello 3D' , { font, size: 2 , depth:0.2 , curveSegments: 12 , bevelEnabled: true , bevelThickness: 0.3 , bevelSize: 0.2 , bevelOffset: 0 , bevelSegments: 5 } ) const textMaterial = new THREE.MeshBasicMaterial({ color : 'blue' , wireframe :true }) const textMesh = new THREE.Mesh(textGeometry, textMaterial) scene.add(textMesh) }) `` `js 3D Text默认position位于文字起点 移动到中心 ` `` jstextGeometry.translate( - (textGeometry.boundingBox.x - 0.2 )*0.5 , - (textGeometry.boundingBox.y - 0.2 )*0.5 , - (textGeometry.boundingBox.z - 0.3 )*0.5 )
阴影 粒子 可视化优化 Three.js常见性能问题和内存泄漏
及时dispose释放资源
减少segment
复用geometry实例
优化requestAnimationFrame 降低帧率和简化回调
webGPU Three.js 支持webGPU,使用webGPU需要浏览器对该功能的支持 见openGL
对于没有原生支持的浏览器可以通过Polyfill实现