基于HTML5的WebGL经典3D虚拟机房漫游动画

码界逸云
• 阅读 2789

第一人称在 3D 中的用法要参考第一人称在射击游戏中的使用,第一人称射击游戏(FPS)是以第一人称视角为中心围绕枪和其他武器为基础的视频游戏类型 ; 也就是说,玩家通过主角的眼睛来体验动作。自从流派开始以来,先进的 3D 和伪 3D 图形已经对硬件发展提出了挑战,而多人游戏已经不可或缺。

基于HTML5的WebGL经典3D虚拟机房漫游动画
Doom 的截图,这个流派的突破游戏之一,展示了第一人称射击游戏的典型视角

现在博物馆或者公司也经常使用到 3D 动画做宣传片等等,3D 动画演绎最大的优势,便是在于内容与形式上给人的真实感受。它比平面作品更直观,比 2D 动画更真实,所以更能给观赏者以置身于广告环境当中的感受,大大增强广告的说服力。3D 技术的发展甚至挑战受众的分辨能力,使受众的判断游离于与虚拟和现实之间。
而且 3D 特效的应用为创意提供了更加广阔的思维空间,并成为创意执行的可靠保证,并丰富了创意的形式和风格手段。根据广告主题的表现诉求,可以营造出梦幻般的神奇氛围来刺激打动受众,从而起到与受众沟通的目的。
3D动画宣传片将 3D 动画、特效镜头、企业视频、照片、未来前景等内容通过后期合成、配音、解说形成一部直观、生动、喜闻乐见的高品位的企业广告宣传片,让社会不同层面的人士对企业产生正面的、积极的、良好的印象,从而建立对企业的好感与信任,并信赖该企业的产品或服务。
现在 3D 发展地如此迅速也要感谢人类对于“现实”的追求,所以学好用好 3D 是未来成功必不可少的一部分。
本文例子的思路是进入一个机房参观,打开门的动作是再生动不过了, 再加上适当地转弯,基本上完全模拟了人在机房中参观的效果。还有一个好处就是,如果要演示给领导看而又不用操作,这种炫酷的效果领导一定会很满意!
http://www.hightopo.com/demo/...
基于HTML5的WebGL经典3D虚拟机房漫游动画
界面上的“reset”和“start”两个按钮是直接加在 body 体中的 button,并在这两个按钮上添加点击事件:

<div class="button" style="right: 50px;background-image: url(run.png);" onclick="startAnim();"></div>
<div class="button" style="right: 100px;background-image: url(reset.png);" onclick="reset();"></div>

整个场景由 HT 封装的 3D 组件搭建形成的,构造这么大的场景是需要一定量的代码的,为了简化,我把场景单独拿出来,并用 HT 封装的 ht.JSONSerializer 类将场景序列化为 json,代码中只引入了生成后的 json 文件,为了让大家更明确,我这边做个示例,假设已经搭建好 3D 场景了:

dm = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dm);
//.......构建好场景
dm.serialize();//可以填入number参数,作为空格缩进值

既然我们已经搭建好环境,转成了 json 文件,代码中不好控制,这种情况下我们会将 DataModel 数据模型再反序列化,这个函数的功能就是将 json 格式转成对象,并将反序列化的对象传入到 DataModel 数据模型中,详情请参考 HT for Web 序列化手册

 var g3d = window.g3d = new ht.graph3d.Graph3dView(),
    dataModel = g3d.dm(),
    view = g3d.getView(),
    path = null;
g3d.setMovableFunc(function(data) {
    return false;
});
g3d.setVisibleFunc(function(data) {
    if (data.getName() === "path") {
        return false;
    }
    return true;
});
g3d.setEye([523, 5600, 8165]);
g3d.setFar(60000);
dataModel.deserialize(json);

我们目前需要操作场景中的“门”、以及我们将要走的路线“path”,遍历 DataModel 数据模型,获取这两个数据:

for (var i = 0; i < dataModel.size(); i++) {
    var data = dataModel.getDatas().get(i);
    if (data.getName() === "门") {//json中设置的名称
       window.door = data;
    }
    if (data.getName() === "path") {
        path = data;
    }
    if (window.door && path) {//获取到door 和 path 的data之后就跳出循环
        break;
    }
}

这个例子中简单来说就只有四个动作,“重置”回到原点、“开始动作”、“向前移动”,“停止”。点击“开始”按钮,在“开始动作”中我们只做了一个动作,“开门”动作,动作结束之后调用“forward”函数向前移动:

function startAnim() {
    if (window.isAnimationRunning) {
        return;
    }
    reset();
    window.isAnimationRunning = true;//动画是否正在进行
    ht.Default.startAnim({
        frames: 30, // 动画帧数,默认采用`ht.Default.animFrames`。
        interval: 20, // 动画帧间隔,默认采用`ht.Default.animInterval`。   
        finishFunc: function() {// 动画结束后调用的函数。
            forward();
        }, 
        action: function(t){ // action函数必须提供,实现动画过程中的属性变化。
            door.setRotationY(-120 * Math.PI / 180 * t);
        }
    });
}

这边的“reset”函数就是“重置”回到原点的功能,我们通过这个函数将所有变化过的都恢复初始的位置,包括“门”的位置:

function reset() {
    if (window.isAnimationRunning) {
        return;
    }
    g3d.setCenter([0,0,0]);
    g3d.setEye([523, 5600, 8165]);
    window.forwardIndex = 0;
    door.setRotationY(0);
}

要“移动”,肯定需要走路的“路径”,也就是我们刚刚获取到的“path”,通过 window.points = path.getPoints()._as; 获取“path”中的所有元素,初始化 window.forwardIndex = 0; 通过控制“path”中前后两点来设置 3D 场景中的 Eye 和 Center,这样就能营造一个我们是第一人的效果:

var point1 = points[forwardIndex],
     point2 = points[forwardIndex + 1];
var distanceX = (point2.x - point1.x),
     distanceY = (point2.y - point1.y),
     distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY)-200;//两点之间的距离通过三角形勾股定理计算 怕碰墙所以-200
g3d.setEye([point1.x, 1600, point1.y]);//眼睛
g3d.setCenter([point2.x, 1600, point2.y]);//我

HT 中 3D 组件有一个 walk(step, anim, firstPersonMode) 方法,该函数同时改变eye和center的位置,也就是eye和center在两点建立的矢量方向上同时移动相同的偏移量。step为偏移的矢量长度值。firstPersonMode参数为空时则默认采用Graph3dView#isFirstPersonMode()当前值, 如果为第一人称模式调用walk操作,该函数会考虑Graph3dView#getBoundaries()边界限制。

g3d.walk(distance, {
    frames: 50,
    interval: 30,
    easing: function(t) {return t; },
    finishFunc: function() {
        forwardIndex += 1;
        if (points.length - 2 > forwardIndex) {//points.length = 5
            g3d.setCenter([point2.x, 1600, point2.y]);//把结束点变成起始点
            g3d.rotate(Math.PI / 2, 0, {
                frames: 30,
                interval: 30,
                easing: function(t) {return t;},
                finishFunc:function() { forward();}
            });
        } else {
            var lastPoint = points[points.length  - 1];//json 中path的points 的最后一个点
            g3d.setCenter([lastPoint.x, 1400, lastPoint.y]);
            g3d.rotate(-Math.PI / 2, 0, {
                frames: 30,
                interval: 30,
                finishFunc: function() {
                    window.isAnimationRunning = false;
                }
            });
        }
    }
});

不管“path”的点有多少个,这个判断语句还是能运作,只在最后一个点是跳出 finishFunc 动画结束后调用的函数,并将 window.isAnimationRunning 值设为 false 停止 startAnim 函数。如果不是最后一个点,用户“旋转”之后,回调 forward 函数。至此,全部代码解释完毕,很短的代码量,却做出了这么大的工程!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这