XR
navigator.xr
지원여부 검토
if( navigator.xr && XRSession.prototype.requestHitTest ) {
// XRDevice 얻기
} else {
// 미지원 브라우저
}
XRDevice
// XRDevice 얻기
this.device = await navigator.xr.requestDevice();
// canvas 생성하고, 출력을 위한 xr 컨텍스트를 얻는다.
var outputCanvas = document.createElement('canvas');
var ctx = outputCanvas.getContext('xrpresent');
XRSession
// XRSession 얻기
const session = await this.device.requestSession( {
environmentIntegration: true,
outputContext: ctx
});
// 세션이 정상적으로 생성되었으면 캔버스를 body에 추가한다.
document.body.appendChild(outputCanvas);
this.session = session;
THREE web gl 설정
// WebGLRenderer 생성
this.renderer = new THREE.WebGLRenderer( {
alpha: true,
preserveDrawingBuffer: true,
});
this.renderer.autoClear = false;
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 렌더러의 컨텍스트 정보 저장
this.gl = this.renderer.getContext();
await this.gl.setCompatibleXRDevice( this.session.device );
// 세션의 기본 레이어를 생성한다.
session.baseLayer = new XRWebGLLayer( this.session, this.gl );
// 바인딩된 프레임버퍼가 변경되는 증상이 있어, 가끔 모델들이 사라져버림
// three.js 오브젝트 렌더전에 프레임 버퍼를 다시 바인딩 하도록 설정
//
THREE.Object3D.prototype.onBeforeRender = ()=> {
this.gl.bindFramebuffer( this.gl.FRAMEBUFFER, this.session.baseLayer.framebuffer );
});
// Scene 구성, 모델 로드 등 필요한 처리
this.clock = new THREE.Clock();
this.objectArray = [];
this.updateFunctionArray=[];
this.scene = new THREE.Scene();
// light
const light = new THREE.AmbientLight(0xffffff, 1 );
const directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( 10, 15, 10 );
this.scene.add( light );
this.scene.add( directionalLight );
// fbx 로더
var loader = new THREE.FBXLoader();
loader.load( 'test.fbx', object => {
if( object.animations != null ) {
if( object.animations.length > 0 ) {
object.mixer = new THREE.AnimationMixer( object );
var action = object.mixer.clipAction( object.animations[0]);
action.play();
}
}
object.traverse( child => {
if( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
// texture
var textureLoader = new THREE.TextureLoader();
// custom texture
var mat = new THREE.MeshStandardMaterial( {
roughness: 0.0,
metalness: 0.0,
map: textureLoader.load( "url" ),
skinning: true,
side : THREE.DoubleSide
});
child.material = mat;
}
});
this.objectArray.push(object);
}, ()=>{}, ()=>{} );
// 애니메이션 믹서를 업데이트하기 위해 업데이트 함수를 등록해 준다.
this. updateFunctionArray.push( ( delta ) => {
if( this.objectArray.length > 0 ) {
for( var i=0; i<this.objectArray.length; i++ ) {
let mixer = this.objectArray[i];
if( mixer ) {
mixer.update( delta );
}
}
}
});
// 카메라
this.camera = new THREE.PerspectiveCamera();
this.camera.matrixAutoUpdate = false;
// 기준이 될 프레임 정보
this.frameOfRef = await this.session.requestFrameOfReference('eye-level');
// 매 프레임 호출할 콜백 함수를 지정한다.
this.session.requestAnimationFrame( this.onXRFrame );
// click
window.addEventListener( 'click', this.onClick );
Frame Callback
onXRFrame( time, frame ) {
let session = frame.session;
let pose = frame.getDevicePose( this.frameOfRef );
// 프레임버퍼를 세션의 프레임버퍼로 연결한다.
this.gl.bindFramebuffer( this.gl.FRAMEBUFFER, this.session.baseLayer.framebuffer);
// 등록한 업데이트 함수들을 호출
var delta = this.clock.getDelta();
this. updateFunctionArray.forEach( ( updateFunction ) => {
updateFunction(delta);
});
if( pose ) {
// frame의 뷰들을 가져와 처리한다.
// vr 의 경우 좌우 스테레오이기에 2개의 뷰가 있고, ar은 1개의 뷰를 가진다.
for( let view of frame.views ) {
// 뷰포트에 맞게 렌더러 사이즈 설정
const viewport = session.baseLayer.getViewport( view );
this.renderer.setSize(viewport.width, viewport.height);
// 뷰의 프로젝션 행렬을 카메라에 맞춤
this.camera.projectionMatrix.fromArray(view.projectionMatrix);
// 포즈에 대한 뷰 행렬 얻기
const viewMatrix = new THREE.Matrix4().fromArray( pose.getViewMatrix(view));
// 뷰행렬 적용
this.camera.matrix.getInverse(viewMatrix);
this.camera.updateMatrixWorld(true);
// 테스트 중 아래 루틴 추가 시 카메라 영상이 표시되지 않는 문제 발생됨.
// this.renderer.clearDepth();
// 렌더링
this.renderer.render( this.scene, this.camera );
}
}
// 반복
session.requestAnimationFrame( this.onXRFrame );
}
클릭 이벤트
async onClick( e ) {
// 아래와 같이 object3d 객체를 가져와 원하는 처리를 추가
let model = this.objectArray[0];
const x = 0;
const y = 0;
this.raycaster = this.raycaster || new THREE.Raycaster();
this.raycaster.setFromCamera( , this.camera);
const ray = this.raycaster.ray;
const origin = new Float32Array(ray.origin.toArray());
const direction = new Float32Array(ray.direction.toArray());
const hits = await this.session.requestHitTest( origin, direction, this.frameOfRef);
if( hits.length ) {
const hit = hits[0];
const hitMatrix = new THREE.Matrix4().fromArray(hit.hitMatrix);
model.position.setFromMatrixPosition(hitMatrix);
DemoUtils.lookAtOnY( model, this.camera);
const shadowMesh = this.scene.children.find(c => c.name === 'shadowMesh');
shadowMesh.position.y = model.position.y;
this.scene.add(model);
}
}
'프로그래밍 > Web' 카테고리의 다른 글
[tauri] 테스트용 앱 기본 구성 (0) | 2024.05.03 |
---|---|
리액트 주요 요소 (0) | 2024.04.24 |
[tauri] 개발환경 (0) | 2024.03.19 |
[JavaScript] Promise, async/await (0) | 2018.08.17 |
javascript 모듈화 패턴 (0) | 2018.07.26 |
VSCode 웹 디버깅 (0) | 2018.07.19 |
openssl (0) | 2018.07.17 |
Windows 레드마인 설치(Bitnami) (1) | 2017.09.22 |
Gradle Wrapper (0) | 2017.03.06 |
스프링 요청/응답 (0) | 2017.02.26 |