让纹理动起来!为几何体设置多个材质!

590 阅读4分钟

在项目实践中可能需要实现一些纹理材质的特殊效果,比如让纹理贴图沿着某个方向动起来,可以实现一些图片轮播或者流光的效果。有时可能需要为一个模型或几何体设置多个材质,在文章中简单的介绍了让纹理动起来、为几何体设置多个材质的简单操作。

初始化一个 Vue 项目环境

这里使用 Vue + three.js 的方式做一些简单的案例。

  1. 初始化一个项目
vue create xxx
  1. 安装three.js
npm i three@xxx.xxx -S
  1. 在需要使用 three.js 模块的地方按需导入模块 ,如:
import {
    AxesHelper,
    ConeGeometry,
    DirectionalLight,
    DoubleSide,
    HemisphereLight,
    Mesh,
    MeshNormalMaterial,
    MeshPhongMaterial,
    MirroredRepeatWrapping,
    PerspectiveCamera,
    PlaneGeometry,
    RepeatWrapping,
    Scene,
    TextureLoader,
    WebGLRenderer
} from "three";

纹理贴图的简单介绍

  1. texture 纹理,使用纹理加载器 textureLoader

常用纹理加载器 textureLoader 的创建


let textureLoad = new TextureLoader();

textureLoader.load('资源的路径url')

纹理加载器常用属性

  1. repeat:设置纹理的平铺次数;第一个参数是x轴的平铺次数、第二个参数是y轴的平铺次数;
let textureLoad = new TextureLoader();
// 设置纹理
this.planTextureLoader = textureLoader.load(require('../../assets/girl.jpg'))

// 设置纹理的平铺次数 x轴3次 轴 2次
this.planTextureLoader.repeat.set(3, 2)
  1. 分别设置x、y轴的平铺方式通过 wrapS 、wrapT
this.planTextureLoader.wrapS = MirroredRepeatWrapping // 相当于x轴的平铺方式为镜像平铺
this.planTextureLoader.wrapT = RepeatWrapping // 相当于y轴的平铺方式
  1. 设置纹理的偏移(在做一些流光效果的时候比较实用):
// 让纹理动起来累加 offset属性的值
this.planTextureLoader.offset.x += 0.01
  1. 纹理坐标:
  • 贴图是需要纹理坐标的,系统提供的几何体都是有纹理坐标的;
  • 建模模型导出的话也可以导入纹理坐标 在模型中有。

使用上面的一些属性实现一个案例: 实现纹理偏移让纹理动起来

纹理偏移让纹理动起来

<script>
    import {
    AxesHelper,
    ConeGeometry,
    DirectionalLight,
    DoubleSide,
    HemisphereLight,
    Mesh,
    MeshNormalMaterial,
    MeshPhongMaterial,
    MirroredRepeatWrapping,
    PerspectiveCamera,
    PlaneGeometry,
    RepeatWrapping,
    Scene,
    TextureLoader,
    WebGLRenderer
} from "three";

    import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'

    export default {
    name: "index",
    data() {
    return {
    // 渲染器
    renderer: null,
    // 相机
    camera: null,
    // 场景
    scene: null,
    // 灯光
    light: null,
    // 长方形的纹理加载器
    planTextureLoader:null
}
},
    methods: {
    init() {

    // 1. 创建一个渲染器
    this.renderer = new WebGLRenderer({antialias: true})
    // 设置渲染器的大小
    this.renderer.setSize(window.innerWidth, window.innerHeight)
    // 设置渲染器的清空颜色
    this.renderer.setClearColor(0xffffff)
    // 将渲染器的canvas挂载到DOM上
    document.body.appendChild(this.renderer.domElement)

    // 2. 创建一个相机
    this.camera = new PerspectiveCamera(80, window.innerWidth / window.innerHeight, window.innerWidth / window.innerHeight, 1000)

    // 监听窗口的改变动态设置渲染器尺寸
    window.addEventListener('resize', () => {
    // 更新渲染器尺寸
    this.renderer.setSize(window.innerWidth, window.innerHeight)
    // 更新相机的宽高比
    this.camera.aspect = window.innerWidth / window.innerHeight
    // 更新相机宽高比之后需要重新更新相机的投影矩阵
    this.camera.updateProjectionMatrix()
})

    // 设置相机的位置
    this.camera.position.set(0, 0, 100)

    // 创建一个场景
    this.scene = new Scene()

    // 创建一个灯光
    this.light = new HemisphereLight(0xeeeeee, 0xff9900)
    // this.light = new DirectionalLight(0xeeeeee , 0xff9900)

    // 将灯光添加到场景中
    this.scene.add(this.light)

    // 创建辅助线
    let auxLine = new AxesHelper(10000)
    // this.scene.add(auxLine)

    // 创建一个纹理加载器
    let textureLoader = new TextureLoader()

    // 创建形状 一个圆锥
    let coneGeometry = new ConeGeometry(30, 35, 8)
    // 创建一个材质
    let coneMaterial = new MeshPhongMaterial({
    map: textureLoader.load(require('../../assets/girl.jpg'))
})
    // 根据形状和材质创建一个模型
    let coneMesh = new Mesh(coneGeometry, coneMaterial)

    // 将模型添加到场景中
    this.scene.add(coneMesh)

    /** 设置纹理开始 **/
    // 设置纹理
    this.planTextureLoader = textureLoader.load(require('../../assets/girl.jpg'))

    // 设置纹理的平铺次数 x轴3次 轴 2次
    this.planTextureLoader.repeat.set(3, 2)
    this.planTextureLoader.wrapS = MirroredRepeatWrapping // 相当于x轴的平铺方式为镜像平铺
    this.planTextureLoader.wrapT = RepeatWrapping // 相当于y轴的平铺方式

    /**
     * 纹理坐标 :
     *    填图是需要纹理坐标的,我们系统提供的几何体都是有纹理坐标的
     *    建模模型导出的话也可以导入纹理坐标 在模型中有
     * */

    // 让纹理动起来累加 offset属性的值
    this.planTextureLoader.offset.x += 0.01

    /** 设置纹理结束 */

    // 创建一个平面
    let planGeometry = new PlaneGeometry(30, 10)
    // 创建材质
    let planMaterial = new MeshPhongMaterial({
    // 设置为双面显示
    side: DoubleSide,
    map: this.planTextureLoader
})
    // 根据形状和材质创建一个新模型
    let planMesh = new Mesh(planGeometry, planMaterial)
    planMesh.position.z = 50
    this.scene.add(planMesh)

    // 使用渲染器渲染场景和相机
    this.renderer.render(this.scene, this.camera)
    // 创建一个控制器
    new OrbitControls(this.camera, this.renderer.domElement)
    this.update()
},
    update() {
    this.renderer.render(this.scene, this.camera)
    this.planTextureLoader.offset.x += 0.001
    requestAnimationFrame(this.update)
}
},
    created() {
    this.init()
}
}
</script>

案例 : 为几何体设置多个材质

几何体多种材质

在图中可以看见该物体有一个网格材质和一个普通的实体材质。这里需要为物体添加两种材质该怎么做呢?

案例实践

  1. 创建一个几何体:
// 4. 创建一个长方体
let boxGeometry = new BoxBufferGeometry(20, 20, 20, 10, 10, 10)
  1. 为几何体创建一个线框(网格)材质,并设置线框颜色:
 // 创建材质  并设置为线框模式
let boxMaterial = new MeshPhongMaterial({
    wireframe: true,
    color: 0x0007ff
})
  1. 为物体添加一个实体材质默认颜色:
 // 再创建材质
let boxMaterialB = new MeshPhongMaterial({})
  1. 当需要添加多个材质的时候需要添加一个材质组:
 boxGeometry.addGroup(0, +Infinity, 0)
boxGeometry.addGroup(0, +Infinity, 1)
  1. 以数组的方式传递材质,和几何体创建一个网格模型:
 let boxMesh = new Mesh(boxGeometry, [boxMaterial, boxMaterialB])
  1. 整体代码实现:
               // 4. 创建一个长方体
