增強現實之開源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圖片: