傻瓜開發——three.js 粒子組成字體

1、不廢話,先上圖。目標效果:使用亮的點,組成字。(完整代碼,請滑到最後)
css

2、核心思路html

①用THREE.Points畫點。git

②用THREE.TextGeometry加載字體文件(不用渲染),而後獲取它的vertices屬性獲取組成字體的點集。github


2、實現。web

引入核心代碼json

//核心js插件
<script src="../../js/lib/three.js"></script>
//控制器(鼠標鍵盤交互的控制)
<script src="../../js/lib/OrbitControls.js"></script>
//性能監測
<script src="../../js/lib/stats.min.js"></script>
//動畫
<script src="../../js/lib/tween.min.js"></script>複製代碼

加載場景、相機、光、控制器、性能監測器等3d基礎環境canvas

//渲染器
    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xeeeeee);
        renderer.shadowMap.enabled = true;
        //告訴渲染器須要陰影效果
        document.body.appendChild(renderer.domElement);
    }
    //初始化相機
    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
        camera.position.set(0, 20, 80 );
    }
    //初始化場景
    function initScene() {
        scene = new THREE.Scene();
        scene.background = new THREE.Color( 0xa0a0a0 );
        scene.fog = new THREE.Fog( 0xa0a0a0, 5, 250 );
    }
    //初始化性能插件
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }
    //初始換控制器
    function initControls() {
        controls = new THREE.OrbitControls(camera, renderer.domElement);
        //設置控制器的中心點
        controls.target.set( 0, 5, 0 );
        // 若是使用animate方法時,將此函數刪除
        //controls.addEventListener( 'change', render );
        // 使動畫循環使用時阻尼或自轉 意思是否有慣性
        controls.enableDamping = true;
        //動態阻尼係數 就是鼠標拖拽旋轉靈敏度
        //controls.dampingFactor = 0.25;
        //是否能夠縮放
        controls.enableZoom = true;
        //是否自動旋轉
        controls.autoRotate = false;
        controls.autoRotateSpeed = 0.5;
        //設置相機距離原點的最遠距離
        controls.minDistance = 1;
        //設置相機距離原點的最遠距離
        controls.maxDistance = 2000;
        //是否開啓右鍵拖拽
        controls.enablePan = true;
    }
    //窗口變更觸發的函數    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }
    function render() {
        controls.update();
        //更新性能插件
        stats.update();
         //更新動畫
       // TWEEN.update();
         renderer.render(scene, camera);
    }

    
    //循環渲染
    function animate() {
        //更新控制器
        render();       		  
        requestAnimationFrame(animate);
    }
    //總體初始化的函數
    function draw() {

        initRender();
        initScene();
        initCamera();
        initLight();
        //具體的物體
       // initModel();
        initControls();
        initStats();

        animate();
        window.onresize = onWindowResize;
    }
③複製代碼

③加入具體的展現物體(基礎平面及點集)
後端

var  geo_ver;

function initModel() {
        //輔助工具
        var helper = new THREE.AxesHelper(50);
        scene.add(helper);
        // 地板
        var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 400, 400 ), new THREE.MeshPhongMaterial( { color: 0xffffff, depthWrite: false } ) );
        mesh.rotation.x = - Math.PI / 2;
        mesh.receiveShadow = true;
        scene.add( mesh );
        //添加地板割線
        var grid = new THREE.GridHelper( 400, 50, 0x000000, 0x000000 );
        grid.material.opacity = 0.2;
        grid.material.transparent = true;
        scene.add( grid );

        //獲取點集
	getFontPoints('白狗',-23,0,0);					
	//用點畫字
	pointsMaterial = new THREE.PointsMaterial({
	    color:0xffffff,
	    size:0.5,
	    transparent:true,//使材質透明
	    blending:THREE.AdditiveBlending,
	    depthTest:false,//深度測試關閉,不消去場景的不可見面
	    map:createLightMateria()//剛剛建立的粒子貼圖就在這裏用上
	   });
		
	var originGeo = new THREE.Geometry();
	for (var i = 0; i <baseLens; i++){//循環建立Geo
	    var x = basePoint[i].x;
	    var y = basePoint[i].y;
	    var z = basePoint[i].z;
	    originGeo.vertices.push(new THREE.Vector3(x,y,z));
	}
	originParticleField = new THREE.Points(originGeo,pointsMaterial);
	scene.add(originParticleField);
    }

//獲取 文字的點集
    function getFontPoints(txt,x,y,z){
    	if(!x){var x=0;} 
    	if(!y){var y=0;}
    	if(!z){var z=0;}
    	var res = [];
    	var loader = new THREE.FontLoader();
    	loader.load("../../json/KaiTi.json",function(text){
    		var gem2 = new THREE.TextGeometry(txt, {
	            size: 16, //字號大小,通常爲大寫字母的高度
	            height: 0.5, //文字的厚度
	            weight: 'normal', //值爲'normal''bold',表示是否加粗
	            font: text, //字體,默認是'helvetiker',需對應引用的字體文件
	            style: 'normal', //值爲'normal''italics',表示是否斜體
	            bevelThickness: 0.5, //倒角厚度
	            bevelSize: 0.5, //倒角寬度
	            curveSegments: 8,//弧線分段數,使得文字的曲線更加光滑
	            bevelEnabled: true, //布爾值,是否使用倒角,意爲在邊緣處斜切
	        });
		   res = gem2.vertices;
		    //字體通常不是居中的,須要手動調下
		    var lens= res.length;
		    var attrs = [];
		    for(var i=0;i<lens;i++){
		    	var obj = {
		    		x:res[i].x+x,
		    		y:res[i].y+y,
		    		z:res[i].z+z,
		    	    }
		    	attrs.push(obj);
		    }
		    res = attrs;
		    geo_ver = res;		    	
    	});  	
    }複製代碼

3、總體代碼(包括,粒子切換動畫)(代碼可能比較雜亂)bash

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>粒子字體</title>
    <style type="text/css">
        html, body {
            margin: 0;
            height: 100%;
        }

        canvas {
            display: block;
            position: relative;
            z-index: 55;
        }
        .box{
        	position: absolute;
        	z-index: 66;
        	width: 200px;
        	height: 50px;
        	bottom: 20px;
        	left: 47%;
        	border:2px solid skyblue;
        	color: skyblue;
        	cursor: pointer;
        	font-size: 30px;
        	line-height: 46px;
        	text-align: center;
        	font-weight: bold;
        }
        .box1{
        	left: 20%;
        }
        .box2{
        	left: 50%;
        }
        .box3{
        	left: 80%;
        }
    </style>
</head>

<body onload="draw();">
	<div class="box box2" onclick="btnClick1()">白狗</div>
	<div class="box box1" onclick="btnClick2()">汪</div>
	<div class="box box3" onclick="btnClick3()">心</div>
</body>
<script src="../../js/lib/three.js"></script>
<script src="../../js/lib/OrbitControls.js"></script>
<script src="../../js/lib/stats.min.js"></script>
<script src="../../js/lib/dat.gui.min.js"></script>
<script src="../../js/lib/Detector.js"></script>
<script src="../../js/lib/tween.min.js"></script>
<!--<script src="../../js/lib/Projector.js"></script>-->

<script>                                                     
    var renderer, camera, scene, gui, light, stats, controls;
    //字體的點集
	var originParticleField,pointsMaterial,geo_ver,geo_ver2,tween,hearts=[];
	//初始點
	var baseLens =10000,basePoint=[],baseArea=160;
	for(var i=0;i<baseLens;i++){
		var x= Number(Math.random()*baseArea)-baseArea/2;
		var y= Number(Math.random()*baseArea)-baseArea/2;
		var z= Number(Math.random()*baseArea)-baseArea/2;
		var obj = {
			x:x,
			y:y,
			z:z
		}
		basePoint.push(obj);
	}
	
	
	
    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xeeeeee);
        renderer.shadowMap.enabled = true;
        //告訴渲染器須要陰影效果
        document.body.appendChild(renderer.domElement);
    }

    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
        camera.position.set(0, 20, 80 );
    }

    function initScene() {
        scene = new THREE.Scene();
        scene.background = new THREE.Color( 0xa0a0a0 );
        scene.fog = new THREE.Fog( 0xa0a0a0, 5, 250 );
    }

    //初始化dat.GUI簡化試驗流程
    function initGui() {
        //聲明一個保存需求修改的相關數據的對象
        gui = {
            bump:0.03,
            animation:false
        };
        var datGui = new dat.GUI();
        //將設置屬性添加到gui當中,gui.add(對象,屬性,最小值,最大值)
        datGui.add(gui, "bump", -1, 1).onChange(function (e) {
            cube1.material.bumpScale = e;
        });

        datGui.add(gui, "animation");
    }

    function initLight() {
        scene.add(new THREE.AmbientLight(0x444444));

        light = new THREE.DirectionalLight(0xffffff);
        light.position.set(0, 600, 240 );

        light.castShadow = true;
        light.shadow.camera.top = 10;
        light.shadow.camera.bottom = -10;
        light.shadow.camera.left = -10;
        light.shadow.camera.right = 10;

        //告訴平行光須要開啓陰影投射
        light.castShadow = true;

//      scene.add(light);
    }

    function initModel() {

        //輔助工具
        var helper = new THREE.AxesHelper(50);
        scene.add(helper);

        // 地板
        var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 400, 400 ), new THREE.MeshPhongMaterial( { color: 0xffffff, depthWrite: false } ) );
        mesh.rotation.x = - Math.PI / 2;
        mesh.receiveShadow = true;
        scene.add( mesh );

        //添加地板割線
        var grid = new THREE.GridHelper( 400, 50, 0x000000, 0x000000 );
        grid.material.opacity = 0.2;
        grid.material.transparent = true;
        scene.add( grid );

        //獲取點集
		getFontPoints('白狗','geo_ver',-23,0,0);			
		getFontPoints('汪','geo_ver2',-8,0,0);
        
			
		//用點畫字
		pointsMaterial = new THREE.PointsMaterial({
		    color:0xffffff,
		    size:0.5,
		    transparent:true,//使材質透明
		    blending:THREE.AdditiveBlending,
		    depthTest:false,//深度測試關閉,不消去場景的不可見面
		    map:createLightMateria()//剛剛建立的粒子貼圖就在這裏用上
		});
		
		var originGeo = new THREE.Geometry();
		for (var i = 0; i <baseLens; i++){//循環建立Geo
			var x = basePoint[i].x;
			var y = basePoint[i].y;
			var z = basePoint[i].z;
		    originGeo.vertices.push(new THREE.Vector3(x,y,z));
		}
		originParticleField = new THREE.Points(originGeo,pointsMaterial);
//		originParticleField.position.set(-20,10,0);
		scene.add(originParticleField);

    }
    //獲取 文字的點集
    function getFontPoints(txt,type,x,y,z){
    	if(!x){var x=0;}
    	if(!y){var y=0;}
    	if(!z){var z=0;}
    	var move =true;
    	if(x==0&&y==0&&z==0){move = false;}
    	var res = [];
    	var loader = new THREE.FontLoader();
    	loader.load("../../json/KaiTi.json",function(text){
    		var gem2 = new THREE.TextGeometry(txt, {
	            size: 16, //字號大小,通常爲大寫字母的高度
	            height: 0.5, //文字的厚度
	            weight: 'normal', //值爲'normal''bold',表示是否加粗
	            font: text, //字體,默認是'helvetiker',需對應引用的字體文件
	            style: 'normal', //值爲'normal''italics',表示是否斜體
	            bevelThickness: 0.5, //倒角厚度
	            bevelSize: 0.5, //倒角寬度
	            curveSegments: 8,//弧線分段數,使得文字的曲線更加光滑
	            bevelEnabled: true, //布爾值,是否使用倒角,意爲在邊緣處斜切
	        });
		    res = gem2.vertices;
		    //字體通常不是居中的,須要手動調下
		    if(move){
		    	var lens= res.length;
		    	var attrs = [];
		    	for(var i=0;i<lens;i++){
		    		var obj = {
		    			x:res[i].x+x,
		    			y:res[i].y+y,
		    			z:res[i].z+z,
		    		}
		    		attrs.push(obj);
		    	}
		    	res = attrs;
		    }
		    switch(type){
		    	case 'geo_ver':
		    	geo_ver = res;
		    	break;
		    	case 'geo_ver2':
		    	geo_ver2 = res;
		    	break;
		    }
    	});  	
    }
    
    //獲取桃心的點集
	function heart(){
		var res = [];
		var a=10,x=-180,a0=0,b0=0,c0=0,y=0;
		
		t = 0;
		for(var i=0;i<360;i++){
			t=(i+t)*Math.PI/180;
			y=a*(2*Math.cos(t)-Math.cos(2*t));
			x=a*(2*Math.sin(t)-Math.sin(2*t));
			var obj = {
				x:x,
				y:y+20,
				z:0
			}
			res.push(obj);
			var obj = {
				x:x*(17/20),
				y:y*(17/20)+20,
				z:0
			}
			res.push(obj);
			var obj = {
				x:x*(13/20),
				y:y*(13/20)+20,
				z:0
			}
			res.push(obj);
		}
		
		
		hearts =res;
	}
	
	function pointMove(position1){
		//移除粒子浮動動畫
//		TWEEN.removeAll();
		var pointNow = originParticleField.geometry.vertices;
		var lens0 = position1.length;
       	var pos = {val:1};
       	var callback=function(){
		    var val = this.val;
		    var particles = originParticleField.geometry.vertices;		    
//		    return console.log(particles);
		    for(var i = 0; i < baseLens; i++) {
		    	var pos = particles[i];
		    	
		    	if(i<lens0){			
			        pos.x = pointNow[i].x * val + position1[i].x * (1-val);
			        pos.y = pointNow[i].y * val + position1[i].y * (1-val);
			        pos.z = pointNow[i].z * val + position1[i].z * (1-val);			
		    	}else{
			    	pos.x = pointNow[i].x * val + basePoint[i].x * (1-val);
			        pos.y = pointNow[i].y * val + basePoint[i].y * (1-val);
			        pos.z = pointNow[i].z * val + basePoint[i].z * (1-val);		
		    	}		       	        
		    }
		    originParticleField.geometry.verticesNeedUpdate = true;
		}
       	tween = new TWEEN.Tween(pos).to({val: 0}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(500).onUpdate(callback);
		tween.start();
    }
	//粒子浮動動畫

	//繪製粒子貼圖,
	function createLightMateria() {
		var canvasDom = document.createElement('canvas');
		canvasDom.width = 16;
		canvasDom.height = 16;
		var ctx = canvasDom.getContext('2d');
		//根據參數肯定兩個圓的座標,繪製放射性漸變的方法,一個圓在裏面,一個圓在外面
		var gradient = ctx.createRadialGradient(
		    canvasDom.width/2,
		    canvasDom.height/2,
		    0,
		    canvasDom.width/2,
		    canvasDom.height/2,
		    canvasDom.width/2);
		gradient.addColorStop(0,'rgba(255,255,255,1)');
		gradient.addColorStop(0.005,'rgba(139,69,19,1)');
		gradient.addColorStop(0.4,'rgba(139,69,19,1)');
		gradient.addColorStop(1,'rgba(0,0,0,1)');
		
		//設置顏色爲漸變
		ctx.fillStyle = gradient;
		//繪圖
		ctx.fillRect(0,0,canvasDom.width,canvasDom.height);
		 
		//貼圖使用
		let texture = new THREE.Texture(canvasDom);
		texture.needsUpdate = true;//使用貼圖時進行更新
		return texture;
	}

    //初始化性能插件
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }

    function initControls() {

        controls = new THREE.OrbitControls(camera, renderer.domElement);
        //設置控制器的中心點
        controls.target.set( 0, 5, 0 );
        // 若是使用animate方法時,將此函數刪除
        //controls.addEventListener( 'change', render );
        // 使動畫循環使用時阻尼或自轉 意思是否有慣性
        controls.enableDamping = true;
        //動態阻尼係數 就是鼠標拖拽旋轉靈敏度
        //controls.dampingFactor = 0.25;
        //是否能夠縮放
        controls.enableZoom = true;
        //是否自動旋轉
        controls.autoRotate = false;
        controls.autoRotateSpeed = 0.5;
        //設置相機距離原點的最遠距離
        controls.minDistance = 1;
        //設置相機距離原點的最遠距離
        controls.maxDistance = 2000;
        //是否開啓右鍵拖拽
        controls.enablePan = true;
    }

    function render() {
        controls.update();
    }

    //窗口變更觸發的函數
    function onWindowResize() {

        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);

    }

    function animate() {
        //更新控制器
        render();

        //更新性能插件
        stats.update();
        
		 TWEEN.update();
		
        renderer.render(scene, camera);

        requestAnimationFrame(animate);
    }

    function draw() {
        //兼容性判斷
        if (!Detector.webgl) Detector.addGetWebGLMessage();

        initGui();
        initRender();
        initScene();
        initCamera();
        initLight();
        initModel();
        initControls();
        initStats();

        animate();
        window.onresize = onWindowResize;
    }

//汪
	function btnClick2(){
   		pointMove(geo_ver2);
   	}
	//白狗
	function btnClick1(){
   		pointMove(geo_ver);
   	}
	//白狗
	function btnClick3(){
   		pointMove(hearts);
   	}
	
	heart();

</script>
</html>
複製代碼

4、注意事項:app


    (1)本身下載.ttf字體文件。經過https://gero3.github.io/facetype.js/工具能夠轉化成.json文件。

    (2)字體文件通常很大,性能很差。(建議存在後端,而後發請求。或者前期先使用工具裁剪字體文件)。(我用的wqfontcreator,可是大家可能找不到,真的要要,@我)

    (3)以上代碼沒有作過多優化。(各位將就着看···)(見諒)

    (4)以上代碼不能直接用,須要起個服務,由於請求.json文件的問題

相關文章
相關標籤/搜索