1. 程式人生 > >增強現實之開源AR庫——AR.js

增強現實之開源AR庫——AR.js

AR.js是一個web端的AR庫,它完全開源免費,獲得了很高的熱度。我們要實現的效果如下:

首先去github下載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.將物體新增到場景

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建立物體
    // 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>

原始碼檔案:

密碼:t6vp

Marker圖片: