初始化
import { RoomEnvironment } from './assets/vendor/three/jsm/environments/RoomEnvironment.js';//引入环境贴图
let threejs = {
init() {
this.con = document.querySelector("#threeContainer")
/*** 1. 初始化场景***/
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xF0F2F5); // 设置背景颜色
/*** 2. 初始化相机***/
this.camera = new THREE.PerspectiveCamera(75, threejs.con.clientWidth / threejs.con.clientHeight, 0.1, 2000);
this.camera.position.set(-50, 50, 130);
this.camera.aspect = threejs.con.clientWidth / threejs.con.clientHeight;// 更新摄像头宽高比
this.camera.updateProjectionMatrix();// 更新摄像头投影矩阵
this.scene.add(this.camera);
/*** 3.初始化渲染器***/
this.renderer = new THREE.WebGLRenderer( { antialias: true, });// 抗锯齿
this.renderer.outputEncoding = THREE.sRGBEncoding; // 处理色彩空间问题
this.renderer.setSize(threejs.con.clientWidth, threeCon.value.clientHeight);// 设置渲染宽高
threejs.con.appendChild(this.renderer.domElement);// 将渲染器添加到页面
let pmremGenerator = new THREE.PMREMGenerator(this.renderer); // 设置环境贴图
pmremGenerator.compileEquirectangularShader(); //阴影
this.scene.environment = pmremGenerator.fromScene(
new RoomEnvironment(),
0.04
).texture;
/*** 3.初始化鼠标控制器***/
this.controls = new OrbitControls(this.camera, this.renderer.domElement); //创建控件对象
/*** 4.辅助坐标系***/
const axesHelper = new THREE.AxesHelper(200);
this.scene.add(axesHelper);
this.scene.environment = new THREE.Color("#ccc")
this.animate();
},
render(){// 渲染场景
this.renderer.render(this.scene, this.camera);
},
animate(){
this.render()
this.animationTimer=requestAnimationFrame(this.animate.bind(this));
},
reset(){
this.controls.target = new THREE.Vector3(0,0,0)
this.controls.update();
},
resize(){
// 更新摄像头宽高比
threejs.camera.aspect = threejs.con.clientWidth / threejs.con.clientHeight;
// 更新摄像头投影矩阵
threejs.camera.updateProjectionMatrix();
// 设置渲染宽高
threejs.renderer.setSize(threejs.con.clientWidth, threejs.con.clientHeight);
},
};
watch(// 监听页面改变调用animate函数:(只有当前页面下才渲染3d模型,否则不渲染,有效避免卡顿)
() => props.page,(val, oldVal) => {
if(val==1){
setTimeout(function () {
threejs.resize();
threejs.animate();
}, 20);
}else{
cancelAnimationFrame(threejs.animationTimer)
}
})
// 浏览器窗口resize时更新相机矩阵
function onWindowResize() {
threejs.resize()
threejs.reset();//重置鼠标控件的位置
threejs.animate();// 重新渲染
}`
相机:
// 1.调整相机朝向:
camera.target = new THREE.Vector3(0,0,0);
camera.lookAt(new THREE.Vector3(0,0,0));
// 如果想让相机朝着某物体:
camera.target = mesh.position; // 方法1
camera.lookAt(mesh.position)//方法2
// 改完相机的属性要更新相机的投影矩阵:updateProjectionMatrix()
// 浏览器窗口resize时更新相机矩阵
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; //重新设置宽高比
camera.updateProjectionMatrix(); //更新相机投影矩阵
renderer.setSize(window.innerWidth,window.innerHeight);//更新染页面大小
renderer.setPixelRatio(window.devicePixelRatio)//设置渲染器的像素比
}
// 更新相机:
threejs.camera.position.set(0, 300, 700); // 重置相机位置
threejs.camera.updateProjectionMatrix()
threejs.reset();//重置鼠标控件的位置
threejs.animate();// 重新渲染
// 案例:
// 自定义改变相机位置和朝向的函数
let timeLine1 = gsap.timeline();
let timeLine2 = gsap.timeline();
function translateCamera(position,target){
timeline1.to(camera.position,{
x:position.x,
y:position.y,
z:position.z,
duration:1,
ease:"power2.inOut"
});
timeline2.to(camera.target,{
x:target.x,
y:target.y,
z:target.z,
duration:1,
ease:"power2.inOut"
});
}
// 使用
translateCamera(
new THREE.Vector3(-3.23,3,4),
new THREE.Vector3(-8.23,2,0),
)
渲染器
renderer.physicallyCorrectLights = true;//使用物理渲染,效果更逼真
const renderer = new THREE.WebGLRenderer({//解决面太多导致会闪烁问题
logarithmicDepthBuffer : true,// 对数深度缓冲区
})
// 透过threejs看canvas的背景图
// canvas{
// background-image:url(imgs/star.jpg);
// background-size:over;
// }
// 创建渲染器
renderer=new THREE.WebGLRenderer({
alpha:true,//透过threejs看canvas的背景图
})
后处理:辉光BloomPass
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';//效果的总控制器器
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';//第一个后期处理器:负责渲染
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js';//辉光效果的处理器
// 1.new一个composer:后期处理的总控制器()
var composer = new EffectComposer(renderer);//表示基于render进行后期处理
// 2.声明具体的处理器
const renderPass = new RenderPass(scene,camera);//主体处理器
const bloomPass = new BloomPass(1.25);//处理器1:参数表示亮度值
// 3.把处理器加进composer
composer.addPass(renderPass);
composer.addPass(bloomPass);
// 4.在声明canmera,render等的地方:加入下面这句
renderer.autoClear = false;
// 5.在render函数中:
renderer.render(scene,camera)//去掉这句话,用composer的render取代
renderer.clear();
composer.render(0.1);
后处理:outline
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';//效果的总控制器器
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';//第一个后期处理器:负责渲染
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';//outline的处理器
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';//
import { FXAAShader } from 'three/examples/jsm/postprocessing/FXAAShader.js';//
// 1.new一个composer:后期处理的总控制器()
var composer = new EffectComposer(renderer);//表示基于render进行后期处理
// // 2.声明具体的处理器
const renderPass = new RenderPass(scene,camera);//主体处理器
const outlinePass = new OutlinePass(
new THREE.Vector2(window.innerWidth,window.innerHeight),
scene,camera
);//处理器1:outlinePass
// 3.把处理器加进composer
composer.addPass(renderPass);
composer.addPass(outlinePass);
// // 4.在声明canmera,render等的地方:加入下面这句,酌情添加
// renderer.autoClear = false;
// 5.在render函数中:
renderer.render(scene,camera)//去掉这句话,用composer的render取代
// renderer.clear();//酌情添加
composer.render();
// 以下代码是在找鼠标点击的模型也在在render函数中
Raycaster.setFromCamera(mouse,camera);
const intersects = raycaster.intersectObjects(scene,true);
if(intersects.length>0){
const selectedObject = intersects[0].object;
outlinePass.selectedObject = [];
outlinePass.selectedObject.push(selectedObject);//当前选中的模型设置outline
}
光照
-
环境光 AmbientLight(无阴影) 四面八方的光
-
平行光 DirectionalLight(有阴影) 从哪个位置打到哪个位置的光。主要用于模拟太阳光线。
-
点光源 PointLight (有阴影) 是从一个点的位置向四面八方发射出去光,类似一个裸露的灯泡。
-
聚光灯 SpotLight (有阴影) 是从一个点发出光线,然后沿着一个一个圆锥体进行照射,可以模仿手电筒,带有灯罩的灯泡等效果。
-
室外光源,半球光 HemisphereLight (无阴影) 模拟在户外的环境光效果,实例化室外光源支持三个参数:天空的颜色,地面的颜色,和光的强度
-
矩形区域光源 RectArealLight (无阴影) 从一个矩形面均地发射,可以用来模拟明亮的窗户或者带状的照明;可以产生投影
// 阴影效果 // 1、创建一个几何体和一个材质,创建一个平面 // 2、设置渲染器开启阴影的计算 renderer.shadowMap.enabled = true; // 3、设置光照投射阴影 Light.castshadow = true; // 4、设置物体投射阴影 sphere.castshadow = true; // 5、设置物体接收阴影 plane.receiveshadow = true; // directionalLightshadow.camera.near = 20;//产生阴影的最近距离 // directionalLight.shadow.camera.far = 100;//产生阴影的最远距离 // directionalLight,shadow.camera.left = -50; //产生阴影距离位置的最左边位置 // directionalLight.shadow.camera.right = 50;//最右边 // directionalLight.shadow.camera.tot = 50; //最上边 // directionalLight.shadow.camera.bottom = -50; //最下面 // directionalLight.shadow.mapSize.height = 1024; //这个值决定生成阴影密度 默认512 // directionalLight.shadow.mapSize.width = 1024; //这个值决定生成阴影密度 默认512 // directionalLight.shadow.radius = 20;//修改阴影的模糊程度 // 调整不同光源的参数 var light = new THREE.DirectionalLight(0xffffff); //添加一个白色的平行光 light.color.set(0x000000); //将光照的颜色修改为黑色 light.intensity = 2.0; // 光照的强度改为默认的两倍 directionalLight.position.set(10,10,10);//设置平行光的位置 directionallight.target.set(0,0,0);//设置当前的平行光的朝向位置 pointLight.color.set(0x000000); //修改光照额色 pointLight.intensity = 0.5;//修改光的强度 pointlight.distance = 50; //修改光的照射范围 pointLight.decay = 1.0; //修改亵减度 spotLight.angle = Math.PI/3; //修改光的照射弧度,可改变阴影马赛克的粗细 spotLight.distance = 50; //修改光的照射范围 spotLight.penumbra = 1.0;//修改交界过渡 hemisphereLight.color.set(0xffffff); //将天空颜色修改为白色 hemisphereLight.groundColor.set(0x000000); //将地面颜色修改为里色 hemisphereLight.position.set(0,-1,0); //默认从上往下染,也就是天空在上方,当前修改为了,天空颜色从下往上渲染
把光源添加到mesh里面得到发光的小球
const sphereGeometry = new THREE.SphereGeometry(0.2,32,32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const sphere= new THREE.Mesh(sphereGeometry, sphereMaterial);
let pointLight = new THREE.PointLight(0xffffff, 1);
sphere.add(pointLight)
gui的使用
// 一:安装gui
npm i --save dat.gui
// 二:导入dat.GUI
import \* as dat from 'dat.gui'
// 三:实例化并使用
this.gui = new dat.GUI()\
// 添加组
var folder = this.gui.addFolder("设置闪烁体");
const params = {color:"#ffff00",
fn:()=>{
//让闪烁体运动起来
gsap.to(this.cylinder.position,{x:400,duration:2,yoyo:true,repeat:-1})
}
};
// 改颜色
folder
.addColor(params ,"color")
.name("闪烁体的颜色")
.onChange((value)=>{
console.log("闪烁体的颜色",value)
this.cylinder.material.color.set(value)
})
// 切换显示隐藏
folder
.add(this.cylinder,"visible")
.name("是否隐藏闪烁体")
// 点击触发某个事件
folder
.add(params,"fn").name("闪烁体运动")
folder
.add(this.PET.position,"x")
.min(-100)
.max(200)
.step(0.5)
.name("Pet模型的x位置")
// gui的简便写法(min,max,step)
folder.add(this.PET.position,"x",0,90,0.05).onchange(updateSun)
Mesh
geometry
BufferGeometry
创建一个物体,物体由折线构成:用setFromPoints方法传入点
const points = [];
points.push(new THREE.Vector3(0, 0, 0));
points.push(new THREE.Vector3(10, 0, 0));
points.push(new THREE.Vector3(50, 10, 0));
points.push(new THREE.Vector3(0, 20, 0));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.MeshBasicMaterial({color:0x787878});
const line = new THREE.Line(geometry,material);
sceneManager.scene.add(line)
创建一个物体,物体由折线构成:用setAttribute设置position,color
const segments = 5;//折线数量是segments-1,segments表示点的数量
const range = 800;
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
const color = new THREE.Color();
for(let i = 0;i<segments;i++){
const x =range*(Math.random() - 0.5);//-range/2~range/2
const y =range*(Math.random() - 0.5);
const z =range*(Math.random() - 0.5);
// 生成position数组
positions.push(x,y,z);
// 生成color数组
color.setRGB(
x/range+0.5,
y/range+0.5,
z/range+0.5
);
colors.push(color.r,color.g,color.b);
}
geometry.setAttribute("position",new THREE.Float32BufferAttribute(positions,3));
geometry.setAttribute("color",new THREE.Float32BufferAttribute(color,3));
const material = new THREE.LineBasicMaterial({
vertexColors : true,//只有设置这个属性每个点的color才会生效
});
let line = new THREE.Line(geometry,material);
scene.add(line);
// 给直线加动画
geometry.computeBoundingSphere();//位置不要变,写到morgh前
// 设置morgh目标(都要写到创建mesh前面)
const data = []; // 存储变形后的位置
for (let i = 0;i<segments;i++){
const x =range*(Math.random() - 0.5);
const y =range*(Math.random() - 0.5);
const z =range*(Math.random() - 0.5);
data.push(x,y,z);
}
const morphTarget = new THREE.Float32BufferAttribute(data,3);//定义morph属性
morphTarget.name = "morphTarget1";
geometry.morphAttributes.position = [ morphTarget ];
// 设置运动:0表示初始,1表示结束
function render(){
line.morphTargetInfluences[0] = 0;
render.render(scene,camera);
}
// 让折线在0~1之间sin动起来:
function render(){
const delta = clock.getDelta();
t+=delta*0.5;
line.morphTargetInfluences[0] = Math.abs(Math.sin(t));
render.render(scene,camera);
}
创建一个物体,物体由三角形构成:用setAttribute设置position,normal,color
const triangles = 20000;//三角形数量
const range = 800;
const size = 12;
const geometry = new THREE.BufferGeometry();
const positions = [];
const normals = [];
const colors = [];
const positionA = new THREE.Vector3();
const positionB = new THREE.Vector3();
const positionC = new THREE.Vector3();
const cb = new THREE.Vector3();
const ab = new THREE.Vector3();
const color = new THREE.Color();
for(let i = 0;i<triangles;i++){
const x =range*(Math.random() - 0.5);//-range/2~range/2
const y =range*(Math.random() - 0.5);
const z =range*(Math.random() - 0.5);
const ax =x+size*(Math.random() - 0.5);
const ay =y+size*(Math.random() - 0.5);
const az =z+size*(Math.random() - 0.5);
const bx =x+size*(Math.random() - 0.5);
const by =y+size*(Math.random() - 0.5);
const bz =z+size*(Math.random() - 0.5);
const cx =x+size*(Math.random() - 0.5);
const cy =y+size*(Math.random() - 0.5);
const cz =z+size*(Math.random() - 0.5);
// position:三角形的三个点
positions.push(ax,ay,az);
positions.push(bx,by,bz);
positions.push(cx,cy,cz);
// normal
positionA.set(ax,ay,az);
positionB.set(bx,by,bz);
positionC.set(cx,cy,cz);
cb.subVectors(positionC,positionB);//表示向量减法c-b
ab.subVectors(positionA,positionB);//表示向量减法a-b
cb.cross(ab);//cb=cb叉积ab,结果是cb变成了与cb,ab都垂直的一个向量
cb.normalize();//cb=cb
normals.push(cb.x,cb.y,cb.z);//三个点的normal值都是一样的
normals.push(cb.x,cb.y,cb.z);
normals.push(cb.x,cb.y,cb.z);
// color
color.setRGB(
x/range+0.5,
y/range+0.5,
z/range+0.5
);
const alpha = Math.random();
colors.push(color.r,color.g,color.b,alpha);
colors.push(color.r,color.g,color.b,alpha);
colors.push(color.r,color.g,color.b,alpha);
}
geometry.setAttribute("position",new THREE.Float32BufferAttribute(positions,3));//position赋值
geometry.setAttribute("normal",new THREE.Float32BufferAttribute(normals,3));//normals赋值
geometry.setAttribute("color",new THREE.Float32BufferAttribute(color,4));//color赋值
const material = new THREE.MeshPhongMaterial({
color:0x0000ff,//要加normals才会起作用
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
创建一个物体,物体由点构成:用setAttribute设置position;---粒子
const geometry = new THREE.BufferGeometry();//多点物体的geo是一个
const vertices = [];//创建顶点数组(x*1000,y*1000,z*1000)
for(let i = 0;i<1000;i++){
const x =2000*Math.random() - 1000;//-1000~1000
const y =2000*Math.random() - 1000;//-1000~1000
const z =2000*Math.random() - 1000;//-1000~1000
vertices.push(x,y,z);
}
geometry.setAttribute("position",new THREE.Float32BufferAttribute(vertices,3));//赋值
const texture = new THREE.TextureLoader().load(texture/sprites/bindAll.png);//给每个点设置一个雪花贴图
const material = new THREE.PointsMaterial({//多点物体的材质是一个
size:50,//每个点大小
map:texture,
alphaTest:0.5,//看情况加此属性
});
const point = new THREE.Points(geometry,material);
scene.add(point);
指定index顺序的buffergeometry
// indices
const indices = [];
for(let i = 0; i <segments;i++){
for(j=0;j<segments;j++){
const p0 = i*(segments +1)+j;
const p1 = i*(segments +1)+j+1;
const p2 = (i+1)*(segments +1)+j;
const p3 = (i+1)*(segments +1)+(j+1);
indices.push(p0,p1,p2);
indices.push(p2,p3,p1);
}
}
// geometry
const geometry = new THREE.BufferGeometry();
geometry.setIndex(indices)
TextGeometry
loader.load( "/src/assets/font/helvetiker_bold.typeface.json", function ( font ) {
const text = new TextGeometry( '文本内容', {
font: font,
size: 110,
height: 50,
curveSegments: 12,
// bevelEnabled: true,
bevelThickness: 10,
bevelSize: 8,
bevelSegments: 5
} );
const mesh = new THREE.Mesh(text, new THREE.MeshBasicMaterial({ color: 0x000 }))
sceneManager.scene.add(mesh);
});
material
内置材质
不受灯光影响几种材质:
-
MeshBasicMaterial 整个物体的颜色都是一样,没有立体的感觉
-
MeshNormalMaterial 会根据面的方向不同自动改变颜色
-
LineBasicMaterial 线条材质
受灯光影响几种材质
-
MeshLambertMaterial 可模拟一些粗糙的材质,比如木头,石头
-
MeshPhongMaterial 有高光效果,可模拟一些光滑的物体,如油漆面,瓷瓦
-
MeshStandarMaterial Pbr,需要有灯光才能看到物体
-
MeshToonMaterial 卡通材质
着色器材质
GLSL语法:
- uniforms:可以在2个着色器使用uniform float time;相当于全局变量;是从js代码传递过去的:
- varying :从顶点着色器传递到片元着色器的变量;
- attributes:
- 顶点着色器 里面使用uv和position无需声明就能用,他来自geometry.attributes.uv,geometry.attributes.position
- 片元着色器 里面使用uv和position需要用varying从顶点着色器传递过去才能使用
- 解释attribute变量在着色器中的值:
-
position:以geometry中心为圆心,x轴向右为正,最大值是geometry的width/2,y轴向上为正,最大值是h/2
-
uv:以geometry左下角为圆心,x轴向右为正,最大值是1,y轴向上为正,最大值是1,不会随着geometry的大小而改变
// 1.创建shaderMaterial const shaderMaterial = new THREE.ShaderMaterial(); // 2.给shaderMaterial着色器赋值(着色器变量,顶点着色器,片元着色器) shaderMaterial.uniforms = { time : { value:1.0 } }; shaderMaterial.vertexShader = ` varying vec2 vUv; void main(){ vUv = uv; gl_Position =projectionMatrix*modelViewMatrix* vec4(position,1.0); } `; shaderMaterial.fragmentShader = ` varying vec2 vUv; uniform float time; void main(){ gl_FragColor = vec4(1.0,1.0,1.0,1.0); } `; // 3.修改着色器中的time变量,实现动画(requestAnimationFrame中) shaderMaterial.uniforms['time'].value += 1;
-
纹理贴图
-
颜色贴图 map
const textureLoader = new THREE.TextureLoader(); const ColorTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ color:"#ffff00", map:ColorTeture, }) // 设置纹理偏移 ColorTeture.offset.x = 0.5; ColorTeture.offset.set(0.5,0.5); // 设置纹理旋转(默认按左下角旋转) ColorTeture.rotation = Math.PI/4; // 设置旋转中心 ColorTeture.center.set(0.5,0.5); // 设置纹理重复(水平重复次数,竖直重复次数),此处涉及到wraps和wrapt ColorTeture.repeat.set(2,3); // 设置重复的模式(水平方向),上面是镜像重复,下面是非镜像的平铺 ColorTeture.wrapS = THREE.MirroredRepeatWrapping; ColorTeture.wrapS = THREE.RepeatWrapping; -
透明度贴图 alphaMap
const textureLoader = new THREE.TextureLoader(); const AplhaTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ alphaMap:AplhaTeture, transparent:true,//这是是否支持透明 side:DoubleSide,//设置双面可见 }) -
环境遮挡贴图(AO)aoMap
const textureLoader = new THREE.TextureLoader(); const AoTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ aoMap:AoTeture,//环境遮挡贴图 aoMapIntensity:0.5,//设置ao贴图的强度 }) // ao贴图需要第二组uv,所以给几何体设置第二组uv geometry.setAttribute( "uv2", new THREE.BufferAttribute(geometry.attributes.uv.array,2) ) -
置换贴图 (改变高度)displacementMap
const textureLoader = new THREE.TextureLoader(); const heightTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ displacementMap : heightTeture, displacementScale : 0.5,//设置影响程度:最大突出5cm }) // 增加几何体顶点,可以让高度贴图不突兀 const planeGeometry = new THREE.PlaneBufferGeometry(1,1,200,200) -
粗糙度贴图 roughnessMap
const textureLoader = new THREE.TextureLoader(); const roughnessTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ roughnessMap : roughnessTeture,//粗糙度贴图 } -
金属贴图 metalnessMap
const textureLoader = new THREE.TextureLoader(); const metalnessTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ metalnessMap : metalnessTeture,//金属贴图 }) -
法线贴图 normalMap
const textureLoader = new THREE.TextureLoader(); const normalTeture = textureLoader.load("url地址") const basicMaterial = new THREE.MeshBasicMaterial({ normalMap : normalTeture,//法线贴图 }) -
视频贴图
let video = document.createElement("video"); video.src =".mp4"; video.loop = true;//规定当视频结束后将重新开始播放 video.muted = true;//静音 video.play(); let videoTexture = new THREE.VideoTexture(video); const videoGeoPlane = new THREE.PlaneBufferGeometry(16,9); const videoMaterial = new THREE.MeshBasicMaterial({ map:videoTexture, transparent:true, side:THREE.DoubleSide, }) -
环境贴图 (针对物体)
// 设置立方体纹理加载器 const cubeTextureLoader = new THREE.CubeTextureLoader(); const envMapTexture = cubeTextureLoader.load(\[ "px","nx","py","ny","pz","nz", ]); const sphereGeometry = new THREE.SphereBufferGeometry(); const material = new THREE.MeshStandardMaterial({ envMap : envMapTexture,//环境贴图 }); -
环境贴图 (针对环境)
-
四张图
// 以下代码不需要创建一个大geo立方体,即可运行 scene.background = envMapTexture//给场景添加背景 scene.environment = envMapTexture//给场景中所有物体添加默认的环境贴图,就不需要自己针对物体再一个一个的设置环境贴图了 // 创建星空的背景 let envTexture = new THREE.TextureLoader().load(url); scene.background = envTexture; scene.environment = envTexture; -
球形 .HDR 细节更多,图片比较大
// 1.导入RGBELoader import {RGBELoader}from 'three/examples/jsm/loaders/RGBELoader' // 2.加载hdr环境图 const rgbeLoader =new RGBELoader() // 3.设置异步加载 rgbeLoader.loadAsync("url").then((texture)=>{ texture.mapping = THREE.EquirectangularReflectionMapping;// 4.修改映射模式(球型或者其他) scene.background = texture; scene.environment = texture;//可以自动为所有模型添加折射;如果不加的话对于外部导入的模型就是全黑的,因为blender导出的模型具有物理的材质 }) -
球形 .JPG 细节少,图片比较小
new THREE.TextureLoader().load(url,(texture)=>{ texture.mapping=THREE.EquirectangularReflectionMapping; // 设置环境纹理: scene.background = texture; scene.environment = texture; });
-
一个立方体贴6张不同的图
var picList = ["left", "right", "top", "bottom", "front", "back"]; var boxGeometry = new THREE.BoxGeometry(10,10,10); var boxMaterials = []; picList.forEach((item)=>{ let texture = new THREE.TextureLoader().load( require(`@/assets/images/${item}.png`) ) boxMaterials.push(new THREE.MeshBasicMaterial({ map: texture })) }) this.box = new THREE.Mesh(boxGeometry,boxMaterials); this.scene.add(this.box); -
纹理加载进度:
// 单张: let event = {} // 是否加载完成 event.onload = function(){ console.log("图片加载完成"); } event.onProgress = function(e){ console.log("图片加载进度",e); } event.onError = function(e){ console.log("图片加载错误",e); } const doorNormalTeture = textureLoader.load("url地址",event.onLoad,event.onProgress,event.onError) // 多张: let event = {} // 是否加载完成 event.onload = function(){ console.log("图片加载完成"); } event.onProgress = function(url,num,total){ console.log("图片总数:",total); console.log("图片加载进度:",num); console.log("图片加载完成:",url); console.log("图片加载进度百分比:",((num/total)\*100).tofixed(2)+"%"); } event.onError = function(e){ console.log("图片加载错误",e); } // 设置加载管理器 const loadingManager = new THREE.LoadingManager( event.onLoad,event.onProgress,event.onError ) // 导入纹理 const textureLoader = new THREE.TextureLoader(loadingManager); const doorNormalTeture = textureLoader.load("url地址");
内置纹理
-
水面
import {Water} from "three/examples/jsm/objects/Water"; const waterGeometry = new THREE.CircleBufferGeometry(300,64); const water = new Water(waterGeometry,{ textureWidth: 1024, textureHeight: 1024, color: 0xeeeeff, flowDirection: new THREE.Vector2(1,1), scale: 1, }) water.rotation.x = -Math.PI / 2;// 水面旋转至水平 scene.add(water); -
反射
import {Reflector} from "three/examples/jsm/objects/Reflector" let reflectorGeometry = new THREE.PlaneBufferGeometry(100,100); let reflectorPlane = new Reflector(reflectorGeometry,{ textureWidth: window.innerWidth, textureHeight: window.innerHeight, color: 0x332222, }); reflectorPlane.rotation.x = -Math.PI / 2;// 水面旋转至水平 scene.add(reflectorPlane);
mesh
instanceMesh
用instanceMesh实现多个同样形状、不同位置的mesh
const geometry = new THREE.IcosahedronBufferGeometry(0.5);//正20面体(半径)
const material = new THREE.MeshPhongMaterial({color:0xffffff});
var meshes = new THREE.InstancedMesh(geometry, material, 1000);//创建一组mesh:InstancedMesh
this.scene.add(meshes);
const matrix = new THREE.Matrix4();//转换矩阵
const color = new THREE.Color();
for (let index = 0; index < 1000; index++) {
matrix.setPosition(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);//给每个小球设置位置
meshes.setMatrixAt(index, matrix);
meshes.setColorAt( index, color.setHex( Math.random() * 0xffffff ) );//给每个小球设置颜色
}
helper使用
// 顶点法线
import {VertexNormalsHelper} from 'three/examples/jsm/helpers/VertexNormalsHelper.js'
let vertexNormalHelper = new VertexNormalsHelper(mesh,10);
this.scene.add(vertexNormalHelper)
// 顶点切线
import {VertexTangentsHelper} from 'three/examples/jsm/helpers/VertexTangentsHelper.js'
mesh.geometry.computeTangents()
let VertexTangentsHelper = new VertexTangentsHelper(mesh,5);
this.scene.add(VertexTangentsHelper)
// 包围盒
this.scene.add(new THREE.BoxHelper(mesh))
其他:
CSS2DRenderer(div加载到threejs场景中,自由定义文字内容,有点类似精灵图,显示为二维,主要用于鼠标交互之类的事件)
import { CSS2DRenderer, CSS2DObject } from "../node_modules/three/examples/jsm/renderers/CSS2DRenderer.js";
// 创建div并添加
let earth = new THREE.Mesh(earthGeometry,earthMaterial)
earth.receiveShadow = true;earth.castshadow = true;
scene.add(earth)
const earthDiv = document.createElement('div');
earthDiv.className = 'label';
earthDiv.textContent = 'Earch';
const eartchLabel = new css2Dobject(earthDiv)
eartchLabel.position.set(0,EARTH_RADIUS + 0.5,0)
earth.add(eartchLabel)
// 修改样式:
// .label{
// color: #fff;
// font-size: 16px;
// }
// 标签渲染器
labelRenderer = new CSS2DRenderer();
labelRenderer.setsize(window.innerWidth);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
function animate() {
labelRenderer.render(scene, camera)
}
创建永远朝着正方向的精灵
let spriteTexture = new THREE.TextureLoader().load("./images/glow.png");
let spriteMaterial = new THREE.SpriteMaterial({
map: spriteTexture,
color: 0x4d76cf,
transparent: true,
depthwrite: false,
depthTest:false,
blending: THREE.AdditiveBlending,
});
let sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(155,155, 0);
sprite.content = item.content; // 设置标签内容
scene.add(sprite);
深度问题
function createRect(option = {}, position = [0, 0, 0], renderOrder = 0,) {
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshStandardMaterial({ ...option });
var cube = new THREE.Mesh(geometry, material);
cube.position.set(...position);
cube.renderOrder = renderOrder;
scene.add(cube);
return cube
}
createRect({ color: 0x00ff00 })
createRect({ color: 0xff0000 },[0.5,0,0])
createRect({ color: 0x00ff00,polygonOffset:true, polygonOffsetFactor: 1.0,},[2,0,0])
createRect({ color: 0xff0000, },[2.5,0,0])
createRect({ color: 0x00ff00, depthTest:false},[4,0,0],3)
createRect({ color: 0xff0000, },[4.5,0,0],2)
createRect({ color: 0x00ff00, transparent:true,opacity:0.5},[-2,0,0],3)
var rect=createRect({ color: 0xff0000, transparent:true,opacity:0.5},[-2,0,0],2)
rect.scale.set(0.5,0.5,0.5)
createRect({ color: 0x00ff00, transparent:true,opacity:0.5},[-4,0,0],1)
var rect=createRect({ color: 0xff0000, transparent:true,opacity:0.5},[-4,0,0],2)
rect.scale.set(0.5,0.5,0.5)
世界坐标到屏幕坐标转换
// 注意 有OrbitControls,一定要在controls.update()之前调用这段代码;
var rect =renderer.domElement.getBoundingClientRect();
var meshPosition=cube.position.clone();
var standardVector = meshPosition.project(camera);
let a=rect.width/2;
let b= rect.height/2;
let x = ~~(standardVector.x*a+a);
let y = ~~(b-standardVector.y*b);
div.innerHTML='x:'+x+'<br>'+'y:'+y;
div.style=`--x:${x}px;--y:${y}px`;
双击this.container后,进入全屏
this.container.addEventListener("dblclick",() => {
const fullscreenElement = document.fullscreenElement;
if (!fullscreenElement) {
sceneManager.renderer.domElement.requestFullscreen();
}else{
document.exitFullscreen();
}
})
鼠标控制物体:
// 1.全局创建raycaster对象
var raycaster = new THREE.Raycaster();
// 2.全局创建参数mouse:
var mouse = new THREE.Vector2();
document.addEventListener("mousemove", function(e) {
mouse.x = (e.clientX/window.innerWidth) * 2 - 1; // -1~1
mouse.y = -(e.clientY/window.innerHeight) * 2 + 1; // -1~1
})
// 3.在重复执行的渲染函数render中:发出一条射线(render中)
raycaster.setFromCamera(mouse,camera);
// log:Raycaster.ray.origin//表示相机位置
// log:Raycaster.ray.direction//相机射向的方向
// 4.拿射线和我们想要进行交互的物体mesh做一个交集(render中)
const intersection = raycaster.intersectObject(mesh);
// 5.获取相交的第一个物体的ID:目标物体(render中)
if(intersection.length > 0){
const instanceId = intersection[0];
// 6.对目标instanceId做交互
instanceId.object//接触到的mesh
instanceId.distance//camera到接触点距离
instanceId.point//接触点坐标
instanceId.face.a//组成face(三角面)的三个点的索引值,face指的是每个三角面
instanceId.face.b
instanceId.face.c
instanceId.face.normal//三角面的normal值
instanceId.face.materialIndex//三角面的每个面的材质索引
instanceId.faceIndex//当前点击的这个三角面的索引号
instanceId.uv//接触点在其所在平面的uv值
}
封装函数来让其旋转中心在自身中心
THREE.Mesh.prototype.setAngle = function(deg){
// 先获取geometey的中心点位置并留存
let center2 = new THREE.Vector3();
this.geometry.computeBoundingBox();
this.geometry.boundingBox.getCenter(center2);
let x2 = center2.x;
let y2 = center2.y;
let z2 = center2.z;
// 把对象放到坐标原点
this.geometry.center();
// 绕轴旋转
this.geometry.rotateZ(deg);
// 再把对象放回原来的地方
this.geometry.translate(x2, y2, z2);
// 这种方式是不会刷新显示的,需要执行一下render
renderer.render(scene, camera);
if(this.children.length>0){
this.traverse((obj)=>{
if(obj.isMesh && obj!==this){
obj.setAngle(deg)
}
})
}
}
// 找到目标:
var leftBasic =obj.scene.getObjectByName("color-6")
// 调用setAngle设置角度:
leftBasic.setAngle(Math.PI)
控制器限制平移范围(防止跑出天空盒)
const minPan = new THREE.Vector3(-10, 0, -10);
const maxPan = new THREE.Vector3(10, 0, 10);
const _v = new THREE.Vector3();
controls.addEventListener('change', (event) => {
_v.copy(controls.target);
controls.target.clamp(minPan, maxPan);
_v.sub(controls.target);
camera.position.sub(_v);
})
从满天星变成心形的动画
function makeHeart(){
let params = {
time:0,
};
gsap.to(params,{
time:1,
duration:1,
onUpdate:()=>{
for(let i=0;i<100;i++){
let x = starsArr[i].x + (endArr[i].x - startArr[i].x) * params.time;
let y = starsArr[i].y + (endArr[i].y - startArr[i].y) * params.time;
let z = starsArr[i].z + (endArr[i].z - startArr[i].z) * params.time;
let matrix = new THREE.Matrix4();
matrix.setPosition(x,y,z);
startInstance.setMatrixAt(i,matrix);
}
startInstance.instanceMatrix.needsUpdate = true;
}
})
}
自定义贝塞尔曲线,通过getpoint得到路径上的点
let heartShape = new THREE.Shape();
heartShape.moveTo(25,25);
heartShape.bezierCurveTo(25,25,20,0,0,0);
heartShape.bezierCurveTo(-30,0,-30,35,-30,35);
heartShape.bezierCurveTo(-30,55,-10,77,25,95);
heartShape.bezierCurveTo(60,77,80,55,80,35);
heartShape.bezierCurveTo(80,35,80,0,50,0);
heartShape.bezierCurveTo(35,0,25,25,25,25);
// 根据爱心路径获取点
for(let i = 0;i<100;i++){
let point = heartShape.getPoint(i/100);
endArr.push(new THREE.Vector3(point.x, point.y, point.z))
}
vr看房:
// 一个立方体贴6张不同的图
var picList = ["left", "right", "top", "bottom", "front", "back"];
var boxGeometry = new THREE.BoxGeometry(10,10,10);
var boxMaterials = [];
picList.forEach((item)=>{
// 纹理加载
let texture = new THREE.TextureLoader().load(`@/assets/images/${item}.png`);
boxMaterials.push(new THREE.MeshBasicMaterial({//创建材质
map: texture
}))
})
this.box = new THREE.Mesh(boxGeometry,boxMaterials);
box.boxGeometry.scale(1,1,-1)//设置贴图转到立方体内部
this.scene.add(this.box);
// 一张hdr实现:
// 1.导入RGBELoader
import {RGBELoader}from 'three/examples/jsm/loaders/RGBELoader'
var geometry = new THREE.sphereGeometry(10,10,10);
// 2.加载hdr环境图
const rgbeLoader =new RGBELoader()
rgbeLoader.load(".hdr",(texture)=>{
const material = new THREE.MeshBasicMaterial({
map:texture
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
})
// 一张jpg:
var geometry = new THREE.sphereGeometry(10,10,10);
let texture = new THREE.TextureLoader().load(require('url'));
let sphereMaterial = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh(geometry, sphereMaterial);
this.scene.add(mesh);
让模型跟随鼠标移动而旋转
window.addEventListener("mousemove", ()=>{
let x = (e.clientX / window.innerWidth) * 2 - 1;
let y = (e.clientY / window.innerHeight) * 2 - 1;
let timeline = gsap.timeline();
timeline.to(glb.rotation,{
duration:0.5,
x:y,
y:x,
})
})
切换不同的glb模型案例
// 先删除原来的模型,再添加新模型,利用缓存提升性能
let model = reactive({
name:["url1","","url2","url3","","url4"]
})
// params.input.type=0,1,2,3
watch(// 监听参数变化改变把变量名称传进去
[() => params.input.type],
(val, oldVal) => {
let name = model.name[params.input.type] //烧杯 玻璃
threejs.name = `./static/model/${name}.glb`;
let model = threejs.scene.getObjectByName("烧杯");
if(model){ // 有这个模型的话先删除
threejs.scene.remove(model);
}
if(threejs.modelStore[name]){// 模型之前被加载过,就从缓存中取模型
threejs.scene.add(threejs.modelStore[name])
}else{ // 首次加载模型
threejs.loadGltf(threejs.name)
} },
);
let threejs = {
modelStore:{},
loadGltf(name){
this.loader = new GLTFLoader();
this.loader.load(name, function(gltf) {
let model3 = gltf.scene.getObjectByName("烧杯");
threejs.scene.add(model3);
// 缓存模型
let modelName = model.name[params.input.type]
threejs.modelStore[modelName] = model3
})
},
}
切换不同的贴图案例:mesh.material.map = deskTexture
// textureDesk:"./static/texture/石蜡.png",
//创建桌面
var planeGeometry = new THREE.PlaneGeometry(300, 200);
const deskTexture = new THREE.TextureLoader().load(this.textureDesk); // 给桌面设置贴图
var planeMaterial = new THREE.MeshStandardMaterial({
color: 0xD3D3D3,
map: deskTexture,
roughness: 0.1,
side: THREE.DoubleSide,
});
this.desk = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(this.desk);
// 缓存贴图
let textureName = customData.surfaceType[params.input.type] //当前的url
this.textureStore[textureName] = deskTexture
let customData = reactive({ surfaceType:["玻璃","石蜡","塑料","亚克力","尼龙","","钢"]})
watch(// 监听容器材料变化
[() => params.input.type],(val, oldVal) => {
let name = customData.surfaceType[params.input.type] //改变之后的url
threejs.textureDesk = `./static/texture/${name}.png`; //存储的textureDesk
if(threejs.textureStore[name] && threejs.textureStore[name].image){
threejs.desk.material.map = threejs.textureStore[name]
}else{
let deskTexture = new THREE.TextureLoader().load(threejs.textureDesk); // 给桌面设置贴图
threejs.desk.material.map = deskTexture
threejs.textureStore[name] = deskTexture
}
},
);