mmx实现图片淡入淡出_JS实现最美的3D宇宙特效

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第1张图片

好久没更新文章了。算下来大概有五个多月了吧。之前本人更新的比较频繁是因为疫情在家,不能出门,所以有充足的时间来更新文章。之后随着疫情越来越好转,本人就出去找工作了,毕竟本人的经济条件不允许本人闲着,哈哈。之后本人会更新很频繁的,很抱歉!

这里给各位同僚说一下,本人已经取消了之前所有分享的百度网盘的资料链接以及本人之前分享的AIRobot链接。之所以这样,是因为本人最近一段时间有个“大动作“。就是将本人之前所有写的项目案例和技术知识点的代码重新封装一下,并且给出demo示例和环境搭建教程,以便各位想了解或者初学者更容易上手。这些项目案例和技术知识点的代码会放到我自己开发的一个云平台中,而且支持远程调试,到时会免费开放给各位。本人正在用自己的业余时间加班加点的开发我的云平台,各位敬请期待!嘿嘿!

通过前面写过的几篇文章,表现结果是C++处理图像方面是阅读量最多的。感觉各位很倾向于图像操作方面,所以本人之后打算围绕图像操作上做一个专栏系列。还有就是本人这次的云平台中包含的技术类型有网络,图像,音视频,游戏等方面。这些都是本人一个人编写和维护,里面包含的很多知识点都是本人花了大量的时间整合的,因为网上的知识都是零碎的,所以如果有你感兴趣的方面会大大节省各位的时间。

说了半天的废话,好了,言归正传。前几周,本人在网上看见一个号称“最美的地球特效”,是用H5实现的(文章末尾给出源码路径),离今天已经有好几年了。本人早在几年前,也就是读大学那会,用Direct3D C++分别实现过地球和宇宙效果,但是那会仅仅实现了一个太阳系效果,而且也有些不足之处。现在之所以想实现这个3D宇宙特效,是因为本人想把这个特效放在我的个人网站上。这个效果是基于TreeJS(是一个3D图形库,类似Direct3D)编写的,前端没有使用任何框架(因为本人比较喜欢造轮子,哈哈)。除了TreeJS外,其他都是纯html+js+css代码和纯纹理贴图实现的。

首先,给各位说明一下TreeJS初始化的过程:1)创建场景;2)创建摄像机并添加至场景;3)创建渲染器、绑定画布并循环渲染。主要就这么三个主要步骤,其他就是业务逻辑了。所有创建模型都需要添加至场景才可被渲染。有过3D图像开发的经验这块应该都很清楚,就是一些API不一样而已,如果不清楚的可以自行谷歌科普,因为这块内容不是本文的重点。

3D环境初始化完毕之后就开始具体的效果逻辑编写了。本人将这个特效划分七大部分,一是宇宙爆炸效果;二是星云、星空粒子效果;三是太阳系效果;四是镜头拉伸效果;五是猿猴关键点动画效果;六是城市交替效果;七是文本淡入淡出效果

宇宙爆炸效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第2张图片

使用一张发白色爆炸纹理贴图即可,爆炸之前将纹理放在距离摄像机很远的位置。看上去就是一个白色的点。等到爆炸的时候将纹理的x,y按照一定的速度等比放大,待纹理中的白色遮满整个屏幕时,在慢慢淡出(如果不懂,见最后文本淡入淡出效果),和下一个效果衔接。

//每帧调用(下面省略了部分路径代码)
if (universeEffectComponent.titleConfig.zoom < 1500) {
        universeEffectComponent.titleConfig.zoom += universeEffectComponent.titleConfig.boomSpeed;
        
        //放大(这里z轴可以不用缩放,因为是纹理贴图,是没有厚度的)
        universeEffectCdeomponent.titleConfig.obj.scale.x += universeEffectComponent.titleConfig.boomSpeed;
        universeEffectComponent.titleConfig.obj.scale.y += universeEffectComponent.titleConfig.boomSpeed;
        universeEffectComponent.titleConfig.obj.scale.z += universeEffectComponent.titleConfig.boomSpeed;

        universeEffectComponent.titleConfig.boomSpeed *= 1.08;
    }
    else {
        //更新透明度(淡出)
        for (let i = 0; i < universeEffectComponent.material.title.length; i++) {
            if (universeEffectComponent.material.title[i].opacity - 0.01 <= 0) {
                universeEffectComponent.material.title[i].opacity = 1;
            }
            else {
                universeEffectComponent.material.title[i].opacity -= 0.01;
            }
        }
    }

星云、星空粒子效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第3张图片

在实际宇宙中是由许多星星、星云以及其他天体构成。我这里要尽量的模拟的逼真一点。在这个特效中,星星使用了2000颗,星云30片。那么如何将2000颗星星和30片星云,充满整个宇宙空间呢?一颗一颗、一片一片手动去设置坐标?当然不是,一切都是可控的随机实现。

首先随机生成坐标,我需要设置一个三维空间的范围(x,y,z的阈值),让随机出来的坐标只能在这个范围内。关键是这个阈值是多少?需要考虑两个条件,一是不能离摄像机太近,不然会显的很大,一看就是一张图片;二是要在视角范围内,超出视角的摄像机(即屏幕的画面)肯定看不到。

其次随机移动速度、缩放速度、方向、临界值系数。如果仅仅设置坐标,那么星星和星云都是静止不动的。为了更加逼真需要在加上移速和缩放属性。移动速度设置一个移速的范围(x方向,y方向的移速即可,z方向就不需要了,因为有缩放因子),需要随机方向(正反方向),不然移动的时候都是朝同一个方向。缩放系数生成和移动速度生成类似,也需要设置一个缩放的范围(x方向,y方向,z方向无意义,考虑到是纹理贴图)。这里需要说明一下的是缩放分三种类型,一种不缩放,一种是逐渐缩放,一种是快速缩放(闪烁的星星)。不管移动还是缩放都需要一个最大最小临界值,否则会出现在朝同一方向无限移动和缩放下去。这不是我想要的效果。所以为了避免出现这种情况,需要累计记录每个方向的移动变化量和缩放变化量,当累计的移动变化量和缩放变化量高于/低于最大临界值/最小临界值时,改变方向即可。

最后是星云特有的,引入了旋转系数。和上述移动、缩放系数类似。设置旋转的范围(x,y,z三个方向)。也需要设置临界值,是因为渲染的时候会背面剔除,如果不设置临界值,在有绕X和Y轴旋转的星云,会可能出现看不见的情况。

如何形成五颜六色的星空?需要2000张不同颜色的星星图片?当然不是,这样会造成资源过大,我这里只有十多张星星纹理,和几张星云纹理,然后在创建材质的时候随机颜色通道就可以实现五颜六色的纹理材质了。

这里及下面也不给出代码片段了,因为太多了。都是类似上面的代码片段。

太阳系效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第4张图片

首先给各位简单科普一下太阳系知识,早些时间的太阳系一直是9大星系,分别是水星、金星、地球、火星、木星、土星、天王星、海王星以及冥王星。后来觉得冥王星离太阳太遥远,就把冥王星划分出太阳系了,也就是现在的八大星系。这里我模拟的太阳系效果,不是完全按照各行星的体积比例的,如果按照实际比例,那么有的行星几乎看不见,有的会占满整个屏幕。所以这里本人只确保了体积的大小顺序,然后调整了一个合适比例。本人的太阳系效果,包含一个星云(银河系),八大行星以及地球专属的卫星(月球)。各星体的属性:太阳(即恒星,只有自转),行星(自转,围绕恒星公转),卫星(自转,围绕行星公转,跟随行星一起围绕恒星公转)。

大概了解了基本星体知识后,就可以开始编代码了。恒星、行星以及卫星的自转只需以自身的Y轴旋转即可。这里就是随机一下各个星体的自转速度,在每帧渲染时累计叠加。行星围绕恒星公转就稍微比较麻烦,需要设置旋转轴(就是恒星的Y轴)。卫星围绕行星和恒星公转就更麻烦一点,这里需要将行星和卫星添加到一组中,卫星围绕恒星公转就和行星公转一样了。至于卫星围绕行星公转则在单独计算,和行星围绕恒星公转类似。自转很容易计算,那么公转如何计算?这里提供两种方法,一种是通过矩阵或者四元数的方式计算,这里如果对这块很了解的同僚可以采用这种方式;一种是通过方程式计算,求出各个星体的轨迹方程。然后就能求出星体各个时刻的对应的位置(即世界坐标)。我这里使用的是方程式计算,考虑到行星公转轨迹有近日点和远日点之分,所以这里使用椭圆轨迹方程更加准确一点,嘿嘿。这里以恒星(太阳)为中心,并设置各个行星的长短半轴即可构建出椭圆方程。卫星的话,中心则是对应的行星。为了方便程序求解,不要通过椭圆的标准方程(x^2/a^2 + y^2/b^2 = 1)去求,这样计算量很大而且麻烦;使用椭圆极坐标方程会很方便(x = acos(θ); y = bsin(θ))。考虑到是围绕Y轴旋转,而且摄像机的朝向是Z的负半轴。所以将y = bsin(θ)改为z = -bsin(θ)。最后求出来的x,z需要加上恒星/行星的中心坐标,就是行星/卫星的世界坐标了。所以这里我们只需要每帧改变θ(弧度制),就可以求出行星/卫星任意时刻的位置了。

还需要说明星体和轨迹白点是如何生成的?星体是创建一个球体几何体,然后每个星体对应一种纹理,将纹理材质绑定到球体几何体上就行了;轨迹白点,是创建的平面模型,设置材质延时为白色即可。至于每个行星的轨迹点数量,根据椭圆的轨迹方程等分就行,这里为了让轨迹白点效果更好一点,越靠近恒星的轨迹点数量少,越离恒星的轨迹点数量多。

最后说明一点,由于本人这里设置相机的位置是源点(0,0,0),朝向是向z轴负方向的。而且整个太阳系都是和XOZ平面平行的,所以这里渲染的时候会有遮挡关系,不是很好区分各个行星的旋转轨迹和方向。为了避免这个问题,我这里将整个太阳系包括的模型添加到一组中,整体围绕X轴顺时针旋转一定角度(比如30度),里面还做了其他运算,这里不做说明。其实有更简单的方法就是改变摄像机位置和朝向,但是考虑到后面的摄像机镜头动画和粒子位置计算的方便性,本人并未采取这种方式。

镜头拉伸效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第5张图片

镜头拉伸主要是用于将镜头定位到指定行星上。这里只需要更改摄像机(即镜头)的位置和朝向属性就可以了。先说明一下摄像机的位置改变,就是一个比较简单的插值方程(pos = (end - start) * factor + start)。因为我是知道摄像机的起点位置(我这里是源点)、终点位置(是指定行星的世界坐标),这里需要注意的是因为镜头在运动的过程中,行星也是在运动的,所以需要每帧更新插值方程。行星一直在运动,那么如何判断镜头运动结束?这里我是每帧计算摄相机和行星的距离,当距离小于某个值(该值需要自己设定)时,则镜头不再向行星运动,而是和行星保持相对静止。

在镜头运动完后,有时你可能会看见行星并非在行星的视野范围内。这里是因为摄像机朝向没有改变的结果。要想行星在屏幕正中央显示,则需要将摄像机的朝向指向行星的位置(世界坐标)。这里为了让视野平滑过渡,也是需要做插值运算。已知摄像机的起始朝向(即向量,带有方向的)、终点朝向(这里需要简单的运算一下,用行星的世界坐标减去摄像机的世界坐标即可)。这里因为行星和摄像机都是运动的,所以也需要每帧更新插值方程。

镜头的绑定效果,玩过rpg游戏的同僚应该知道,其实就是将人物模型绑定一个摄像机,让摄像机和人物模型相对静止。我这里的当镜头运动到行星附近时就是将镜头和行星保持相对静止,但是为了方便计算,本人在镜头和行星相对静止时,行星不在做公转运动了。

猿猴关键点动画效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第6张图片

这个动画,分为四个子阶段。一是淡入和自由运动,二是定点缩放运动,三是浮动缩放运动,四是淡出。这里需要说明的是,这里是2D渲染绘制,本人在这里创建了另一个相机和场景,用于绘制UI的。3D用的透视相机,物体大小会随离摄像机距离而变大变小;UI用的是正交相机则不会。要想深入了解,请自行科普。

淡入和自由运动

淡入(见文本淡入淡出效果,这里不做说明),自由运动和之前的星星和星云粒子效果类似。设置细胞粒子的阈值范围,然后随机坐标和移动速度,然后每帧更新即可。

定点缩放运动

这里说的定点是指会给定一个位置,然后各个细胞粒子朝给定位置运动(也就是做插值运算)。这里的比较麻烦的一点是,如何获取细胞粒子的位置?因为细胞粒子要过渡到猿猴。本人这里提供两种方法,第一种方法是使用一些做图软件(美图秀秀、PS)将猿猴的纹理图片标记一些关键点,然后使用类似PS的软件读出各个点的位置,并记录这些关键点的相对坐标;第二种也是先要在猿猴的纹理图片上标记一些关键点,然后使用图像识别的工具程序识别出关键点并生成各个点的相对坐标。第一种比较麻烦,不够灵活;第二种相对简一些(前提是你得会图像识别技术,哈哈),很灵活。本人这里就是用的第二种方法,本人用C++写了一个基于RGB颜色模型分析的简易图像识别工具,对我而言开发这个工具成本很小(简单而且快),所以当然采用第二种方式。缩放运动和定点运动类似,指定一个最终的缩放系数,然后每帧做插值运算。

