|
|
@@ -39,6 +39,7 @@ import { WebGLIndexedBufferRenderer } from './webgl/WebGLIndexedBufferRenderer.j
|
|
|
import { WebGLInfo } from './webgl/WebGLInfo.js';
|
|
|
import { WebGLMorphtargets } from './webgl/WebGLMorphtargets.js';
|
|
|
import { WebGLObjects } from './webgl/WebGLObjects.js';
|
|
|
+import { WebGLOutput } from './webgl/WebGLOutput.js';
|
|
|
import { WebGLPrograms } from './webgl/WebGLPrograms.js';
|
|
|
import { WebGLProperties } from './webgl/WebGLProperties.js';
|
|
|
import { WebGLRenderLists } from './webgl/WebGLRenderLists.js';
|
|
|
@@ -82,6 +83,7 @@ class WebGLRenderer {
|
|
|
powerPreference = 'default',
|
|
|
failIfMajorPerformanceCaveat = false,
|
|
|
reversedDepthBuffer = false,
|
|
|
+ outputBufferType = UnsignedByteType,
|
|
|
} = parameters;
|
|
|
|
|
|
/**
|
|
|
@@ -111,6 +113,8 @@ class WebGLRenderer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ const _outputBufferType = outputBufferType;
|
|
|
+
|
|
|
const INTEGER_FORMATS = new Set( [
|
|
|
RGBAIntegerFormat,
|
|
|
RGIntegerFormat,
|
|
|
@@ -138,6 +142,10 @@ class WebGLRenderer {
|
|
|
const renderListStack = [];
|
|
|
const renderStateStack = [];
|
|
|
|
|
|
+ // internal render target for non-UnsignedByteType color buffer
|
|
|
+
|
|
|
+ let output = null;
|
|
|
+
|
|
|
// public properties
|
|
|
|
|
|
/**
|
|
|
@@ -533,6 +541,14 @@ class WebGLRenderer {
|
|
|
|
|
|
initGLContext();
|
|
|
|
|
|
+ // initialize internal render target for non-UnsignedByteType color buffer
|
|
|
+
|
|
|
+ if ( _outputBufferType !== UnsignedByteType ) {
|
|
|
+
|
|
|
+ output = new WebGLOutput( _outputBufferType, canvas.width, canvas.height, depth, stencil );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
// xr
|
|
|
|
|
|
const xr = new WebXRManager( _this, _gl );
|
|
|
@@ -655,6 +671,12 @@ class WebGLRenderer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ if ( output !== null ) {
|
|
|
+
|
|
|
+ output.setSize( canvas.width, canvas.height );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
this.setViewport( 0, 0, width, height );
|
|
|
|
|
|
};
|
|
|
@@ -698,6 +720,39 @@ class WebGLRenderer {
|
|
|
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Sets the post-processing effects to be applied after rendering.
|
|
|
+ *
|
|
|
+ * @param {Array} effects - An array of post-processing effects.
|
|
|
+ */
|
|
|
+ this.setEffects = function ( effects ) {
|
|
|
+
|
|
|
+ if ( _outputBufferType === UnsignedByteType ) {
|
|
|
+
|
|
|
+ console.error( 'THREE.WebGLRenderer: setEffects() requires outputBufferType set to HalfFloatType or FloatType.' );
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( effects ) {
|
|
|
+
|
|
|
+ for ( let i = 0; i < effects.length; i ++ ) {
|
|
|
+
|
|
|
+ if ( effects[ i ].isOutputPass === true ) {
|
|
|
+
|
|
|
+ console.warn( 'THREE.WebGLRenderer: OutputPass is not needed in setEffects(). Tone mapping and color space conversion are applied automatically.' );
|
|
|
+ break;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ output.setEffects( effects || [] );
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
/**
|
|
|
* Returns the current viewport definition.
|
|
|
*
|
|
|
@@ -1547,6 +1602,12 @@ class WebGLRenderer {
|
|
|
|
|
|
if ( _isContextLost === true ) return;
|
|
|
|
|
|
+ // use internal render target for HalfFloatType color buffer (only when tone mapping is enabled)
|
|
|
+
|
|
|
+ const isXRPresenting = xr.enabled === true && xr.isPresenting === true;
|
|
|
+
|
|
|
+ const useOutput = output !== null && ( _currentRenderTarget === null || isXRPresenting ) && output.begin( _this, _currentRenderTarget );
|
|
|
+
|
|
|
// update scene graph
|
|
|
|
|
|
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
|
|
@@ -1555,7 +1616,7 @@ class WebGLRenderer {
|
|
|
|
|
|
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
|
|
|
|
|
- if ( xr.enabled === true && xr.isPresenting === true ) {
|
|
|
+ if ( xr.enabled === true && xr.isPresenting === true && ( output === null || output.isCompositing() === false ) ) {
|
|
|
|
|
|
if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera );
|
|
|
|
|
|
@@ -1627,46 +1688,52 @@ class WebGLRenderer {
|
|
|
|
|
|
if ( this.info.autoReset === true ) this.info.reset();
|
|
|
|
|
|
- // render scene
|
|
|
+ // render scene (skip if first effect is a render pass - it will render the scene itself)
|
|
|
|
|
|
- const opaqueObjects = currentRenderList.opaque;
|
|
|
- const transmissiveObjects = currentRenderList.transmissive;
|
|
|
+ const skipSceneRender = useOutput && output.hasRenderPass();
|
|
|
|
|
|
- currentRenderState.setupLights();
|
|
|
+ if ( skipSceneRender === false ) {
|
|
|
|
|
|
- if ( camera.isArrayCamera ) {
|
|
|
+ const opaqueObjects = currentRenderList.opaque;
|
|
|
+ const transmissiveObjects = currentRenderList.transmissive;
|
|
|
|
|
|
- const cameras = camera.cameras;
|
|
|
+ currentRenderState.setupLights();
|
|
|
|
|
|
- if ( transmissiveObjects.length > 0 ) {
|
|
|
+ if ( camera.isArrayCamera ) {
|
|
|
|
|
|
- for ( let i = 0, l = cameras.length; i < l; i ++ ) {
|
|
|
+ const cameras = camera.cameras;
|
|
|
|
|
|
- const camera2 = cameras[ i ];
|
|
|
+ if ( transmissiveObjects.length > 0 ) {
|
|
|
+
|
|
|
+ for ( let i = 0, l = cameras.length; i < l; i ++ ) {
|
|
|
+
|
|
|
+ const camera2 = cameras[ i ];
|
|
|
+
|
|
|
+ renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 );
|
|
|
|
|
|
- renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 );
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- }
|
|
|
+ if ( _renderBackground ) background.render( scene );
|
|
|
|
|
|
- if ( _renderBackground ) background.render( scene );
|
|
|
+ for ( let i = 0, l = cameras.length; i < l; i ++ ) {
|
|
|
|
|
|
- for ( let i = 0, l = cameras.length; i < l; i ++ ) {
|
|
|
+ const camera2 = cameras[ i ];
|
|
|
|
|
|
- const camera2 = cameras[ i ];
|
|
|
+ renderScene( currentRenderList, scene, camera2, camera2.viewport );
|
|
|
|
|
|
- renderScene( currentRenderList, scene, camera2, camera2.viewport );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ } else {
|
|
|
|
|
|
- } else {
|
|
|
+ if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera );
|
|
|
|
|
|
- if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera );
|
|
|
+ if ( _renderBackground ) background.render( scene );
|
|
|
|
|
|
- if ( _renderBackground ) background.render( scene );
|
|
|
+ renderScene( currentRenderList, scene, camera );
|
|
|
|
|
|
- renderScene( currentRenderList, scene, camera );
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -1684,6 +1751,14 @@ class WebGLRenderer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // copy from internal render target to canvas using fullscreen quad
|
|
|
+
|
|
|
+ if ( useOutput ) {
|
|
|
+
|
|
|
+ output.end( _this );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
//
|
|
|
|
|
|
if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
|
|
|
@@ -1872,9 +1947,11 @@ class WebGLRenderer {
|
|
|
|
|
|
if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) {
|
|
|
|
|
|
+ const hasHalfFloatSupport = extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' );
|
|
|
+
|
|
|
currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, {
|
|
|
generateMipmaps: true,
|
|
|
- type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType,
|
|
|
+ type: hasHalfFloatSupport ? HalfFloatType : UnsignedByteType,
|
|
|
minFilter: LinearMipmapLinearFilter,
|
|
|
samples: capabilities.samples,
|
|
|
stencilBuffer: stencil,
|
|
|
@@ -2722,7 +2799,6 @@ class WebGLRenderer {
|
|
|
_currentActiveCubeFace = activeCubeFace;
|
|
|
_currentActiveMipmapLevel = activeMipmapLevel;
|
|
|
|
|
|
- let useDefaultFramebuffer = true;
|
|
|
let framebuffer = null;
|
|
|
let isCube = false;
|
|
|
let isRenderTarget3D = false;
|
|
|
@@ -2733,9 +2809,21 @@ class WebGLRenderer {
|
|
|
|
|
|
if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) {
|
|
|
|
|
|
- // We need to make sure to rebind the framebuffer.
|
|
|
- state.bindFramebuffer( _gl.FRAMEBUFFER, null );
|
|
|
- useDefaultFramebuffer = false;
|
|
|
+ // Externally-managed framebuffer (e.g. XR)
|
|
|
+ // Bind to the stored framebuffer (may be null for default, or a WebGLFramebuffer)
|
|
|
+ state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );
|
|
|
+
|
|
|
+ _currentViewport.copy( renderTarget.viewport );
|
|
|
+ _currentScissor.copy( renderTarget.scissor );
|
|
|
+ _currentScissorTest = renderTarget.scissorTest;
|
|
|
+
|
|
|
+ state.viewport( _currentViewport );
|
|
|
+ state.scissor( _currentScissor );
|
|
|
+ state.setScissorTest( _currentScissorTest );
|
|
|
+
|
|
|
+ _currentMaterialId = - 1;
|
|
|
+
|
|
|
+ return;
|
|
|
|
|
|
} else if ( renderTargetProperties.__webglFramebuffer === undefined ) {
|
|
|
|
|
|
@@ -2834,7 +2922,7 @@ class WebGLRenderer {
|
|
|
|
|
|
const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
|
|
|
|
|
|
- if ( framebufferBound && useDefaultFramebuffer ) {
|
|
|
+ if ( framebufferBound ) {
|
|
|
|
|
|
state.drawBuffers( renderTarget, framebuffer );
|
|
|
|
|
|
@@ -3469,6 +3557,7 @@ class WebGLRenderer {
|
|
|
* Note that this setting uses `gl_FragDepth` if available which disables the Early Fragment Test optimization and can cause a decrease in performance.
|
|
|
* @property {boolean} [reversedDepthBuffer=false] Whether to use a reverse depth buffer. Requires the `EXT_clip_control` extension.
|
|
|
* This is a more faster and accurate version than logarithmic depth buffer.
|
|
|
+ * @property {number} [outputBufferType=UnsignedByteType] Defines the type of the output buffer. Use `HalfFloatType` for HDR rendering with tone mapping and post-processing support.
|
|
|
**/
|
|
|
|
|
|
/**
|