Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified examples/screenshots/webgpu_camera_array.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 45 additions & 33 deletions examples/webgpu_camera_array.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>

<script type="importmap">
{
"imports": {
Expand All @@ -22,51 +23,43 @@

import * as THREE from 'three';

import Stats from 'three/addons/libs/stats.module.js';

let camera, scene, renderer;
let mesh;
let stats;

const AMOUNT = 6;

init();

function init() {

const ASPECT_RATIO = window.innerWidth / window.innerHeight;

const WIDTH = ( window.innerWidth / AMOUNT );
const HEIGHT = ( window.innerHeight / AMOUNT );
const subCameras = [];

const cameras = [];
for ( let i = 0; i < AMOUNT * AMOUNT; i ++ ) {

for ( let y = 0; y < AMOUNT; y ++ ) {
const subCamera = new THREE.PerspectiveCamera( 40, 1, 0.1, 10 );
subCamera.viewport = new THREE.Vector4();

for ( let x = 0; x < AMOUNT; x ++ ) {

const subcamera = new THREE.PerspectiveCamera( 40, ASPECT_RATIO, 0.1, 10 );
subcamera.viewport = new THREE.Vector4( Math.floor( x * WIDTH ), Math.floor( y * HEIGHT ), Math.ceil( WIDTH ), Math.ceil( HEIGHT ) );
subcamera.position.x = ( x / AMOUNT ) - 0.5;
subcamera.position.y = 0.5 - ( y / AMOUNT );
subcamera.position.z = 1.5;
subcamera.position.multiplyScalar( 2 );
subcamera.lookAt( 0, 0, 0 );
subcamera.updateMatrixWorld();
cameras.push( subcamera );

}
subCameras.push( subCamera );

}

camera = new THREE.ArrayCamera( cameras );
camera = new THREE.ArrayCamera( subCameras );
camera.position.z = 3;

updateCameras();

scene = new THREE.Scene();

scene.add( new THREE.AmbientLight( 0x999999 ) );

const light = new THREE.DirectionalLight( 0xffffff, 3 );
light.position.set( 0.5, 0.5, 1 );
light.castShadow = true;
light.shadow.camera.zoom = 4; // tighter shadow map
light.shadow.bias = - 0.001;
light.shadow.camera.zoom = 4; // tighter shadow map
scene.add( light );

const geometryBackground = new THREE.PlaneGeometry( 100, 100 );
Expand All @@ -77,15 +70,15 @@
background.position.set( 0, 0, - 1 );
scene.add( background );

const geometryCylinder = new THREE.BoxGeometry();
const geometryCylinder = new THREE.CylinderGeometry( 0.5, 0.5, 1, 32 );
const materialCylinder = new THREE.MeshPhongMaterial( { color: 0xff0000 } );

mesh = new THREE.Mesh( geometryCylinder, materialCylinder );
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add( mesh );

renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer = new THREE.WebGPURenderer( /*{ forceWebGL: true }*/ );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
Expand All @@ -96,13 +89,18 @@

window.addEventListener( 'resize', onWindowResize );

//

stats = new Stats();
document.body.appendChild( stats.dom );

}

function onWindowResize() {
function updateCameras() {

const ASPECT_RATIO = window.innerWidth / window.innerHeight;
const WIDTH = ( window.innerWidth / AMOUNT );
const HEIGHT = ( window.innerHeight / AMOUNT );
const WIDTH = window.innerWidth / AMOUNT;
const HEIGHT = window.innerHeight / AMOUNT;

camera.aspect = ASPECT_RATIO;
camera.updateProjectionMatrix();
Expand All @@ -112,28 +110,42 @@
for ( let x = 0; x < AMOUNT; x ++ ) {

const subcamera = camera.cameras[ AMOUNT * y + x ];
subcamera.copy( camera ); // copy fov, aspect ratio, near, far from the root camera

subcamera.viewport.set(
Math.floor( x * WIDTH ),
Math.floor( y * HEIGHT ),
Math.ceil( WIDTH ),
Math.ceil( HEIGHT ) );

subcamera.aspect = ASPECT_RATIO;
subcamera.viewport.set( Math.floor( x * WIDTH ), Math.floor( y * HEIGHT ), Math.ceil( WIDTH ), Math.ceil( HEIGHT ) );
subcamera.updateProjectionMatrix();

subcamera.position.x = ( x / AMOUNT ) - 0.5;
subcamera.position.y = 0.5 - ( y / AMOUNT );
subcamera.position.z = 1.5 + ( ( x + y ) * .5 );
subcamera.position.multiplyScalar( 2 );

subcamera.lookAt( 0, 0, 0 );
subcamera.updateMatrixWorld();

}

}

}

function onWindowResize() {

updateCameras();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

mesh.rotation.x += 0.005;
mesh.rotation.z += 0.01;

renderer.render( scene, camera );

stats.update();

}

</script>
Expand Down
1 change: 1 addition & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const bypass = TSL.bypass;
export const cache = TSL.cache;
export const call = TSL.call;
export const cameraFar = TSL.cameraFar;
export const cameraIndex = TSL.cameraIndex;
export const cameraNear = TSL.cameraNear;
export const cameraNormalMatrix = TSL.cameraNormalMatrix;
export const cameraPosition = TSL.cameraPosition;
Expand Down
1 change: 1 addition & 0 deletions src/cameras/ArrayCamera.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class ArrayCamera extends PerspectiveCamera {
this.isArrayCamera = true;

this.cameras = array;
this.index = 0;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this addition required? It seems there is no read/write access to this property.


}

Expand Down
67 changes: 64 additions & 3 deletions src/nodes/accessors/Camera.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { uniform } from '../core/UniformNode.js';
import { renderGroup } from '../core/UniformGroupNode.js';
import { renderGroup, sharedUniformGroup } from '../core/UniformGroupNode.js';
import { Vector3 } from '../../math/Vector3.js';
import { Fn } from '../tsl/TSLBase.js';
import { uniformArray } from './UniformArrayNode.js';

/** @module Camera **/

/**
* TSL object that represents the current `index` value of the camera if used ArrayCamera.
*
* @type {UniformNode<uint>}
*/
export const cameraIndex = /*@__PURE__*/ uniform( 'uint' ).setGroup( sharedUniformGroup( 'cameraIndex' ) ).vertexStage();

/**
* TSL object that represents the `near` value of the camera used for the current render.
*
Expand All @@ -23,7 +32,33 @@ export const cameraFar = /*@__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).s
*
* @type {UniformNode<mat4>}
*/
export const cameraProjectionMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix );
export const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {

let cameraProjectionMatrix;

if ( camera.isArrayCamera ) {

const matrices = [];

for ( const subCamera of camera.cameras ) {

matrices.push( subCamera.projectionMatrix );

}

const cameraProjectionMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatrices' );

cameraProjectionMatrix = cameraProjectionMatrices.element( cameraIndex ).toVar( 'cameraProjectionMatrix' );

} else {

cameraProjectionMatrix = uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix );

}

return cameraProjectionMatrix;

} ).once() )();

/**
* TSL object that represents the inverse projection matrix of the camera used for the current render.
Expand All @@ -37,7 +72,33 @@ export const cameraProjectionMatrixInverse = /*@__PURE__*/ uniform( 'mat4' ).lab
*
* @type {UniformNode<mat4>}
*/
export const cameraViewMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse );
export const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
Comment thread
sunag marked this conversation as resolved.

let cameraViewMatrix;

if ( camera.isArrayCamera ) {

const matrices = [];

for ( const subCamera of camera.cameras ) {

matrices.push( subCamera.matrixWorldInverse );

}

const cameraViewMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraViewMatrices' );

cameraViewMatrix = cameraViewMatrices.element( cameraIndex ).toVar( 'cameraViewMatrix' );

} else {

cameraViewMatrix = uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse );

}

return cameraViewMatrix;

} ).once() )();

/**
* TSL object that represents the world matrix of the camera used for the current render.
Expand Down
20 changes: 20 additions & 0 deletions src/renderers/common/RenderObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,26 @@ class RenderObject {

}

/**
* Returns a binding group by group name of this render object.
*
* @param {String} name - The name of the binding group.
* @return {BindGroup?} The bindings.
*/
getBindingGroup( name ) {

for ( const bindingGroup of this.getBindings() ) {

if ( bindingGroup.name === name ) {

return bindingGroup;

}

}

}

/**
* Returns the index of the render object's geometry.
*
Expand Down
52 changes: 13 additions & 39 deletions src/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1229,9 +1229,19 @@ class Renderer {
if ( camera.coordinateSystem !== coordinateSystem ) {

camera.coordinateSystem = coordinateSystem;

camera.updateProjectionMatrix();

if ( camera.isArrayCamera ) {

for ( const subCamera of camera.cameras ) {

subCamera.coordinateSystem = coordinateSystem;
subCamera.updateProjectionMatrix();

}

}

}

//
Expand Down Expand Up @@ -2593,47 +2603,11 @@ class Renderer {
*/
_renderObjects( renderList, camera, scene, lightsNode, passId = null ) {

// process renderable objects

for ( let i = 0, il = renderList.length; i < il; i ++ ) {

const renderItem = renderList[ i ];

const { object, geometry, material, group, clippingContext } = renderItem;

if ( camera.isArrayCamera ) {

const cameras = camera.cameras;

for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
const { object, geometry, material, group, clippingContext } = renderList[ i ];

const camera2 = cameras[ j ];

if ( object.layers.test( camera2.layers ) ) {

const vp = camera2.viewport;
const minDepth = ( vp.minDepth === undefined ) ? 0 : vp.minDepth;
const maxDepth = ( vp.maxDepth === undefined ) ? 1 : vp.maxDepth;

const viewportValue = this._currentRenderContext.viewportValue;
viewportValue.copy( vp ).multiplyScalar( this._pixelRatio ).floor();
viewportValue.minDepth = minDepth;
viewportValue.maxDepth = maxDepth;
this._currentRenderContext.viewport = true;

this.backend.updateViewport( this._currentRenderContext );

this._currentRenderObjectFunction( object, scene, camera2, geometry, material, group, lightsNode, clippingContext, passId );

}

}

} else {

this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId );

}
this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId );

}

Expand Down
Loading