浮动缩放运动

浮动就是通过改变细胞粒子的缩放系数,和方向以及临界值。和之前的星星和星云类似粒子类似,都是可控的随机生成。

淡出

见文本淡入淡出效果,这里不做说明

城市交替效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第7张图片

城市效果其实就是一张张纹理在缩放和透明度渐变的过程。这里为了让位置方便计算,我将这些纹理是通过UI摄像机渲染的。缩放处理和上述说的细胞缩放类似,可控随机生成一些参数,然后每帧参与运算即可。透明度的渐变(即交替)过程到底是什么样的?先说明一下,在程序当中都是用不透明度表示的,所以这个过程就是不透明度先变高在变低,类似一个抛物线这样的过程。交替就是当一张城市纹理的不透明度正在变低时,这时需要绘制另一张城市纹理(不透明度逐渐变高)这样就表现的是交替效果。

文本淡入淡出效果

mmx实现图片淡入淡出_JS实现最美的3D宇宙特效_第8张图片

为了方便显示文本淡入淡出效果,我这里是基于jquery实现的。文本的淡入淡出和之前的纹理淡入淡出的渲染器是不同的,文本基于html标签直接操作。其他纹理图片是基于canvas画布标签的。这里细心的同僚会发现,在canvas(3D渲染区域)标签内右键是没有刷新的,只有图片另存为选项。说明canvas标签实则是一张图片。

淡入效果如何实现?其实比较简单,每帧更新纹理的不透明度(从0到1),调整合适更新速度就是这个效果了。

淡出效果如何实现?和淡入效果类似,每帧更新纹理的不透明度(从1到0),调整合适更新速度就是这个效果了。

最后说明一下淡入淡出的巧用,如果在一个场景中有很多物体,但这个场景需要淡入淡出效果。首先最可能想到的办法就是每帧更新每个物体的不透明度,当然这样也是可以实现,但是很耗机器性能。这里其实有更好的方式,就是在场景的最上层绘制一张纯黑纹理,不透明度由1渐变到0或者由0渐变到1,就是淡入淡出效果了。其实还有很多类似的效果,比如遮罩啊什么的,这些转场效果在视频编辑中会常常用到,感兴趣的同僚可以去实现一下。

最后

到这里,宇宙3D特效的实现已经讲完了。是不是很美,是不是以为是纪录片的视频(哈哈,也许是我自作多情)。让你想不到的是,这其实是纯程序实现。

前几天在哔站看了一个视频,一个玩绝地求生的玩家发的。使用两个C4炸弹炸死了很远的一个敌人,怎么做的呢?在一摩托车上绑定一个C4炸弹,另一个C4炸弹做为助推器,计算好时间,待摩托车落到指定地方后,绑在摩托车上的C4引爆。要完成这个过程需要精准的计算出C4引爆后的助推摩托车的距离和助推摩托车的到指定位置所需时间。那个玩家讲了一堆的数学物理方程。所以懂科学和知识的方法的人,哪怕玩游戏也是很厉害的(哈哈)。其实编程涉及很多数理化知识,不仅仅是编程,生活中也是。就目前很火热的图形学和AI技术来说,都是和这些知识有很大的联系。像数学、物理学、线性代数、概率学以及统计学我们都尽量都多少懂点,不然和这些好玩的技术都无缘了,哈哈。哎,学习这条路,任重而道远啊,正应了“学好数理化,走遍天下都不怕”那句口号,各位和我一起加油吧!

【说明】:本人的文章都会同步到我的微信公众号和CSDN上,欢迎大家关注访问。本人的个人网址也搭建起来了,以后本人的所有的产品都可以通过我的个人网址找到,目前这个宇宙特效是在我的个人网址首页进行展示(首次加载可能有点慢,因为本人租用的服务器带宽不高)。

  • 微信公众号:程序员JC
  • CSDN:https://blog.csdn.net/Jack_chen8888
  • 最美的地球源码地址:https://github.com/JackGit/xplan/
  • 最美的地球预览地址:http://xplan.jackyang.me/
  • 图形识别工具源码:https://pan.baidu.com/s/18Hw4D0Sb0aQJHbLTx_2B0w 提取码 2d0l
  • 宇宙特效预览:https://www.jackchenboss.xyz
  • https://www.jackchen.world(正在备案)
  • https://www.jackchen.word(正在备案)

你可能感兴趣的:(mmx实现图片淡入淡出)