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学习网(WebGLStudy.COM)专注提供WebGL 、ThreeJS、BabylonJS等WEB3D开发案例源码下载。
声明信息:
1. 本站部分资源来源于用户上传和网络,如有侵权请邮件联系站长:1218436398@qq.com!我们将尽快处理。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源打赏售价用于赞助本站提供的服务支出(包括不限服务器、网络带宽等费用支出)!
7.欢迎加QQ群学习交流:549297468 ,或者搜索微信公众号:WebGL学习网
WEBGL学习网 » threejs文字粒子化(cpu&gpu)

发表评论

提供优质的WebGL、ThreeJS源码

立即查看 了解详情