对读书类app的小说美高梅4688.com

重大面向Web前端工程师,需要肯定Javascript及three.js基础;
本文重要分享内容为按照three.js开发WebVR思路及碰着的问题;
有趣味的同校,欢迎跟帖探究。

最近心得了多少个阅读类app,也就其中的六个做了一部分意义与用户体验上的争持统一,想结合自己的阅历,说一说自己的部分小想法,可能是一时起来,所以这一个想法或许会天马行空,不着边际,欢迎拍砖啊

目录:
一、项目体验
1.1、项目简介
1.2、效用介绍
1.3、游戏体验
二、技术方案
2.1、为何使用WebVR
2.2、常用的WebVR解决方案
2.2.1、Mozilla的A-Frame方案
2.2.2、three.js及webvr-polyfill方案
三、技术实现
3.1、知识储备
3.2、实现步骤
3.3、工作原理
四、技术难题
4.1、程序与用户一起决定录像头
4.2、多重蒙板贴图
4.3、镜头移动
4.4、3d自适应长度文字指示
4.5、unity3d地形导出
4.6、3dmax动画导出问题
五、完整的源代码及相应组件

话不多说,顿时进入正题。

一、项目体验
1.1、项目简介:
1.1.1、名称:
“重历阿尔特里(Terry)亚”——龙之谷手游手首发China乔伊(Joy)2016预热VR小游戏
美高梅4688.com 1

首先个idea是:在阅读的时候,将不同的读者对此该章节的评价/笔记(限制字数)在页面顶端的空白点飘过,对于好的褒贬/笔记,用户可以通过按住进行点赞,并据此筛选出top10评价。读者可以自行接纳开启/关闭弹幕。

1.1.2、开发背景:
依据龙之谷手游具备的3D属性,全景视角体会,以及China乔伊先发的线下场景,我们和品牌探究除了基于VR的线下体验项目。由于基于Web技术较好的兼容性、开发的高效性,我们运用了WebVR技术来促成全部体验。

以此想法缘于游戏/录像直播中的弹幕效率,弹幕最近特别猛烈,备受90后追捧,我以为紧要缘由有以下两点:一,加强用户与录像作者,用户与用户之间的竞相,用户有很强的出席感;二,刷存在感。平时会看到“…….+1,
…….+2,
…….+3”这样的弹幕,这就是用户对好的弹幕的终将,弹幕的原创者肯定会很有成就感。所以,直播既然可以有弹幕,为何阅读不可以啊?

1.1.3、使用WebVR优势:
1.1.3.1、普通web前端工程师可以涉足VR应用开发,降低了支出门槛;
1.1.3.2、跨设备终端、跨操作系统、跨APP载体;
1.1.3.3、开发急速、维护方便、随时调整、传播便捷;
1.1.3.4、浏览器即可体验,无需安装。

第二个idea是:一些相比较出色的图书著作,在翻阅停止后,可以推荐给用户同名/相关品种的电影、游戏链接等,增添赢利点。尤其是当前激烈的网络小说,都会有与之相关的手游随笔,在读完小说后再玩一玩同名手游,我想大部分人都很愿意试试啊。

1.2、效率介绍
基于游戏内3D场景、人物和道具模型,通过WebGL框架three.js开发的VR小游戏,在China乔伊龙之谷手游展台给玩家提供线下VR互动体验,并在继续应用于线上营销传播。不享有VR眼镜设备的用户可选用一般性情势展开互动体验。

其五个idea:可以品尝在书的末尾设置投票,比如,你以为xx应该嫁给谁?A,张xx;B李xx。。。;或者在有的书的中级设置投票,比如,你认为凶手是谁?A,张xx;B李xx。。。

1.3、游戏体验
假使您身边正好有VR眼镜,请采取VR情势体验;如若没有,请采纳通常情势。
亟待证实的是,由于此次应用针对线下场景,而合作方三星提供了风尚的S7手机和GearVR设备,所以项目只针对S7做了体验优化,所以可能有些部手机会有卡顿或者3D模型错乱的情事。

以此想法缘于qq空间疯转的投票,究其原因,就是引发了用户的五个思维:一,参预感,用户通过投票表明自己的视角;二,好奇心,用户都会惊奇和团结持相同看法的人占了多大比例(投票结果一般都安装为投票后可查阅)。所以,我以为将投票那么些效应应用于部分爱情类小说和探明类散文应该会有诸多读者出席。延伸开来,对于那多少个周周更新的小说,甚至可以安装赔率。比如,张三最终选取了何人?A小李,赔率1:3,B小张,赔率1:2,可以用阅读币等看似的虚构货币做赌注,当然笔者不可能看出下注情形,那一个只是为着活跃用户而非作为盈利点,固对于投注的金额及赔率要从严限制。

您可以扫描如下二维码或打开http://dn.qq.com/act/vr/进展体验:
美高梅4688.com 2

第六个idea:在书籍推荐和排行中,每本书下面彰显读过的人口,而毫不点开图书之后才显得,这样用户可以很直观的看到每本书的开卷人数,而读书人数对于用户来讲是控制是否读这本书的一个首要目的。在百度读书与iReader中对此的处理都不是很到位。

二、技术方案
2.1、为何是时候尝试WebVR了?
2.1.1、时机逐步成熟,我们由此几件事件即可感知:
2015年终,Mozilla在firefox nightly扩充了对WebVR的匡助;
2015年终,MozVR团队推出开源框架A-Frame,能过HTML标签,即可成立VR网页;
2015年初,Egret3D发表,开发团队称将在其后版本中落实WebVR的援助;
2016年底,Google与Mozilla联合创办WebVR标准;
2016年九月,Google计划将一切Chrome浏览器搬进VR世界中。
2.1.2、WebVR开发成本更低。
2015年VR硬件连忙进步,但迄今,VR内容仍旧稍显单薄。原因在于,VR开发成本过高,而WebVR依托于WebGL及类似threeJS等框架,大大降低开发者进入VR领域的奥妙。
2.1.3、Web自身的优势
上文中已有提及,依托也Web,具有不需安装、便于传播、便于快捷迭代等风味。

第几个idea:登陆和揭橥可以做的一发炫酷一些,比如设计为翻开书和合上书的动画。

2.2、最近阶段,常用的WebVR解决方案:
2.2.1、A-frame
介绍:Mozilla的开源框架,通过定制HTML元素即可构建WebVR方案的框架,适用于尚未webGL与threeJS基础的初学者。
亮点:基于threeJS的卷入,通过一定的标签就可以快捷创设VR网页;
症结:所提供的零部件有限,难以完成较复杂的连串。
实例:
2.2.1.1、成立一个简便的情状。

第四个idea:阅读类软件可总是微信,获取已安装该软件的微信好友,并在每月首绘出微信好友的阅读量(当然还可以够是其他参数目标)排名榜,比据此付出相应的职称,如:文盲,小学生,高中生,助教等等。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
</head>
<body>
    <a-scene>
        <!-- 环境光. -->
        <a-entity light="type: ambient; color: #888"></a-entity>
        <a-entity position="0 2.2 4">
        <!-- 添加相机 -->
        <a-entity camera look-controls wasd-controls>
            <!-- 添加圆环 -->
            <a-entity cursor
            geometry="primitive: ring; radiusOuter: 0.015; radiusInner: 0.01; segmentsTheta: 32" material="color: #283644; shader: flat" raycaster="far: 30" position="0 0 -0.75"></a-entity>
        </a-entity>
        </a-entity>
    </a-scene>
</body>
</html>

其一想法来源于手游玩家在微信好友中的名次这多少个功用。由于微信是按照熟人关系建立的涉及链,所以大家互动攀比炫耀一下,很容易助长app的不胫而走。

源码讲解:
如上大概的多少个标签,即可构建一个富含灯光、相机、跟随相机的物体的场馆,其余工作,都将由A-frame举办辨析,具体标签与性能不多作讲解,可以参考 A-frame
DOC

大致就是这么多,可能有点想法过于天马行空,然则自己的确就是这般想的,所以我毫无畏惧的讲出来。恩,就这样,以后想到了还会再补偿。

2.2.1.1、加载一个由软件(比如3dmax)导出的模子。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
<script>
    AFRAME.registerComponent('json-model', {
        schema: {
            type: 'src'
        },
        init: function () {
            this.loader = new THREE.JSONLoader();
        },
        update: function () {
            var mesh = this.el.getOrCreateObject3D('mesh', THREE.Mesh);
            this.loader.load(this.data, function (geometry) {
            mesh.geometry = geometry;
            });
        }
    });
</script>
</head>
<body>
<a-scene>
    <a-assets>
        <a-asset-item id="sculpture" src="data/building-ground.js"></a-asset-item>
    </a-assets>
    <a-entity id="car" json-model="#sculpture"  position="0 0 0" scale="5 5 5" rotation="0 45 0" material="src: url(cross-domain/skin/xianxiasq_zhujianqiangmian_001.png)"></a-entity>
