快速过一下基本用法
文章目录
入门
01 场景
//场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );//大小
document.body.appendChild( renderer.domElement );
//加物体
const geometry = new THREE.BoxGeometry( 1, 1, 1 );//形状
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );//材质
const cube = new THREE.Mesh( geometry, material );//物体 = 形状+材质
scene.add( cube );//加到场景中
camera.position.z = 5;//设置位置
function animate() {
//使渲染器能够在每次屏幕刷新时对场景进行绘制的循环,当用户切换到其它的标签页时,它会暂停(跟setInterval差不多)。不然以上代码画面是空的
requestAnimationFrame( animate );
cube.rotation.x += 0.01;//动画
cube.rotation.y += 0.01;//动画
renderer.render( scene, camera );//渲染
}
animate();
02 兼容性检查
引入WebGL.js
if (WebGL.isWebGLAvailable()) {
// Initiate function or other initializations here
animate();
} else {
const error = WebGL.getWebGLErrorMessage();
console.error(error)
}
03 画线
const scene = new THREE.Scene();//场景
const renderer = new THREE.WebGLRenderer();//渲染器
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
camera.position.set( 0, 0, 100 );//相机位置
camera.lookAt( 0, 0, 0 );//相机朝向
const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );//材质
//构建三维几何体
const points = [];
points.push( new THREE.Vector3( - 10, 0, 0 ) );//Vector3--三维空间中的向量
points.push( new THREE.Vector3( 0, 10, 0 ) );//Vector3--三维空间中的向量
points.push( new THREE.Vector3( 10, 0, 0 ) );//Vector3--三维空间中的向量
//线是画在每一对连续的顶点之间的,不闭合
const geometry = new THREE.BufferGeometry().setFromPoints( points );//BufferGeometry--定义3D几何体
const line = new THREE.Line( geometry, material );//物体=几何体+材质
scene.add( line );//加到场景中
renderer.render( scene, camera );//渲染
04 字体
- CSS2DRenderer 或CSS3DRenderer
2.TextureLoader
const texture = new THREE.TextureLoader().load( "textures/water.jpg" );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 4, 4 );
3.TextGeometry
new THREE.TextGeometry( text, parameters );
import * as THREE from 'three';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
THREE.Cache.enabled = true;
let container;
let camera, cameraTarget, scene, renderer;
let group, textMesh1, textMesh2, textGeo, materials;
let firstLetter = true;
let text = 'three.js',
bevelEnabled = true,
font = undefined,
fontName = 'optimer', // helvetiker, optimer, gentilis, droid sans, droid serif
fontWeight = 'bold'; // normal bold
const depth = 20,
size = 70,
hover = 30,
curveSegments = 4,
bevelThickness = 2,
bevelSize = 1.5;
const mirror = true;
const fontMap = {
helvetiker: 0,
optimer: 1,
gentilis: 2,
'droid/droid_sans': 3,
'droid/droid_serif': 4,
};
const weightMap = {
regular: 0,
bold: 1,
};
const reverseFontMap = [];
const reverseWeightMap = [];
for (const i in fontMap) reverseFontMap[fontMap[i]] = i;
for (const i in weightMap) reverseWeightMap[weightMap[i]] = i;
let targetRotation = 0;
let targetRotationOnPointerDown = 0;
let pointerX = 0;
let pointerXOnPointerDown = 0;
let windowHalfX = window.innerWidth / 2;
let fontIndex = 1;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
//相机
camera = new THREE.PerspectiveCamera(30,window.innerWidth / window.innerHeight,1,1500,);
camera.position.set(0, 400, 700);
cameraTarget = new THREE.Vector3(0, 150, 0);
//场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0x000000, 250, 1400);//雾
//光
const dirLight = new THREE.DirectionalLight(0xffffff, 0.4);
dirLight.position.set(0, 0, 1).normalize();
scene.add(dirLight);
const pointLight = new THREE.PointLight(0xffffff, 4.5, 0, 0);
pointLight.color.setHSL(Math.random(), 1, 0.5);
pointLight.position.set(0, 100, 90);
scene.add(pointLight);
materials = [
new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true }), // front
new THREE.MeshPhongMaterial({ color: 0xffffff }), // side
];
//字体组
group = new THREE.Group();
group.position.y = 100;
scene.add(group);
loadFont();
//平面
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(10000, 10000),
new THREE.MeshBasicMaterial({
color: 0xffffff,
opacity: 0.5,
transparent: true,
}),
);
plane.position.y = 100;
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
//渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
}
function createText() {
textGeo = new TextGeometry(text, {
font: font,
size: size,
depth: depth,
curveSegments: curveSegments,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: bevelEnabled,
});
textGeo.computeBoundingBox();
const centerOffset =
-0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
textMesh1 = new THREE.Mesh(textGeo, materials);
textMesh1.position.x = centerOffset;
textMesh1.position.y = hover;
textMesh1.position.z = 0;
textMesh1.rotation.x = 0;
textMesh1.rotation.y = Math.PI * 2;
group.add(textMesh1);
//倒影
if (mirror) {
textMesh2 = new THREE.Mesh(textGeo, materials);
textMesh2.position.x = centerOffset;
textMesh2.position.y = -hover;
textMesh2.position.z = depth;
textMesh2.rotation.x = Math.PI;
textMesh2.rotation.y = Math.PI * 2;
group.add(textMesh2);
}
}
function refreshText() {
group.remove(textMesh1);
if (mirror) group.remove(textMesh2);
if (!text) return;
createText();
}
//载入字体模型
function loadFont() {
const loader = new FontLoader();
loader.load(
'fonts/' + fontName + '_' + fontWeight + '.typeface.json',
function (response) {
font = response;
refreshText();
},
);
}
function animate() {
requestAnimationFrame(animate);//动起来
render();//渲染
}
function render() {
group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
camera.lookAt(cameraTarget);//相机朝向
renderer.clear();//清除
renderer.render(scene, camera);//渲染
}
05 载入载入3D模型
const loader = new GLTFLoader();
loader.load( 'path/to/model.glb', function ( gltf ) {
scene.add( gltf.scene );
}, undefined, function ( error ) {
console.error( error );
} );
进阶
01 场景中更新物体等
Object3D
Object3D对象默认会自动更新
const object = new THREE.Object3D();
scene.add( object );
//手动更新
object.matrixAutoUpdate = false;
object.updateMatrix();
BufferGeometry
不能调整 buffers 大小(这种操作开销很大,相当于创建了个新的geometry)。 但你可以更新 buffers的内容。必须预先分配足够大的buffer来容纳可能创建的任何新顶点。
重点
//更改第一次渲染后渲染的点数
line.geometry.setDrawRange( 0, newValue );
//在第一次渲染后更改position数值
positionAttribute.needsUpdate = true;
完整
//预先分配足够大的buffer
const MAX_POINTS = 500;
// geometry
const geometry = new THREE.BufferGeometry();
// attributes
const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
// setDrawRange来改变
const drawCount = 2; // draw the first 2 points, only
geometry.setDrawRange( 0, drawCount );
// 材质
const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
// 线
const line = new THREE.Line( geometry, material );
scene.add( line );
//加点
const positionAttribute = line.geometry.getAttribute( 'position' );
let x = 0, y = 0, z = 0;
for ( let i = 0; i < positionAttribute.count; i ++ ) {
positionAttribute.setXYZ( i, x, y, z );
x += ( Math.random() - 0.5 ) * 30;
y += ( Math.random() - 0.5 ) * 30;
z += ( Math.random() - 0.5 ) * 30;
}
//更改第一次渲染后渲染的点数
line.geometry.setDrawRange( 0, newValue );
//在第一次渲染后更改position数值
positionAttribute.needsUpdate = true;
材质(Materials)
- 所有
uniforms
值都可以自由改变(比如 colors, textures, opacity 等等),这些数值在每帧都发给shader。 GL状态相关参数
也可以随时改变(depthTest, blending, polygonOffset 等)。- 在运行时无法轻松更改以下属性(一旦material被渲染了一次):
uniforms
的数量和类型、是否存在texture
|fog
|vertex colors
|morphing
|shadow map
|alpha test
|transparent
//需要设置
material.needsUpdate = true
纹理(Textures)
//如果更改了图像,画布,视频和数据纹理,需要设置
material.needsUpdate = true
相机(Cameras)
相机的位置和目标会自动更新。 如果你需要改变fov
aspect
near
far
,需要重新计算投影矩阵:
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
02 废置对象
BufferGeometry.dispose()
Material.dispose()
Texture.dispose()
WebGLRenderTarget.dispose()
03 创建VR内容
对于VR项目来说,使用的是setAnimationLoop调整动画循环
import { VRButton } from 'three/addons/webxr/VRButton.js';
renderer.xr.enabled = true;
renderer.setAnimationLoop( function () {
renderer.render( scene, camera );
} );
04 后期处理
应用一个或多个图形效果,例如景深、发光、胶片微粒或是各种类型的抗锯齿等可以使用后期处理达成。EffectComposer(效果合成器)
const composer = new EffectComposer( renderer );
function animate() {
requestAnimationFrame( animate );
//不再调用WebGLRenderer的render方法,而是使用EffectComposer中对应的render方法
composer.render();
}
//后期处理过程链
const renderPass = new RenderPass( scene, camera );
composer.addPass( renderPass );
const glitchPass = new GlitchPass();
composer.addPass( glitchPass );
const outputPass = new OutputPass();
composer.addPass( outputPass );
自定义后期处理着色器
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
// later in your init routine
const luminosityPass = new ShaderPass( LuminosityShader );
composer.addPass( luminosityPass );
05 矩阵变换
matrixAutoUpdate
默认情况下,matrixAutoUpdate属性设置为true,并且将自动重新计算矩阵。如果对象是静态的,或者您希望在重新计算时手动控制,则可以通过将属性设置为false来获得更好的性能
- 修改对象的position,quaternion和scale属性
object.position.copy( start_position );
object.quaternion.copy( quaternion );
object.matrixAutoUpdate = false;
object.updateMatrix();
- 直接修改对象的矩阵
object.matrix.setRotationFromQuaternion( quaternion );
object.matrix.setPosition( start_position );
object.matrixAutoUpdate = false;
对象和世界矩阵
一个对象的matrix存储了该对象 相对于 其Object3D.parent(父节点)的变换。要在 世界 坐标系中获取对象的转换,您必须访问该对象的Object3D.matrixWorld。
当父对象或子对象的变换发生更改时,可以通过调用[page:Object3D.updateMatrixWorld updateMatrixWorld()]来请求更新子对象的matrixWorld。
旋转和四元数
Three.js提供了两种表示3D旋转的方式:Euler angles(欧拉角)和Quaternions(四元数),以及两者之间的转换方法。 欧拉角有称为“万向节锁定”的问题,其中某些配置可能失去一定程度的自由度(防止物体绕一个轴旋转)。 因此,对象旋转 始终 存储在对象的quaternion中。
您应该使用setRotationFromEuler方法,该方法将更新四元数。
06 动画系统
let mesh;
// 新建一个AnimationMixer, 并取得AnimationClip实例列表
const mixer = new THREE.AnimationMixer( mesh );
const clips = mesh.animations;
// 在每一帧中更新mixer
function update () {
mixer.update( deltaSeconds );
}
// 播放一个特定的动画
const clip = THREE.AnimationClip.findByName( clips, 'dance' );
const action = mixer.clipAction( clip );
action.play();
// 播放所有动画
clips.forEach( function ( clip ) {
mixer.clipAction( clip ).play();
} );