在项目实践中可能需要实现一些纹理材质的特殊效果,比如让纹理贴图沿着某个方向动起来,可以实现一些图片轮播或者流光的效果。有时可能需要为一个模型或几何体设置多个材质,在文章中简单的介绍了让纹理动起来、为几何体设置多个材质的简单操作。
初始化一个 Vue 项目环境
这里使用 Vue + three.js 的方式做一些简单的案例。
- 初始化一个项目
vue create xxx
- 安装three.js
npm i three@xxx.xxx -S
- 在需要使用 three.js 模块的地方按需导入模块 ,如:
import {
AxesHelper,
ConeGeometry,
DirectionalLight,
DoubleSide,
HemisphereLight,
Mesh,
MeshNormalMaterial,
MeshPhongMaterial,
MirroredRepeatWrapping,
PerspectiveCamera,
PlaneGeometry,
RepeatWrapping,
Scene,
TextureLoader,
WebGLRenderer
} from "three";
纹理贴图的简单介绍
texture
纹理,使用纹理加载器textureLoader
。
常用纹理加载器 textureLoader 的创建
let textureLoad = new TextureLoader();
textureLoader.load('资源的路径url')
纹理加载器常用属性
repeat
:设置纹理的平铺次数;第一个参数是x轴的平铺次数、第二个参数是y轴的平铺次数;
let textureLoad = new TextureLoader();
// 设置纹理
this.planTextureLoader = textureLoader.load(require('../../assets/girl.jpg'))
// 设置纹理的平铺次数 x轴3次 轴 2次
this.planTextureLoader.repeat.set(3, 2)
- 分别设置x、y轴的平铺方式通过
wrapS 、wrapT
:
this.planTextureLoader.wrapS = MirroredRepeatWrapping // 相当于x轴的平铺方式为镜像平铺
this.planTextureLoader.wrapT = RepeatWrapping // 相当于y轴的平铺方式
- 设置纹理的偏移(在做一些流光效果的时候比较实用):
// 让纹理动起来累加 offset属性的值
this.planTextureLoader.offset.x += 0.01
- 纹理坐标:
- 贴图是需要纹理坐标的,系统提供的几何体都是有纹理坐标的;
- 建模模型导出的话也可以导入纹理坐标 在模型中有。
使用上面的一些属性实现一个案例: 实现纹理偏移让纹理动起来
<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>
案例 : 为几何体设置多个材质
在图中可以看见该物体有一个网格材质和一个普通的实体材质。这里需要为物体添加两种材质该怎么做呢?
案例实践
- 创建一个几何体:
// 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])
- 整体代码实现:
// 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)
- 案例完整代码:
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()
}
}
- demo的全局样式:
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
/* 隐藏滚动条 */
overflow: hidden;
}