</a-scene>
</body>
</html>

源码讲解:
本条例子紧要演示,A-Frame怎么着添加组件,对,因为A-Frame现阶段组件太少,加载自定义情势需要协调壮大组件。而组件添加需要three.js基础。
so,A-Frame出发点是不行美好的,学习多少个简单的价签及性能,即可以搭建3d/webvr情景,不过具体却是方今它还并不成熟,并且伴随着A-Frame主设计师跳槽到Google,所以自己很已经舍弃这些方案了。

2、基于threeJS与webVR组件,事实上,A-frame就是按照这两者的卷入。
可取:可以成功复杂项目,可以结合原生的webGL;
缺陷:需要控制threeJS,需要了然webGL,学习成本较高。

在本项目中,选取的就是那多少个方案,在下章节中,将会开展详细介绍。

三、技术实现
3.1、知识储备:
three.js(掌握)、webGL(了解)、javascript
对three.js没有基础的同窗,可以移动至 Three.js实例教程

3.2、实现步骤:
简单易行来说,完成一个WebVR应用,需要以下五个步骤:
3.2.1、搭建场景
美高梅4688.com 3

如上图与示:
首先我们需要载入我们的资源,这多少个资源包括地形、角色、动画、及协助元素;
接下来创制大家需要的元素,比如灯光、相机、天空等;
接下来成功主业务逻辑。

3.2.2、交互
即用户的动作输入,这个动作包括:
职务移动、旋转、视线要旨、声音、甚至浑身所有问题动作。
理所当然,当前大家可利用的硬件配备有限,手机自身可选择的如陀螺仪、罗盘、听筒。此外协理设施常用如Leap
Motion、Kinect等。
更多的附加装备意识着更高的运用基金,在该案例中使用的到的动作输入信息:
用户眼前趋势,由VRControls.js与webvr-polyfill.js实现到位;
用户意见核心,完成按钮点击、攻击等动作,通过尾随相机的实体检测碰撞来形成。

3.2.3、分屏
美高梅4688.com 4

 

如上图所示,为让用户更具沉侵感,日常会依据用户瞳距将屏幕分割成具有自然视差的两有些,勿需担心,这一部分行事由VREffect.js来形成。

3.3、工作原理
上节中涉及了webvr相关组件,本来我们得以大概利用它提供的接口就可以形成,但一定如故有同学会好奇,它的做事规律是什么样的吗。
美高梅4688.com 5

这得从Mozilla与谷歌 2016年终联手推出的WebVR API提案最先,WebVR
Specification
,该提案给VR硬件定义了专门定制的接口,让开发者可以构建出沉浸感强,舒适度高的VR体验。但鉴于该专业还地处草案阶段,所以我们付出需要WebVR
Polyfill
,那多少个组件不需要一定浏览器,就足以应用WebVR
API中的接口。
就此我们只需要在项目中,引入webvr-polyfill.js及VRControls、VREffect多个类,并调用即可。

vrEffect = new THREE.VREffect(renderer);
vrControls = new THREE.VRControls(camera);

webvr-polyfill基于普通浏览器实现了WebVR API 1.0功用;
VRControls更新视频头消息,让用户以率先人称置于场景中;
VREffect负责分屏。

四、技术困难

4.1、程序与用户一起决定视频头
当程序在机关移动镜头的经过中,允许用户四处考察,这时候需要一个增援容器共同决定镜头旋转与活动。

// 添加摄像机
camera = new THREE.PerspectiveCamera(60, size.w / size.h, 1, 10000);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0,0,0));

// 辅助镜头移动
dolly  = dolly = new THREE.Group();
dolly.position.set(10, 40, 40);
dolly.rotation.y = Math.PI/10;
dolly.add(camera);
scene.add(dolly);

4.2、多重蒙板贴图
美高梅4688.com 6

 

如上图所示,该地形由两种贴图通过蒙板共同合成,这时候我们需要采用自定义Shader来实现,由rbg五个通道控制展现。
骨干代码(片元着色器):

fragmentShader: [
    'uniform sampler2D texture1;',
    'uniform sampler2D texture2;',
    'uniform sampler2D texture3;',
    'uniform sampler2D mask;',
    'void main() {',
        'vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);',
        'vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);',
        'vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);',
        'vec4 colorMask = texture2D(mask, vUv);',
        'vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;',
        'gl_FragColor =  vec4(outgoingLight, 1.0);',
    '}'
].join("\n")

 完整代码(添加three.js灯光,雾化):