let boxGeometry = new BoxBufferGeometry(20, 20, 20, 10, 10, 10)
// 创建材质  并设置为线框模式
let boxMaterial = new MeshPhongMaterial({
    wireframe: true,
    color: 0x0007ff
})
// 再创建材质
let boxMaterialB = new MeshPhongMaterial({})
// 当有多个材质的时候需要添加多个几何组数组和材质数量是一致的
boxGeometry.addGroup(0, +Infinity, 0)
boxGeometry.addGroup(0, +Infinity, 1)
let boxMesh = new Mesh(boxGeometry, [boxMaterial, boxMaterialB])
this.scene.add(boxMesh)
  1. 案例完整代码:
  import {
    AmbientLight,
    BoxBufferGeometry,
    DirectionalLight, Mesh,
    MeshPhongMaterial,
    PerspectiveCamera,
    Scene,
    WebGLRenderer
} from "three";

import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'

export default {
    name: "index",
    data() {
        return {
            // 渲染器
            renderer: null,
            // 相机
            camera: null,
            // 场景
            scene: null
        }
    },
    methods: {
        /**
         * 初始化
         * */
        init() {

            /**
             * threeJS中自带的几何体:
             *
             * BoxBufferGeometry 长方体
             *
             * PlaneBufferGeometry 平面
             *
             * CylinderBufferGeometry 圆柱体
             *
             * ConeBufferGeometry 圆锥
             *
             * CircleBufferGeometry 圆盘
             *
             * SphereBufferGeometry 球体
             *
             * EdgeBufferGeometry 边缘几何体
             */
            // 1. 创建一个渲染器
            this.renderer = new WebGLRenderer({antialias: true})
            // 设置渲染器的尺寸
            this.renderer.setSize(window.innerWidth, window.innerHeight)
            // 将渲染器的renderer canvas 挂载到DOM上
            document.body.appendChild(this.renderer.domElement)

            // 2. 创建一个相机
            this.camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, window.innerWidth / window.innerHeight, 2000)
            // 设置相机的位置
            this.camera.position.set(0, 0, 50)

            // 监听当前窗口的变化
            window.addEventListener('resize', () => {
                // 更新渲染器的尺寸
                this.renderer.setSize(window.innerWidth, window.innerHeight)
                // 更新相机的宽高比
                this.camera.aspect = window.innerWidth / window.innerHeight
                // 更新相机的投影矩阵
                this.camera.updateProjectionMatrix()
            })

            // 3. 创建一个场景
            this.scene = new Scene()
            // 创建一个环境光
            let ambientLight = new AmbientLight(0xffffff)
            // 将光添加到场景中
            this.scene.add(ambientLight)

            // 创建一个方向光
            let directionalLight = new DirectionalLight(0xaaffff);
            this.scene.add(directionalLight)
            this.scene.add(directionalLight)

            // 4. 创建一个长方体
            let boxGeometry = new BoxBufferGeometry(20, 20, 20, 10, 10, 10)
            // 创建材质  并设置为线框模式
            let boxMaterial = new MeshPhongMaterial({
                wireframe: true,
                color: 0x0007ff
            })
            // 再创建材质
            let boxMaterialB = new MeshPhongMaterial({})
            // 当有多个材质的时候需要添加多个几何组数组和材质数量是一致的
            boxGeometry.addGroup(0, +Infinity, 0)
            boxGeometry.addGroup(0, +Infinity, 1)
            let boxMesh = new Mesh(boxGeometry, [boxMaterial, boxMaterialB])
            this.scene.add(boxMesh)

            // 渲染场景和相机
            this.renderer.render(this.scene, this.camera)
            new OrbitControls(this.camera, this.renderer.domElement)
            this.update();
        },
        update() {
            this.renderer.render(this.scene, this.camera)
            requestAnimationFrame(this.update)
        }
    },
    created() {
        this.init()
    }
}
  1. demo的全局样式:
 html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
/* 隐藏滚动条 */
overflow: hidden;
}