通过THREE.PointsMaterial材质使用HTML5画布格式化粒子
1.使用画布格式化粒子介绍
老版的three.js提供了两种使用THML5画布格式化粒子的方法,即使用CanvasRenderer类和使用WebGLRenderer类,但是新版three.js将CanvasRenderer类移除了
所以我们讨论学习通过画布格式化粒子,只针对WebGLRenderer类,即我们创建的渲染器都是使用WebGLRenderer类。
接下来介绍一下PointsMaterial材质的属性
属性 | 描述 |
---|---|
color | 该属性指定粒子材质的颜色,默认值是0xFFFFFF |
map | 通过该属性可以在粒子上应用某种材质。例如看起来像雪花的材质 |
size | 通过该属性指定粒子的大小,默认值是1.0 |
sizeAnnutation | 该属性是一个布尔值,若为true表示粒子的大小由粒子到相机的距离决定,若为false表示无论粒子到相机的距离是多少,粒子都会有相同的尺寸 |
morphTargets | 该属性是一个布尔值,指定材质是否使用morphTargets。默认值为false |
2.使用HTML5画布格式化粒子
2.1创建Texture画布纹理
示例中粒子展示的是一群小怪兽,创建画布纹理就是获取小怪兽的样子,具体实现如下
// 绘制小怪兽纹理
getTexture () {
const canvas = document.createElement('canvas')
canvas.width = 32
canvas.height = 32
const ctx = canvas.getContext('2d')
// 身体
ctx.translate(-81, -84)
ctx.fillStyle = 'orange'
ctx.beginPath()
ctx.moveTo(83, 116)
ctx.lineTo(83, 102)
ctx.bezierCurveTo(83, 94, 89, 88, 97, 88)
ctx.bezierCurveTo(105, 88, 111, 94, 111, 102)
ctx.lineTo(111, 116)
ctx.lineTo(106.333, 111.333)
ctx.lineTo(101.666, 116)
ctx.lineTo(97, 111.333)
ctx.lineTo(92.333, 116)
ctx.lineTo(87.666, 111.333)
ctx.lineTo(83, 116)
ctx.fill()
// 眼睛
ctx.fillStyle = 'white'
ctx.beginPath()
ctx.moveTo(91, 96)
ctx.bezierCurveTo(88, 96, 87, 99, 87, 101)
ctx.bezierCurveTo(87, 103, 88, 106, 91, 106)
ctx.bezierCurveTo(94, 106, 95, 103, 95, 101)
ctx.bezierCurveTo(95, 99, 94, 96, 91, 96)
ctx.moveTo(103, 96)
ctx.bezierCurveTo(100, 96, 99, 99, 99, 101)
ctx.bezierCurveTo(99, 103, 100, 106, 103, 106)
ctx.bezierCurveTo(106, 106, 107, 103, 107, 101)
ctx.bezierCurveTo(107, 99, 106, 96, 103, 96)
ctx.fill()
// 眼球
ctx.fillStyle = 'blue'
ctx.beginPath()
ctx.arc(101, 102, 2, 0, Math.PI * 2, true)
ctx.fill()
ctx.beginPath()
ctx.arc(89, 102, 2, 0, Math.PI * 2, true)
ctx.fill()
const texture = new THREE.Texture(canvas)
texture.needsUpdate = true
return texture
}
2.2 创建粒子材质
使用THREE.PointsMaterial类创建粒子材质和创建其他材质一样。不过我们需要将上面的创建纹理的函数赋值给材质的map属性,具体看示例代码
// 创建粒子材质
const material = new THREE.PointsMaterial({
size: this.properties.size.value,
transparent: this.properties.transparent,
map: this.getTexture(),
opacity: this.properties.opacity.value,
vertexColors: this.properties.vertexColors,
sizeAttenuation: this.properties.sizeAttenuation,
color: this.properties.color
})
2.2 创建Points对象并添加到场景
// 创建粒子系统对象
this.points = new THREE.Points(geom, material)
// 将粒子系统对象添加到场景
this.scene.add(this.points)
3.demo效果
4.demo代码
<template>
<div>
<div id="container"></div>
<div class="controls-box">
<section>
<el-row>
<el-checkbox v-model="properties.transparent" @change="redraw">transparent</el-checkbox>
</el-row>
<el-row>
<div v-for="(item,key) in properties" :key="key">
<div v-if="item&&item.name!=undefined">
<el-col :span="8">
<span class="vertice-span">{{item.name}}</span>
</el-col>
<el-col :span="13">
<el-slider v-model="item.value" :min="item.min" :max="item.max" :step="item.step" :format-tooltip="formatTooltip" @change="redraw"></el-slider>
</el-col>
<el-col :span="3">
<span class="vertice-span">{{item.value}}</span>
</el-col>
</div>
</div>
</el-row>
<el-row>
<el-checkbox v-model="properties.vertexColors" @change="redraw">vertexColors</el-checkbox>
</el-row>
<el-row>
<el-col :span="8" class="label-col"><label> color</label></el-col>
<el-col :span="16">
<div @click="inputClick">
<el-input :value="properties.color"></el-input>
</div>
<div v-show="isShowColors" class="color-select-layer">
<sketch-picker v-model="properties.color" @input="colorChange"></sketch-picker>
</div>
</el-col>
</el-row>
<el-row>
<el-checkbox v-model="properties.sizeAttenuation" @change="redraw">sizeAttenuation</el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="properties.rotateSystem" @change="redraw">rotateSystem</el-checkbox>
</el-row>
</section>
</div>
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { Sketch } from 'vue-color'
export default {
components: {
'sketch-picker': Sketch
},
data () {
return {
properties: {
size: {
name: 'size',
value: 9,
min: 0,
max: 20,
step: 0.1
},
opacity: {
name: 'opacity',
value: 0.6,
min: 0,
max: 1,
step: 0.1
},
transparent: true,
vertexColors: true,
sizeAttenuation: true,
rotateSystem: true,
color: '#ccffcc'
},
isShowColors: false,
points: null,
camera: null,
scene: null,
renderer: null,
controls: null
}
},
mounted () {
this.init()
},
methods: {
formatTooltip (val) {
return val
},
inputClick () {
this.isShowColors = !this.isShowColors
},
colorChange (val) {
this.properties.color = val.hex
this.redraw()
},
// 初始化
init () {
this.createScene() // 创建场景
this.createParticles() // 创建粒子系统
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染
},
// 创建场景
createScene () {
this.scene = new THREE.Scene()
},
// 创建粒子系统
createParticles () {
// 创建几何体
const geom = new THREE.Geometry()
// 创建粒子材质
const material = new THREE.PointsMaterial({
size: this.properties.size.value,
transparent: this.properties.transparent,
map: this.getTexture(),
opacity: this.properties.opacity.value,
vertexColors: this.properties.vertexColors,
sizeAttenuation: this.properties.sizeAttenuation,
color: this.properties.color
})
const range = 500
for (let i = 0; i < 5000; i++) {
const particle = new THREE.Vector3(
Math.random() * range - range / 2,
Math.random() * range - range / 2,
Math.random() * range - range / 2
)
// 给几何体添加顶点坐标
geom.vertices.push(particle)
const color = new THREE.Color(0xffffff * Math.random())
// 给几何体添加顶点颜色
geom.colors.push(color)
}
// 创建粒子系统对象
this.points = new THREE.Points(geom, material)
this.points.name = 'particles'
// 将粒子系统对象添加到场景
this.scene.add(this.points)
},
// 绘制小怪兽纹理
getTexture () {
const canvas = document.createElement('canvas')
canvas.width = 32
canvas.height = 32
const ctx = canvas.getContext('2d')
// 身体
ctx.translate(-81, -84)
ctx.fillStyle = 'orange'
ctx.beginPath()
ctx.moveTo(83, 116)
ctx.lineTo(83, 102)
ctx.bezierCurveTo(83, 94, 89, 88, 97, 88)
ctx.bezierCurveTo(105, 88, 111, 94, 111, 102)
ctx.lineTo(111, 116)
ctx.lineTo(106.333, 111.333)
ctx.lineTo(101.666, 116)
ctx.lineTo(97, 111.333)
ctx.lineTo(92.333, 116)
ctx.lineTo(87.666, 111.333)
ctx.lineTo(83, 116)
ctx.fill()
// 眼睛
ctx.fillStyle = 'white'
ctx.beginPath()
ctx.moveTo(91, 96)
ctx.bezierCurveTo(88, 96, 87, 99, 87, 101)
ctx.bezierCurveTo(87, 103, 88, 106, 91, 106)
ctx.bezierCurveTo(94, 106, 95, 103, 95, 101)
ctx.bezierCurveTo(95, 99, 94, 96, 91, 96)
ctx.moveTo(103, 96)
ctx.bezierCurveTo(100, 96, 99, 99, 99, 101)
ctx.bezierCurveTo(99, 103, 100, 106, 103, 106)
ctx.bezierCurveTo(106, 106, 107, 103, 107, 101)
ctx.bezierCurveTo(107, 99, 106, 96, 103, 96)
ctx.fill()
// 眼球
ctx.fillStyle = 'blue'
ctx.beginPath()
ctx.arc(101, 102, 2, 0, Math.PI * 2, true)
ctx.fill()
ctx.beginPath()
ctx.arc(89, 102, 2, 0, Math.PI * 2, true)
ctx.fill()
const texture = new THREE.Texture(canvas)
texture.needsUpdate = true
return texture
},
// 创建相机
createCamera () {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(35, k, 0.1, 1000)
this.camera.position.set(-80, 60, 40) // 设置相机位置
this.camera.lookAt(new THREE.Vector3(10, 0, 0)) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender () {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
this.renderer.shadowMap.enabled = true // 显示阴影
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},
// 重新绘制
redraw () {
this.scene.remove(this.points)
this.createParticles()
},
render () {
if (this.properties.rotateSystem) {
this.points.rotation.y += 0.01
}
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls () {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
}
}
}
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
.controls-box {
position: absolute;
right: 5px;
top: 5px;
width: 300px;
padding: 10px;
background-color: #fff;
border: 1px solid #c3c3c3;
}
.label-col {
padding: 8px 5px;
}
.color-select-layer {
position: relative;
left: -20px;
padding: 15px 0;
}
.vertice-span {
line-height: 38px;
padding: 0 2px 0 10px;
}
</style>