// 合成材质
var map1 = texLoader.load('cross-domain/skins/foor_stone02.png' );
var map2 = texLoader.load('cross-domain/skins/green_wet09.png');
var map3 = texLoader.load('cross-domain/skins/stone_dry02.png');

// 自定义复合蒙板shader
THREE.FogShader = {
    uniforms: lib.extend( [

        THREE.UniformsLib[ "fog" ],
        THREE.UniformsLib[ "lights" ],
        THREE.UniformsLib[ "shadowmap" ],
        {
            'texture1': { type: "t", value: map1},
            'texture2': { type: "t", value: map2},
            'texture3': { type: "t", value: map3},
            'mask': { type: "t", value: texLoader.load('cross-domain/skins/mask.png')}
        }
    ] ),
    vertexShader: [
        "varying vec2 vUv;",
        "varying vec3 vNormal;",
        "varying vec3 vViewPosition;",

        THREE.ShaderChunk[ "skinning_pars_vertex" ],
        THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
        THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],

        "void main() {",

            THREE.ShaderChunk[ "skinbase_vertex" ],
            THREE.ShaderChunk[ "skinnormal_vertex" ],


            "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",

            "vUv = uv;",
            "vNormal = normalize( normalMatrix * normal );",
            "vViewPosition = -mvPosition.xyz;",

            "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
            THREE.ShaderChunk[ "logdepthbuf_vertex" ],
        "}"
    ].join('\n'),

    fragmentShader: [
        'uniform sampler2D texture1;',
        'uniform sampler2D texture2;',
        'uniform sampler2D texture3;',
        'uniform sampler2D mask;',

        'varying vec2 vUv;',
        'varying vec3 vNormal;',
        'varying vec3 vViewPosition;',
        // "vec3 outgoingLight = vec3( 0.0 );",
        THREE.ShaderChunk[ "common" ],
        THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
        THREE.ShaderChunk[ "fog_pars_fragment" ],
        THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],

        'void main() {',
            THREE.ShaderChunk[ "logdepthbuf_fragment" ],
            THREE.ShaderChunk[ "alphatest_fragment" ],

            'vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);',
            'vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);',
            'vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);',
            'vec4 colorMask = texture2D(mask, vUv);',

            'vec3 normal = normalize( vNormal );',
            'vec3 lightDir = normalize( vViewPosition );',

            'float dotProduct = max( dot( normal, lightDir ), 0.0 ) + 0.2;',

            'vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;',


            THREE.ShaderChunk[ "shadowmap_fragment" ],
            THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
            THREE.ShaderChunk[ "fog_fragment" ],

            // 'gl_FragColor = vec4( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b, 1.0 )  + vec4(outgoingLight, 1.0);',
            // 'gl_FragColor = outgoingLight;',
            'gl_FragColor =  vec4(outgoingLight, 1.0);',
        '}'
    ].join("\n")

};
THREE.FogShader.uniforms.texture1.value.wrapS = THREE.FogShader.uniforms.texture1.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture2.value.wrapS = THREE.FogShader.uniforms.texture2.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture3.value.wrapS = THREE.FogShader.uniforms.texture3.value.wrapT = THREE.RepeatWrapping;

var material = new THREE.ShaderMaterial({
    uniforms        : THREE.FogShader.uniforms,
    vertexShader    : THREE.FogShader.vertexShader,
    fragmentShader  : THREE.FogShader.fragmentShader,
    fog: true
});

3、 镜头移动(依赖Tween类)
功用函数:

cameraTracker: function(paths){
    var tweens = [];
    for(var i = 0; i < paths.length; i++) {
        (function(i){
            var tween = new TWEEN.Tween({pos: 0}).to({pos: 1}, paths[i].duration || 5000);
            tween.easing(paths[i].easing || TWEEN.Easing.Linear.None);
            tween.onStart(function(){
                var oriPos =  dolly.position;
                var oriRotation = dolly.rotation;
                this.oriPos = {x: oriPos.x, y: oriPos.y, z: oriPos.z};
                this.oriRotation = {x: oriRotation.x, y: oriRotation.y, z: oriRotation.z};
            });
            tween.onUpdate(paths[i].onupdate || function(){
                if(paths[i].pos) {
                    dolly.position.x = this.oriPos.x + this.pos * (paths[i].pos.x -  this.oriPos.x);
                    dolly.position.y = this.oriPos.y + this.pos * (paths[i].pos.y -  this.oriPos.y);
                    dolly.position.z = this.oriPos.z + this.pos * (paths[i].pos.z -  this.oriPos.z);        
                }
                if(paths[i].rotation) {
                    dolly.rotation.x = this.oriRotation.x + this.pos * (paths[i].rotation.x -  this.oriRotation.x);
                    dolly.rotation.y = this.oriRotation.y + this.pos * (paths[i].rotation.y -  this.oriRotation.y);
                    dolly.rotation.z = this.oriRotation.z + this.pos * (paths[i].rotation.z -  this.oriRotation.z);
                }
            });
            tween.onComplete(function(){
                paths[i].fn && paths[i].fn();
                var fn = tweens.shift();
                fn && fn.start();
            });
            tweens.push(tween);
        })(i);
    }
    tweens.shift().start();
}

