增强现实之开源AR库——AR.js

AR.js是 一个应用于Web端的 AR库,它完全开源免费,至今获得了很高的热度。我们要实现的效果如下:


首先取github下载AR.js库:

点击AR.js下载

建议顺带看下作者给出的介绍。介绍里给出一个示例,我们在此示例的源码进行分析并尝试修改示例中的三维模型。

解压缩后目录如下:


示例存three.js目录的example目录下。只有在服务器环境内才可以访问。我们可以将AR.js拷贝至Apache服务器中访问,最简单的方法是:将AR.js用集成开发工具(webstorm,hbuilder等)打开,然后在开发环境中打开three.js/example,找到dev.html将其打开,点击运行即可。

我们会看到以下效果:


(由于是晚上拍的,可能效果不是很好)

我们将dev.html进行修改,改变显示的三维模型。

首先我们在example文件夹中创建一个class文件夹,存放我们自己写的实例。在class文件夹中新建一个Charactor.html文件。

路径如图:


在Charactor.html文件中编辑代码:

1.引入three.js库 及帧数查看组件

<script src='vendor/three.js/build/three.js'></script>
<script src='vendor/three.js/examples/js/libs/stats.min.js'></script>

three.js是前端实现三维显示的库,我们用它来创建的三维模型并添加页面(场景)中显示。

stats是帧数查看组件,它用来检测前端动画的运行状态。AR.js能够达到60帧以上,它足够优秀。

2.引入jsartoolkit

<script src='../vendor/jsartoolkit5/build/artoolkit.min.js'></script>
<script src='../vendor/jsartoolkit5/js/artoolkit.api.js'></script>

three.js的作用是实现三维显示,jsartoolkit则是实现摄像头的调用以及摄像头所获取影像的分析(AR的核心功能正是在此)。

此处注意一下,谷歌浏览器已经不允许http开头网址访问摄像头(本地调试除外),但火狐支持仍然支持,如果你是网站开发者,而又没有https的网址,可以推荐你的用户使用火狐浏览器。

3.引入threex.artoolkit

<script src='../src/threex/threex-artoolkitsource.js'></script>
<script src='../src/threex/threex-artoolkitcontext.js'></script>
<script src='../src/threex/threex-artoolkitprofile.js'></script>
<script src='../src/threex/threex-arbasecontrols.js'></script>
<script src='../src/threex/threex-armarkercontrols.js'></script>
<script src='../src/threex/threex-arsmoothedcontrols.js'></script>
<script>THREEx.ArToolkitContext.baseURL = '../'</script>

three.js与jsartoolkit本来毫无关系,而threex.artoolkit将他们联系到一起,事实上,这才是AR.js库的精髓所在。

4.three.js要素初始化

//////////////////////////////////////////////////////////////////////////////////
//    Init
//////////////////////////////////////////////////////////////////////////////////

// init renderer
var renderer   = new THREE.WebGLRenderer({
   // antialias   : true,
   alpha: true
});
renderer.setClearColor(new THREE.Color('lightgrey'), 0)
// renderer.setPixelRatio( 2 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.domElement.style.position = 'absolute'
renderer.domElement.style.top = '0px'
renderer.domElement.style.left = '0px'
document.body.appendChild( renderer.domElement );

// array of functions for the rendering loop
var onRenderFcts= [];

// init scene and camera
var scene  = new THREE.Scene();

var ambient = new THREE.AmbientLight( 0x666666 );
scene.add( ambient );

var directionalLight = new THREE.DirectionalLight( 0x887766 );
directionalLight.position.set( -1, 1, 1 ).normalize();
scene.add( directionalLight );

这是基本的three.js的知识,如果你接触过three.js,那么你会发现这与three.js创建要素别无二致。

5.开启摄像头

//////////////////////////////////////////////////////////////////////////////////
//    Initialize a basic camera
//////////////////////////////////////////////////////////////////////////////////

// Create a camera
var camera = new THREE.Camera();
scene.add(camera);

////////////////////////////////////////////////////////////////////////////////
//          handle arToolkitSource
////////////////////////////////////////////////////////////////////////////////

var arToolkitSource = new THREEx.ArToolkitSource({
   // to read from the webcam 
   sourceType : 'webcam',

   // // to read from an image
   // sourceType : 'image',
   // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/images/img.jpg',    
   // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/images/armchair.jpg',       

   // to read from a video
   // sourceType : 'video',
   // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/videos/headtracking.mp4',       
})

arToolkitSource.init(function onReady(){
   onResize()
})

// handle resize
window.addEventListener('resize', function(){
   onResize()
})
function onResize(){
   arToolkitSource.onResizeElement()  
   arToolkitSource.copyElementSizeTo(renderer.domElement) 
   if( arToolkitContext.arController !== null ){
      arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas)    
   }  
}

分析源码发现,此处还有其他识别模式,可以选择从照片或是从视频中识别要素。

6.初始化arToolKitContext

////////////////////////////////////////////////////////////////////////////////
//          initialize arToolkitContext
////////////////////////////////////////////////////////////////////////////////   

// create atToolkitContext
var arToolkitContext = new THREEx.ArToolkitContext({
   cameraParametersUrl: THREEx.ArToolkitContext.baseURL + '../data/data/camera_para.dat',
   // debug: true,
   // detectionMode: 'mono_and_matrix',
   detectionMode: 'mono',
   // detectionMode: 'color_and_matrix',
   // matrixCodeType: '3x3',

   canvasWidth: 80*3,
   canvasHeight: 60*3,

   maxDetectionRate: 30,
})
// initialize it
arToolkitContext.init(function onCompleted(){
   // copy projection matrix to camera
   camera.projectionMatrix.copy( arToolkitContext.getProjectionMatrix() );
})

// update artoolkit on every frame
onRenderFcts.push(function(){
   if( arToolkitSource.ready === false )  return

   arToolkitContext.update( arToolkitSource.domElement )
})

这一步的作用是通过Canvas来联系摄像头与three.js,即在摄像界面上添加画板,以实现在摄像界面中作图的目的。

6.创建识别标记(ArMarker)

////////////////////////////////////////////////////////////////////////////////
//          Create a ArMarkerControls
////////////////////////////////////////////////////////////////////////////////

var markerRoot = new THREE.Group
scene.add(markerRoot)
var markerControls = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, {
   // type: 'barcode',
   // barcodeValue: 5,
   
   type : 'pattern',
   patternUrl : THREEx.ArToolkitContext.baseURL + 'examples/marker-training/examples/pattern-files/pattern-marker.patt',
})


// build a smoothedControls
var smoothedRoot = new THREE.Group()
scene.add(smoothedRoot)
var smoothedControls = new THREEx.ArSmoothedControls(smoothedRoot, {
   lerpPosition: 0.4,
   lerpQuaternion: 0.3,
   lerpScale: 1,
   // minVisibleDelay: 1,
   // minUnvisibleDelay: 1,
})
onRenderFcts.push(function(delta){
   smoothedControls.update(markerRoot)
}) 

// smoothedControls.addEventListener('becameVisible', function(){
//     console.log('becameVisible event notified')
// })
// smoothedControls.addEventListener('becameUnVisible', function(){
//     console.log('becameUnVisible event notified')
// })

此处我们用到的带黑框的图片标记是ArToolKit的第一代标记,之后发展到可以直接识别图片,如果有兴趣,可以到github上专门下载ArtoolKit。

7.将物体添加到场景

//////////////////////////////////////////////////////////////////////////////////
//    add an object in the scene
//////////////////////////////////////////////////////////////////////////////////
var arWorldRoot=smoothedRoot

var mesh=new THREE.AxisHelper();
//markerRoot.add(mesh)
arWorldRoot.add(mesh);

var loadingManager = new THREE.LoadingManager( function() {
    arWorldRoot.add( elf );
} );

var loader = new THREE.ColladaLoader( loadingManager );
loader.load( '../examples/models/collada/elf/elf.dae', function ( collada ) {
    elf = collada.scene;
} );

我们在此修改了添加到场景当中的模型,并添加到场中当中。

8.渲染整个three.js画面

//////////////////////////////////////////////////////////////////////////////////
//    render the whole thing on the page
//////////////////////////////////////////////////////////////////////////////////
var stats = new Stats();
document.body.appendChild( stats.dom );
// render the scene
onRenderFcts.push(function(){
   renderer.render( scene, camera );
   stats.update();
})

