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>