AFrame:如何将相机渲染到纹理
AFrame: How to render a camera to a texture
我正在尝试使用第二台相机来显示 AFrame 场景的 "from the sky" 视图。我已经学会了如何使用 2D canvas 进行渲染,遵循 this example:
但我想知道这是否可以在不使用外部 <div>
的情况下完成,比如直接渲染到某些资产,或者直接渲染到纹理...
我当前的代码是:
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script>
// Original code:
// https://wirewhiz.com/how-to-use-a-cameras-output-as-a-texture-in-aframe/
//
AFRAME.registerComponent('view',{
'schema': {
canvas: {
type: 'string',
default: ''
},
// desired FPS
fps: {
type: 'number',
default: 90.0
}
},
'init': function() {
var targetEl = document.querySelector(this.data.canvas);
this.counter = 0;
this.renderer = new THREE.WebGLRenderer( { antialias: true } );
this.renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.setSize( targetEl.offsetWidth, targetEl.offsetHeight );
// creates spectator canvas
targetEl.appendChild(this.renderer.domElement);
this.renderer.domElement.id = "canvas";
this.renderer.domElement.crossorigin="anonymous"
this.renderer.domElement.height=300;
this.renderer.domElement.width=400;
this.el.removeAttribute('look-controls');
this.el.removeAttribute('wasd-controls');
console.log(this.renderer.domElement);
console.log(document.querySelector('a-scene'))
},
'tick': function(time, timeDelta) {
var loopFPS = 1000.0 / timeDelta;
var hmdIsXFasterThanDesiredFPS = loopFPS / this.data.fps;
var renderEveryNthFrame = Math.round(hmdIsXFasterThanDesiredFPS);
if(this.counter % renderEveryNthFrame === 0){
this.render(timeDelta);
}
this.counter += 1;
},
'render': function(){
this.renderer.render( this.el.sceneEl.object3D , this.el.object3DMap.camera );
}
});
</script>
<body>
<a-scene physics="debug: true">
<a-plane static-body position="0 0 -4" rotation="-90 0 0" width="30" height="40"
color="yellow"></a-plane>
<a-box color="red" position="0 2 0" depth="8" width="8"></a-box>
<a-entity id="secondaryCamera" position="0 40 0" rotation="-90 0 0">
<a-camera view="canvas:#spectatorDiv;" active="false">
</a-camera>
</a-entity>
<a-entity position="0 0 10" look-controls>
<a-entity camera position="0 1.6 0" wasd-controls>
<a-entity geometry="primitive:plane; width:.2; height:.2" material="src:#canvas; opacity: .6"
position="0.2 -0.3 -0.7" rotation="0 -10 0"></a-entity>
<a-cylinder radius="2" color="green"></a-box>
</a-entity>
</a-entity>
</a-scene>
<div style="height:300px; width:400px;" id='spectatorDiv'></div>
</body>
</html>
查看 screenshot component implementation。渲染到渲染目标,然后用像素数据做任何你想做的事(在 WebGL 中将其用作纹理,复制到 canvas...)
经过一番修改并考虑了所有建议后,我找到了这段代码,它给了我想要的东西。简而言之,我构建了一个组件 (camrenderer
),它在 a-assets
元素中使用 canvas 来渲染相机的输出。这允许任何 material 引用它(在下面的代码中,看到它添加到连接到主相机的平面)。为确保 material 在渲染更改时得到更新,您还需要向充当屏幕的对象添加另一个组件 (canvas-updater
)。
因此,相机渲染器可以被任何组件中的任何 material 引用,没有额外的修改。
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script>
AFRAME.registerComponent('camrender',{
'schema': {
// desired FPS
fps: {
type: 'number',
default: 90.0
},
// Id of the canvas element used for rendering the camera
cid: {
type: 'string',
default: 'camRenderer'
},
// Height of the renderer element
height: {
type: 'number',
default: 300
},
// Width of the renderer element
width: {
type: 'number',
default: 400
}
},
'update': function(oldData) {
var data = this.data
if (oldData.cid !== data.cid) {
// Find canvas element to be used for rendering
var canvasEl = document.getElementById(this.data.cid);
// Create renderer
this.renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: canvasEl
});
// Set properties for renderer DOM element
this.renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.domElement.crossorigin = "anonymous";
};
if (oldData.width !== data.width || oldData.height !== data.height) {
// Set size of canvas renderer
this.renderer.setSize(data.width, data.height);
this.renderer.domElement.height = data.height;
this.renderer.domElement.width = data.width;
};
if (oldData.fps !== data.fps) {
// Set how often to call tick
this.tick = AFRAME.utils.throttleTick(this.tick, 1000 / data.fps , this);
};
},
'tick': function(time, timeDelta) {
this.renderer.render( this.el.sceneEl.object3D , this.el.object3DMap.camera );
}
});
AFRAME.registerComponent('canvas-updater', {
dependencies: ['geometry', 'material'],
tick: function () {
var el = this.el;
var material;
material = el.getObject3D('mesh').material;
if (!material.map) { return; }
material.map.needsUpdate = true;
}
});
</script>
</head>
<body>
<a-scene>
<a-assets>
<canvas id="cam2"></canvas>
</a-assets>
<a-plane position="0 0 -4" rotation="-90 0 0" width="30" height="40"
color="yellow"></a-plane>
<a-box color="red" position="0 2 0" depth="8" width="16"></a-box>
<a-box color="blue" position="0 2 6" depth="2" width="6"></a-box>
<a-entity position="0 40 0" rotation="-90 0 0">
<a-camera camrender="cid: cam2" active="false">
</a-camera>
</a-entity>
<a-entity position="0 0 10" look-controls>
<a-entity camera position="0 1.6 0" wasd-controls>
<a-entity geometry="primitive:plane; width:.2; height:.2"
material="src:#cam2; opacity: .6" canvas-updater
position="0.2 -0.3 -0.7" rotation="0 -10 0"></a-entity>
<a-cylinder radius="2" color="green"></a-cylinder>
</a-entity>
</a-entity>
</a-scene>
</body>
</html>
备注:
感谢 Diego Marcos 让我走上正轨。
感谢Piotr Adam Milewski who suggested using Utils.ThrottleTick,大大简化了代码
此版本包含对 problem found with current master 的修复(但也适用于 0.8.2
)
我用以下组件创建了一个 npm 包:A-Frame Playground Components。所以你可以直接使用它来代替上面的脚本。
我正在尝试使用第二台相机来显示 AFrame 场景的 "from the sky" 视图。我已经学会了如何使用 2D canvas 进行渲染,遵循 this example:
但我想知道这是否可以在不使用外部 <div>
的情况下完成,比如直接渲染到某些资产,或者直接渲染到纹理...
我当前的代码是:
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script>
// Original code:
// https://wirewhiz.com/how-to-use-a-cameras-output-as-a-texture-in-aframe/
//
AFRAME.registerComponent('view',{
'schema': {
canvas: {
type: 'string',
default: ''
},
// desired FPS
fps: {
type: 'number',
default: 90.0
}
},
'init': function() {
var targetEl = document.querySelector(this.data.canvas);
this.counter = 0;
this.renderer = new THREE.WebGLRenderer( { antialias: true } );
this.renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.setSize( targetEl.offsetWidth, targetEl.offsetHeight );
// creates spectator canvas
targetEl.appendChild(this.renderer.domElement);
this.renderer.domElement.id = "canvas";
this.renderer.domElement.crossorigin="anonymous"
this.renderer.domElement.height=300;
this.renderer.domElement.width=400;
this.el.removeAttribute('look-controls');
this.el.removeAttribute('wasd-controls');
console.log(this.renderer.domElement);
console.log(document.querySelector('a-scene'))
},
'tick': function(time, timeDelta) {
var loopFPS = 1000.0 / timeDelta;
var hmdIsXFasterThanDesiredFPS = loopFPS / this.data.fps;
var renderEveryNthFrame = Math.round(hmdIsXFasterThanDesiredFPS);
if(this.counter % renderEveryNthFrame === 0){
this.render(timeDelta);
}
this.counter += 1;
},
'render': function(){
this.renderer.render( this.el.sceneEl.object3D , this.el.object3DMap.camera );
}
});
</script>
<body>
<a-scene physics="debug: true">
<a-plane static-body position="0 0 -4" rotation="-90 0 0" width="30" height="40"
color="yellow"></a-plane>
<a-box color="red" position="0 2 0" depth="8" width="8"></a-box>
<a-entity id="secondaryCamera" position="0 40 0" rotation="-90 0 0">
<a-camera view="canvas:#spectatorDiv;" active="false">
</a-camera>
</a-entity>
<a-entity position="0 0 10" look-controls>
<a-entity camera position="0 1.6 0" wasd-controls>
<a-entity geometry="primitive:plane; width:.2; height:.2" material="src:#canvas; opacity: .6"
position="0.2 -0.3 -0.7" rotation="0 -10 0"></a-entity>
<a-cylinder radius="2" color="green"></a-box>
</a-entity>
</a-entity>
</a-scene>
<div style="height:300px; width:400px;" id='spectatorDiv'></div>
</body>
</html>
查看 screenshot component implementation。渲染到渲染目标,然后用像素数据做任何你想做的事(在 WebGL 中将其用作纹理,复制到 canvas...)
经过一番修改并考虑了所有建议后,我找到了这段代码,它给了我想要的东西。简而言之,我构建了一个组件 (camrenderer
),它在 a-assets
元素中使用 canvas 来渲染相机的输出。这允许任何 material 引用它(在下面的代码中,看到它添加到连接到主相机的平面)。为确保 material 在渲染更改时得到更新,您还需要向充当屏幕的对象添加另一个组件 (canvas-updater
)。
因此,相机渲染器可以被任何组件中的任何 material 引用,没有额外的修改。
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script>
AFRAME.registerComponent('camrender',{
'schema': {
// desired FPS
fps: {
type: 'number',
default: 90.0
},
// Id of the canvas element used for rendering the camera
cid: {
type: 'string',
default: 'camRenderer'
},
// Height of the renderer element
height: {
type: 'number',
default: 300
},
// Width of the renderer element
width: {
type: 'number',
default: 400
}
},
'update': function(oldData) {
var data = this.data
if (oldData.cid !== data.cid) {
// Find canvas element to be used for rendering
var canvasEl = document.getElementById(this.data.cid);
// Create renderer
this.renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: canvasEl
});
// Set properties for renderer DOM element
this.renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.domElement.crossorigin = "anonymous";
};
if (oldData.width !== data.width || oldData.height !== data.height) {
// Set size of canvas renderer
this.renderer.setSize(data.width, data.height);
this.renderer.domElement.height = data.height;
this.renderer.domElement.width = data.width;
};
if (oldData.fps !== data.fps) {
// Set how often to call tick
this.tick = AFRAME.utils.throttleTick(this.tick, 1000 / data.fps , this);
};
},
'tick': function(time, timeDelta) {
this.renderer.render( this.el.sceneEl.object3D , this.el.object3DMap.camera );
}
});
AFRAME.registerComponent('canvas-updater', {
dependencies: ['geometry', 'material'],
tick: function () {
var el = this.el;
var material;
material = el.getObject3D('mesh').material;
if (!material.map) { return; }
material.map.needsUpdate = true;
}
});
</script>
</head>
<body>
<a-scene>
<a-assets>
<canvas id="cam2"></canvas>
</a-assets>
<a-plane position="0 0 -4" rotation="-90 0 0" width="30" height="40"
color="yellow"></a-plane>
<a-box color="red" position="0 2 0" depth="8" width="16"></a-box>
<a-box color="blue" position="0 2 6" depth="2" width="6"></a-box>
<a-entity position="0 40 0" rotation="-90 0 0">
<a-camera camrender="cid: cam2" active="false">
</a-camera>
</a-entity>
<a-entity position="0 0 10" look-controls>
<a-entity camera position="0 1.6 0" wasd-controls>
<a-entity geometry="primitive:plane; width:.2; height:.2"
material="src:#cam2; opacity: .6" canvas-updater
position="0.2 -0.3 -0.7" rotation="0 -10 0"></a-entity>
<a-cylinder radius="2" color="green"></a-cylinder>
</a-entity>
</a-entity>
</a-scene>
</body>
</html>
备注:
感谢 Diego Marcos 让我走上正轨。
感谢Piotr Adam Milewski who suggested using Utils.ThrottleTick,大大简化了代码
此版本包含对 problem found with current master 的修复(但也适用于
0.8.2
)我用以下组件创建了一个 npm 包:A-Frame Playground Components。所以你可以直接使用它来代替上面的脚本。