// run the rendering loop
var lastTimeMsec= null
requestAnimationFrame(function animate(nowMsec){
   // keep looping
   requestAnimationFrame( animate );
   // measure time
   lastTimeMsec   = lastTimeMsec || nowMsec-1000/60
   var deltaMsec  = Math.min(200, nowMsec - lastTimeMsec)
   lastTimeMsec   = nowMsec
   // call each update function
   onRenderFcts.forEach(function(onRenderFct){
      onRenderFct(deltaMsec/1000, nowMsec/1000)
   })
})

场景的动态效果都通过onRenderFct函数数组来实现。若要实现动画效果,我们要把实现动态的方法添加到数组中。


完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Charactor</title>
    <!-- three.js library -->
    <script src='../examples/vendor/three.js/build/three.js'></script>
    <script src='../examples/vendor/three.js/examples/js/libs/stats.min.js'></script>
    <!-- jsartookit -->
    <script src='../vendor/jsartoolkit5/build/artoolkit.min.js'></script>
    <script src='../vendor/jsartoolkit5/js/artoolkit.api.js'></script>
    <!-- include threex.artoolkit -->
    <script src='../src/threex/threex-artoolkitsource.js'></script>
    <script src='../src/threex/threex-artoolkitcontext.js'></script>
    <script src='../src/threex/threex-artoolkitprofile.js'></script>
    <script src='../src/threex/threex-arbasecontrols.js'></script>
    <script src='../src/threex/threex-armarkercontrols.js'></script>
    <script src='../src/threex/threex-arsmoothedcontrols.js'></script>
    <script>THREEx.ArToolkitContext.baseURL = '../'</script>
</head>
<body>
<script src="../examples/js/loaders/ColladaLoader.js"></script>
<script src="../examples/js/controls/OrbitControls.js"></script>
<script src="../examples/js/Detector.js"></script>
<script src="../examples/js/libs/stats.min.js"></script>
<script>
    //init renderer(初始化渲染器)
    var renderer=new THREE.WebGLRenderer({
        alpha:true
    });
    renderer.setClearColor(new THREE.Color('lightgrey'),0);
    //renderer setPiexRatio(2);
    renderer.setSize(window.innerWidth,window.innerHeight);
    renderer.domElement.style.position='absolute';
    renderer.domElement.style.top='0px';
    renderer.domElement.style.left='0px';
    document.body.appendChild(renderer.domElement);

    //array of  functions for the rendering loop(渲染处理函数组初始化)
    var onRenderFcts=[];

    //init scene and camera
    var scene=new THREE.Scene();//初始化场景和环境

    var ambient=new THREE.AmbientLight(0x666666);
    scene.add(ambient);

    var directctionalLight=new THREE.DirectionalLight(0x887766);
    directctionalLight.position.set(-1,1,1).normalize();
    scene.add(directctionalLight);

    //Initialize a basic camera

    //Create a camera(初始化相机添加到场景)
    var camera=new THREE.Camera();
    scene.add(camera);

    //handle arToolkitSource(调用打开相机事件,由THREEx提供)
    var arToolkitSource=new THREEx.ArToolkitSource({
        //to read from the webcam
        sourceType:'webcam'

        // // to read from an image
        // sourceType : 'image',
        // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/images/img.jpg',
        // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/images/armchair.jpg',

        // to read from a video
        // sourceType : 'video',
        // sourceUrl : THREEx.ArToolkitContext.baseURL + '../data/videos/headtracking.mp4',
    })

    arToolkitSource.init(function onReady() {
        onResize();
    })

    //handle resize(处理重新调整大小后正常显示)
    window.addEventListener('resize',function () {
        onResize();
    })
    function onResize() {
        arToolkitSource.onResizeElement()
        arToolkitSource.copyElementSizeTo(renderer.domElement)
        if(arToolkitContext.arController!==null){
            arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas);
        }
    }

    //初始化 ArcToolkit环境, 相机内部场景
    //initialize arToolkitContext

    //create acToolkitContext
    var arToolkitContext=new THREEx.ArToolkitContext({
        //相机参数设置
        cameraParametersUrl:THREEx.ArToolkitContext.baseURL+'../data/data/camera_para.dat',
        //debug:true,
        //detectionMode:'mono_and_matrix',
        detectionMode:'mono',
//        detectionMode:'color_and_matrix',
//        matrixCodeType:'3x3',
        canvasWidth:80*3,
        canvasHeight:60*3,
        maxDetectionRate:30, //最大旋转角度还是什么滴
    })
    //initialize it
    arToolkitContext.init(function onCompleted() {
        //copy projection matrix to camera
        camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
    });
    //update artoolkit on every frame
    onRenderFcts.push(function () {
        if(arToolkitSource.ready==false) return;

        arToolkitContext.update(arToolkitSource.domElement)
    })

    //Create a ArMakerControls
    //创建一个Ar标记
    var markerRoot=new THREE.Group(); //用threejs的点集合初始化。
    scene.add(markerRoot);

    var markerControls=new THREEx.ArMarkerControls(arToolkitContext,markerRoot,{
        //type:'barcode',
        //barcodeValue:5,
        type:'pattern',
        patternUrl:THREEx.ArToolkitContext.baseURL+'./examples/marker-training/examples/pattern-files/pattern-marker.patt',
    })
    //build a smoothedControls
    var smoothedRoot=new  THREE.Group();
    scene.add(smoothedRoot);
    var smoothedControls=new THREEx.ArSmoothedControls(smoothedRoot,{
        lerpPosition:0.4,
        lerpQuaternion:0.3,
        lerpScale:1,
        //minVisibleDaly:1,
        //minUnvisibleDely:1,
    })
    onRenderFcts.push(function (delta) {
        smoothedControls.update(markerRoot)
    })
    smoothedControls.addEventListener('becameVisible',function () {
        console.log('becameVisible event notified')
    })

    //add Object in the scene
    //添加物体
    var arWorldRoot=smoothedRoot

    var mesh=new THREE.AxisHelper();
    //markerRoot.add(mesh)
    arWorldRoot.add(mesh);

    //add a torus knot创建物体的重要步骤
