UNPKG

three

Version:

JavaScript 3D library

344 lines (231 loc) • 9.25 kB
import { Frustum } from '../../math/Frustum.js'; import { FrustumArray } from '../../math/FrustumArray.js'; import { Matrix4 } from '../../math/Matrix4.js'; import { Vector4 } from '../../math/Vector4.js'; import { error } from '../../utils.js'; const _vector4 = /*@__PURE__*/ new Vector4(); /** * Decides which objects of a scene are rendered and how they are submitted to the render list. * The default implementation performs frustum culling and pushes every renderable object as a * separate render item. Subclasses can override the hooks {@link StandardDrawingPipeline#isCulled} and * {@link StandardDrawingPipeline#pushRenderItem} to implement other strategies like GPU-driven rendering. * * This class is experimental and its interface might change. */ class StandardDrawingPipeline { /** * Constructs a new standard drawing pipeline. */ constructor() { /** * The renderer this pipeline is assigned to. It is set when * the pipeline is assigned via {@link Renderer#drawingPipeline}. * * @type {?Renderer} * @default null */ this.renderer = null; /** * The frustum of the current camera. * * @protected * @type {Frustum} */ this._frustum = new Frustum(); /** * The frustum array of the current array camera, kept by the render context. * * @protected * @type {?FrustumArray} * @default null */ this._frustumArray = null; /** * The view projection matrix of the current camera. * * @protected * @type {Matrix4} */ this._projScreenMatrix = new Matrix4(); /** * The number of render bundles being recorded in the current projection. * * @protected * @type {number} * @default 0 */ this._bundleDepth = 0; } /** * Prepares resources needed to precompile the given scene. Subclasses can use this * hook to process geometry before the synchronous projection in `Renderer.compileAsync()`. * * @param {Object3D} scene - The scene or object to precompile. * @param {Camera} camera - The camera. * @return {Promise|undefined} Resolves when the resources are ready. */ prepareAsync( /* scene, camera */ ) {} /** * Frees the resources of the pipeline. Called by {@link Renderer#dispose}, subclasses holding GPU * resources release them here. */ dispose() {} /** * Prepares the pipeline for projecting a scene with the given camera. * * @param {Camera} camera - The camera. * @param {RenderContext} renderContext - The render context. */ begin( camera, renderContext ) { this._projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); if ( camera.isArrayCamera ) { // the frustums of the sub-cameras are kept per render context, the backends cull the draws of each sub-camera with them if ( renderContext.frustumArray === null ) renderContext.frustumArray = new FrustumArray(); renderContext.frustumArray.setFromArrayCamera( camera ); this._frustumArray = renderContext.frustumArray; } else { this._frustum.setFromProjectionMatrix( this._projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); } } /** * Begins the projection into the given render list. * * @param {RenderList} renderList - The render list. */ beginRenderList( renderList ) { renderList.begin(); } /** * Finishes the projection into the given render list. * * @param {RenderList} renderList - The render list. */ finishRenderList( renderList ) { renderList.finish(); } /** * Returns `true` if the given object is culled by the camera and should not be rendered. * Objects of render bundles are not culled, a bundle is recorded once and drawn with * any camera. * * @param {Object3D} object - The 3D object. * @param {Camera} camera - The camera. * @return {boolean} Whether the object is culled or not. */ isCulled( object, camera ) { if ( ! object.frustumCulled || this._bundleDepth > 0 ) return false; const frustum = camera.isArrayCamera ? this._frustumArray : this._frustum; return ! object.intersectsFrustum( frustum ); } /** * Pushes a render item to the render list. * * @param {RenderList} renderList - The current render list. * @param {Object3D} object - The 3D object. * @param {BufferGeometry} geometry - The 3D object's geometry. * @param {Material} material - The 3D object's material. * @param {number} groupOrder - The current group order. * @param {number} z - The 3D object's depth value (z value in clip space). * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. * @param {ClippingContext} clippingContext - The current clipping context. */ pushRenderItem( renderList, object, geometry, material, groupOrder, z, group, clippingContext ) { renderList.push( object, geometry, material, groupOrder, z, group, clippingContext ); } /** * This method is used to traverse the scene graph and collect all renderable objects * and lights into the given render list. Call {@link StandardDrawingPipeline#begin} before. * * @param {Object3D} object - The 3D object to process (usually a scene). * @param {Camera} camera - The camera the object should be rendered with. * @param {number} groupOrder - The group order is derived from the `renderOrder` of groups and is used to group 3D objects within groups. * @param {RenderList} renderList - The current render list. * @param {ClippingContext} clippingContext - The current clipping context. */ project( object, camera, groupOrder, renderList, clippingContext ) { if ( object.visible === false ) return; const renderer = this.renderer; const visible = object.layers.test( camera.layers ); if ( visible ) { if ( object.isGroup ) { groupOrder = object.renderOrder; if ( object.isClippingGroup && object.enabled ) clippingContext = clippingContext.getGroupContext( object ); } else if ( object.isLOD ) { if ( object.autoUpdate === true ) object.update( camera ); } else if ( object.isLight ) { renderList.pushLight( object ); } else if ( object.isSprite ) { if ( this.isCulled( object, camera ) === false ) { if ( renderer.sortObjects === true ) { _vector4.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( this._projScreenMatrix ); } const { geometry, material } = object; if ( material.visible ) { this.pushRenderItem( renderList, object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); } } } else if ( object.isLineLoop ) { error( 'Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' ); } else if ( object.isMesh || object.isLine || object.isPoints ) { if ( this.isCulled( object, camera ) === false ) { const { geometry, material } = object; if ( renderer.sortObjects === true ) { if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _vector4 .copy( geometry.boundingSphere.center ) .applyMatrix4( object.matrixWorld ) .applyMatrix4( this._projScreenMatrix ); } if ( Array.isArray( material ) ) { const groups = geometry.groups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; const groupMaterial = material[ group.materialIndex ]; if ( groupMaterial && groupMaterial.visible ) { this.pushRenderItem( renderList, object, geometry, groupMaterial, groupOrder, _vector4.z, group, clippingContext ); } } } else if ( material.visible ) { this.pushRenderItem( renderList, object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); } } } } if ( object.isBundleGroup === true && renderer.backend.beginBundle !== undefined ) { const baseRenderList = renderList; // replace render list renderList = renderer._renderLists.get( object, camera, renderer.lighting ); const renderBundle = renderer._bundles.get( object, camera, renderer._currentRenderContext ); const renderBundleData = renderer.backend.get( renderBundle ); const renderBundleNeedsUpdate = renderer._bundleNeedsUpdate( object, renderBundleData ); if ( renderBundleNeedsUpdate ) { // update render list if necessary this.beginRenderList( renderList ); if ( renderBundleData.renderObjects === undefined ) { renderBundleData.renderObjects = []; } else { renderBundleData.renderObjects.length = 0; } const children = object.children; this._bundleDepth ++; for ( let i = 0, l = children.length; i < l; i ++ ) { this.project( children[ i ], camera, groupOrder, renderList, clippingContext ); } this._bundleDepth --; this.finishRenderList( renderList ); } baseRenderList.pushBundle( { bundleGroup: object, camera, renderList, } ); return; } // const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { this.project( children[ i ], camera, groupOrder, renderList, clippingContext ); } } } export default StandardDrawingPipeline;