threejs

501 阅读7分钟

初始化

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

image.png

    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

image.png

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;---粒子

image.png

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:
    1. 顶点着色器 里面使用uv和position无需声明就能用,他来自geometry.attributes.uv,geometry.attributes.position
    2. 片元着色器 里面使用uv和position需要用varying从顶点着色器传递过去才能使用
  • 解释attribute变量在着色器中的值:
    1. position:以geometry中心为圆心,x轴向右为正,最大值是geometry的width/2,y轴向上为正,最大值是h/2

    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,//环境贴图
    });
    
  • 环境贴图 (针对环境)

  1. 四张图

      // 以下代码不需要创建一个大geo立方体,即可运行
      scene.background = envMapTexture//给场景添加背景
      scene.environment = envMapTexture//给场景中所有物体添加默认的环境贴图,就不需要自己针对物体再一个一个的设置环境贴图了
      // 创建星空的背景
      let envTexture = new THREE.TextureLoader().load(url);
      scene.background = envTexture;
      scene.environment = envTexture;
    
  2. 球形 .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导出的模型具有物理的材质
      })
    
  3. 球形 .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
        }
    },
    );

boxgeometry如何删除面:根据法向量去判断是否discard