threejs文字粒子化(cpu&gpu)
基于threejs来是实现文字&图片3d粒子化的一些方案。
3d框架选择: threejs R88dev
实现一: THREE.TextGeometry()
利用threejs提供的TextGeometry
方法来实现
思路: 利用FontLoader加载一个字体,再通过TextGeometry来将文字粒子化。
实现code如下:
function loadFont() {
var loader = new THREE.FontLoader();
loader.load( 'fonts/' + 'text.typeface.json', function ( response ) {
font = response;
refreshText();
} );
}
function createText() {
textGeo = new THREE.TextGeometry('需要粒子化的文字', {
font: font,
size: 10
});
// ...
}
图一
tips:中文字体进行转码
官方demo: https://threejs.org/examples/?q=tex#webgl_geometry_text
实现二:模型导入
提前在3d软件中设置好需要的文字,然后导出模型,再通过threejs各类导入模型的方法将模型导入到页面中。
思路:导入得到模型后,得到模型中的vertices
,然后对vertices
进行操作,最后通过THREE.Points
方法将模型粒子显示。
实现code如下:
var loader = new THREE.JSONLoader();
loader.load( 'models/text.json', function ( geometry, materials ) {
// ..
addScene()
});
function addScene () {
var mesh = new THREE.Points( geometry, new THREE.PointsMaterial({
color: '#fff '
}));
scene.add( mesh )
}
图二
tips: 这种方式,也适用于各类需要粒子化的人物、动物等模型。
官方模型粒子化demo: https://threejs.org/examples/#webgl_points_dynamic
本文的重点来了,准备好小板凳吧。
实现三:canvas实现
核心方法 getImageData()
。
思路:通过fillText()
将需要的文字绘制到canvas上,在用getImageData()
获取绘制文字的像素值信息。再将这些像素数据通过THREE.BufferGeometry()
存储。最后通过THREE.Points()
将粒子绘制出来。
具体实现:
1、 文字绘制 & 获得文字像素值信息 实现code如下:
// 单行文字
// 获得文字像素值
function getTextInfo (text) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = '32px adobe-text-pro';
var width = canvas.width = Math.ceil(ctx.measureText(text).width);
canvas.height = 32;
ctx.font = '32px adobe-text-pro';
// 将文字绘制到canvas上
ctx.fillText(text, 0, 32 * 3 / 4);
var x, y;
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
var xyas = [];
for(var i = 0, len = data.length / 4; i < len; i++) {
if(data[i * 4 + 3] > 0) {
xyas.push(
i % width, // x位置
i / width | 0, // y位置
data[i * 4 + 3] / 255 // alpha (glsl是0-1 所以/255)
);
}
}
return {
width: width, // 文字宽度
amount: xyas.length / 3, // particles数量
xyas: xyas // 位置数据
}
}
getTextInfo('填写绘制的文字');
tips: 通过
ctx.measureText()
获取宽度返回的是浮点数而不是整型,所以记得使用Math.ceil()
。
// 多行文字
function getTextInfo (text) {
// ..
var textLines = text.split('/n');
var maxWidth = 0;
for(var i = 0, len = textLines.length; i < len; ++i) {
maxWidth = Math.max(maxWidth, Math.ceil(ctx.measureText(textLines[i]).width));
}
canvas.width = maxWidth;
// ..
}
getTextInfo('第一行文字/n第二行文字');
现在文字的像素值得到了,然后创建particles,将文字绘制到页面上。
2、首先创建BufferGeometry。将所需要的值存储起来。
var arr = [];
var _textXY = getTextInfo();
var pointGeometry = new THREE.BufferGeometry();
var amount = maxAmount;
var positionData = new Float32Array( amount * 3 );
var randomData = new Float32Array( amount * 3);
var textXY, x, y, a, i, j, offset;
for (j = 0; j < amount; j++) {
textXY = _textXY.xyas;
offset = j * 3;
x = textXY[j * 3 + 0];
y = textXY[j * 3 + 1];
a = textXY[j * 3 + 2];
var radius = 30 + Math.floor(j / 360.0) / 200;
positionData[offset + 0] = x - _textXY[0].width / 2;
positionData[offset + 1] = -(y - 32);
positionData[offset + 2] = 0;
randomData[offset] = Math.random();
}
pointGeometry.addAttribute( 'a_random', new THREE.BufferAttribute( randomData, 1 ) ); //用于动画
pointGeometry.addAttribute( 'position', new THREE.BufferAttribute( positionData, 3 ) ); // particles位置
3、创建Material的两种方案
使用threejs提供的Material:
var pMaterial = new THREE.PointsMaterial({
color: 0xFFFFFF,
// size: 20,
blending: THREE.AdditiveBlending,
transparent: true
});
var points = new THREE.Points(pointGeometry, pMaterial);
scene.add(points); // 添加至场景
官方粒子demo: https://threejs.org/examples/#webgl_points_sprites
使用threejs提供的ShaderMaterial:
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {},
vertexShader: document.getElementById( 'vs' ).textContent, //顶点着色器
fragmentShader: document.getElementById( 'fs' ).textContent, //片元着色器
// blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true
});
tips: 使用
ShaderMaterial
来实现particles。对particles的控制性高很多。能用shader实现各种复杂效果。
4、着色器
简单的使用shader来绘制particles。
// 片元着色器
precision highp float;
void main() {
float len = length(gl_PointCoord.xy - .5) * 2.0;
float radialAlpha = pow(clamp(1.0 - len, 0.0, 1.0), 3.4);
float alpha = radialAlpha;
gl_FragColor = vec4(1.0, 1.0, 1.0, alpha);
}
// 顶点着色器
attribute float a_size;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = 4. * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
绘制的效果如下:
图三
静态的文字绘制出来了,现在让文字动起来。
6、两个文字切换animation
particles animation for cpu:
思路: 获取points.geometry.attributes
获取位置,然后给定一个新位置,再通过TweenLite
来实现文字的切换。
function startEvent1 () {
var particlesAttr = points.geometry.attributes;
TweenLite.to(particlesAttr.position.array, 1, centerArr);
centerArr.onUpdate = function () {
particlesAttr.position.needsUpdate = true;
};
centerArr.onComplete = function () {
TweenLite.to(particlesAttr.position.array, 2, arr1);
arr1.onUpdate = function () {
particlesAttr.position.needsUpdate = true;
};
};
}
简陋的效果:
图四
particles animation for gpu:
文字的动画切换都在着色器中完成,而不是通过TweenLite
去改变数组值完成,这样大大减少了动画的cpu使用量。
思路:在创建BufferGeometry
的时候,把需要切换的文字信息都存储好。然后通过改变uniforms
的值去实现文字的切换。而切换的效果,利用snoise4
来帮我们完成。具体实现如下:
// ----------BufferGeometry 部分----------
var geometry = new THREE.BufferGeometry();
var a_text_pos_arr = [];
var randomData = new Float32Array( _maxPixelAmount * 3);
var position = new Float32Array( _maxPixelAmount * 3 );
var offset = 0;
var a;
for (var i = 0; i < _pixelInfos.length; i++) {
a_text_pos_arr.push(new Float32Array( _maxPixelAmount * 3 ));
for (j = 0; j < _maxPixelAmount; j++) {
offset = j * 3;
if(offset) {
a_text_pos_arr[i][j * 3 + 0] = _pixelInfos[i].pixelsXY[j * 3 + 0] - _pixelInfos[i].width / 2;
a_text_pos_arr[i][j * 3 + 1] = _pixelInfos[i].pixelsXY[j * 3 + 1];
a_text_pos_arr[i][j * 3 + 2] = _pixelInfos[i].pixelsXY[j * 3 + 2];
} else {
a = 0;
}
randomData[offset] = Math.random();
position[j] = 0;
}
geometry.addAttribute( 'a_text_pos_'+ i, new THREE.BufferAttribute( a_text_pos_arr[i], 3 ) );
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( position, 3 ) );
geometry.addAttribute('a_randoms', new THREE.BufferAttribute(randomData, 3));
// ----------material部分----------
var material = new THREE.ShaderMaterial({
uniforms: {
u_text_offset_1: { type: 'v2', value: {x: 0, y: 0}},
u_text_offset_2: { type: 'v2', value: {x: 0, y: 70}},
u_max_particle_size: { type: 'f', value: 15 },
u_noise: { type: 'f', value: 1 },
u_level: { type: 'f', value: 0 },
u_time: { type: 'f', value: 0 }
},
vertexShader: document.getElementById( 'vs' ).textContent,
fragmentShader: document.getElementById( 'fs' ).textContent,
blending: THREE.AdditiveBlending,
transparent: true,
depthTest: false
});
// ----------着色器部分-----------
// 片元
precision highp float;
varying float v_alpha;
varying float v_intensity;
varying vec3 v_rgb_noise;
void main(void) {
float len = length(gl_PointCoord.xy - .5) * 2.0;
float c = pow(clamp(1.0 - len, 0.0, 1.0), 2.0 + v_intensity);
gl_FragColor = vec4(c - v_rgb_noise.r, c - v_rgb_noise.g, c - v_rgb_noise.b, v_alpha);
}
// 顶点
attribute vec3 a_text_pos_1;
attribute vec3 a_text_pos_2;
attribute vec3 a_randoms;
varying vec3 v_pos;
varying float v_alpha;
varying float v_intensity;
varying vec3 v_rgb_noise;
uniform float u_level;
uniform float u_time;
uniform float u_noise;
uniform float u_max_particle_size;
uniform vec2 u_text_offset_1;
uniform vec2 u_text_offset_2;
float clampNorm(float val, float min, float max) {
return clamp((val - min) / (max - min), 0.0, 1.0);
}
// 缓动函数
float easeOutBack (float t) {
const float s = 1.70158;
return (t -= 1.0) * t * ((s + 1.0) * t + s) + 1.0;
}
float powInOut(float t, float p) {
return (1.-step(.5, t))*pow(t*2.,p)*.5+step(.5,t)*(1.-pow((1.-t)*2.,p)*.5);
}
#include <snoise4>
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
void main(void) {
vec3 pos = position;
float randValue = rand(a_randoms.xx);
float ratio = easeOutBack(smoothstep(randValue * 0.3, 0.7 + randValue * 0.3, u_level));
vec3 textInfo = mix(
a_text_pos_1 + vec3(u_text_offset_1, 0.0),
a_text_pos_2 + vec3(u_text_offset_2, 0.0),
ratio
);
float noiseRatio = 1.0 - abs(ratio - 0.5) * 2.0;
pos.x = textInfo.x + 0.5 + snoise4(vec4(a_randoms.x * 0.2 + 50.0 + u_time * 0.006)) * (30.0 + randValue * 120.0) * noiseRatio * u_noise;
pos.y = -textInfo.y + 0.5 + snoise4(vec4(a_randoms.x * 0.1 + 2.0 + u_time * 0.006)) * (30.0 + randValue * 120.0) * noiseRatio * u_noise;
pos.z = 0.0;
v_alpha = mix(textInfo.z, (textInfo.z + 0.3) * noiseRatio, noiseRatio);
v_intensity = noiseRatio * randValue * 4.0;
gl_PointSize = 2.0 + noiseRatio * u_max_particle_size * pow(randValue, 4.0);
v_rgb_noise = vec3(
noiseRatio * fract(randValue * 7542.0) * 0.1,
noiseRatio * fract(randValue * 5324.0) * 0.05,
0.0
);
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
实现效果如下:
图五
性能: 两种实现方案在chrome中Performance monitor面板中对比。如图
图六
tips: 图片的粒子化更文字一样,通过将图片绘制到canvas上,然后同样的方法去操作像素值。
最后说一点:
通过shader可以创建出更厉害的效果,正在摸索中,欢迎探讨。
关于threejs的场景建立,有兴趣的可以去threejs的官网查看,
原文地址:https://flowers1225.com/lessons/2018/03/07/1
WEBGL学习网 » threejs文字粒子化(cpu&gpu)