//    var geometry=new THREE.CubeGeometry(2,2,2)
//    var material=new THREE.MeshNormalMaterial({
//        transparent:true,
//        opacity:0.5,
//        side:THREE.DoubleSide
//    })
//
//    var mesh=new THREE.Mesh(geometry,material);
//    mesh.position.y=geometry.parameters.height/2
//    //markerRoot.add(mesh)
//    arWorldRoot.add(mesh);
//
//    var geometry = new THREE.TorusKnotGeometry(0.3,0.1,64,16);
//    var material=new THREE.MeshNormalMaterial();
//    var mesh=new THREE.Mesh(geometry,material);
//    mesh.position.y=0.5;
//    //markerRoot.add(mesh)
//    arWorldRoot.add(mesh);
//
//    onRenderFcts.push(function (delta){
//        //又加了一个旋转事件
//        mesh.rotation.x+=delta*Math.PI
//    })

    // collada

    var loader = new THREE.ColladaLoader();
    loader.load( '../examples/models/collada/stormtrooper/stormtrooper.dae', function ( collada ) {
        var animations = collada.animations;

        //调整对象状态
        var avatar = collada.scene;
        avatar.rotation.x=Math.PI;
        avatar.rotation.z=Math.PI;
        avatar.scale.set(0.5,0.5,0.5);
        mixer = new THREE.AnimationMixer( avatar );

        arWorldRoot.add( avatar );
        var action = mixer.clipAction( animations[ 0 ] ).play();
        onRenderFcts.push(function () {
            avatar.rotation.z+=0.02*Math.PI;
        })
    } );

    //renderer the Whole thing on the page
    //渲染场景到页面中
    //渲染率查看器
    var stats=new Stats();
    document.body.appendChild(stats.dom);

    //renderer the scene
    onRenderFcts.push(function () {
        renderer.render(scene,camera);
        stats.update();
    })

    //行程渲染事件环路
    //run the rendering loop
    var lastTimeMsec=null;
    requestAnimationFrame(function animate(nowMsec){
        //keep looping
        requestAnimationFrame(animate);
        //measure time
        lastTimeMsec=lastTimeMsec||nowMsec-1000/60;
        var deltaMsec=Math.min(200,nowMsec-lastTimeMsec)
        //call all each update function
        onRenderFcts.forEach(function (onRenderFct) {
            onRenderFct(deltaMsec/1000,nowMsec/1000)
        })
    })
</script>
</body>
</html>
 

猜你喜欢

转载自blog.csdn.net/scaped/article/details/80087290