做有态度的前端团队

网易FEG前端团队

Augmented Reality

噔噔!一个类似阴阳师现世抽卡的小demo,由于getUserMedia的兼容性问题,目前只有安卓手机可以看到...(建议拿一些高端点的安卓机)

demo.png

玩法:扫左图的二维码后,点击允许访问摄像头后,再扫右边的二维码(相当于阴阳师里的召唤阵)

未标题-4.jpg

或者你也可以拿另外一台手机扫下面的码来显示召唤阵

qrcode.png

下面探索一下其实现的原理,其中主要的交互包括:

开启摄像头 > 扫码 > 显示模型

转换为相对应的技术,奏是...

· getUserMedia,开启摄像头获取设备的媒体信息
· OpenCv,js-aruco, 图像处理,识别二维码
· 画出3d模型,threejs,threex_webar, MMDLoader-app
· 进一步交互(TO DO)

1. 开启摄像头

getUserMedia提示用户允许使用一个视频和/或一个音频输入设备,如果用户给予许可,successCallback回调就会被调用。第一个参数指定了请求使用媒体的类型


    navigator.getUserMedia ( {
        video: true, // 这里只请求相机
        audio: false
    }, successCallback, errorCallback );

1) 后摄像头

getUserMedia默认是开启前摄像头的,被突然出现的丑萌的自己吓到...这不是我们想要的效果。。解决方案是利用MediaStreamTrack遍历设备的媒体源,返回后摄像头的sourceId


    // 遍历设备的媒体源
    MediaStreamTrack.getSources(function(sourceInfos) {
        
        //getUserMedia的constraints参数
        var constraints = {
            video: true,
            audio: false
        }
       

        for (var i = 0; i != sourceInfos.length; ++i) {
            var sourceInfo = sourceInfos[i];

            // 后摄像头
            if(sourceInfo.kind == "video" && sourceInfo.facing == "environment") {
                constraints.video = {
                    optional: [{sourceId: sourceInfo.id}]
                }
            }
        }

        navigator.getUserMedia( constraints, function(stream){
                video.src = URL.createObjectURL(stream); //获取到的视频流传给页面上的video来显示
        }, function(error) {
               alert(error.message);
        });
});

2) 全屏问题

拍摄的内容并没有全屏显示,解决方案就是:在video外层增加一个div并且设置为浏览器宽高,再增加 overflow:hidden,注意video不用设宽高,这样就可以模拟全屏的效果了

2. js-aruco

js-aruco是一个Aruco库的js,而Aruco是一个基于OpenCv的图像处理库,大概的作用就是可以识别图片的轮廓,这样就可以达到扫码的作用来做进一步的交互了。

js-aruco可识别的图片要满足以下要求:一张7x7的网格图(类似一张二维码),最外一层是黑色的,然后中间5x5的网格每一行都要遵循下列规则之一

white - black - black - black - black

white - black - white - white - white

black - white - black - black - white

black - white - white - white - black

(可以看到刚刚扫过的id:256二维码就满足以上规则)

不同的组合拼成不同的二维码,每一个二维码都有对应的id。(戳demo)[https://test.nie.163.com/test_html/test/dropping/js_aruco.html]

3. threex_webar

threex_webar
直接就把前面的步骤都封装好,让你把注意力放在画3d模型上。


    //二维码识别
    var jsArucoMarker = new THREEx.JsArucoMarker();
    //视频图像采集
    var videoGrabbing = new THREEx.WebcamGrabbing();


    //建立一个Object3D对象
    var markerObject3D = new THREE.Object3D()
    scene.add(markerObject3D)

    //把3d模型画在Object3D对象上, 待会下一步会详细说明
    drawMMDmodel();

    //一开始设为不可见
    markerObject3D.visible= false;
        

    onRenderFcts.push(function(){
        
        var domElement  = videoGrabbing.domElement
        var markers = jsArucoMarker.detectMarkers(domElement)

        markerObject3D.visible = false

        //等识别到二维码后,显示Object3D对象
        markers.forEach(function(marker){

            jsArucoMarker.markerToObject3D(marker, markerObject3D)
            markerObject3D.visible = true;

        })
    })


注意事项

上面提到的摄像内容的全屏问题


    //要去修改下threex_webar的threex.webcamgrabbing.js,取消默认设置的宽高
    //domElement.style.width = '100%'
    //domElement.style.height = '100%'

4. 3d model

MMDLoader-app绘制mmd模型


    function drawMMDmodel() {

        //模型加载成功回调
        var onProgress = function(xhr) {
            if (xhr.lengthComputable) {
                var percentComplete = xhr.loaded / xhr.total * 100;
                console.log(Math.round(percentComplete, 2) + '% downloaded');
            }
        };
        //模型加载失败回调
        var onError = function(xhr) {};


                //Polygon Model Document,一种三维模型格式
        var modelFile = 'https://cdn.rawgit.com/mrdoob/three.js/dev/examples/models/mmd/miku/miku_v2.pmd'; 
                //Vocaloid Motion Data,模型动作
        var vmdFiles = ['https://cdn.rawgit.com/mrdoob/three.js/dev/examples/models/mmd/vmds/wavefile_v2.vmd']; 

        helper = new THREE.MMDHelper();

        var loader = new THREE.MMDLoader();

        loader.load(modelFile, vmdFiles, function(object) {

            mesh = object;

            mesh.scale.set(1, 1, 1).multiplyScalar(1 / 35);
            mesh.rotation.x = Math.PI / 2;
            mesh.material = makePhongMaterials(mesh.material.materials);


            var i = 0,
                materials = mesh.material.materials,
                len = materials.length;
            for (; i < len; i++) {
                materials[i].emissive.multiplyScalar(1);
            }


            helper.add(mesh);
            helper.setAnimation(mesh);
            helper.setPhysics(mesh);

            helper.unifyAnimationDuration();

            markerObject3D.add(mesh);

            ready = true;

        }, onProgress, onError);

    }

参考资料

http://tgideas.qq.com/webplat/info/news_version3/804/7104/7106/m5723/201612/537832.shtml
https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/getUserMedia
http://opencv.org/

手机阅读请扫描下方二维码:

已有 2 条评论

  1. 瑶一瑶

    66666

  2. 有bug你就吱一声

    你是不是傻

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif