一直彩红猫中毒,在学了一些three.js之后,更想做一个属于自己的彩虹猫了。
最后的完成效果:彩红猫 手机显示效果也还可以。
目标
在做一件事情之前 最为重要的就是列出要做到的事情目标,同时目标要记得是可测量的(得知道怎么样算是完成)
- 彩红可调整猫使用帧动画进行动画效果
- 彩红猫的速度是可以动态调整的(即帧动画速度可调整)
- 彩红猫的贴图可以切换 切换不同风格的彩红猫
- 循环向左的彩色背景,速度可调整
- 切换彩红猫的时候同时更换速度与背景音乐
- (附加)音乐可视化效果 音频音强变化展示在视觉效果上
实现过程
帧动画
实现彩红猫的帧动画其实就和在canvas 2d上绘制差不多,相比起2d的绘制,无非是将canvas贴图放在three.js 中的Sprite上面,并且更新材质信息
cat对象
function Cat(name,speed,imgPack,canvas){
this.name = name || 'v';
this.speed = speed || 0.5;
this.startTime = new Date() - 0;
this.imgPack = imgPack || [] ;
this.ctx = document.getElementById('cat').getContext('2d') || canvas.getContext('2d');
this.canvas = document.getElementById('cat');
}
Cat.prototype = {
//展示第几帧
_drawImg(image){
if(!image){
return false;
}
var imgWidth = image.width;
var imgHeight = image.height;
var positionX = this.canvas.width/2 - imgWidth/2;
var positionY = this.canvas.height/2 - imgHeight/2;
this.ctx.drawImage(image,positionX,positionY)
},
animate(delta){
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)
//delta时间在在运行良好的时候接近13.3ms 真实时间片
var allTime = this.speed * this.imgPack.length * 1000;//所有帧运行一个循环所需的时间 ms 这里正好1000
var duration = (new Date()-0) - this.startTime;//距离对象创建的时间
var singleLoopTime = duration % allTime;//在当前循环下的时间
var imgClipNum = Math.floor(singleLoopTime / (this.speed * 1000));
var showTime = this.speed * 1000;//每一帧要展示的时间
var imgPack = this.imgPack;
var imgToShow = imgPack[imgClipNum];
this._drawImg(imgToShow);
if(this.rainbow){//这里是让彩虹按照三角函数方式以时间作为参数进行动画的方法
var rainbows = this.rainbow.children;
var xDistance = rainbows[0].position.x - rainbows[rainbows.length-1].position.x;
for(var x = 0 ; x < rainbows.length; x++){
var SingleRainbow = rainbows[x];
SingleRainbow.position.y =Math.sin((SingleRainbow.position.x + singleLoopTime/1000*(Math.PI*2))*2*(1/(this.speed*this.imgPack.length)))/5;
}
}
},
}
cat对象有自己的速度,开始时间用于计算目前的时间应该绘制哪帧的图片,imgPack用于存储各个帧的图片对象,canvas元素用于暂存图像信息, 在页面中可以是隐形的。因为最终需要绘制在3d场景当中。
animate函数放置在动画主循环中,决定这个canvas在当前的时间应该绘制哪帧。
彩虹
本来想把彩虹单独作为一个对象绘制,但是想了一下彩虹也是属于彩红猫的一部分,所以还是添加到了彩虹猫对象当中
cat.prototype = {
...
//生成一条彩虹的方法
initRainbow(){
var points;
var positions = [];
var colors = [1.0,0.0,0.0,
1.0,0.5,0.0,
1.0,1.0,0.0,
0.0,1.0,0.0,
0.0,1.0,1.0,
0.0,0.0,1.0];
for(var i = 0 ; i < 6 ; i++){
var x = 0;
var y = (3-(i+1)+0.5)/(2);
var z = 0;
positions.push(x,y,z);
}
var PointsGeometry = new THREE.BufferGeometry();
PointsGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
PointsGeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ));
var material = new THREE.PointsMaterial( { size: 0.52, vertexColors: THREE.VertexColors } );
points = new THREE.Points( PointsGeometry, material );
return points;
},
//一条彩虹
initAllRainBow(num){
var p = this.initRainbow();
var rainBowGroup = new THREE.Group();
for(var i = 0 ; i < num ; i++){
var x = ((num/2)-(i+1)+0.5)/2;
var y = Math.sin(x)*0.2;
var SingleRainbow = p.clone();
SingleRainbow.position.set(x,y,0);
rainBowGroup.add(SingleRainbow);
}
rainBowGroup.name = 'rainbow';
rainBowGroup.position.x = -30.25;
return rainBowGroup;
}
}
同时将生成生成的彩虹添加到猫的属性里:
function Cat(name,speed,imgPack,canvas){
this.rainbow = this.initAllRainBow(120);//这个彩虹一共120竖条
}
随机彩色点背景
生成彩色点比较简单。使用的是three里面的Points,这个方法封装了gl原生的点所以性能上没什么问题
function Background(name,speed){
this.name = name || 'background';
this.speed = speed || 0.5;
this.startTime = new Date();
this.points = this._initBackground();
}
Background.prototype = {
//生成一个简单的背景1k随机点 x(-50~50) y(-50~50) z(-50~6)
_initBackground:function(){
var colors = [];
var positions = [];
for(var i = 0 ; i < 1000 ; i++){
//生成随机位置
var x = Math.random()*100 - 50;
var y = Math.random()*100 - 50;
var z = -Math.random()*56 + 6;
positions.push(x,y,z);
//随机颜色
var r = Math.random();
var g = Math.random();
var b = Math.random();
colors.push(r,g,b);
}
var PointsGeometry = new THREE.BufferGeometry();
PointsGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
PointsGeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ));
var material = new THREE.PointsMaterial( { size: .51, vertexColors: THREE.VertexColors } );
var points = new THREE.Points( PointsGeometry, material );
var PointsGroup = new THREE.Group();
for(var z = 0 ; z < 20; z ++){
var clonedPoints = points.clone();
clonedPoints.position.x = (10-z)*50;
PointsGroup.add(clonedPoints);
}
return PointsGroup;
}
}
此处z的范围在-50到6之间的原因是,彩虹猫以及彩虹的位置在z相机的位置距离彩红猫的位置为10,再比10大相机也看不到了,所以只要少部分点在比彩红猫离相机更近的位置,这样有种点在相机面前飘过的感觉。
创建场景 加载图片
;(function(undefined){
'use strict';
var _global;
var renderer,scene,light,camera,manager,imgLoader,musicShape;
var imgArr = [];
var show = {
renderer:renderer,
scene:scene,
light:light,
camera:camera,
manager:manager,
imgLoader:imgLoader,
imgArr:imgArr,
musicShape:musicShape,
cat:new THREE.Sprite(),
init:function(){
var canvas = document.getElementById('stage');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//初始化three的加载器
manager = initLoadingManager();
imgLoader = initImgLoader(manager);
this.scene = initScene();
this.renderer = initRenderer();
this.camera = initCamera();
this.light = initLight();
//引用加载器
this.manager = manager;
this.imgLoader = imgLoader;
this.musicShape = initObjs();
initTest();
//this.camera.lookAt(scene.position);
//var axes = new THREE.AxesHelper(20);
var catSprite = initCat();
catSprite.scale.set(11,11,1);
this.cat = catSprite;
catSprite.position.set(1,0,0);
scene.add(catSprite);
//scene.add(axes);
//addControler();
},
render:function(){
renderer.render(scene,camera)
},
//获取单个图片并且返回图片对象
getImg(url,target,index){
var that = this;
that.imgLoader.load(
url,
function(image){
//加载完成之后将图片添加进去
if(typeof(index) === 'undefined'){
console.log('此处的加载需要提供索引');
return false;
}
target[index] = image;
},
undefined,
function(){
console.log('加载'+url+'失败,原因是:我也不知道。。。');
}
)
},
LoadAllImgs(srcArr,tempSpace){
var imgArr = [];
for(var i = 0 ; i < srcArr.length;i++){
var imgSrc = srcArr[i];
//var img = this.getImg(imgSrc);
this.getImg(imgSrc,tempSpace,i)
}
}
};
//这是单纯测试用的而已
function initTest(){
//scene.add(points2);
var cubeG = new THREE.BoxGeometry(1,1,1);
var CubeMaterial = new THREE.MeshPhongMaterial({
color:0xff0000,
transparent:true,
opacity:0.5
})
var cube = new THREE.Mesh(cubeG,CubeMaterial);
cube.position.z = -0.5;
//scene.add(cube);
}
//three的加载器 加载开始加载过程中以及加载之后的事件
function initLoadingManager(){
var manager = new THREE.LoadingManager();
manager.onStart = function(url, itemsLoaded, itemsTotal){
console.log('开始加载文件:'+url+',在'+itemsTotal+'中已经加载完成的文件:'+itemsLoaded);
};
manager.onLoad = function(){
console.log('所有文件加载完毕')
};
manager.onProgress = function(url,itemsLoaded,itemsTotal){
console.log('正在加载文件:'+url+',已完成:'+Math.round(itemsLoaded*100/itemsTotal)+'%')
};
manager.onError = function(url){
console.log('加载过程中出现失败,加载失败的文件是'+url);
};
return manager;
}
function initImgLoader(manager){
var loader = new THREE.ImageLoader(manager);
return loader;
}
//创建并且返回场景
function initScene(){
scene = new THREE.Scene();
return scene;
}
//创建渲染器
function initRenderer(){
renderer = new THREE.WebGLRenderer({antialias:true,canvas:document.getElementById('stage')});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setClearColor(0xffffff);
return renderer;
}
//创建相机
function initCamera(){
camera = new THREE.PerspectiveCamera(90,window.innerWidth/window.innerHeight,0.1,200);
camera.position.set(2,0,9);
if(!IsPC()){
camera.position.x = 6;
camera.position.z = 9;
console.log('手机')
}
camera.lookAt(scene.position);
return camera;
}
//在此处其实没什么意义 创建灯光的, 而在彩红猫这个项目中没有使用会因为灯光而影响的材质 可以去掉
function initLight(){
scene.add(new THREE.AmbientLight(0x444444));
light = new THREE.DirectionalLight(0xffffff);
light.position.set(-20, 30, 30);
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);
return light;
}
//最开始的音频可视化用了长条形方块 就和另外一个博客中一样
function initObjs(){//此函数生成一个会根据声音来改变形状或者位置的形状组
var group = new THREE.Group();
for(var i = 0 ; i < 128 ; i++){
var cubeG = new THREE.BoxGeometry(0.1,0.1,0.1);
var cubeM = new THREE.MeshPhongMaterial({color:0xff00ff})
var cube = new THREE.Mesh(cubeG,cubeM);
var x = (64-i)*(20/128) - 5;
var y = -3;
var z = 2;
cube.position.set(x,y,z);
group.add(cube);
}
var transparentCubeG = new THREE.BoxGeometry(20,10,0.2);
var transparentCubeM = new THREE.MeshBasicMaterial({
color:0xffffff,
transparent:true,
opacity:1,
})
var transparentCube = new THREE.Mesh(transparentCubeG,transparentCubeM);
transparentCube.position.set(0,-8,2);
//group.add(transparentCube);
//scene.add(group);
return group;
}
function initCat(canvas){
canvas = canvas || document.getElementById('cat');
if(canvas){
var spriteMaterial = new THREE.SpriteMaterial({
map:new THREE.CanvasTexture(canvas),
});
return new THREE.Sprite(spriteMaterial);
}else{
console.log('nothing will init')
}
}
_global = (function(){return this || (0,eval)('this')}());
if(typeof module !== 'undefined' && module.exports){
module.exports = show;
}else if(typeof define === 'function' && define.amd){
define(function(){return show});
}else{
!('plugin' in _global) && (_global.show = show);
}
}());
开始初始化的过程
show.init();
var cats = {
v:getCatsImgs('nyan'),
original:getCatsImgs('original'),
technyan:getCatsImgs('technyancolor')
}
function getCatsImgs(name){
var technyancolor00;
var arr = [];
for(var x = 0 ;x < 12 ; x++){
var str = 'imgs/nyan/'+name+'0'+x+'.png';
if(x>=10){str = 'imgs/nyan/'+name+x+'.png'};
arr.push(str);
}
return arr;
}
var delta = new Date() - 0;
var cat = new Cat('v2',1/12);
show.LoadAllImgs(cats.v,
cat.imgPack);
var bg = new Background('BG',1);
console.log(bg.points);
var mv = new Musicvisualizer({
size:128,
draw:function(){
}
})
//这个很可能会被策略拦截掉,按按钮才能播放声音
mv.play('https://towrabbit.oss-cn-beijing.aliyuncs.com/three/media/vday.ogg');
function loop(){
var now = new Date() - 0;
var d = (now - delta)/(1000/60);
delta = now;
update(d);//数据层更新
render();
//将渲染层的更新放在这
requestAnimationFrame(loop);
}
function render(){
show.renderer.render(show.scene,show.camera);
//requestAnimationFrame(render);
}
function update(deltaTime){
var arr = mv.getFrequencyArr();
var max = 1;
for(var i = 15 ; i < show.musicShape.children.length; i++){
var currentValue = arr[i];
if(currentValue > max){max = currentValue};
//获取某一端频率声音内最响的音频 让彩红猫和彩虹的大小随之变化
}
var p = max/256;
cat.rainbow.scale.y = p*0.5+1;
show.cat.scale.x = p*5+11;
show.cat.scale.y = p*5+11;
bg.points.position.x -= 0.16*deltaTime*bg.speed;
if(bg.points.position.x<=-400){
bg.points.position.x +=700;
}
cat.animate();
show.cat.material.map.needsUpdate = true;
//此处deltaTime真实事件片段 在60帧的时候接近13.3ms
}
window.onload = function(){
addControler();
console.log(cat.imgPack);
//cat._drawImg(cat.imgPack[0]);
document.getElementById('stage').addEventListener('mousedown',function(){
console.log('yes?')
//playmusic();
});
show.scene.add(bg.points);
show.scene.add(cat.rainbow);
loop();
};
function addControler(){
var controls;
controls = new THREE.OrbitControls( show.camera, show.renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 5;
controls.maxDistance = 30;
controls.maxPolarAngle = Math.PI / 2;
}
function IsPC(){
var userAgentInfo = navigator.userAgent;
var Agents = new Array("Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod");
var flag = true;
for (var v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) { flag = false; break; }
}
return flag;
}
function playmusic(){
var media = document.getElementById('music');
console.log('mousedown'+media.paused);
if(media && media.paused){
media.play();
console.log('play');
}
}
var catType = 0;
function changeCat(){
catType = catType+=1;
if(catType >= 3){
catType = 0;
}
var catname = '';
switch(catType){
case 0:
catname = 'v';
show.cat.scale.set(11,11,1);
mv.play('http://towrabbit.oss-cn-beijing.aliyuncs.com/three/media/vday.ogg');
cat.speed = 1/12;
bg.speed = 1;
break;
case 1:
catname = 'original';
show.cat.scale.set(11,11,1);
mv.play('http://towrabbit.oss-cn-beijing.aliyuncs.com/three/media/original.ogg')
cat.speed = 1/14;
bg.speed = 14/12;
break;
case 2:
catname = 'technyan';
show.cat.scale.set(12,12,1);
mv.play('http://towrabbit.oss-cn-beijing.aliyuncs.com/three/media/technyancolor.mp3')
cat.speed = 1/18;
bg.speed = 18/12;
break;
}
show.LoadAllImgs(cats[catname],cat.imgPack);
}
后记
额感觉有好多东西没说
其中的音频可视化在另外一个文件中,不详细阐述
所有的文件源码可以在网站源文件中查看到
直接f12就行-0-
或者github彩红猫-towrabbit
towrabbit
欢迎大家评论点赞