调用:

lib.cameraTracker([
    {'pos': { x: -45,y: 5, z: -38},'rotation': {x: 0, y: -1.8, z: 0},  'easing': TWEEN.Easing.Cubic.Out,'duration':4000}
]); 

 4、自适应长度文字指示
遵照文字长度生成canvas作为贴图到可口可乐对象。

hint = function(text, type, posY, fadeTime){
    var chinense = text.replace(/[u4E00-u9FA5]/g, '');
    var dbc = chinense.length;
    var sbc = text.length - dbc;
    var length = dbc * 2 + sbc;
    var fontsize = 40;
    var textWidth = fontsize* length / 2;
    posY = posY || 0.3;
    type = type || 1;
    fadeTime = fadeTime === window.undefined ? 500 : fadeTime;

    if(text == 'sucess' || text == 'fail') {
        text = ' ';
    }

    var canvas = document.createElement("canvas");      
    var width = 1024, height = 512;
    canvas.width = width;
    canvas.height = height;
    var context = canvas.getContext('2d');

    var imageObj = document.querySelector('#img-hint-' + type);


    context.drawImage(imageObj, width/2 - imageObj.width/2, height/2 - imageObj.height/2);
    context.font = 'Bold '+ fontsize +'px simhei';
    context.fillStyle = "rgba(255,255,255,1)";
    context.fillText(text, width/2-textWidth/2, height/2+15);           


    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;

    var mesh;
    var material = new THREE.SpriteMaterial({
        map: texture,
        transparent: true,
        opacity: 0
    });
    mesh = new THREE.Sprite(material);
    mesh.scale.set(width/400, height/400, 1);
    mesh.position.set(0, posY, -3);
    camera.add(mesh);   

    var tweenIn = new TWEEN.Tween({pos: 0}).to({pos: 1}, fadeTime);
    tweenIn.onUpdate(function(){
        material.opacity = this.pos;
    });
    if(fadeTime === 0) {
        material.opacity = 1;
    } else {
        tweenIn.start();
    }


    var tweenOut = new TWEEN.Tween({pos: 1}).to({pos: 0}, fadeTime);
    tweenOut.onUpdate(function(){
        material.opacity = this.pos;
    });
    tweenOut.onComplete(function(){
        camera.remove(mesh);
    });
    tweenOut.fadeOut = tweenOut.start;
    tweenOut.remove = function(){
        camera.remove(mesh);
    }

    return tweenOut;
};

5、unity地形导出
5.1、首先将unity地形导出为obj
美高梅4688.com 7

5.2、然后导入3dmax,使用ThreeJSExporter.ms导出为js格式。

6、3dmax动画导出题目
6.1、动画导出错误
一般是目标为可编制多边形,需要转换成网格对象。
美高梅4688.com 8

操作步骤:
6.1.1、拔取对象,右键转换为可编制网络;
6.1.2、采纳蒙皮修改器,重新蒙皮;
6.1.3、点击蒙皮修改器下的骨骼 > 添加,添加原有的骨骼。
6.2、动画导出错乱
很容易令人觉着是权重出问题了,但就自己要好六个连串动画导出的经历来看,大部分涌出在骨骼添加上。在3dmax及unity中,不添加根节点往往不影响动画执行,但导出到three.js,需要添加根节点。假如问题还留存,则精心察看是哪些骨骼引起的,多余骨骼或缺失骨骼都可能引起动画错乱。
美高梅4688.com 9

五、完整的源代码及相应组件
点击下载
main.js – 完整的源代码
tween.min.js – 动画类
OrbitControls.js – 视图控制器,旋转、移动、缩放场景,方便调试
audio.min.js – motion音频组件,解决自动播放音频问题
任何vr相关组件上文已有介绍

发表评论

电子邮件地址不会被公开。 必填项已用*标注