From b2ea47ac7a575032af050b43afb8f57478efc8b6 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Sun, 19 Sep 2021 16:18:39 -0700 Subject: [PATCH 1/8] Clean up WebXR manager by calling setRenderTarget --- src/renderers/WebGLMultisampleRenderTarget.js | 6 +- src/renderers/WebGLRenderTarget.js | 4 + src/renderers/WebGLRenderer.js | 59 ++++- src/renderers/webgl/WebGLState.js | 17 -- src/renderers/webgl/WebGLTextures.js | 139 ++++++++++-- src/renderers/webxr/WebXRManager.js | 206 +++++++----------- 6 files changed, 255 insertions(+), 176 deletions(-) diff --git a/src/renderers/WebGLMultisampleRenderTarget.js b/src/renderers/WebGLMultisampleRenderTarget.js index ccbb38e4aee7bc..9b64d91a14d7e5 100644 --- a/src/renderers/WebGLMultisampleRenderTarget.js +++ b/src/renderers/WebGLMultisampleRenderTarget.js @@ -2,11 +2,13 @@ import { WebGLRenderTarget } from './WebGLRenderTarget.js'; class WebGLMultisampleRenderTarget extends WebGLRenderTarget { - constructor( width, height, options ) { + constructor( width, height, options = {} ) { super( width, height, options ); this.samples = 4; + this.useMultisampleRenderToTexture = ( options.useMultisampleRenderToTexture !== undefined ) ? options.useMultisampleRenderToTexture : false; + this.useMultisampleRenderbuffer = this.useMultisampleRenderToTexture === false; } @@ -15,6 +17,8 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { super.copy.call( this, source ); this.samples = source.samples; + this.useMultisampleRenderToTexture = source.useMultisampleRenderToTexture; + this.useMultisampleRenderbuffer = source.useMultisampleRenderbuffer; return this; diff --git a/src/renderers/WebGLRenderTarget.js b/src/renderers/WebGLRenderTarget.js index 41ad23cccf91f4..22833d3c2c069f 100644 --- a/src/renderers/WebGLRenderTarget.js +++ b/src/renderers/WebGLRenderTarget.js @@ -35,6 +35,10 @@ class WebGLRenderTarget extends EventDispatcher { this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; + this.ignoreDepthForMultisampleCopy = options.ignoreDepth !== undefined ? options.ignoreDepth : true; + this.hasExternalTextures = false; + this.useMultisampleRenderToTexture = false; + this.useMultisampleRenderbuffer = false; } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 32514d7e407c5f..c113d96c5f59f7 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1280,7 +1280,8 @@ function WebGLRenderer( parameters = {} ) { minFilter: LinearMipmapLinearFilter, magFilter: NearestFilter, wrapS: ClampToEdgeWrapping, - wrapT: ClampToEdgeWrapping + wrapT: ClampToEdgeWrapping, + useMultisampleRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) } ); } @@ -1853,15 +1854,61 @@ function WebGLRenderer( parameters = {} ) { }; - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0, options = {} ) { _currentRenderTarget = renderTarget; _currentActiveCubeFace = activeCubeFace; _currentActiveMipmapLevel = activeMipmapLevel; + const useDefaultFramebuffer = options.framebuffer === undefined; - if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) { + if ( renderTarget ) { + + const renderTargetProperties = properties.get( renderTarget ); + let hasNewExternalTextures = false; + + if ( options.colorTexture !== undefined ) { + + hasNewExternalTextures = true; + properties.get( renderTarget.texture ).__webglTexture = options.colorTexture; + + renderTarget.autoAllocateDepthBuffer = options.depthTexture === undefined; + + if ( ! renderTarget.autoAllocateDepthBuffer ) { - textures.setupRenderTarget( renderTarget ); + properties.get( renderTarget.depthTexture ).__webglTexture = options.depthTexture; + + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth buffer. Disable use of the extension. + if ( renderTarget.useMultisampleRenderToTexture ) { + + console.warn( 'render-to-texture extension was disabled because an external texture was provided' ); + renderTarget.useMultisampleRenderToTexture = false; + renderTarget.useMultisampleRenderbuffer = true; + + } + + } + + renderTarget.hasExternalTextures = true; + + } + + if ( ! useDefaultFramebuffer ) { + + // We need to make sure to rebind the framebuffer. + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + renderTargetProperties.__webglFramebuffer = options.framebuffer; + + } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { + + textures.setupRenderTarget( renderTarget ); + + } else if ( hasNewExternalTextures ) { + + // Color and depth texture must be rebound in order for the swapchain to update. + textures.rebindTextures( renderTarget, options.colorTexture, options.depthTexture ); + + } } @@ -1886,7 +1933,7 @@ function WebGLRenderer( parameters = {} ) { framebuffer = __webglFramebuffer[ activeCubeFace ]; isCube = true; - } else if ( renderTarget.isWebGLMultisampleRenderTarget ) { + } else if ( renderTarget.useMultisampleRenderbuffer ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; @@ -1910,7 +1957,7 @@ function WebGLRenderer( parameters = {} ) { const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - if ( framebufferBound && capabilities.drawBuffers ) { + if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { let needsUpdate = false; diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js index 1904d7d66e1fbb..675da3dea9d9ef 100644 --- a/src/renderers/webgl/WebGLState.js +++ b/src/renderers/webgl/WebGLState.js @@ -316,7 +316,6 @@ function WebGLState( gl, extensions, capabilities ) { let enabledCapabilities = {}; - let xrFramebuffer = null; let currentBoundFramebuffers = {}; let currentProgram = null; @@ -428,22 +427,8 @@ function WebGLState( gl, extensions, capabilities ) { } - function bindXRFramebuffer( framebuffer ) { - - if ( framebuffer !== xrFramebuffer ) { - - gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); - - xrFramebuffer = framebuffer; - - } - - } - function bindFramebuffer( target, framebuffer ) { - if ( framebuffer === null && xrFramebuffer !== null ) framebuffer = xrFramebuffer; // use active XR framebuffer if available - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { gl.bindFramebuffer( target, framebuffer ); @@ -991,7 +976,6 @@ function WebGLState( gl, extensions, capabilities ) { currentTextureSlot = null; currentBoundTextures = {}; - xrFramebuffer = null; currentBoundFramebuffers = {}; currentProgram = null; @@ -1035,7 +1019,6 @@ function WebGLState( gl, extensions, capabilities ) { disable: disable, bindFramebuffer: bindFramebuffer, - bindXRFramebuffer: bindXRFramebuffer, useProgram: useProgram, diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 887c4bada18a4a..ac34cd69e9c8bd 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -9,6 +9,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const maxCubemapSize = capabilities.maxCubemapSize; const maxTextureSize = capabilities.maxTextureSize; const maxSamples = capabilities.maxSamples; + const msaartcSupported = extensions.has( 'EXT_multisampled_render_to_texture' ); + const msaaExt = msaartcSupported ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; const _videoTextures = new WeakMap(); let _canvas; @@ -859,22 +861,36 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType ); - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { + if ( ! renderTarget.hasExternalTextures ) { - state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null ); + if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - } else { + state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null ); + + } else { - state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + + } } state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 ); + if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 ); + + } + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } + // Setup storage for internal depth/stencil buffers and bind to correct framebuffer function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { @@ -884,7 +900,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, let glInternalFormat = _gl.DEPTH_COMPONENT16; - if ( isMultisample ) { + if ( isMultisample || renderTarget.useMultisampleRenderToTexture ) { const depthTexture = renderTarget.depthTexture; @@ -904,7 +920,15 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + + } else { + + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + + } } else { @@ -916,12 +940,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - if ( isMultisample ) { + const samples = getRenderTargetSamples( renderTarget ); - const samples = getRenderTargetSamples( renderTarget ); + if ( isMultisample && renderTarget.useMultisampleRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + } else if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + } else { _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); @@ -939,13 +967,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format ); const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType ); + const samples = getRenderTargetSamples( renderTarget ); - if ( isMultisample ) { - - const samples = getRenderTargetSamples( renderTarget ); + if ( isMultisample && renderTarget.useMultisampleRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + } else if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + } else { _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); @@ -986,14 +1017,31 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, setTexture2D( renderTarget.depthTexture, 0 ); const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; + const samples = getRenderTargetSamples( renderTarget ); if ( renderTarget.depthTexture.format === DepthFormat ) { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + if ( renderTarget.useMultisampleRenderToTexture ) { + + msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } } else { @@ -1007,10 +1055,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function setupDepthRenderbuffer( renderTarget ) { const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - if ( renderTarget.depthTexture ) { + if ( renderTarget.depthTexture && ! renderTarget.autoAllocateDepthBuffer ) { if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); @@ -1044,6 +1091,25 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + // rebind framebuffer with external textures + function rebindTextures( renderTarget, colorTexture, depthTexture ) { + + const renderTargetProperties = properties.get( renderTarget ); + + if ( colorTexture !== undefined ) { + + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); + + } + + if ( depthTexture !== undefined ) { + + setupDepthRenderbuffer( renderTarget ); + + } + + } + // Set up GL resources for the render target function setupRenderTarget( renderTarget ) { @@ -1056,7 +1122,12 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( renderTarget.isWebGLMultipleRenderTargets !== true ) { - textureProperties.__webglTexture = _gl.createTexture(); + if ( textureProperties.__webglTexture === undefined ) { + + textureProperties.__webglTexture = _gl.createTexture(); + + } + textureProperties.__version = texture.version; info.memory.textures ++; @@ -1064,7 +1135,6 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true ); const isRenderTarget3D = texture.isDataTexture3D || texture.isDataTexture2DArray; const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2; @@ -1120,7 +1190,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - } else if ( isMultisample ) { + } else if ( renderTarget.useMultisampleRenderbuffer ) { if ( isWebGL2 ) { @@ -1275,23 +1345,43 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function updateMultisampleRenderTarget( renderTarget ) { - if ( renderTarget.isWebGLMultisampleRenderTarget ) { + if ( renderTarget.useMultisampleRenderbuffer ) { if ( isWebGL2 ) { const width = renderTarget.width; const height = renderTarget.height; let mask = _gl.COLOR_BUFFER_BIT; + const invalidationArray = [ _gl.COLOR_ATTACHMENT0 ]; + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + + if ( renderTarget.depthBuffer ) { + + invalidationArray.push( depthStyle ); - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; + } + + if ( ! renderTarget.ignoreDepthForMultisampleCopy ) { + + if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; + if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; + + } const renderTargetProperties = properties.get( renderTarget ); state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + if ( renderTarget.ignoreDepthForMultisampleCopy ) { + + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); + + } + _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); @@ -1308,7 +1398,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function getRenderTargetSamples( renderTarget ) { - return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ? + return ( isWebGL2 && ( renderTarget.useMultisampleRenderbuffer || renderTarget.useMultisampleRenderToTexture ) ) ? Math.min( maxSamples, renderTarget.samples ) : 0; } @@ -1381,9 +1471,12 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, this.setTexture2DArray = setTexture2DArray; this.setTexture3D = setTexture3D; this.setTextureCube = setTextureCube; + this.rebindTextures = rebindTextures; this.setupRenderTarget = setupRenderTarget; this.updateRenderTargetMipmap = updateRenderTargetMipmap; this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; + this.setupDepthRenderbuffer = setupDepthRenderbuffer; + this.setupFrameBufferTexture = setupFrameBufferTexture; this.safeSetTexture2D = safeSetTexture2D; this.safeSetTextureCube = safeSetTextureCube; diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index 3ce77cd97953c7..58557188eb3eff 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -4,7 +4,19 @@ import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js'; import { Vector3 } from '../../math/Vector3.js'; import { Vector4 } from '../../math/Vector4.js'; import { WebGLAnimation } from '../webgl/WebGLAnimation.js'; +import { WebGLRenderTarget } from '../WebGLRenderTarget.js'; import { WebXRController } from './WebXRController.js'; +import { DepthTexture } from '../../textures/DepthTexture.js'; +import { WebGLMultisampleRenderTarget } from '../WebGLMultisampleRenderTarget.js'; +import { + DepthFormat, + DepthStencilFormat, + RGBAFormat, + RGBFormat, + UnsignedByteType, + UnsignedShortType, + UnsignedInt248Type, +} from '../../constants.js'; class WebXRManager extends EventDispatcher { @@ -13,28 +25,23 @@ class WebXRManager extends EventDispatcher { super(); const scope = this; - const state = renderer.state; let session = null; let framebufferScaleFactor = 1.0; let referenceSpace = null; let referenceSpaceType = 'local-floor'; + const msaartcSupported = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); let pose = null; let glBinding = null; - let glFramebuffer = null; let glProjLayer = null; let glBaseLayer = null; let isMultisample = false; - let glMultisampledFramebuffer = null; - let glColorRenderbuffer = null; - let glDepthRenderbuffer = null; let xrFrame = null; - let depthStyle = null; - let clearStyle = null; - const msaartcSupported = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); - let msaaExt = null; + const attributes = gl.getContextAttributes(); + let initialRenderTarget = null; + let newRenderTarget = null; const controllers = []; const inputSourcesMap = new Map(); @@ -139,21 +146,13 @@ class WebXRManager extends EventDispatcher { // restore framebuffer/rendering state - state.bindXRFramebuffer( null ); - renderer.setRenderTarget( renderer.getRenderTarget() ); - - if ( glFramebuffer ) gl.deleteFramebuffer( glFramebuffer ); - if ( glMultisampledFramebuffer ) gl.deleteFramebuffer( glMultisampledFramebuffer ); - if ( glColorRenderbuffer ) gl.deleteRenderbuffer( glColorRenderbuffer ); - if ( glDepthRenderbuffer ) gl.deleteRenderbuffer( glDepthRenderbuffer ); - glFramebuffer = null; - glMultisampledFramebuffer = null; - glColorRenderbuffer = null; - glDepthRenderbuffer = null; + renderer.setRenderTarget( initialRenderTarget ); + glBaseLayer = null; glProjLayer = null; glBinding = null; session = null; + newRenderTarget = null; // @@ -225,6 +224,8 @@ class WebXRManager extends EventDispatcher { if ( session !== null ) { + initialRenderTarget = renderer.getRenderTarget(); + session.addEventListener( 'select', onSessionEvent ); session.addEventListener( 'selectstart', onSessionEvent ); session.addEventListener( 'selectend', onSessionEvent ); @@ -234,18 +235,16 @@ class WebXRManager extends EventDispatcher { session.addEventListener( 'end', onSessionEnd ); session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - const attributes = gl.getContextAttributes(); - if ( attributes.xrCompatible !== true ) { await gl.makeXRCompatible(); } - if ( session.renderState.layers === undefined ) { + if ( ( session.renderState.layers === undefined ) || ( gl instanceof WebGLRenderingContext ) ) { const layerInit = { - antialias: attributes.antialias, + antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, alpha: attributes.alpha, depth: attributes.depth, stencil: attributes.stencil, @@ -256,43 +255,29 @@ class WebXRManager extends EventDispatcher { session.updateRenderState( { baseLayer: glBaseLayer } ); - } else if ( gl instanceof WebGLRenderingContext ) { - - // Use old style webgl layer because we can't use MSAA - // WebGL2 support. - - const layerInit = { - antialias: true, - alpha: attributes.alpha, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; - - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - - session.updateRenderState( { layers: [ glBaseLayer ] } ); + newRenderTarget = new WebGLRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight + ); } else { isMultisample = attributes.antialias; let depthFormat = null; - + let depthType = null; + let glDepthFormat = null; if ( attributes.depth ) { - clearStyle = gl.DEPTH_BUFFER_BIT; - - if ( attributes.stencil ) clearStyle |= gl.STENCIL_BUFFER_BIT; - - depthStyle = attributes.stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; - depthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT16; + depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; + depthType = attributes.stencil ? UnsignedInt248Type : UnsignedShortType; } const projectionlayerInit = { - colorFormat: attributes.alpha ? gl.RGBA8 : gl.RGB8, - depthFormat: depthFormat, + colorFormat: ( attributes.alpha || isMultisample ) ? gl.RGBA8 : gl.RGB8, + depthFormat: glDepthFormat, scaleFactor: framebufferScaleFactor }; @@ -300,45 +285,42 @@ class WebXRManager extends EventDispatcher { glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - glFramebuffer = gl.createFramebuffer(); - session.updateRenderState( { layers: [ glProjLayer ] } ); - if ( isMultisample && msaartcSupported ) { + if ( isMultisample ) { - msaaExt = renderer.extensions.get( 'EXT_multisampled_render_to_texture' ); - - } else if ( isMultisample ) { - - glMultisampledFramebuffer = gl.createFramebuffer(); - glColorRenderbuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer( gl.RENDERBUFFER, glColorRenderbuffer ); - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - 4, - gl.RGBA8, + newRenderTarget = new WebGLMultisampleRenderTarget( glProjLayer.textureWidth, - glProjLayer.textureHeight ); - state.bindFramebuffer( gl.FRAMEBUFFER, glMultisampledFramebuffer ); - gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, glColorRenderbuffer ); - gl.bindRenderbuffer( gl.RENDERBUFFER, null ); - - if ( depthFormat !== null ) { + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + ignoreDepth: glProjLayer.ignoreDepthValues, + useMultisampleRenderToTexture: msaartcSupported, + } ); - glDepthRenderbuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer( gl.RENDERBUFFER, glDepthRenderbuffer ); - gl.renderbufferStorageMultisample( gl.RENDERBUFFER, 4, depthFormat, glProjLayer.textureWidth, glProjLayer.textureHeight ); - gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, glDepthRenderbuffer ); - gl.bindRenderbuffer( gl.RENDERBUFFER, null ); - - } + } else { - state.bindFramebuffer( gl.FRAMEBUFFER, null ); + newRenderTarget = new WebGLRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: attributes.alpha ? RGBAFormat : RGBFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + ignoreDepth: glProjLayer.ignoreDepthValues, + } ); } } + // Set foveation to maximum. + this.setFoveation( 0 ); + referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); animation.setContext( session ); @@ -597,7 +579,13 @@ class WebXRManager extends EventDispatcher { if ( glBaseLayer !== null ) { - state.bindXRFramebuffer( glBaseLayer.framebuffer ); + renderer.setRenderTarget( + newRenderTarget, + 0, + 0, + { + framebuffer: glBaseLayer.framebuffer + } ); } @@ -608,7 +596,6 @@ class WebXRManager extends EventDispatcher { if ( views.length !== cameraVR.cameras.length ) { cameraVR.cameras.length = 0; - cameraVRNeedsUpdate = true; } @@ -626,33 +613,22 @@ class WebXRManager extends EventDispatcher { } else { const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); + viewport = glSubImage.viewport; - state.bindXRFramebuffer( glFramebuffer ); - - if ( isMultisample && msaartcSupported ) { - - if ( glSubImage.depthStencilTexture !== undefined ) { - - msaaExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, glSubImage.depthStencilTexture, 0, 4 ); - - } - - msaaExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glSubImage.colorTexture, 0, 4 ); - - } else { - - if ( glSubImage.depthStencilTexture !== undefined ) { - - gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, glSubImage.depthStencilTexture, 0 ); - - } + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { - gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glSubImage.colorTexture, 0 ); + renderer.setRenderTarget( + newRenderTarget, + 0, + 0, + { + colorTexture: glSubImage.colorTexture, + depthTexture: glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture + } ); } - viewport = glSubImage.viewport; - } const camera = cameras[ i ]; @@ -675,14 +651,6 @@ class WebXRManager extends EventDispatcher { } - if ( isMultisample && ! msaartcSupported ) { - - state.bindXRFramebuffer( glMultisampledFramebuffer ); - - if ( clearStyle !== null ) gl.clear( clearStyle ); - - } - } // @@ -700,26 +668,6 @@ class WebXRManager extends EventDispatcher { if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - if ( isMultisample && ! msaartcSupported ) { - - const width = glProjLayer.textureWidth; - const height = glProjLayer.textureHeight; - - state.bindFramebuffer( gl.READ_FRAMEBUFFER, glMultisampledFramebuffer ); - state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, glFramebuffer ); - // Invalidate the depth here to avoid flush of the depth data to main memory. - gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, [ depthStyle ] ); - gl.invalidateFramebuffer( gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST ); - // Invalidate the MSAA buffer because it's not needed anymore. - gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, [ gl.COLOR_ATTACHMENT0 ] ); - state.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - - state.bindFramebuffer( gl.FRAMEBUFFER, glMultisampledFramebuffer ); - - } - xrFrame = null; } From d86e2af4bcf9e237a14bf847ebe671c2b07c2a31 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Fri, 24 Sep 2021 14:48:03 -0700 Subject: [PATCH 2/8] Addressed review comments --- src/renderers/WebGLMultisampleRenderTarget.js | 9 ++-- src/renderers/WebGLRenderTarget.js | 5 +-- src/renderers/WebGLRenderer.js | 16 +++---- src/renderers/webgl/WebGLExtensions.js | 2 +- src/renderers/webgl/WebGLTextures.js | 45 ++++++++++--------- src/renderers/webxr/WebXRManager.js | 4 +- 6 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/renderers/WebGLMultisampleRenderTarget.js b/src/renderers/WebGLMultisampleRenderTarget.js index 9b64d91a14d7e5..8898923e5bbc7a 100644 --- a/src/renderers/WebGLMultisampleRenderTarget.js +++ b/src/renderers/WebGLMultisampleRenderTarget.js @@ -7,8 +7,9 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { super( width, height, options ); this.samples = 4; - this.useMultisampleRenderToTexture = ( options.useMultisampleRenderToTexture !== undefined ) ? options.useMultisampleRenderToTexture : false; - this.useMultisampleRenderbuffer = this.useMultisampleRenderToTexture === false; + + this.useMultisampledRenderToTexture = ( options.useMultisampledRenderToTexture !== undefined ) ? options.useMultisampledRenderToTexture : false; + this.useMultisampledRenderbuffer = this.useMultisampledRenderToTexture === false; } @@ -17,8 +18,8 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { super.copy.call( this, source ); this.samples = source.samples; - this.useMultisampleRenderToTexture = source.useMultisampleRenderToTexture; - this.useMultisampleRenderbuffer = source.useMultisampleRenderbuffer; + this.useMultisampledRenderToTexture = source.useMultisampledRenderToTexture; + this.useMultisampledRenderbuffer = source.useMultisampledRenderbuffer; return this; diff --git a/src/renderers/WebGLRenderTarget.js b/src/renderers/WebGLRenderTarget.js index 22833d3c2c069f..0a7fe39e89c688 100644 --- a/src/renderers/WebGLRenderTarget.js +++ b/src/renderers/WebGLRenderTarget.js @@ -36,9 +36,8 @@ class WebGLRenderTarget extends EventDispatcher { this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; this.ignoreDepthForMultisampleCopy = options.ignoreDepth !== undefined ? options.ignoreDepth : true; - this.hasExternalTextures = false; - this.useMultisampleRenderToTexture = false; - this.useMultisampleRenderbuffer = false; + this.useMultisampledRenderToTexture = false; + this.useMultisampledRenderbuffer = false; } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 5dccaebd7cf2b1..eddb9a8f929589 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1281,7 +1281,7 @@ function WebGLRenderer( parameters = {} ) { magFilter: NearestFilter, wrapS: ClampToEdgeWrapping, wrapT: ClampToEdgeWrapping, - useMultisampleRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) + useMultisampledRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) } ); } @@ -1871,25 +1871,25 @@ function WebGLRenderer( parameters = {} ) { hasNewExternalTextures = true; properties.get( renderTarget.texture ).__webglTexture = options.colorTexture; - renderTarget.autoAllocateDepthBuffer = options.depthTexture === undefined; + renderTargetProperties.__autoAllocateDepthBuffer = options.depthTexture === undefined; - if ( ! renderTarget.autoAllocateDepthBuffer ) { + if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { properties.get( renderTarget.depthTexture ).__webglTexture = options.depthTexture; // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( renderTarget.useMultisampleRenderToTexture ) { + if ( renderTarget.useMultisampledRenderToTexture ) { console.warn( 'render-to-texture extension was disabled because an external texture was provided' ); - renderTarget.useMultisampleRenderToTexture = false; - renderTarget.useMultisampleRenderbuffer = true; + renderTarget.useMultisampledRenderToTexture = false; + renderTarget.useMultisampledRenderbuffer = true; } } - renderTarget.hasExternalTextures = true; + renderTargetProperties.__hasExternalTextures = true; } @@ -1933,7 +1933,7 @@ function WebGLRenderer( parameters = {} ) { framebuffer = __webglFramebuffer[ activeCubeFace ]; isCube = true; - } else if ( renderTarget.useMultisampleRenderbuffer ) { + } else if ( renderTarget.useMultisampledRenderbuffer ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; diff --git a/src/renderers/webgl/WebGLExtensions.js b/src/renderers/webgl/WebGLExtensions.js index ef08b5d0d854bf..0f3d8f861e644f 100644 --- a/src/renderers/webgl/WebGLExtensions.js +++ b/src/renderers/webgl/WebGLExtensions.js @@ -65,12 +65,12 @@ function WebGLExtensions( gl ) { getExtension( 'OES_element_index_uint' ); getExtension( 'OES_vertex_array_object' ); getExtension( 'ANGLE_instanced_arrays' ); + getExtension( 'EXT_multisampled_render_to_texture' ); } getExtension( 'OES_texture_float_linear' ); getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'EXT_multisampled_render_to_texture' ); }, diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index ec948826977fa5..6e1171eaf389e9 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -9,8 +9,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const maxCubemapSize = capabilities.maxCubemapSize; const maxTextureSize = capabilities.maxTextureSize; const maxSamples = capabilities.maxSamples; - const msaartcSupported = extensions.has( 'EXT_multisampled_render_to_texture' ); - const msaaExt = msaartcSupported ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; + const MultisampledRenderToTextureSupported = extensions.has( 'EXT_multisampled_render_to_texture' ); + const MultisampledRenderToTextureExtension = MultisampledRenderToTextureSupported ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; const _videoTextures = new WeakMap(); let _canvas; @@ -860,8 +860,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format ); const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding ); + const renderTargetProperties = properties.get( renderTarget ); - if ( ! renderTarget.hasExternalTextures ) { + if ( ! renderTargetProperties.__hasExternalTextures ) { if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { @@ -876,9 +877,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - if ( renderTarget.useMultisampleRenderToTexture ) { + if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); } else { @@ -900,7 +901,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, let glInternalFormat = _gl.DEPTH_COMPONENT16; - if ( isMultisample || renderTarget.useMultisampleRenderToTexture ) { + if ( isMultisample || renderTarget.useMultisampledRenderToTexture ) { const depthTexture = renderTarget.depthTexture; @@ -920,9 +921,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const samples = getRenderTargetSamples( renderTarget ); - if ( renderTarget.useMultisampleRenderToTexture ) { + if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else { @@ -942,13 +943,13 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const samples = getRenderTargetSamples( renderTarget ); - if ( isMultisample && renderTarget.useMultisampleRenderbuffer ) { + if ( isMultisample && renderTarget.useMultisampledRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - } else if ( renderTarget.useMultisampleRenderToTexture ) { + } else if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); } else { @@ -969,13 +970,13 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding ); const samples = getRenderTargetSamples( renderTarget ); - if ( isMultisample && renderTarget.useMultisampleRenderbuffer ) { + if ( isMultisample && renderTarget.useMultisampledRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } else if ( renderTarget.useMultisampleRenderToTexture ) { + } else if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else { @@ -1021,9 +1022,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( renderTarget.depthTexture.format === DepthFormat ) { - if ( renderTarget.useMultisampleRenderToTexture ) { + if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); } else { @@ -1033,9 +1034,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - if ( renderTarget.useMultisampleRenderToTexture ) { + if ( renderTarget.useMultisampledRenderToTexture ) { - msaaExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); } else { @@ -1057,7 +1058,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const renderTargetProperties = properties.get( renderTarget ); const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - if ( renderTarget.depthTexture && ! renderTarget.autoAllocateDepthBuffer ) { + if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); @@ -1190,7 +1191,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - } else if ( renderTarget.useMultisampleRenderbuffer ) { + } else if ( renderTarget.useMultisampledRenderbuffer ) { if ( isWebGL2 ) { @@ -1345,7 +1346,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function updateMultisampleRenderTarget( renderTarget ) { - if ( renderTarget.useMultisampleRenderbuffer ) { + if ( renderTarget.useMultisampledRenderbuffer ) { if ( isWebGL2 ) { @@ -1398,7 +1399,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function getRenderTargetSamples( renderTarget ) { - return ( isWebGL2 && ( renderTarget.useMultisampleRenderbuffer || renderTarget.useMultisampleRenderToTexture ) ) ? + return ( isWebGL2 && ( renderTarget.useMultisampledRenderbuffer || renderTarget.useMultisampledRenderToTexture ) ) ? Math.min( maxSamples, renderTarget.samples ) : 0; } diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index 58557188eb3eff..31fb41868af8e5 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -31,7 +31,7 @@ class WebXRManager extends EventDispatcher { let referenceSpace = null; let referenceSpaceType = 'local-floor'; - const msaartcSupported = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); + const MultisampledRenderToTextureSupported = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); let pose = null; let glBinding = null; @@ -298,7 +298,7 @@ class WebXRManager extends EventDispatcher { depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), stencilBuffer: attributes.stencil, ignoreDepth: glProjLayer.ignoreDepthValues, - useMultisampleRenderToTexture: msaartcSupported, + useMultisampledRenderToTexture: MultisampledRenderToTextureSupported, } ); } else { From 08c8767abfa09d9b6e31d49ca960784ff04f22f1 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Mon, 4 Oct 2021 12:24:14 -0700 Subject: [PATCH 3/8] Merge branch 'dev' of git://github.com/mrdoob/three.js into mrdoob-dev --- docs/api/ar/cameras/CubeCamera.html | 3 +- docs/api/en/cameras/CubeCamera.html | 3 +- docs/api/en/lights/AmbientLight.html | 1 - docs/api/en/materials/ShaderMaterial.html | 1 - docs/api/ko/cameras/CubeCamera.html | 3 +- docs/api/zh/cameras/CubeCamera.html | 3 +- docs/api/zh/extras/core/Curve.html | 77 +-- docs/api/zh/lights/AmbientLight.html | 1 - docs/api/zh/materials/ShaderMaterial.html | 1 - docs/index.html | 2 +- .../ko/buildTools/Testing-with-NPM.html | 6 +- .../ko/introduction/Animation-system.html | 7 +- docs/manual/ko/introduction/FAQ.html | 6 +- .../How-to-dispose-of-objects.html | 8 +- .../How-to-run-things-locally.html | 7 +- docs/manual/ko/introduction/Installation.html | 8 +- docs/manual/ko/introduction/Useful-links.html | 2 +- editor/css/main.css | 22 +- editor/js/Menubar.File.js | 4 +- editor/js/Player.js | 6 + editor/js/Resizer.js | 14 +- editor/sw.js | 2 +- examples/files.json | 2 - examples/js/exporters/USDZExporter.js | 12 +- examples/js/geometries/ParametricGeometry.js | 115 ++++ examples/js/geometries/TextGeometry.js | 49 ++ examples/js/loaders/FontLoader.js | 183 +++++ examples/js/loaders/GLTFLoader.js | 131 ++-- examples/js/loaders/RGBMLoader.js | 9 +- examples/js/loaders/SVGLoader.js | 1 + examples/js/postprocessing/SSRPass.js | 3 - examples/js/postprocessing/SSRrPass.js | 5 +- examples/js/utils/PackedPhongMaterial.js | 6 +- examples/jsm/exporters/USDZExporter.js | 13 +- examples/jsm/geometries/ParametricGeometry.js | 2 +- examples/jsm/geometries/TextGeometry.js | 2 +- examples/jsm/loaders/GLTFLoader.js | 79 ++- examples/jsm/loaders/RGBMLoader.js | 8 +- examples/jsm/loaders/SVGLoader.js | 1 + examples/jsm/renderers/nodes/Nodes.js | 8 +- examples/jsm/renderers/nodes/ShaderNode.js | 193 ++++++ .../renderers/nodes/accessors/CameraNode.js | 32 +- .../accessors/ModelViewProjectionNode.js | 6 +- .../renderers/nodes/accessors/NormalNode.js | 52 +- .../renderers/nodes/accessors/Object3DNode.js | 42 +- .../renderers/nodes/accessors/PointUVNode.js | 7 +- .../renderers/nodes/accessors/PositionNode.js | 73 +- .../nodes/accessors/ReferenceNode.js | 4 +- .../jsm/renderers/nodes/core/AttributeNode.js | 23 +- examples/jsm/renderers/nodes/core/CodeNode.js | 6 +- .../jsm/renderers/nodes/core/ConstNode.js | 7 +- .../renderers/nodes/core/ExpressionNode.js | 11 +- .../renderers/nodes/core/FunctionCallNode.js | 7 +- examples/jsm/renderers/nodes/core/Node.js | 21 + .../jsm/renderers/nodes/core/PropertyNode.js | 10 +- .../jsm/renderers/nodes/core/StructVarNode.js | 4 +- examples/jsm/renderers/nodes/core/TempNode.js | 16 +- examples/jsm/renderers/nodes/core/VarNode.js | 4 +- examples/jsm/renderers/nodes/core/VaryNode.js | 4 +- .../renderers/nodes/display/ColorSpaceNode.js | 28 +- .../renderers/nodes/display/NormalMapNode.js | 33 +- .../nodes/lights/LightContextNode.js | 8 +- .../jsm/renderers/nodes/lights/LightNode.js | 6 +- .../jsm/renderers/nodes/lights/LightsNode.js | 4 +- examples/jsm/renderers/nodes/math/MathNode.js | 8 +- .../jsm/renderers/nodes/math/OperatorNode.js | 6 +- .../renderers/nodes/procedural/CheckerNode.js | 38 ++ .../jsm/renderers/nodes/utils/JoinNode.js | 6 +- .../jsm/renderers/nodes/utils/SplitNode.js | 22 +- .../nodes/utils/SpriteSheetUVNode.js | 53 +- .../renderers/webgl/nodes/WebGLNodeBuilder.js | 16 +- .../webgpu/nodes/WebGPUNodeBuilder.js | 34 +- examples/models/gltf/SittingBox.glb | Bin 422392 -> 0 bytes .../screenshots/webgl_animation_cloth.jpg | Bin 38837 -> 0 bytes .../screenshots/webgl_animation_multiple.jpg | Bin 13670 -> 13998 bytes .../screenshots/webgl_morphtargets_face.jpg | Bin 10360 -> 9178 bytes .../screenshots/webgl_shading_physical.jpg | Bin 30817 -> 0 bytes examples/screenshots/webgpu_sandbox.jpg | Bin 21592 -> 6702 bytes examples/tags.json | 2 - .../textures/patterns/bright_squares256.png | Bin 26698 -> 0 bytes .../textures/patterns/circuit_pattern.png | Bin 5417 -> 0 bytes examples/textures/patterns/readme.txt | 6 - examples/webgl_animation_cloth.html | 629 ------------------ examples/webgl_morphtargets_face.html | 29 +- examples/webgl_shading_physical.html | 363 ---------- examples/webgpu_sandbox.html | 4 +- package.json | 2 +- src/constants.js | 2 +- src/core/Object3D.js | 5 +- src/objects/SkinnedMesh.js | 2 +- src/renderers/WebGLRenderer.js | 35 +- test/e2e/puppeteer.js | 7 +- 92 files changed, 1137 insertions(+), 1549 deletions(-) create mode 100644 examples/js/geometries/ParametricGeometry.js create mode 100644 examples/js/geometries/TextGeometry.js create mode 100644 examples/js/loaders/FontLoader.js create mode 100644 examples/jsm/renderers/nodes/ShaderNode.js create mode 100644 examples/jsm/renderers/nodes/procedural/CheckerNode.js delete mode 100644 examples/models/gltf/SittingBox.glb delete mode 100644 examples/screenshots/webgl_animation_cloth.jpg delete mode 100644 examples/screenshots/webgl_shading_physical.jpg delete mode 100644 examples/textures/patterns/bright_squares256.png delete mode 100644 examples/textures/patterns/circuit_pattern.png delete mode 100644 examples/textures/patterns/readme.txt delete mode 100644 examples/webgl_animation_cloth.html delete mode 100644 examples/webgl_shading_physical.html diff --git a/docs/api/ar/cameras/CubeCamera.html b/docs/api/ar/cameras/CubeCamera.html index 034bb7cd57d800..29ef9769e87ea7 100644 --- a/docs/api/ar/cameras/CubeCamera.html +++ b/docs/api/ar/cameras/CubeCamera.html @@ -41,8 +41,7 @@

مثال التعليمة البرمجية

أمثلة (Examples)

- [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
- [example:webgl_shading_physical shading / physical ] + [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]

المنشئ (Constructor)

diff --git a/docs/api/en/cameras/CubeCamera.html b/docs/api/en/cameras/CubeCamera.html index e178e9dfd547a1..b8c0e9013f1dde 100644 --- a/docs/api/en/cameras/CubeCamera.html +++ b/docs/api/en/cameras/CubeCamera.html @@ -41,8 +41,7 @@

Code Example

Examples

- [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
- [example:webgl_shading_physical shading / physical ] + [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]

Constructor

diff --git a/docs/api/en/lights/AmbientLight.html b/docs/api/en/lights/AmbientLight.html index b3b588bb0e6b52..d70d2fe8b6670d 100644 --- a/docs/api/en/lights/AmbientLight.html +++ b/docs/api/en/lights/AmbientLight.html @@ -26,7 +26,6 @@

Code Example

Examples

- [example:webgl_animation_cloth animation / cloth ]
[example:webgl_animation_skinning_blending animation / skinning / blending ]

diff --git a/docs/api/en/materials/ShaderMaterial.html b/docs/api/en/materials/ShaderMaterial.html index f972c5d85463ae..543e87755ebd96 100644 --- a/docs/api/en/materials/ShaderMaterial.html +++ b/docs/api/en/materials/ShaderMaterial.html @@ -91,7 +91,6 @@

Code Example

Examples

- [example:webgl_animation_cloth webgl / animation / cloth ]
[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]
[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]
[example:webgl_custom_attributes webgl / custom / attributes]
diff --git a/docs/api/ko/cameras/CubeCamera.html b/docs/api/ko/cameras/CubeCamera.html index e32dab186e4e04..5070b66457a2d0 100644 --- a/docs/api/ko/cameras/CubeCamera.html +++ b/docs/api/ko/cameras/CubeCamera.html @@ -41,8 +41,7 @@

코드 예제

예제

- [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
- [example:webgl_shading_physical shading / physical ] + [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]

생성자

diff --git a/docs/api/zh/cameras/CubeCamera.html b/docs/api/zh/cameras/CubeCamera.html index 5f7743e59047a6..532f85f0dc7db3 100644 --- a/docs/api/zh/cameras/CubeCamera.html +++ b/docs/api/zh/cameras/CubeCamera.html @@ -41,8 +41,7 @@

代码示例

例子

- [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
- [example:webgl_shading_physical shading / physical ] + [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]

构造器

diff --git a/docs/api/zh/extras/core/Curve.html b/docs/api/zh/extras/core/Curve.html index 1e008e54822738..cd5df8aa155c89 100644 --- a/docs/api/zh/extras/core/Curve.html +++ b/docs/api/zh/extras/core/Curve.html @@ -10,8 +10,8 @@

[name]

- An abstract base class for creating a [name] object that contains methods for interpolation. - For an array of [name]s see [page:CurvePath]. + 用于创建包含插值方法的[name]对象的抽象基类。 + 有关[name]的数组,请参见[page:CurvePath]。

Constructor

@@ -19,102 +19,93 @@

Constructor

[name]()

- This constructor creates a new [name]. + 创建一个 [name].

-

Properties

+

属性

[property:Integer arcLengthDivisions]

-

This value determines the amount of divisions when calculating the cumulative segment lengths of a curve via [page:.getLengths]. - To ensure precision when using methods like [page:.getSpacedPoints], it is recommended to increase [page:.arcLengthDivisions] if the curve is very large. Default is 200.

+

确定[page:.GetLength]计算曲线的累积分段长度时的分段量。 + 为确保[page:.getSpacedPoints]等方法时的精度,如果曲线非常大,建议增加[page:.arcLengthDivisions]。默认值为200

-

Methods

+

方法

[method:Vector getPoint]( [param:Float t], [param:Vector optionalTarget] )

- [page:Float t] - A position on the curve. Must be in the range [ 0, 1 ].
- [page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector, - otherwise a new Vector will be created.

+ [page:Float t] - 曲线上的位置。必须在[0,1]范围内
+ [page:Vector optionalTarget] — (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。

- Returns a vector for a given position on the curve. + 返回曲线上给定位置的点。

[method:Vector getPointAt]( [param:Float u], [param:Vector optionalTarget] )

- [page:Float u] - A position on the curve according to the arc length. Must be in the range [ 0, 1 ].
- [page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector, - otherwise a new Vector will be created.

+ [page:Float u] - 根据弧长在曲线上的位置。必须在范围[0,1]内。
+ [page:Vector optionalTarget] — (可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。

- Returns a vector for a given position on the curve according to the arc length. + 根据弧长返回曲线上给定位置的点。

[method:Array getPoints]( [param:Integer divisions] )

- divisions -- number of pieces to divide the curve into. Default is *5*.

- - Returns a set of divisions + 1 points using getPoint( t ). + divisions -- 要将曲线划分为的分段数。默认是 *5*.

+ 使用getPoint(t)返回一组divisions+1的点

[method:Array getSpacedPoints]( [param:Integer divisions] )

- divisions -- number of pieces to divide the curve into. Default is *5*.

+ divisions -- 要将曲线划分为的分段数。默认是 *5*.

- Returns a set of divisions + 1 equi-spaced points using getPointAt( u ). + 使用getPointAt(u)返回一个分段+1的等距点的数组。

[method:Float getLength]()

-

Get total curve arc length.

+

获取总曲线弧长。

[method:Array getLengths]( [param:Integer divisions] )

-

Get list of cumulative segment lengths.

+

获取累积段长度的列表。

[method:null updateArcLengths]()

-

Update the cumlative segment distance cache.

+

更新累积段距离缓存。

[method:Float getUtoTmapping]( [param:Float u], [param:Float distance] )

- Given u in the range ( 0 .. 1 ), returns [page:Float t] also in the range ( 0 .. 1 ). - u and t can then be used to give you points which are equidistant from the ends of the curve, - using [page:.getPoint]. + 给定范围(0..1)内的u,返回范围(0..1)内的[page:Float t], + 然后可以用t来使用 [page:.getPoint]给出与曲线末端等距的点。

[method:Vector getTangent]( [param:Float t], [param:Vector optionalTarget] )

- [page:Float t] - A position on the curve. Must be in the range [ 0, 1 ].
- [page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector, - otherwise a new Vector will be created.

+ [page:Float t] -在曲线上的点,必须在范围 [ 0, 1 ].
+ [page:Vector optionalTarget] — (可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。

- Returns a unit vector tangent at t. If the derived curve does not implement its - tangent derivation, two points a small delta apart will be used to find its gradient - which seems to give a reasonable approximation. + 返回t处的单位向量切线。如果派生曲线未实现其 + 切线求导,将使用相距一个小三角形的两个点来求与其实际梯度的近似值

[method:Vector getTangentAt]( [param:Float u], [param:Vector optionalTarget] )

- [page:Float u] - A position on the curve according to the arc length. Must be in the range [ 0, 1 ].
- [page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector, - otherwise a new Vector will be created.

- - Returns tangent at a point which is equidistant to the ends of the curve from the - point given in [page:.getTangent]. + [page:Float u] - 根据弧长在曲线上的位置,必须在范围[ 0, 1 ]。
+ [page:Vector optionalTarget] —(可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。

+ 返回一个点处的切线,该点与 [page:.getTangent]中给定的曲线的端点距离相等

[method:Object computeFrenetFrames]( [param:Integer segments], [param:Boolean closed] )

- Generates the Frenet Frames. Requires a curve definition in 3D space. Used in geometries like [page:TubeGeometry] or [page:ExtrudeGeometry]. + 生成Frenet帧。需要三维空间中的曲线定义。用于[page:TubeGeometry]或[page:ExtradeGeometry]等几何图形。

[method:Curve clone]()

-

Creates a clone of this instance.

+

创建此实例的克隆。

[method:Curve copy]( [param:Curve source] )

-

Copies another [name] object to this instance.

+

将另一个[name]对象复制到此实例。

[method:Object toJSON]()

-

Returns a JSON object representation of this instance.

+

返回此实例的JSON对象表示形式。

[method:Curve fromJSON]( [param:Object json] )

-

Copies the data from the given JSON object to this instance.

+

将给定的JSON数据复制到此实例。

Source

diff --git a/docs/api/zh/lights/AmbientLight.html b/docs/api/zh/lights/AmbientLight.html index ca37532598fcec..890c290d3a1189 100644 --- a/docs/api/zh/lights/AmbientLight.html +++ b/docs/api/zh/lights/AmbientLight.html @@ -26,7 +26,6 @@

代码示例

例子

- [example:webgl_animation_cloth animation / cloth ]
[example:webgl_animation_skinning_blending animation / skinning / blending ]

diff --git a/docs/api/zh/materials/ShaderMaterial.html b/docs/api/zh/materials/ShaderMaterial.html index 7fe12bae441285..75c1d35eb42e20 100644 --- a/docs/api/zh/materials/ShaderMaterial.html +++ b/docs/api/zh/materials/ShaderMaterial.html @@ -81,7 +81,6 @@

代码示例

例子

- [example:webgl_animation_cloth webgl / animation / cloth ]
[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]
[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]
[example:webgl_custom_attributes webgl / custom / attributes]
diff --git a/docs/index.html b/docs/index.html index be0e2dbdd40ba5..69c5a1b644cde9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -18,7 +18,7 @@

three.js

docs - examples + examples
diff --git a/docs/manual/ko/buildTools/Testing-with-NPM.html b/docs/manual/ko/buildTools/Testing-with-NPM.html index 6d0a198f995256..6faa49f6a9051b 100644 --- a/docs/manual/ko/buildTools/Testing-with-NPM.html +++ b/docs/manual/ko/buildTools/Testing-with-NPM.html @@ -131,7 +131,7 @@

three.js 추가

$ npm install three@0.84.0 --save (이 예제에서는 0.84.0 버전을 사용했습니다.). --save 는 dev 설정이 아닌 이 프로젝트의 의존성으로 추가하는 명령어입니다. - [link:https://www.npmjs.org/doc/json.html 여기]에서 더 많은 내용을 확인하세요. + 여기([link:https://www.npmjs.org/doc/json.html link])에서 더 많은 내용을 확인하세요. @@ -181,12 +181,12 @@

자신의 코드 추가하기

  1. 자신의 코드의 예상 결과가 들어있는 예제를 만들어, test/ 폴더 안에 두세요. - [link:https://github.com/air/encounter/blob/master/test/Physics-test.js 여기]에서 진짜 프로젝트의 예제를 확인할 수 있습니다. + 여기([link:https://github.com/air/encounter/blob/master/test/Physics-test.js link])에서 진짜 프로젝트의 예제를 확인할 수 있습니다.
  2. nodeJS에서 알아볼 수 있는, require를 사용해 기능들을 내보내기 하세요. - [link:https://github.com/air/encounter/blob/master/js/Physics.js 여기]를 참고하세요. + 여기([link:https://github.com/air/encounter/blob/master/js/Physics.js link])를 참고하세요.
  3. diff --git a/docs/manual/ko/introduction/Animation-system.html b/docs/manual/ko/introduction/Animation-system.html index e4574e768c85a7..5d1c99f73947b1 100644 --- a/docs/manual/ko/introduction/Animation-system.html +++ b/docs/manual/ko/introduction/Animation-system.html @@ -13,15 +13,14 @@

    Overview

    three.js 애니메이션 시스템에서는 모델의 다양한 속성을 설정할 수 있습니다: - [page:SkinnedMesh 스킨 및 리깅 모델], 모프타깃의 골자, 서로 다른 재질의 속성(색상, 불투명도, 참/거짓 연산), + [page:SkinnedMesh skinned and rigged model]의 뼈, 모프타깃, 서로 다른 재질의 속성(색상, 불투명도, 참/거짓 연산), 가시성과 변환이 그 예입니다. 애니메이션의 속성은 페이드 아웃, 페이드 아웃, 크로스페이드, 랩이 있습니다. 한 오브젝트에 대한 동시에 일어나는 다른 확대 시간 및 가중치 조절이나, 서로 다른 오브젝트간의 애니메이션도 전부 개별로 변화시킬 수 있습니다. 같은, 혹은 서로 다른 오브젝트틀간의 다양한 애니메이션도 싱크를 맞출 수 있습니다.

    - 이를 한 시스템 안에 구현하기 위해서, three.js 애니메이션 시스템은 - [link:https://github.com/mrdoob/three.js/issues/6881 2015년에 완전히 변경] - (지난 정보임에 주의하세요!)되었으며, 현재는 Unity/Unreal Engine 4와 유사한 구조를 가지고 있습니다. + 이를 한 시스템 안에 구현하기 위해서, three.js 애니메이션 시스템은 2015년에 완전히 변경되었으며([link:https://github.com/mrdoob/three.js/issues/6881 Link]) + 되었으며(지난 정보에 주의하세요!), 현재는 Unity/Unreal Engine 4와 유사한 구조를 가지고 있습니다. 이 페이지에서는 어떻게 시스템 메인 컴포넌트가 구성되고 동작되는지를 간단하게 알아보겠습니다.

    diff --git a/docs/manual/ko/introduction/FAQ.html b/docs/manual/ko/introduction/FAQ.html index 9febcb2c1adfa8..495c37bd5d2c59 100644 --- a/docs/manual/ko/introduction/FAQ.html +++ b/docs/manual/ko/introduction/FAQ.html @@ -15,7 +15,7 @@

    어떤 3D 모델 포맷이 가장 잘 지원되나요?

    불러오기 및 내보내기 용으로 추천되는 포맷은 glTF (GL Transmission Format)입니다. glTF는 런타임 에셋 딜리버리에 초점이 맞추어져 있기 때문에, 전송에 적합하고 로딩이 빠릅니다.

    - three.js 널리 쓰이는 포맷인 FBX, Collada 나 OBJ 도 지원합니다. 하지만 첫 프로젝트에서는 glTF 기반의 워크플로우로 작업해야 합니다. 더 자세한 내용은, [link:#manual/introduction/Loading-3D-models 3D 모델 로딩]을 참고하세요. + three.js 널리 쓰이는 포맷인 FBX, Collada 나 OBJ 도 지원합니다. 하지만 첫 프로젝트에서는 glTF 기반의 워크플로우로 작업해야 합니다. 더 자세한 내용은, [link:#manual/introduction/Loading-3D-models loading 3D models]를 참고하세요.

    @@ -41,8 +41,8 @@

    화면 확대 정도가 리사이징 시에 유지될 수 있나요?

    화면 높이를 특정 비율로 늘리면, 모든 가시 높이와 거리가 같은 비율로 증가해야 합니다. - 이는 카메라 위치를 변경하는 것으로는 불가능합니다. 대신에 카메라 field-of-view를 변경해야합니다.. - [link:http://jsfiddle.net/Q4Jpu/ 예제]. + 이는 카메라 위치를 변경하는 것으로는 불가능합니다. 대신에 카메라 field-of-view를 변경해야합니다. + [link:http://jsfiddle.net/Q4Jpu/ Example].

    왜 오브젝트 일부가 안 보일까요?

    diff --git a/docs/manual/ko/introduction/How-to-dispose-of-objects.html b/docs/manual/ko/introduction/How-to-dispose-of-objects.html index 7ceecabdbc89c2..1d712d65470cf0 100644 --- a/docs/manual/ko/introduction/How-to-dispose-of-objects.html +++ b/docs/manual/ko/introduction/How-to-dispose-of-objects.html @@ -21,7 +21,7 @@

    오브젝트를 폐기하는 방법([name])

    기하학

    - 기하학은 속성 집합으로 정의된 꼭짓점 정보를 표시하는데, *three.js*는 속성마다 하나의 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] 유형의 대상을 만들어 내부에 저장합니다. + 기하학은 속성 집합으로 정의된 꼭짓점 정보를 표시하는데, *three.js*는 속성마다 하나의 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] 유형의 대상을 만들어 내부에 저장합니다. 이러한 개체는 [page:BufferGeometry.dispose]를 호출할 때만 폐기됩니다. 만약 애플리케이션에서 기하학을 더이상 사용하지 않는다면, 이 방법을 실행하여 모든 관련 자원을 폐기하세요.

    @@ -38,15 +38,15 @@

    텍스쳐

    재질의 폐기는 텍스쳐에 영향을 미치지 않습니다. 이들은 분리되어 있어 하나의 텍스쳐를 여러 재질로 동시에 사용할 수 있습니다. - [page:Texture] 인스턴스를 만들 때마다 three.js는 내부에서 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] 인스턴스를 만듭니다. + [page:Texture] 인스턴스를 만들 때마다 three.js는 내부에서 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] 인스턴스를 만듭니다. buffer와 비슷하게, 이 오브젝트는 [page:Texture.dispose]() 호출로만 삭제가 가능합니다.

    렌더링 대상

    - [page:WebGLRenderTarget] 타입의 오브젝트는 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]의 인스턴스가 할당되어 있을 뿐만 아니라, - [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]와 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] 도 할당되어, + [page:WebGLRenderTarget] 타입의 오브젝트는 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]의 인스턴스가 할당되어 있을 뿐만 아니라, + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]와 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] 도 할당되어, 커스텀 렌더링 목표를 실체화합니다. 이러한 오브젝트는 [page:WebGLRenderTarget.dispose]()를 실행해야만 폐기할 수 있습니다.

    diff --git a/docs/manual/ko/introduction/How-to-run-things-locally.html b/docs/manual/ko/introduction/How-to-run-things-locally.html index 819b69ce231485..07ee684db11be0 100644 --- a/docs/manual/ko/introduction/How-to-run-things-locally.html +++ b/docs/manual/ko/introduction/How-to-run-things-locally.html @@ -43,8 +43,7 @@

    외부 파일에서 컨텐츠 불러오기

    로컬 서버에서 실행

    - 많은 프로그래밍 언어는 기본적으로 간단한 HTTP 서버가 설치되어 있습니다. [link:https://www.apache.org/ Apache]나 [link:https://nginx.org - NGINX]같은 프로덕션용 정도로 갖추어져 있지는 않지만, three.js를 테스트해보기에는 충분합니다. + 많은 프로그래밍 언어는 기본적으로 간단한 HTTP 서버가 설치되어 있습니다. [link:https://www.apache.org/ Apache]나 [link:https://nginx.org NGINX]같은 프로덕션용 정도로 갖추어져 있지는 않지만, three.js를 테스트해보기에는 충분합니다.

    유명 코드 에디터 관련 플러그인

    @@ -120,7 +119,7 @@

    Lighttpd

  4. 웹서버에서 실행하고자 하는 디렉토리에 lighttpd.conf라는 설정파일을 만듭니다. - 예제는 [link:http://redmine.lighttpd.net/projects/lighttpd/wiki/TutorialConfiguration 여기]에서 확인할 수 있습니다. + 예제는 여기([link:http://redmine.lighttpd.net/projects/lighttpd/wiki/TutorialConfiguration link])에서 확인할 수 있습니다.
  5. 설정 파일에서, server.document-root를 서버로 쓰고자 하는 디렉토리로 설정합니다. @@ -141,7 +140,7 @@

    IIS

    기본적으로 IIS는 .fbx, .obj 파일의 다운로드를 막아 놓았습니다. IIS에서 이러한 파일들이 다운로드 될 수 있도록 설정해야 합니다.

    - 다른 간단한 방법으로는 Stack Overflow의 [link:http://stackoverflow.com/q/12905426/24874 이곳에서 토론]을 확인해 보세요. + 다른 간단한 방법으로는 Stack Overflow에서 논의된 내용([link:http://stackoverflow.com/q/12905426/24874 link])을 확인해 보세요.

    diff --git a/docs/manual/ko/introduction/Installation.html b/docs/manual/ko/introduction/Installation.html index a0f27c0f1f612f..e66b0bdc0a58ac 100644 --- a/docs/manual/ko/introduction/Installation.html +++ b/docs/manual/ko/introduction/Installation.html @@ -141,8 +141,7 @@

    CommonJS 불러오기

    대부분의 자바스크립트 번들러는 이제 ES 모듈을 기본적으로 지원하지만, 오래된 빌드 도구들은 그렇지 않을 수 있습니다. - 이 경우에, 번들러에 ES 모듈을 인식할 수 있도록 설정해줄 수 있습니다. 예를들어 [link:http://browserify.org/ - Browserify] 는 [link:https://github.com/babel/babelify babelify] 플러그인을 불러오기만 하면 됩니다. + 이 경우에, 번들러에 ES 모듈을 인식할 수 있도록 설정해줄 수 있습니다. 예를들어 [link:http://browserify.org/ Browserify] 는 [link:https://github.com/babel/babelify babelify] 플러그인을 불러오기만 하면 됩니다.

    maps 불러오기

    @@ -159,7 +158,7 @@

    maps 불러오기

    이는 npm 패키지에서 주로 쓰이는 경로 작성법과 일치하고, 두 사용자군 모두에게 파일을 불러오는 데에 동일한 코드를 사용할 수 있게 해 줄 것입니다. 빌드 도구를 사용하지 않는 것을 선호하는 사용자들에게도, 간단한 JSON 맵핑을 통해 CDN이나 직접 파일 폴더에서 불러오는 것을 가능하게 해 줄 것입니다. 실험적 방법으로, [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map - 예제]처럼 map polyfill과 함께 import 해서 더 간단하게 사용해볼 수도 있습니다. + example]처럼 map polyfill과 함께 import 해서 더 간단하게 사용해볼 수도 있습니다.

    Node.js

    @@ -170,8 +169,7 @@

    Node.js

    첫 번째로, three.js는 웹을 목적으로 만들어졌기때문에, Node.js에서 항상 활용 가능하다고 보증할 수 없는 브라우저와 DOM API에 의존하고 있는 까닭입니다. - 이러한 문제들은 shims를 통해 [link:https://github.com/stackgl/headless-gl - headless-gl]처럼 해결하거나, [page:TextureLoader] 같은 컴포넌트를 커스터마이징 해서 해결 가능합니다. 다른 DOM API는 관련된 코드가 더 복잡하게 연관되어 있어, 수정하기 더 까다롭습니다. + 이러한 문제들은 shims를 통해 [link:https://github.com/stackgl/headless-gl headless-gl]처럼 해결하거나, [page:TextureLoader] 같은 컴포넌트를 커스터마이징 해서 해결 가능합니다. 다른 DOM API는 관련된 코드가 더 복잡하게 연관되어 있어, 수정하기 더 까다롭습니다. Node.js 지원을 향상시키기 위한 더 간단하고, 유지보수 가능한 pull 요청은 언제든지 환영이지만, 본인의 작업을 위한 issue 생성을 더 권장합니다.

    diff --git a/docs/manual/ko/introduction/Useful-links.html b/docs/manual/ko/introduction/Useful-links.html index 6786942cd80d9d..6fdccc23dd3c83 100644 --- a/docs/manual/ko/introduction/Useful-links.html +++ b/docs/manual/ko/introduction/Useful-links.html @@ -20,7 +20,7 @@

    참고 링크([name])

    도움이 되는 포럼

    - Three.js는 공식적으로[link:https://discourse.threejs.org/ 포럼] 과 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]에서 지원 요청을 받고 있습니다. + Three.js는 공식적으로 [link:https://discourse.threejs.org/ forum]과 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]에서 지원 요청을 받고 있습니다. 도움이 필요하다면, 저기로 가면 됩니다. 깃허브에서 도움 요청 이슈를 생성하지 마세요.

    diff --git a/editor/css/main.css b/editor/css/main.css index 693beac99f1319..57166ba30917cd 100644 --- a/editor/css/main.css +++ b/editor/css/main.css @@ -74,19 +74,21 @@ textarea, input { outline: none; } /* osx */ position: relative; display: block; width: 100%; + min-width: 300px; } -.TabbedPanel .Tabs .Tab { - padding: 10px; - text-transform: uppercase; -} + .TabbedPanel .Tabs .Tab { + padding: 10px; + text-transform: uppercase; + } -.TabbedPanel .Tabs .Panels { - position: relative; - display: block; - width: 100%; - height: 100%; -} + .TabbedPanel .Panels { + position: relative; + display: block; + width: 100%; + height: 100%; + min-width: 300px; + } /* Listbox */ .Listbox { diff --git a/editor/js/Menubar.File.js b/editor/js/Menubar.File.js index ea526c9e78964a..24860235b8e9f3 100644 --- a/editor/js/Menubar.File.js +++ b/editor/js/Menubar.File.js @@ -447,19 +447,17 @@ function MenubarFile( editor ) { if ( config.getKey( 'project/editable' ) ) { editButton = [ - '', ' var button = document.createElement( \'a\' );', ' button.href = \'https://threejs.org/editor/#file=\' + location.href.split( \'/\' ).slice( 0, - 1 ).join( \'/\' ) + \'/app.json\';', ' button.style.cssText = \'position: absolute; bottom: 20px; right: 20px; padding: 10px 16px; color: #fff; border: 1px solid #fff; border-radius: 20px; text-decoration: none;\';', ' button.target = \'_blank\';', ' button.textContent = \'EDIT\';', ' document.body.appendChild( button );', - '' ].join( '\n' ); } - content = content.replace( '\n\t\t\t/* edit button */\n', editButton ); + content = content.replace( '\t\t\t/* edit button */', editButton ); toZip[ 'index.html' ] = strToU8( content ); diff --git a/editor/js/Player.js b/editor/js/Player.js index a224fe6790c412..fce69b81578f27 100644 --- a/editor/js/Player.js +++ b/editor/js/Player.js @@ -21,6 +21,12 @@ function Player( editor ) { } ); + signals.windowResize.add( function () { + + player.setSize( container.dom.clientWidth, container.dom.clientHeight ); + + } ); + signals.startPlayer.add( function () { container.setDisplay( '' ); diff --git a/editor/js/Resizer.js b/editor/js/Resizer.js index 331fae479cbaa1..0c584f124a1554 100644 --- a/editor/js/Resizer.js +++ b/editor/js/Resizer.js @@ -31,13 +31,19 @@ function Resizer( editor ) { if ( event.isPrimary === false ) return; - const rect = dom.getBoundingClientRect(); - const x = ( document.body.offsetWidth - rect.right ) - event.movementX; + const offsetWidth = document.body.offsetWidth; + const clientX = event.clientX; + + const cX = clientX < 0 ? 0 : clientX > offsetWidth ? offsetWidth : clientX; + + const x = offsetWidth - cX; dom.style.right = x + 'px'; - document.getElementById( 'sidebar' ).style.width = ( x + rect.width ) + 'px'; - document.getElementById( 'viewport' ).style.right = ( x + rect.width ) + 'px'; + document.getElementById( 'sidebar' ).style.width = x + 'px'; + document.getElementById( 'player' ).style.right = x + 'px'; + document.getElementById( 'script' ).style.right = x + 'px'; + document.getElementById( 'viewport' ).style.right = x + 'px'; signals.windowResize.dispatch(); diff --git a/editor/sw.js b/editor/sw.js index 004dd21a9c70c4..68adb0afa505de 100644 --- a/editor/sw.js +++ b/editor/sw.js @@ -1,4 +1,4 @@ -// r132.1 +// r133 const cacheName = 'threejs-editor'; diff --git a/examples/files.json b/examples/files.json index b7cc2fcd104b7c..9ea70b49c07961 100644 --- a/examples/files.json +++ b/examples/files.json @@ -1,6 +1,5 @@ { "webgl": [ - "webgl_animation_cloth", "webgl_animation_keyframes", "webgl_animation_skinning_blending", "webgl_animation_skinning_additive_blending", @@ -206,7 +205,6 @@ "webgl_shaders_ocean", "webgl_shaders_sky", "webgl_shaders_tonemapping", - "webgl_shading_physical", "webgl_shadow_contact", "webgl_shadowmap", "webgl_shadowmap_performance", diff --git a/examples/js/exporters/USDZExporter.js b/examples/js/exporters/USDZExporter.js index 6c275a18eaffde..9c9e25c1261088 100644 --- a/examples/js/exporters/USDZExporter.js +++ b/examples/js/exporters/USDZExporter.js @@ -418,7 +418,17 @@ ${array.join( '' )} } - inputs.push( `${pad}float inputs:opacity = ${material.opacity}` ); + if ( material.alphaMap !== null ) { + + inputs.push( `${pad}float inputs:opacity.connect = ` ); + inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` ); + samplers.push( buildTexture( material.alphaMap, 'opacity' ) ); + + } else { + + inputs.push( `${pad}float inputs:opacity = ${material.opacity}` ); + + } if ( material.isMeshPhysicalMaterial ) { diff --git a/examples/js/geometries/ParametricGeometry.js b/examples/js/geometries/ParametricGeometry.js new file mode 100644 index 00000000000000..d7ccc4a1b603c3 --- /dev/null +++ b/examples/js/geometries/ParametricGeometry.js @@ -0,0 +1,115 @@ +( function () { + + /** + * Parametric Surfaces Geometry + * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html + */ + + class ParametricGeometry extends THREE.BufferGeometry { + + constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) { + + super(); + this.type = 'ParametricGeometry'; + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + const EPS = 0.00001; + const normal = new THREE.Vector3(); + const p0 = new THREE.Vector3(), + p1 = new THREE.Vector3(); + const pu = new THREE.Vector3(), + pv = new THREE.Vector3(); + + if ( func.length < 3 ) { + + console.error( 'THREE.ParametricGeometry: Function must now modify a THREE.Vector3 as third parameter.' ); + + } // generate vertices, normals and uvs + + + const sliceCount = slices + 1; + + for ( let i = 0; i <= stacks; i ++ ) { + + const v = i / stacks; + + for ( let j = 0; j <= slices; j ++ ) { + + const u = j / slices; // vertex + + func( u, v, p0 ); + vertices.push( p0.x, p0.y, p0.z ); // normal + // approximate tangent vectors via finite differences + + if ( u - EPS >= 0 ) { + + func( u - EPS, v, p1 ); + pu.subVectors( p0, p1 ); + + } else { + + func( u + EPS, v, p1 ); + pu.subVectors( p1, p0 ); + + } + + if ( v - EPS >= 0 ) { + + func( u, v - EPS, p1 ); + pv.subVectors( p0, p1 ); + + } else { + + func( u, v + EPS, p1 ); + pv.subVectors( p1, p0 ); + + } // cross product of tangent vectors returns surface normal + + + normal.crossVectors( pu, pv ).normalize(); + normals.push( normal.x, normal.y, normal.z ); // uv + + uvs.push( u, v ); + + } + + } // generate indices + + + for ( let i = 0; i < stacks; i ++ ) { + + for ( let j = 0; j < slices; j ++ ) { + + const a = i * sliceCount + j; + const b = i * sliceCount + j + 1; + const c = ( i + 1 ) * sliceCount + j + 1; + const d = ( i + 1 ) * sliceCount + j; // faces one and two + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } // build geometry + + + this.setIndex( indices ); + this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); + + } + + } + + THREE.ParametricGeometry = ParametricGeometry; + +} )(); diff --git a/examples/js/geometries/TextGeometry.js b/examples/js/geometries/TextGeometry.js new file mode 100644 index 00000000000000..094e58476ad684 --- /dev/null +++ b/examples/js/geometries/TextGeometry.js @@ -0,0 +1,49 @@ +( function () { + + /** + * Text = 3D Text + * + * parameters = { + * font: , // font + * + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: , // how far from text outline (including bevelOffset) is bevel + * bevelOffset: // how far from text outline does bevel start + * } + */ + + class TextGeometry extends THREE.ExtrudeGeometry { + + constructor( text, parameters = {} ) { + + const font = parameters.font; + + if ( ! ( font && font.isFont ) ) { + + console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' ); + return new THREE.BufferGeometry(); + + } + + const shapes = font.generateShapes( text, parameters.size ); // translate parameters to THREE.ExtrudeGeometry API + + parameters.depth = parameters.height !== undefined ? parameters.height : 50; // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + super( shapes, parameters ); + this.type = 'TextGeometry'; + + } + + } + + THREE.TextGeometry = TextGeometry; + +} )(); diff --git a/examples/js/loaders/FontLoader.js b/examples/js/loaders/FontLoader.js new file mode 100644 index 00000000000000..24521ccae3c221 --- /dev/null +++ b/examples/js/loaders/FontLoader.js @@ -0,0 +1,183 @@ +( function () { + + class FontLoader extends THREE.Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + let json; + + try { + + json = JSON.parse( text ); + + } catch ( e ) { + + console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' ); + json = JSON.parse( text.substring( 65, text.length - 2 ) ); + + } + + const font = scope.parse( json ); + if ( onLoad ) onLoad( font ); + + }, onProgress, onError ); + + } + + parse( json ) { + + return new Font( json ); + + } + + } // + + + class Font { + + constructor( data ) { + + this.type = 'Font'; + this.data = data; + + } + + generateShapes( text, size = 100 ) { + + const shapes = []; + const paths = createPaths( text, size, this.data ); + + for ( let p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + + } + + } + + function createPaths( text, size, data ) { + + const chars = Array.from( text ); + const scale = size / data.resolution; + const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; + const paths = []; + let offsetX = 0, + offsetY = 0; + + for ( let i = 0; i < chars.length; i ++ ) { + + const char = chars[ i ]; + + if ( char === '\n' ) { + + offsetX = 0; + offsetY -= line_height; + + } else { + + const ret = createPath( char, scale, offsetX, offsetY, data ); + offsetX += ret.offsetX; + paths.push( ret.path ); + + } + + } + + return paths; + + } + + function createPath( char, scale, offsetX, offsetY, data ) { + + const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; + + if ( ! glyph ) { + + console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); + return; + + } + + const path = new THREE.ShapePath(); + let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; + + if ( glyph.o ) { + + const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + + for ( let i = 0, l = outline.length; i < l; ) { + + const action = outline[ i ++ ]; + + switch ( action ) { + + case 'm': + // moveTo + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + path.moveTo( x, y ); + break; + + case 'l': + // lineTo + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + path.lineTo( x, y ); + break; + + case 'q': + // quadraticCurveTo + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + break; + + case 'b': + // bezierCurveTo + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + cpx2 = outline[ i ++ ] * scale + offsetX; + cpy2 = outline[ i ++ ] * scale + offsetY; + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + break; + + } + + } + + } + + return { + offsetX: glyph.ha * scale, + path: path + }; + + } + + Font.prototype.isFont = true; + + THREE.Font = Font; + THREE.FontLoader = FontLoader; + +} )(); diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 690e992cc0fa41..db4b9afa66217d 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -581,9 +581,8 @@ if ( extension.clearcoatNormalTexture.scale !== undefined ) { - const scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - - materialParams.clearcoatNormalScale = new THREE.Vector2( scale, - scale ); + const scale = extension.clearcoatNormalTexture.scale; + materialParams.clearcoatNormalScale = new THREE.Vector2( scale, scale ); } @@ -2110,7 +2109,28 @@ _getNodeRef( cache, index, object ) { if ( cache.refs[ index ] <= 1 ) return object; - const ref = object.clone(); + const ref = object.clone(); // Propagates mappings to the cloned object, prevents mappings on the + // original object from being lost. + + const updateMappings = ( original, clone ) => { + + const mappings = this.associations.get( original ); + + if ( mappings != null ) { + + this.associations.set( clone, mappings ); + + } + + for ( const [ i, child ] of original.children.entries() ) { + + updateMappings( child, clone.children[ i ] ); + + } + + }; + + updateMappings( object, ref ); ref.name += '_instance_' + cache.uses[ index ] ++; return ref; @@ -2488,28 +2508,12 @@ const URL = self.URL || self.webkitURL; let sourceURI = source.uri || ''; let isObjectURL = false; - let hasAlpha = true; - const isJPEG = sourceURI.search( /\.jpe?g($|\?)/i ) > 0 || sourceURI.search( /^data\:image\/jpeg/ ) === 0; - if ( source.mimeType === 'image/jpeg' || isJPEG ) hasAlpha = false; if ( source.bufferView !== undefined ) { // Load binary image data from bufferView, if provided. sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { - if ( source.mimeType === 'image/png' ) { - - // Inspect the PNG 'IHDR' chunk to determine whether the image could have an - // alpha channel. This check is conservative — the image could have an alpha - // channel with all values == 1, and the indexed type (colorType == 3) only - // sometimes contains alpha. - // - // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header - const colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false ); - hasAlpha = colorType === 6 || colorType === 4 || colorType === 3; - - } - isObjectURL = true; const blob = new Blob( [ bufferView ], { type: source.mimeType @@ -2557,9 +2561,7 @@ } texture.flipY = false; - if ( textureDef.name ) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space. - - if ( ! hasAlpha ) texture.format = THREE.RGBFormat; + if ( textureDef.name ) texture.name = textureDef.name; const samplers = json.samplers || {}; const sampler = samplers[ textureDef.sampler ] || {}; texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; @@ -2567,8 +2569,7 @@ texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; parser.associations.set( texture, { - type: 'textures', - index: textureIndex + textures: textureIndex } ); return texture; @@ -2638,7 +2639,7 @@ const geometry = mesh.geometry; let material = mesh.material; - const useVertexTangents = geometry.attributes.tangent !== undefined; + const useDerivativeTangents = geometry.attributes.tangent === undefined; const useVertexColors = geometry.attributes.color !== undefined; const useFlatShading = geometry.attributes.normal === undefined; @@ -2680,11 +2681,11 @@ } // Clone the material if it will be modified - if ( useVertexTangents || useVertexColors || useFlatShading ) { + if ( useDerivativeTangents || useVertexColors || useFlatShading ) { let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; - if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; + if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; if ( useVertexColors ) cacheKey += 'vertex-colors:'; if ( useFlatShading ) cacheKey += 'flat-shading:'; let cachedMaterial = this.cache.get( cacheKey ); @@ -2695,7 +2696,7 @@ if ( useVertexColors ) cachedMaterial.vertexColors = true; if ( useFlatShading ) cachedMaterial.flatShading = true; - if ( useVertexTangents ) { + if ( useDerivativeTangents ) { // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; @@ -2832,13 +2833,13 @@ if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { - pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - - materialParams.normalScale = new THREE.Vector2( 1, - 1 ); + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + materialParams.normalScale = new THREE.Vector2( 1, 1 ); if ( materialDef.normalTexture.scale !== undefined ) { - materialParams.normalScale.set( materialDef.normalTexture.scale, - materialDef.normalTexture.scale ); + const scale = materialDef.normalTexture.scale; + materialParams.normalScale.set( scale, scale ); } @@ -2888,8 +2889,7 @@ if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; assignExtrasToUserData( material, materialDef ); parser.associations.set( material, { - type: 'materials', - index: materialIndex + materials: materialIndex } ); if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); return material; @@ -3082,6 +3082,15 @@ } + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + parser.associations.set( meshes[ i ], { + meshes: meshIndex, + primitives: i + } ); + + } + if ( meshes.length === 1 ) { return meshes[ 0 ]; @@ -3089,6 +3098,9 @@ } const group = new THREE.Group(); + parser.associations.set( group, { + meshes: meshIndex + } ); for ( let i = 0, il = meshes.length; i < il; i ++ ) { @@ -3466,10 +3478,13 @@ } - parser.associations.set( node, { - type: 'nodes', - index: nodeIndex - } ); + if ( ! parser.associations.has( node ) ) { + + parser.associations.set( node, {} ); + + } + + parser.associations.get( node ).nodes = nodeIndex; return node; } ); @@ -3499,12 +3514,44 @@ for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) ); } return Promise.all( pending ).then( function () { + // Removes dangling associations, associations that reference a node that + // didn't make it into the scene. + const reduceAssociations = node => { + + const reducedAssociations = new Map(); + + for ( const [ key, value ] of parser.associations ) { + + if ( key instanceof THREE.Material || key instanceof THREE.Texture ) { + + reducedAssociations.set( key, value ); + + } + + } + + node.traverse( node => { + + const mappings = parser.associations.get( node ); + + if ( mappings != null ) { + + reducedAssociations.set( node, mappings ); + + } + + } ); + return reducedAssociations; + + }; + + parser.associations = reduceAssociations( scene ); return scene; } ); @@ -3513,7 +3560,7 @@ } - function buildNodeHierachy( nodeId, parentObject, json, parser ) { + function buildNodeHierarchy( nodeId, parentObject, json, parser ) { const nodeDef = json.nodes[ nodeId ]; return parser.getDependency( 'node', nodeId ).then( function ( node ) { @@ -3587,7 +3634,7 @@ for ( let i = 0, il = children.length; i < il; i ++ ) { const child = children[ i ]; - pending.push( buildNodeHierachy( child, node, json, parser ) ); + pending.push( buildNodeHierarchy( child, node, json, parser ) ); } diff --git a/examples/js/loaders/RGBMLoader.js b/examples/js/loaders/RGBMLoader.js index 475a5336968f69..8570b442b248cc 100644 --- a/examples/js/loaders/RGBMLoader.js +++ b/examples/js/loaders/RGBMLoader.js @@ -310,7 +310,7 @@ } else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { var gr = data[ off + ( x << 1 ) ], - al = rs( data, off + ( x << i ) ) == tr ? 0 : 255; + al = rs( data, off + ( x << 1 ) ) == tr ? 0 : 255; bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr; } @@ -485,7 +485,7 @@ break; - } //else { log("unknown chunk type", type, len); } + } //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } offset += len; @@ -498,7 +498,6 @@ var fr = out.frames[ out.frames.length - 1 ]; fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); - foff = 0; } @@ -1111,8 +1110,8 @@ paeth = UPNG.decode._paeth; bpp = Math.ceil( bpp / 8 ); - var i = 0, - di = 1, + var i, + di, type = data[ off ], x = 0; if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; diff --git a/examples/js/loaders/SVGLoader.js b/examples/js/loaders/SVGLoader.js index d283d1f8653626..7d01d48e9a24c1 100644 --- a/examples/js/loaders/SVGLoader.js +++ b/examples/js/loaders/SVGLoader.js @@ -856,6 +856,7 @@ addStyle( 'fill', 'fill' ); addStyle( 'fill-opacity', 'fillOpacity', clamp ); + addStyle( 'fill-rule', 'fillRule' ); addStyle( 'opacity', 'opacity', clamp ); addStyle( 'stroke', 'stroke' ); addStyle( 'stroke-opacity', 'strokeOpacity', clamp ); diff --git a/examples/js/postprocessing/SSRPass.js b/examples/js/postprocessing/SSRPass.js index 5f8f2c8e2c3b9b..82659fcbb8e253 100644 --- a/examples/js/postprocessing/SSRPass.js +++ b/examples/js/postprocessing/SSRPass.js @@ -9,7 +9,6 @@ width, height, selects, - encoding, bouncing = false, groundReflector } ) { @@ -26,7 +25,6 @@ this.output = 0; this.maxDistance = THREE.SSRShader.uniforms.maxDistance.value; this.thickness = THREE.SSRShader.uniforms.thickness.value; - this.encoding = encoding; this.tempColor = new THREE.Color(); this._selects = selects; this.selective = Array.isArray( this._selects ); @@ -307,7 +305,6 @@ ) { // render beauty and depth - if ( this.encoding ) this.beautyRenderTarget.texture.encoding = this.encoding; renderer.setRenderTarget( this.beautyRenderTarget ); renderer.clear(); diff --git a/examples/js/postprocessing/SSRrPass.js b/examples/js/postprocessing/SSRrPass.js index 1ea37f92b75be7..6093c211a5bb09 100644 --- a/examples/js/postprocessing/SSRrPass.js +++ b/examples/js/postprocessing/SSRrPass.js @@ -8,8 +8,7 @@ camera, width, height, - selects, - encoding + selects } ) { super(); @@ -24,7 +23,6 @@ this.ior = THREE.SSRrShader.uniforms.ior.value; this.maxDistance = THREE.SSRrShader.uniforms.maxDistance.value; this.surfDist = THREE.SSRrShader.uniforms.surfDist.value; - this.encoding = encoding; this.tempColor = new THREE.Color(); this.selects = selects; this._specular = THREE.SSRrShader.defines.SPECULAR; @@ -225,7 +223,6 @@ ) { // render beauty and depth - if ( this.encoding ) this.beautyRenderTarget.texture.encoding = this.encoding; renderer.setRenderTarget( this.beautyRenderTarget ); renderer.clear(); this.scene.children.forEach( child => { diff --git a/examples/js/utils/PackedPhongMaterial.js b/examples/js/utils/PackedPhongMaterial.js index a58db381f431d1..3b7b90e346b5bf 100644 --- a/examples/js/utils/PackedPhongMaterial.js +++ b/examples/js/utils/PackedPhongMaterial.js @@ -21,7 +21,7 @@ value: null } } ] ); - this.vertexShader = [ '#define PHONG', 'varying vec3 vViewPosition;', '#ifndef FLAT_SHADED', 'varying vec3 vNormal;', '#endif', THREE.ShaderChunk.common, THREE.ShaderChunk.uv_pars_vertex, THREE.ShaderChunk.uv2_pars_vertex, THREE.ShaderChunk.displacementmap_pars_vertex, THREE.ShaderChunk.envmap_pars_vertex, THREE.ShaderChunk.color_pars_vertex, THREE.ShaderChunk.fog_pars_vertex, THREE.ShaderChunk.morphtarget_pars_vertex, THREE.ShaderChunk.skinning_pars_vertex, THREE.ShaderChunk.shadowmap_pars_vertex, THREE.ShaderChunk.logdepthbuf_pars_vertex, THREE.ShaderChunk.clipping_planes_pars_vertex, `#ifdef USE_PACKED_NORMAL + this.vertexShader = [ '#define PHONG', 'varying vec3 vViewPosition;', THREE.ShaderChunk.common, THREE.ShaderChunk.uv_pars_vertex, THREE.ShaderChunk.uv2_pars_vertex, THREE.ShaderChunk.displacementmap_pars_vertex, THREE.ShaderChunk.envmap_pars_vertex, THREE.ShaderChunk.color_pars_vertex, THREE.ShaderChunk.fog_pars_vertex, THREE.ShaderChunk.normal_pars_vertex, THREE.ShaderChunk.morphtarget_pars_vertex, THREE.ShaderChunk.skinning_pars_vertex, THREE.ShaderChunk.shadowmap_pars_vertex, THREE.ShaderChunk.logdepthbuf_pars_vertex, THREE.ShaderChunk.clipping_planes_pars_vertex, `#ifdef USE_PACKED_NORMAL #if USE_PACKED_NORMAL == 0 vec3 decodeNormal(vec3 packedNormal) { @@ -87,13 +87,13 @@ #ifdef USE_TANGENT vec3 objectTangent = vec3( tangent.xyz ); #endif - `, THREE.ShaderChunk.morphnormal_vertex, THREE.ShaderChunk.skinbase_vertex, THREE.ShaderChunk.skinnormal_vertex, THREE.ShaderChunk.defaultnormal_vertex, '#ifndef FLAT_SHADED', ' vNormal = normalize( transformedNormal );', '#endif', THREE.ShaderChunk.begin_vertex, `#ifdef USE_PACKED_POSITION + `, THREE.ShaderChunk.morphnormal_vertex, THREE.ShaderChunk.skinbase_vertex, THREE.ShaderChunk.skinnormal_vertex, THREE.ShaderChunk.defaultnormal_vertex, THREE.ShaderChunk.normal_vertex, THREE.ShaderChunk.begin_vertex, `#ifdef USE_PACKED_POSITION #if USE_PACKED_POSITION == 0 transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz; #endif #endif`, THREE.ShaderChunk.morphtarget_vertex, THREE.ShaderChunk.skinning_vertex, THREE.ShaderChunk.displacementmap_vertex, THREE.ShaderChunk.project_vertex, THREE.ShaderChunk.logdepthbuf_vertex, THREE.ShaderChunk.clipping_planes_vertex, 'vViewPosition = - mvPosition.xyz;', THREE.ShaderChunk.worldpos_vertex, THREE.ShaderChunk.envmap_vertex, THREE.ShaderChunk.shadowmap_vertex, THREE.ShaderChunk.fog_vertex, '}' ].join( '\n' ); // Use the original THREE.MeshPhongMaterial's fragmentShader. - this.fragmentShader = [ '#define PHONG', 'uniform vec3 diffuse;', 'uniform vec3 emissive;', 'uniform vec3 specular;', 'uniform float shininess;', 'uniform float opacity;', THREE.ShaderChunk.common, THREE.ShaderChunk.packing, THREE.ShaderChunk.dithering_pars_fragment, THREE.ShaderChunk.color_pars_fragment, THREE.ShaderChunk.uv_pars_fragment, THREE.ShaderChunk.uv2_pars_fragment, THREE.ShaderChunk.map_pars_fragment, THREE.ShaderChunk.alphamap_pars_fragment, THREE.ShaderChunk.aomap_pars_fragment, THREE.ShaderChunk.lightmap_pars_fragment, THREE.ShaderChunk.emissivemap_pars_fragment, THREE.ShaderChunk.envmap_common_pars_fragment, THREE.ShaderChunk.envmap_pars_fragment, THREE.ShaderChunk.cube_uv_reflection_fragment, THREE.ShaderChunk.fog_pars_fragment, THREE.ShaderChunk.bsdfs, THREE.ShaderChunk.lights_pars_begin, THREE.ShaderChunk.lights_phong_pars_fragment, THREE.ShaderChunk.shadowmap_pars_fragment, THREE.ShaderChunk.bumpmap_pars_fragment, THREE.ShaderChunk.normalmap_pars_fragment, THREE.ShaderChunk.specularmap_pars_fragment, THREE.ShaderChunk.logdepthbuf_pars_fragment, THREE.ShaderChunk.clipping_planes_pars_fragment, 'void main() {', THREE.ShaderChunk.clipping_planes_fragment, 'vec4 diffuseColor = vec4( diffuse, opacity );', 'ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );', 'vec3 totalEmissiveRadiance = emissive;', THREE.ShaderChunk.logdepthbuf_fragment, THREE.ShaderChunk.map_fragment, THREE.ShaderChunk.color_fragment, THREE.ShaderChunk.alphamap_fragment, THREE.ShaderChunk.alphatest_fragment, THREE.ShaderChunk.specularmap_fragment, THREE.ShaderChunk.normal_fragment_begin, THREE.ShaderChunk.normal_fragment_maps, THREE.ShaderChunk.emissivemap_fragment, // accumulation + this.fragmentShader = [ '#define PHONG', 'uniform vec3 diffuse;', 'uniform vec3 emissive;', 'uniform vec3 specular;', 'uniform float shininess;', 'uniform float opacity;', THREE.ShaderChunk.common, THREE.ShaderChunk.packing, THREE.ShaderChunk.dithering_pars_fragment, THREE.ShaderChunk.color_pars_fragment, THREE.ShaderChunk.uv_pars_fragment, THREE.ShaderChunk.uv2_pars_fragment, THREE.ShaderChunk.map_pars_fragment, THREE.ShaderChunk.alphamap_pars_fragment, THREE.ShaderChunk.aomap_pars_fragment, THREE.ShaderChunk.lightmap_pars_fragment, THREE.ShaderChunk.emissivemap_pars_fragment, THREE.ShaderChunk.envmap_common_pars_fragment, THREE.ShaderChunk.envmap_pars_fragment, THREE.ShaderChunk.cube_uv_reflection_fragment, THREE.ShaderChunk.fog_pars_fragment, THREE.ShaderChunk.bsdfs, THREE.ShaderChunk.lights_pars_begin, THREE.ShaderChunk.normal_pars_fragment, THREE.ShaderChunk.lights_phong_pars_fragment, THREE.ShaderChunk.shadowmap_pars_fragment, THREE.ShaderChunk.bumpmap_pars_fragment, THREE.ShaderChunk.normalmap_pars_fragment, THREE.ShaderChunk.specularmap_pars_fragment, THREE.ShaderChunk.logdepthbuf_pars_fragment, THREE.ShaderChunk.clipping_planes_pars_fragment, 'void main() {', THREE.ShaderChunk.clipping_planes_fragment, 'vec4 diffuseColor = vec4( diffuse, opacity );', 'ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );', 'vec3 totalEmissiveRadiance = emissive;', THREE.ShaderChunk.logdepthbuf_fragment, THREE.ShaderChunk.map_fragment, THREE.ShaderChunk.color_fragment, THREE.ShaderChunk.alphamap_fragment, THREE.ShaderChunk.alphatest_fragment, THREE.ShaderChunk.specularmap_fragment, THREE.ShaderChunk.normal_fragment_begin, THREE.ShaderChunk.normal_fragment_maps, THREE.ShaderChunk.emissivemap_fragment, // accumulation THREE.ShaderChunk.lights_phong_fragment, THREE.ShaderChunk.lights_fragment_begin, THREE.ShaderChunk.lights_fragment_maps, THREE.ShaderChunk.lights_fragment_end, // modulation THREE.ShaderChunk.aomap_fragment, 'vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;', THREE.ShaderChunk.envmap_fragment, 'gl_FragColor = vec4( outgoingLight, diffuseColor.a );', THREE.ShaderChunk.tonemapping_fragment, THREE.ShaderChunk.encodings_fragment, THREE.ShaderChunk.fog_fragment, THREE.ShaderChunk.premultiplied_alpha_fragment, THREE.ShaderChunk.dithering_fragment, '}' ].join( '\n' ); this.setValues( parameters ); diff --git a/examples/jsm/exporters/USDZExporter.js b/examples/jsm/exporters/USDZExporter.js index 04b5b6207da40c..af26ecd2f86317 100644 --- a/examples/jsm/exporters/USDZExporter.js +++ b/examples/jsm/exporters/USDZExporter.js @@ -449,7 +449,18 @@ function buildMaterial( material, textures ) { } - inputs.push( `${ pad }float inputs:opacity = ${ material.opacity }` ); + if ( material.alphaMap !== null ) { + + inputs.push( `${pad}float inputs:opacity.connect = ` ); + inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` ); + + samplers.push( buildTexture( material.alphaMap, 'opacity' ) ); + + } else { + + inputs.push( `${pad}float inputs:opacity = ${material.opacity}` ); + + } if ( material.isMeshPhysicalMaterial ) { diff --git a/examples/jsm/geometries/ParametricGeometry.js b/examples/jsm/geometries/ParametricGeometry.js index 8bbed826c8b8c2..4f26b7e7405226 100644 --- a/examples/jsm/geometries/ParametricGeometry.js +++ b/examples/jsm/geometries/ParametricGeometry.js @@ -132,4 +132,4 @@ class ParametricGeometry extends BufferGeometry { } -export { ParametricGeometry, ParametricGeometry as ParametricBufferGeometry }; +export { ParametricGeometry }; diff --git a/examples/jsm/geometries/TextGeometry.js b/examples/jsm/geometries/TextGeometry.js index 8a9e4255af08db..891234afdb5397 100644 --- a/examples/jsm/geometries/TextGeometry.js +++ b/examples/jsm/geometries/TextGeometry.js @@ -54,4 +54,4 @@ class TextGeometry extends ExtrudeGeometry { } -export { TextGeometry, TextGeometry as TextBufferGeometry }; +export { TextGeometry }; diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index f79b735786498a..3e95e5391d5bf3 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -692,8 +692,7 @@ class GLTFMaterialsClearcoatExtension { const scale = extension.clearcoatNormalTexture.scale; - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - materialParams.clearcoatNormalScale = new Vector2( scale, - scale ); + materialParams.clearcoatNormalScale = new Vector2( scale, scale ); } @@ -2369,6 +2368,27 @@ class GLTFParser { const ref = object.clone(); + // Propagates mappings to the cloned object, prevents mappings on the + // original object from being lost. + const updateMappings = ( original, clone ) => { + + const mappings = this.associations.get( original ); + if ( mappings != null ) { + + this.associations.set( clone, mappings ); + + } + + for ( const [ i, child ] of original.children.entries() ) { + + updateMappings( child, clone.children[ i ] ); + + } + + }; + + updateMappings( object, ref ); + ref.name += '_instance_' + ( cache.uses[ index ] ++ ); return ref; @@ -2899,7 +2919,7 @@ class GLTFParser { const geometry = mesh.geometry; let material = mesh.material; - const useVertexTangents = geometry.attributes.tangent !== undefined; + const useDerivativeTangents = geometry.attributes.tangent === undefined; const useVertexColors = geometry.attributes.color !== undefined; const useFlatShading = geometry.attributes.normal === undefined; @@ -2944,12 +2964,12 @@ class GLTFParser { } // Clone the material if it will be modified - if ( useVertexTangents || useVertexColors || useFlatShading ) { + if ( useDerivativeTangents || useVertexColors || useFlatShading ) { let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; - if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; + if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; if ( useVertexColors ) cacheKey += 'vertex-colors:'; if ( useFlatShading ) cacheKey += 'flat-shading:'; @@ -2962,7 +2982,7 @@ class GLTFParser { if ( useVertexColors ) cachedMaterial.vertexColors = true; if ( useFlatShading ) cachedMaterial.flatShading = true; - if ( useVertexTangents ) { + if ( useDerivativeTangents ) { // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; @@ -3109,12 +3129,13 @@ class GLTFParser { pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - materialParams.normalScale = new Vector2( 1, - 1 ); + materialParams.normalScale = new Vector2( 1, 1 ); if ( materialDef.normalTexture.scale !== undefined ) { - materialParams.normalScale.set( materialDef.normalTexture.scale, - materialDef.normalTexture.scale ); + const scale = materialDef.normalTexture.scale; + + materialParams.normalScale.set( scale, scale ); } @@ -3848,12 +3869,46 @@ class GLTFParser { for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) ); } return Promise.all( pending ).then( function () { + // Removes dangling associations, associations that reference a node that + // didn't make it into the scene. + const reduceAssociations = ( node ) => { + + const reducedAssociations = new Map(); + + for ( const [ key, value ] of parser.associations ) { + + if ( key instanceof Material || key instanceof Texture ) { + + reducedAssociations.set( key, value ); + + } + + } + + node.traverse( ( node ) => { + + const mappings = parser.associations.get( node ); + + if ( mappings != null ) { + + reducedAssociations.set( node, mappings ); + + } + + } ); + + return reducedAssociations; + + }; + + parser.associations = reduceAssociations( scene ); + return scene; } ); @@ -3862,7 +3917,7 @@ class GLTFParser { } -function buildNodeHierachy( nodeId, parentObject, json, parser ) { +function buildNodeHierarchy( nodeId, parentObject, json, parser ) { const nodeDef = json.nodes[ nodeId ]; @@ -3946,7 +4001,7 @@ function buildNodeHierachy( nodeId, parentObject, json, parser ) { for ( let i = 0, il = children.length; i < il; i ++ ) { const child = children[ i ]; - pending.push( buildNodeHierachy( child, node, json, parser ) ); + pending.push( buildNodeHierarchy( child, node, json, parser ) ); } diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index 2178a3ddf17568..a1e87cd8e842e3 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -251,7 +251,7 @@ UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) { } else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) { - var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << i ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; + var gr = data[ off + ( x << 1 ) ], al = ( rs( data, off + ( x << 1 ) ) == tr ) ? 0 : 255; bf32[ to + x ] = ( al << 24 ) | ( gr << 16 ) | ( gr << 8 ) | gr; } @@ -397,7 +397,7 @@ UPNG.decode = function ( buff ) { } - //else { log("unknown chunk type", type, len); } + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } offset += len; bin.readUint( data, offset ); offset += 4; @@ -406,7 +406,7 @@ UPNG.decode = function ( buff ) { if ( foff != 0 ) { var fr = out.frames[ out.frames.length - 1 ]; - fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); foff = 0; + fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height ); } @@ -819,7 +819,7 @@ UPNG.decode._filterZero = function ( data, out, off, w, h ) { var bpp = UPNG.decode._getBPP( out ), bpl = Math.ceil( w * bpp / 8 ), paeth = UPNG.decode._paeth; bpp = Math.ceil( bpp / 8 ); - var i = 0, di = 1, type = data[ off ], x = 0; + var i, di, type = data[ off ], x = 0; if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ]; if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = ( data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) ) & 255; diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index fc0819e5cae7b7..4c0b2f71802c3f 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -1007,6 +1007,7 @@ class SVGLoader extends Loader { addStyle( 'fill', 'fill' ); addStyle( 'fill-opacity', 'fillOpacity', clamp ); + addStyle( 'fill-rule', 'fillRule' ); addStyle( 'opacity', 'opacity', clamp ); addStyle( 'stroke', 'stroke' ); addStyle( 'stroke-opacity', 'strokeOpacity', clamp ); diff --git a/examples/jsm/renderers/nodes/Nodes.js b/examples/jsm/renderers/nodes/Nodes.js index 754b8584347c80..fabf78dcb9097f 100644 --- a/examples/jsm/renderers/nodes/Nodes.js +++ b/examples/jsm/renderers/nodes/Nodes.js @@ -68,6 +68,9 @@ import SplitNode from './utils/SplitNode.js'; import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js'; import TimerNode from './utils/TimerNode.js'; +// procedural +import CheckerNode from './procedural/CheckerNode.js'; + // core export * from './core/constants.js'; @@ -151,6 +154,9 @@ export { JoinNode, SplitNode, SpriteSheetUVNode, - TimerNode + TimerNode, + + // procedural + CheckerNode }; diff --git a/examples/jsm/renderers/nodes/ShaderNode.js b/examples/jsm/renderers/nodes/ShaderNode.js new file mode 100644 index 00000000000000..df50567de05232 --- /dev/null +++ b/examples/jsm/renderers/nodes/ShaderNode.js @@ -0,0 +1,193 @@ +// inputs +import ColorNode from './inputs/ColorNode.js'; +import FloatNode from './inputs/FloatNode.js'; +import Vector2Node from './inputs/Vector2Node.js'; +import Vector3Node from './inputs/Vector3Node.js'; +import Vector4Node from './inputs/Vector4Node.js'; + +// math +import MathNode from './math/MathNode.js'; +import OperatorNode from './math/OperatorNode.js'; + +// utils +import JoinNode from './utils/JoinNode.js'; +import SplitNode from './utils/SplitNode.js'; + +// core +import { Vector2, Vector3, Vector4, Color } from 'three'; + +const NodeHandler = { + + get: function ( node, prop ) { + + // Split Properties Pass + + if ( typeof prop === 'string' && node[ prop ] === undefined ) { + + const splitProps = prop.match( /^[xyzw]{1,4}$/ ); + + if ( splitProps !== null ) { + + return ShaderNodeObject( new SplitNode( node, splitProps[ 0 ] ) ); + + } + + } + + return node[ prop ]; + + } + +}; + +export const ShaderNodeObject = ( obj ) => { + + const type = typeof obj; + + if ( type === 'number' ) { + + return ShaderNodeObject( new FloatNode( obj ).setConst( true ) ); + + } else if ( type === 'object' ) { + + if ( obj.isNode === true ) { + + const node = obj; + + if ( node.isProxyNode !== true ) { + + node.isProxyNode = true; + + return new Proxy( node, NodeHandler ); + + } + + } + + } + + return obj; + +}; + +export const ShaderNodeArray = ( array ) => { + + const len = array.length; + + for ( let i = 0; i < len; i ++ ) { + + array[ i ] = ShaderNodeObject( array[ i ] ); + + } + + return array; + +}; + +export const ShaderNodeScript = ( jsFunc ) => { + + return ( ...params ) => { + + ShaderNodeArray( params ); + + return ShaderNodeObject( jsFunc( ...params ) ); + + }; + +}; + +export const ShaderNode = ( obj ) => { + + return ShaderNodeScript( obj ); + +}; + +// +// Node Material Shader Syntax +// + +export const uniform = ShaderNodeScript( ( inputNode ) => { + + inputNode.setConst( false ); + + return inputNode; + +} ); + +export const float = ( val ) => { + + return ShaderNodeObject( new FloatNode( val ).setConst( true ) ); + +}; + +export const color = ( ...params ) => { + + return ShaderNodeObject( new ColorNode( new Color( ...params ) ).setConst( true ) ); + +}; + +export const join = ( ...params ) => { + + return ShaderNodeObject( new JoinNode( ShaderNodeArray( params ) ) ); + +}; + +export const vec2 = ( ...params ) => { + + return ShaderNodeObject( new Vector2Node( new Vector2( ...params ) ).setConst( true ) ); + +}; + +export const vec3 = ( ...params ) => { + + return ShaderNodeObject( new Vector3Node( new Vector3( ...params ) ).setConst( true ) ); + +}; + +export const vec4 = ( ...params ) => { + + return ShaderNodeObject( new Vector4Node( new Vector4( ...params ) ).setConst( true ) ); + +}; + +export const add = ( ...params ) => { + + return ShaderNodeObject( new OperatorNode( '+', ...ShaderNodeArray( params ) ) ); + +}; + +export const sub = ( ...params ) => { + + return new OperatorNode( '-', ...ShaderNodeArray( params ) ); + +}; + +export const mul = ( ...params ) => { + + return ShaderNodeObject( new OperatorNode( '*', ...ShaderNodeArray( params ) ) ); + +}; + +export const div = ( ...params ) => { + + return ShaderNodeObject( new OperatorNode( '/', ...ShaderNodeArray( params ) ) ); + +}; + +export const floor = ( ...params ) => { + + return ShaderNodeObject( new MathNode( 'floor', ...ShaderNodeArray( params ) ) ); + +}; + +export const mod = ( ...params ) => { + + return ShaderNodeObject( new MathNode( 'mod', ...ShaderNodeArray( params ) ) ); + +}; + +export const sign = ( ...params ) => { + + return ShaderNodeObject( new MathNode( 'sign', ...ShaderNodeArray( params ) ) ); + +}; diff --git a/examples/jsm/renderers/nodes/accessors/CameraNode.js b/examples/jsm/renderers/nodes/accessors/CameraNode.js index a78a5a669a3b20..160dd82dbf35b9 100644 --- a/examples/jsm/renderers/nodes/accessors/CameraNode.js +++ b/examples/jsm/renderers/nodes/accessors/CameraNode.js @@ -9,6 +9,8 @@ class CameraNode extends Object3DNode { super( scope ); + this._inputNode = null; + } getNodeType( builder ) { @@ -47,37 +49,17 @@ class CameraNode extends Object3DNode { } - generate( builder, output ) { - - const nodeData = builder.getDataFromNode( this ); - - let inputNode = this._inputNode; - - if ( nodeData.inputNode === undefined ) { - - const scope = this.scope; - - if ( scope === CameraNode.PROJECTION_MATRIX ) { - - if ( inputNode === null || inputNode.isMatrix4Node !== true ) { + generate( builder ) { - inputNode = new Matrix4Node( null ); - - } - - } else { - - return super.generate( builder, output ); - - } + const scope = this.scope; - this._inputNode = inputNode; + if ( scope === CameraNode.PROJECTION_MATRIX ) { - nodeData.inputNode = inputNode; + this._inputNode = new Matrix4Node( null ); } - return inputNode.build( builder, output ); + return super.generate( builder ); } diff --git a/examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js b/examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js index 78c511f880d161..e047e8063fd909 100644 --- a/examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js +++ b/examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js @@ -16,14 +16,12 @@ class ModelViewProjectionNode extends Node { } - generate( builder, output ) { - - const type = this.getNodeType( builder ); + generate( builder ) { const mvpSnipped = this._mvpMatrix.build( builder ); const positionSnipped = this.position.build( builder, 'vec3' ); - return builder.format( `( ${mvpSnipped} * vec4( ${positionSnipped}, 1.0 ) )`, type, output ); + return `( ${mvpSnipped} * vec4( ${positionSnipped}, 1.0 ) )`; } diff --git a/examples/jsm/renderers/nodes/accessors/NormalNode.js b/examples/jsm/renderers/nodes/accessors/NormalNode.js index 084e9d425b1059..f974110160c290 100644 --- a/examples/jsm/renderers/nodes/accessors/NormalNode.js +++ b/examples/jsm/renderers/nodes/accessors/NormalNode.js @@ -21,61 +21,29 @@ class NormalNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.getNodeType( builder ); - const nodeData = builder.getDataFromNode( this, builder.shaderStage ); const scope = this.scope; - let localNormalNode = nodeData.localNormalNode; + let outputNode = null; - if ( localNormalNode === undefined ) { + if ( scope === NormalNode.LOCAL ) { - localNormalNode = new AttributeNode( 'normal', 'vec3' ); + outputNode = new AttributeNode( 'normal', 'vec3' ); - nodeData.localNormalNode = localNormalNode; + } else if ( scope === NormalNode.VIEW ) { - } - - let outputNode = localNormalNode; - - if ( scope === NormalNode.VIEW ) { - - let viewNormalNode = nodeData.viewNormalNode; - - if ( viewNormalNode === undefined ) { - - const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), localNormalNode ); - - viewNormalNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) ); - - nodeData.viewNormalNode = viewNormalNode; - - } - - outputNode = viewNormalNode; + const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), new NormalNode( NormalNode.LOCAL ) ); + outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) ); } else if ( scope === NormalNode.WORLD ) { - let worldNormalNode = nodeData.worldNormalNode; - - if ( worldNormalNode === undefined ) { - - const vertexNormalNode = inverseTransformDirection.call( { dir: new NormalNode( NormalNode.VIEW ), matrix: new CameraNode( CameraNode.VIEW_MATRIX ) } ); - - worldNormalNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) ); - - nodeData.worldNormalNode = worldNormalNode; - - } - - outputNode = worldNormalNode; + const vertexNormalNode = inverseTransformDirection.call( { dir: new NormalNode( NormalNode.VIEW ), matrix: new CameraNode( CameraNode.VIEW_MATRIX ) } ); + outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) ); } - const normalSnipped = outputNode.build( builder, type ); - - return builder.format( normalSnipped, type, output ); + return outputNode.build( builder ); } diff --git a/examples/jsm/renderers/nodes/accessors/Object3DNode.js b/examples/jsm/renderers/nodes/accessors/Object3DNode.js index 6ae720aec0c6eb..9bc3ae3b3bc422 100644 --- a/examples/jsm/renderers/nodes/accessors/Object3DNode.js +++ b/examples/jsm/renderers/nodes/accessors/Object3DNode.js @@ -78,49 +78,25 @@ class Object3DNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const nodeData = builder.getDataFromNode( this ); - - let inputNode = this._inputNode; - - if ( nodeData.inputNode === undefined ) { - - const scope = this.scope; - - if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) { - - if ( inputNode === null || inputNode.isMatrix4Node !== true ) { - - inputNode = new Matrix4Node( /*null*/ ); - - } - - } else if ( scope === Object3DNode.NORMAL_MATRIX ) { - - if ( inputNode === null || inputNode.isMatrix3Node !== true ) { - - inputNode = new Matrix3Node( /*null*/ ); - - } - - } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) { + const scope = this.scope; - if ( inputNode === null || inputNode.isVector3Node !== true ) { + if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) { - inputNode = new Vector3Node(); + this._inputNode = new Matrix4Node( /*null*/ ); - } + } else if ( scope === Object3DNode.NORMAL_MATRIX ) { - } + this._inputNode = new Matrix3Node( /*null*/ ); - this._inputNode = inputNode; + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) { - nodeData.inputNode = inputNode; + this._inputNode = new Vector3Node(); } - return inputNode.build( builder, output ); + return this._inputNode.build( builder ); } diff --git a/examples/jsm/renderers/nodes/accessors/PointUVNode.js b/examples/jsm/renderers/nodes/accessors/PointUVNode.js index 889d8f985e137b..54538b94daec17 100644 --- a/examples/jsm/renderers/nodes/accessors/PointUVNode.js +++ b/examples/jsm/renderers/nodes/accessors/PointUVNode.js @@ -10,12 +10,9 @@ class PointUVNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.getNodeType( builder ); - const snippet = 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )'; - - return builder.format( snippet, type, output ); + return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )'; } diff --git a/examples/jsm/renderers/nodes/accessors/PositionNode.js b/examples/jsm/renderers/nodes/accessors/PositionNode.js index fbcfd9c03e7b7c..15ac153bf5a86a 100644 --- a/examples/jsm/renderers/nodes/accessors/PositionNode.js +++ b/examples/jsm/renderers/nodes/accessors/PositionNode.js @@ -13,7 +13,7 @@ class PositionNode extends Node { static VIEW = 'view'; static VIEW_DIRECTION = 'viewDirection'; - constructor( scope = PositionNode.POSITION ) { + constructor( scope = PositionNode.LOCAL ) { super( 'vec3' ); @@ -21,77 +21,34 @@ class PositionNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.getNodeType( builder ); - const nodeData = builder.getDataFromNode( this ); const scope = this.scope; - let localPositionNode = nodeData.localPositionNode; + let outputNode = null; - if ( localPositionNode === undefined ) { + if ( scope === PositionNode.LOCAL ) { + + outputNode = new AttributeNode( 'position', 'vec3' ); + + } else if ( scope === PositionNode.WORLD ) { - localPositionNode = new AttributeNode( 'position', 'vec3' ); - - nodeData.localPositionNode = localPositionNode; - - } - - let outputNode = localPositionNode; - - if ( scope === PositionNode.WORLD ) { - - let worldPositionNode = nodeData.worldPositionNode; - - if ( worldPositionNode === undefined ) { - - const vertexPositionNode = transformDirection.call( { dir: localPositionNode, matrix: new ModelNode( ModelNode.WORLD_MATRIX ) } ); - - worldPositionNode = new VaryNode( vertexPositionNode ); - - nodeData.worldPositionNode = worldPositionNode; - - } - - outputNode = worldPositionNode; + const vertexPositionNode = transformDirection.call( { dir: new PositionNode( PositionNode.LOCAL ), matrix: new ModelNode( ModelNode.WORLD_MATRIX ) } ); + outputNode = new VaryNode( vertexPositionNode ); } else if ( scope === PositionNode.VIEW ) { - let viewPositionNode = nodeData.viewPositionNode; - - if ( viewPositionNode === undefined ) { - - const vertexPositionNode = new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), localPositionNode ); - - viewPositionNode = new VaryNode( vertexPositionNode ); - - nodeData.viewPositionNode = viewPositionNode; - - } - - outputNode = viewPositionNode; + const vertexPositionNode = new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), new PositionNode( PositionNode.LOCAL ) ); + outputNode = new VaryNode( vertexPositionNode ); } else if ( scope === PositionNode.VIEW_DIRECTION ) { - let viewDirPositionNode = nodeData.viewDirPositionNode; - - if ( viewDirPositionNode === undefined ) { - - const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) ); - - viewDirPositionNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexPositionNode ) ); - - nodeData.viewDirPositionNode = viewDirPositionNode; - - } - - outputNode = viewDirPositionNode; + const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) ); + outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexPositionNode ) ); } - const positionSnipped = outputNode.build( builder, type ); - - return builder.format( positionSnipped, type, output ); + return outputNode.build( builder, this.getNodeType( builder ) ); } diff --git a/examples/jsm/renderers/nodes/accessors/ReferenceNode.js b/examples/jsm/renderers/nodes/accessors/ReferenceNode.js index 25a29c1697daa3..7dced176e01e25 100644 --- a/examples/jsm/renderers/nodes/accessors/ReferenceNode.js +++ b/examples/jsm/renderers/nodes/accessors/ReferenceNode.js @@ -80,9 +80,9 @@ class ReferenceNode extends Node { } - generate( builder, output ) { + generate( builder ) { - return this.node.build( builder, output ); + return this.node.build( builder, this.getNodeType( builder ) ); } diff --git a/examples/jsm/renderers/nodes/core/AttributeNode.js b/examples/jsm/renderers/nodes/core/AttributeNode.js index 7fc3a9d132281f..3ac15cfa9ee361 100644 --- a/examples/jsm/renderers/nodes/core/AttributeNode.js +++ b/examples/jsm/renderers/nodes/core/AttributeNode.js @@ -25,32 +25,19 @@ class AttributeNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const attributeName = this.getAttributeName( builder ); - const attributeType = this.getNodeType( builder ); - - const attribute = builder.getAttribute( attributeName, attributeType ); + const attribute = builder.getAttribute( this.getAttributeName( builder ), this.getNodeType( builder ) ); if ( builder.isShaderStage( 'vertex' ) ) { - return builder.format( attribute.name, attribute.type, output ); + return attribute.name; } else { - const nodeData = builder.getDataFromNode( this, builder.shaderStage ); - - let nodeVary = nodeData.varyNode; - - if ( nodeVary === undefined ) { - - nodeVary = new VaryNode( this ); - - nodeData.nodeVary = nodeVary; - - } + const nodeVary = new VaryNode( this ); - return nodeVary.build( builder, output ); + return nodeVary.build( builder, attribute.type ); } diff --git a/examples/jsm/renderers/nodes/core/CodeNode.js b/examples/jsm/renderers/nodes/core/CodeNode.js index d5e0411d607d28..e8c6e26afb9fdd 100644 --- a/examples/jsm/renderers/nodes/core/CodeNode.js +++ b/examples/jsm/renderers/nodes/core/CodeNode.js @@ -30,7 +30,7 @@ class CodeNode extends Node { } - generate( builder, output ) { + generate( builder ) { if ( this.useKeywords === true ) { @@ -66,9 +66,7 @@ class CodeNode extends Node { } - const type = this.getNodeType( builder ); - const nodeCode = builder.getCodeFromNode( this, type ); - + const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) ); nodeCode.code = this.code; return nodeCode.code; diff --git a/examples/jsm/renderers/nodes/core/ConstNode.js b/examples/jsm/renderers/nodes/core/ConstNode.js index 9d83539acebb3c..06f25fc13f2ed3 100644 --- a/examples/jsm/renderers/nodes/core/ConstNode.js +++ b/examples/jsm/renderers/nodes/core/ConstNode.js @@ -12,12 +12,11 @@ class ConstNode extends CodeNode { } - generate( builder, output ) { + generate( builder ) { const code = super.generate( builder ); - const type = this.getNodeType( builder ); - const nodeCode = builder.getCodeFromNode( this, type ); + const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) ); if ( this.name !== '' ) { @@ -31,7 +30,7 @@ class ConstNode extends CodeNode { nodeCode.code = `#define ${propertyName} ${code}`; - return builder.format( propertyName, type, output ); + return propertyName; } diff --git a/examples/jsm/renderers/nodes/core/ExpressionNode.js b/examples/jsm/renderers/nodes/core/ExpressionNode.js index 1637aa75722859..3367d2757fc843 100644 --- a/examples/jsm/renderers/nodes/core/ExpressionNode.js +++ b/examples/jsm/renderers/nodes/core/ExpressionNode.js @@ -1,6 +1,6 @@ -import Node from './Node.js'; +import TempNode from './TempNode.js'; -class ExpressionNode extends Node { +class ExpressionNode extends TempNode { constructor( snipped = '', nodeType = null ) { @@ -10,12 +10,9 @@ class ExpressionNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.getNodeType( builder ); - const snipped = this.snipped; - - return builder.format( `( ${ snipped } )`, type, output ); + return `( ${ this.snipped } )`; } diff --git a/examples/jsm/renderers/nodes/core/FunctionCallNode.js b/examples/jsm/renderers/nodes/core/FunctionCallNode.js index 59ade414c08197..e5609168ad7e8d 100644 --- a/examples/jsm/renderers/nodes/core/FunctionCallNode.js +++ b/examples/jsm/renderers/nodes/core/FunctionCallNode.js @@ -31,7 +31,7 @@ class FunctionCallNode extends TempNode { } - generate( builder, output ) { + generate( builder ) { const params = []; @@ -56,12 +56,9 @@ class FunctionCallNode extends TempNode { } - const type = this.getNodeType( builder ); const functionName = functionNode.build( builder, 'property' ); - const callSnippet = `${functionName}( ${params.join( ', ' )} )`; - - return builder.format( callSnippet, type, output ); + return `${functionName}( ${params.join( ', ' )} )`; } diff --git a/examples/jsm/renderers/nodes/core/Node.js b/examples/jsm/renderers/nodes/core/Node.js index 057785b5ec9fd3..a39a4fb8b21581 100644 --- a/examples/jsm/renderers/nodes/core/Node.js +++ b/examples/jsm/renderers/nodes/core/Node.js @@ -50,6 +50,27 @@ class Node { builder.addNode( this ); + const isGenerateOnce = this.generate.length === 1; + + if ( isGenerateOnce ) { + + const type = this.getNodeType( builder ); + const nodeData = builder.getDataFromNode( this ); + + let snippet = nodeData.snippet; + + if ( snippet === undefined ) { + + snippet = this.generate( builder ); + + nodeData.snippet = snippet; + + } + + return builder.format( snippet, type, output ); + + } + return this.generate( builder, output ); } diff --git a/examples/jsm/renderers/nodes/core/PropertyNode.js b/examples/jsm/renderers/nodes/core/PropertyNode.js index 96dd0af8d4a160..841f5066b24c7b 100644 --- a/examples/jsm/renderers/nodes/core/PropertyNode.js +++ b/examples/jsm/renderers/nodes/core/PropertyNode.js @@ -10,16 +10,12 @@ class PropertyNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.getNodeType( builder ); - - const nodeVary = builder.getVarFromNode( this, type ); + const nodeVary = builder.getVarFromNode( this, this.getNodeType( builder ) ); nodeVary.name = this.name; - const propertyName = builder.getPropertyName( nodeVary ); - - return builder.format( propertyName, type, output ); + return builder.getPropertyName( nodeVary ); } diff --git a/examples/jsm/renderers/nodes/core/StructVarNode.js b/examples/jsm/renderers/nodes/core/StructVarNode.js index b21621f91428ca..a64f6ba6cd1c49 100644 --- a/examples/jsm/renderers/nodes/core/StructVarNode.js +++ b/examples/jsm/renderers/nodes/core/StructVarNode.js @@ -20,7 +20,7 @@ class StructVarNode extends Node { } - generate( builder, output ) { + generate( builder ) { const type = this.getNodeType( builder ); @@ -66,7 +66,7 @@ class StructVarNode extends Node { } - return builder.format( property, type, output ); + return property; } diff --git a/examples/jsm/renderers/nodes/core/TempNode.js b/examples/jsm/renderers/nodes/core/TempNode.js index 8a187388f4ab23..05fa1b0dcfd27d 100644 --- a/examples/jsm/renderers/nodes/core/TempNode.js +++ b/examples/jsm/renderers/nodes/core/TempNode.js @@ -17,15 +17,25 @@ class TempNode extends Node { const nodeVar = builder.getVarFromNode( this, type ); const propertyName = builder.getPropertyName( nodeVar ); - const snippet = super.build( builder, type ); + const nodeData = builder.getDataFromNode( this ); - builder.addFlowCode( `${propertyName} = ${snippet}` ); + let snippet = nodeData.snippet; + + if ( snippet === undefined ) { + + snippet = super.build( builder, type ); + + builder.addFlowCode( `${propertyName} = ${snippet}` ); + + nodeData.snippet = snippet; + + } return builder.format( propertyName, type, output ); } else { - return super.build( builder, type ); + return super.build( builder, output ); } diff --git a/examples/jsm/renderers/nodes/core/VarNode.js b/examples/jsm/renderers/nodes/core/VarNode.js index e491e9f87b35f3..3b83106e676901 100644 --- a/examples/jsm/renderers/nodes/core/VarNode.js +++ b/examples/jsm/renderers/nodes/core/VarNode.js @@ -17,7 +17,7 @@ class VarNode extends Node { } - generate( builder, output ) { + generate( builder ) { const type = builder.getVectorType( this.getNodeType( builder ) ); const name = this.name; @@ -37,7 +37,7 @@ class VarNode extends Node { builder.addFlowCode( `${propertyName} = ${snippet}` ); - return builder.format( propertyName, type, output ); + return propertyName; } diff --git a/examples/jsm/renderers/nodes/core/VaryNode.js b/examples/jsm/renderers/nodes/core/VaryNode.js index 66d09c6c197c8c..ffcd0a15f6e5a8 100644 --- a/examples/jsm/renderers/nodes/core/VaryNode.js +++ b/examples/jsm/renderers/nodes/core/VaryNode.js @@ -19,7 +19,7 @@ class VaryNode extends Node { } - generate( builder, output ) { + generate( builder ) { const type = this.getNodeType( builder ); const value = this.value; @@ -30,7 +30,7 @@ class VaryNode extends Node { // force nodeVary.snippet work in vertex stage builder.flowNodeFromShaderStage( NodeShaderStage.Vertex, value, type, propertyName ); - return builder.format( propertyName, type, output ); + return propertyName; } diff --git a/examples/jsm/renderers/nodes/display/ColorSpaceNode.js b/examples/jsm/renderers/nodes/display/ColorSpaceNode.js index ef6689f19093ba..55c1adcdae7fa0 100644 --- a/examples/jsm/renderers/nodes/display/ColorSpaceNode.js +++ b/examples/jsm/renderers/nodes/display/ColorSpaceNode.js @@ -85,35 +85,27 @@ class ColorSpaceNode extends TempNode { } - generate( builder, output ) { + generate( builder ) { + + const type = this.getNodeType( builder ); const method = this.method; const input = this.input; if ( method !== ColorSpaceNode.LINEAR_TO_LINEAR ) { - const nodeData = builder.getDataFromNode( this ); - - let encodingFunctionCallNode = nodeData.encodingFunctionCallNode; - - if (encodingFunctionCallNode === undefined) { - - const encodingFunctionNode = EncodingFunctions[ method ]; - - encodingFunctionCallNode = encodingFunctionNode.call( { - value: input, - factor: this.factor - } ); - - nodeData.encodingFunctionCallNode = encodingFunctionCallNode; + const encodingFunctionNode = EncodingFunctions[ method ]; - } + const encodingFunctionCallNode = encodingFunctionNode.call( { + value: input, + factor: this.factor + } ); - return encodingFunctionCallNode.build( builder, output ); + return encodingFunctionCallNode.build( builder, type ); } else { - return input.build( builder, output ); + return input.build( builder, type ); } diff --git a/examples/jsm/renderers/nodes/display/NormalMapNode.js b/examples/jsm/renderers/nodes/display/NormalMapNode.js index 67b4cdb67ee540..23ba57c7cf50f0 100644 --- a/examples/jsm/renderers/nodes/display/NormalMapNode.js +++ b/examples/jsm/renderers/nodes/display/NormalMapNode.js @@ -50,12 +50,11 @@ class NormalMapNode extends TempNode { } - generate( builder, output ) { + generate( builder ) { const type = this.getNodeType( builder ); - const normalMapType = this.normalMapType; - const nodeData = builder.getDataFromNode( this ); + const normalMapType = this.normalMapType; const normalOP = new OperatorNode( '*', this.value, new FloatNode( 2.0 ).setConst( true ) ); const normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) ); @@ -66,29 +65,19 @@ class NormalMapNode extends TempNode { const normal = new MathNode( MathNode.NORMALIZE, vertexNormalNode ); - return normal.build( builder, output ); + return normal.build( builder, type ); } else if ( normalMapType === TangentSpaceNormalMap ) { - let perturbNormal2ArbCall = nodeData.perturbNormal2ArbCall; - - if (perturbNormal2ArbCall === undefined) { - - perturbNormal2ArbCall = perturbNormal2Arb.call( { - eye_pos: new PositionNode( PositionNode.VIEW ), - surf_norm: new NormalNode( NormalNode.VIEW ), - mapN: normalMap, - faceDirection: new FloatNode( 1.0 ).setConst( true ), - uv: new UVNode() - } ); + const perturbNormal2ArbCall = perturbNormal2Arb.call( { + eye_pos: new PositionNode( PositionNode.VIEW ), + surf_norm: new NormalNode( NormalNode.VIEW ), + mapN: normalMap, + faceDirection: new FloatNode( 1.0 ).setConst( true ), + uv: new UVNode() + } ); - nodeData.perturbNormal2ArbCall = perturbNormal2ArbCall; - - } - - const snippet = perturbNormal2ArbCall.build( builder, output ); - - return builder.format( snippet, type, output ); + return perturbNormal2ArbCall.build( builder, type ); } diff --git a/examples/jsm/renderers/nodes/lights/LightContextNode.js b/examples/jsm/renderers/nodes/lights/LightContextNode.js index aa5e83b414727c..24547b798aa154 100644 --- a/examples/jsm/renderers/nodes/lights/LightContextNode.js +++ b/examples/jsm/renderers/nodes/lights/LightContextNode.js @@ -15,7 +15,7 @@ class LightContextNode extends ContextNode { } - generate( builder, output ) { + generate( builder ) { const type = this.getNodeType( builder ); @@ -40,13 +40,11 @@ class LightContextNode extends ContextNode { } - const totalLightSnippet = `( ${reflectedLight}.directDiffuse + ${reflectedLight}.directSpecular )`; - // add code - super.generate( builder, output ); + super.generate( builder, type ); - return builder.format( totalLightSnippet, type, output ); + return `( ${reflectedLight}.directDiffuse + ${reflectedLight}.directSpecular )`; } diff --git a/examples/jsm/renderers/nodes/lights/LightNode.js b/examples/jsm/renderers/nodes/lights/LightNode.js index 26ebcda2ca5294..8a9b12d4f9ebe6 100644 --- a/examples/jsm/renderers/nodes/lights/LightNode.js +++ b/examples/jsm/renderers/nodes/lights/LightNode.js @@ -52,7 +52,9 @@ class LightNode extends Node { } - generate( builder, output ) { + generate( builder ) { + + const type = this.getNodeType( builder ); this.lightPositionView.object3d = this.light; @@ -72,7 +74,7 @@ class LightNode extends Node { } - return this.color.build( builder, output ); + return this.color.build( builder, type ); } diff --git a/examples/jsm/renderers/nodes/lights/LightsNode.js b/examples/jsm/renderers/nodes/lights/LightsNode.js index 3c6aa7b8ba07ad..e439b8ed29820e 100644 --- a/examples/jsm/renderers/nodes/lights/LightsNode.js +++ b/examples/jsm/renderers/nodes/lights/LightsNode.js @@ -11,7 +11,7 @@ class LightsNode extends Node { } - generate( builder, output ) { + generate( builder ) { const lightNodes = this.lightNodes; @@ -21,7 +21,7 @@ class LightsNode extends Node { } - return builder.format( 'vec3( 0.0 )', this.getNodeType( builder ), output ); + return 'vec3( 0.0 )'; } diff --git a/examples/jsm/renderers/nodes/math/MathNode.js b/examples/jsm/renderers/nodes/math/MathNode.js index 1227dfdbe6ccff..2222efe073da3a 100644 --- a/examples/jsm/renderers/nodes/math/MathNode.js +++ b/examples/jsm/renderers/nodes/math/MathNode.js @@ -105,7 +105,7 @@ class MathNode extends TempNode { } - generate( builder, output ) { + generate( builder ) { const method = this.method; @@ -114,11 +114,11 @@ class MathNode extends TempNode { if ( method === MathNode.NEGATE ) { - return builder.format( '( -' + this.a.build( builder, inputType ) + ' )', type, output ); + return '( -' + this.a.build( builder, inputType ) + ' )'; } else if ( method === MathNode.INVERT ) { - return builder.format( '( 1.0 - ' + this.a.build( builder, inputType ) + ' )', type, output ); + return '( 1.0 - ' + this.a.build( builder, inputType ) + ' )'; } else { @@ -177,7 +177,7 @@ class MathNode extends TempNode { } - return builder.format( `${method}( ${params.join(', ')} )`, type, output ); + return `${method}( ${params.join(', ')} )`; } diff --git a/examples/jsm/renderers/nodes/math/OperatorNode.js b/examples/jsm/renderers/nodes/math/OperatorNode.js index 7e59a03e0dbe3f..4e393295e14e35 100644 --- a/examples/jsm/renderers/nodes/math/OperatorNode.js +++ b/examples/jsm/renderers/nodes/math/OperatorNode.js @@ -22,13 +22,13 @@ class OperatorNode extends TempNode { // matrix x vector - return typeB; + return builder.getVectorFromMatrix( typeA ); } else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) { // vector x matrix - return typeA; + return builder.getVectorFromMatrix( typeB ); } else if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { @@ -72,7 +72,7 @@ class OperatorNode extends TempNode { const a = this.a.build( builder, typeA ); const b = this.b.build( builder, typeB ); - return builder.format( `( ${a} ${this.op} ${b} )`, type, output ); + return `( ${a} ${this.op} ${b} )`; } diff --git a/examples/jsm/renderers/nodes/procedural/CheckerNode.js b/examples/jsm/renderers/nodes/procedural/CheckerNode.js new file mode 100644 index 00000000000000..189d598b2f66c7 --- /dev/null +++ b/examples/jsm/renderers/nodes/procedural/CheckerNode.js @@ -0,0 +1,38 @@ +import FunctionNode from '../core/FunctionNode.js'; +import Node from '../core/Node.js'; +import UVNode from '../accessors/UVNode.js'; + +import { ShaderNode, float, add, mul, floor, mod, sign } from '../ShaderNode.js'; + +// Three.JS Shader Language +const checkerShaderNode = ShaderNode( ( uv ) => { + + uv = mul( uv, 2.0 ); + + const cx = floor( uv.x ); + const cy = floor( uv.y ); + const result = mod( add( cx, cy ), 2.0 ); + + return sign( result ); + +} ); + +class CheckerNode extends Node { + + constructor( uv = new UVNode() ) { + + super( 'float' ); + + this.uv = uv; + + } + + generate( builder, output ) { + + return checkerShaderNode( this.uv ).build( builder, output ); + + } + +} + +export default CheckerNode; diff --git a/examples/jsm/renderers/nodes/utils/JoinNode.js b/examples/jsm/renderers/nodes/utils/JoinNode.js index 642b88c04e1042..447fb0758a203c 100644 --- a/examples/jsm/renderers/nodes/utils/JoinNode.js +++ b/examples/jsm/renderers/nodes/utils/JoinNode.js @@ -16,7 +16,7 @@ class JoinNode extends Node { } - generate( builder, output ) { + generate( builder ) { const type = this.getNodeType( builder ); const values = this.values; @@ -33,9 +33,7 @@ class JoinNode extends Node { } - const snippet = `${type}( ${ snippetValues.join( ', ' ) } )`; - - return builder.format( snippet, type, output ); + return `${type}( ${ snippetValues.join( ', ' ) } )`; } diff --git a/examples/jsm/renderers/nodes/utils/SplitNode.js b/examples/jsm/renderers/nodes/utils/SplitNode.js index b373d4f7df68e8..4fad8bf2189912 100644 --- a/examples/jsm/renderers/nodes/utils/SplitNode.js +++ b/examples/jsm/renderers/nodes/utils/SplitNode.js @@ -17,14 +17,26 @@ class SplitNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const type = this.node.getNodeType( builder ); - const nodeSnippet = this.node.build( builder, type ); + const node = this.node; + const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); - const snippet = `${nodeSnippet}.${this.components}`; + const components = this.components; - return builder.format( snippet, this.getNodeType( builder ), output ); + let type = null; + + if ( components.length >= nodeTypeLength ) { + + // need expand the input node + + type = this.getNodeType( builder ); + + } + + const nodeSnippet = node.build( builder, type ); + + return `${nodeSnippet}.${this.components}`; } diff --git a/examples/jsm/renderers/nodes/utils/SpriteSheetUVNode.js b/examples/jsm/renderers/nodes/utils/SpriteSheetUVNode.js index 4cd753e2ffcc10..c3777899f4696e 100644 --- a/examples/jsm/renderers/nodes/utils/SpriteSheetUVNode.js +++ b/examples/jsm/renderers/nodes/utils/SpriteSheetUVNode.js @@ -18,49 +18,38 @@ class SpriteSheetUVNode extends Node { } - generate( builder, output ) { + generate( builder ) { - const nodeData = builder.getDataFromNode( this ); + const uv = this.uv; + const count = this.count; + const frame = this.frame; - let uvFrame = nodeData.uvFrame; + const one = new FloatNode( 1 ).setConst( true ); - if ( nodeData.uvFrame === undefined ) { + const width = new SplitNode( count, 'x' ); + const height = new SplitNode( count, 'y' ); - const uv = this.uv; - const count = this.count; - const frame = this.frame; + const total = new OperatorNode( '*', width, height ); - const one = new FloatNode( 1 ).setConst( true ); + const roundFrame = new MathNode( MathNode.FLOOR, new MathNode( MathNode.MOD, frame, total ) ); - const width = new SplitNode( count, 'x' ); - const height = new SplitNode( count, 'y' ); + const frameNum = new OperatorNode( '+', roundFrame, one ); - const total = new OperatorNode( '*', width, height ); + const cell = new MathNode( MathNode.MOD, roundFrame, width ); + const row = new MathNode( MathNode.CEIL, new OperatorNode( '/', frameNum, width ) ); + const rowInv = new OperatorNode( '-', height, row ); - const roundFrame = new MathNode( MathNode.FLOOR, new MathNode( MathNode.MOD, frame, total ) ); + const scale = new OperatorNode( '/', one, count ); - const frameNum = new OperatorNode( '+', roundFrame, one ); + const uvFrameOffset = new JoinNode( [ + new OperatorNode( '*', cell, new SplitNode( scale, 'x' ) ), + new OperatorNode( '*', rowInv, new SplitNode( scale, 'y' ) ) + ] ); - const cell = new MathNode( MathNode.MOD, roundFrame, width ); - const row = new MathNode( MathNode.CEIL, new OperatorNode( '/', frameNum, width ) ); - const rowInv = new OperatorNode( '-', height, row ); + const uvScale = new OperatorNode( '*', uv, scale ); + const uvFrame = new OperatorNode( '+', uvScale, uvFrameOffset ); - const scale = new OperatorNode( '/', one, count ); - - const uvFrameOffset = new JoinNode( [ - new OperatorNode( '*', cell, new SplitNode( scale, 'x' ) ), - new OperatorNode( '*', rowInv, new SplitNode( scale, 'y' ) ) - ] ); - - const uvScale = new OperatorNode( '*', uv, scale ); - - uvFrame = new OperatorNode( '+', uvScale, uvFrameOffset ); - - nodeData.uvFrame = uvFrame; - - } - - return uvFrame.build( builder, output ); + return uvFrame.build( builder, this.getNodeType( builder ) ); } diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index acdf02491ac298..df3c17efa93b0d 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -2,7 +2,7 @@ import NodeBuilder from '../../nodes/core/NodeBuilder.js'; import NodeSlot from '../../nodes/core/NodeSlot.js'; import WebGLPhysicalContextNode from './WebGLPhysicalContextNode.js'; -import { ShaderChunk } from 'three'; +import { ShaderChunk, LinearEncoding, RGBAFormat, UnsignedByteType, sRGBEncoding } from 'three'; const shaderStages = [ 'vertex', 'fragment' ]; @@ -262,6 +262,20 @@ class WebGLNodeBuilder extends NodeBuilder { } + getTextureEncodingFromMap( map ) { + + const isWebGL2 = this.renderer.capabilities.isWebGL2; + + if ( isWebGL2 && map && map.isTexture && map.format === RGBAFormat && map.type === UnsignedByteType && map.encoding === sRGBEncoding ) { + + return LinearEncoding; // disable inline decode for sRGB textures in WebGL 2 + + } + + return super.getTextureEncodingFromMap( map ); + + } + build() { super.build(); diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js index 17c775b7ed8213..e263af2a77adc0 100644 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js @@ -393,21 +393,6 @@ class WebGPUNodeBuilder extends NodeBuilder { } - composeShaderCode( code, snippet ) { - - // use regex maybe for security? - const versionStrIndex = code.indexOf( '\n' ); - - let finalCode = code.substr( 0, versionStrIndex ) + '\n\n'; - - finalCode += snippet; - - finalCode += code.substr( versionStrIndex ); - - return finalCode; - - } - build() { const keywords = this.getContextValue( 'keywords' ); @@ -422,13 +407,28 @@ class WebGPUNodeBuilder extends NodeBuilder { super.build(); - this.vertexShader = this.composeShaderCode( this.nativeShader.vertexShader, this.vertexShader ); - this.fragmentShader = this.composeShaderCode( this.nativeShader.fragmentShader, this.fragmentShader ); + this.vertexShader = this._composeShaderCode( this.nativeShader.vertexShader, this.vertexShader ); + this.fragmentShader = this._composeShaderCode( this.nativeShader.fragmentShader, this.fragmentShader ); return this; } + _composeShaderCode( code, snippet ) { + + // use regex maybe for security? + const versionStrIndex = code.indexOf( '\n' ); + + let finalCode = code.substr( 0, versionStrIndex ) + '\n\n'; + + finalCode += snippet; + + finalCode += code.substr( versionStrIndex ); + + return finalCode; + + } + _getNodeUniform( uniformNode, type ) { if ( type === 'float' ) return new FloatNodeUniform( uniformNode ); diff --git a/examples/models/gltf/SittingBox.glb b/examples/models/gltf/SittingBox.glb deleted file mode 100644 index 7cdd7d78d19deafdd644473244f3ca0ec41100f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422392 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 =UM&tKeE2;C9QCb)S_y@lr-x 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 delta 9149 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 RpVBtdqMp#6($Src{RgRipiuw- diff --git a/examples/screenshots/webgl_morphtargets_face.jpg b/examples/screenshots/webgl_morphtargets_face.jpg index bab8ce93d9f191ec79f375df25a9963b5af02a3c..a1d77c1af547125af7cb1bcc2b652faefacfeb7d 100644 GIT binary patch delta 6797 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 Xu5)UW)Y|`oV|SV-MLigN@#p^lh~ar- diff --git a/examples/screenshots/webgl_shading_physical.jpg b/examples/screenshots/webgl_shading_physical.jpg deleted file mode 100644 index 83963e2564c866552d505d2e2414630b1022809e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30817 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 X_awjwa4Je0A9?_K^&eUQ{{a8ly8o4H diff --git a/examples/screenshots/webgpu_sandbox.jpg b/examples/screenshots/webgpu_sandbox.jpg index d84dff22b8c1c11aaf94c1ef598ea74ddb550e96..35b06935974bb9787ab26daee5a8712bd8be7e9b 100644 GIT binary patch literal 6702 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 literal 21592 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 DeX{Gm diff --git a/examples/tags.json b/examples/tags.json index 793652c762005d..7834bba330460d 100644 --- a/examples/tags.json +++ b/examples/tags.json @@ -1,5 +1,4 @@ { - "webgl_animation_cloth": [ "physics", "integration", "shadow" ], "webgl_clipping": [ "solid" ], "webgl_clipping_advanced": [ "solid" ], "webgl_clipping_intersection": [ "solid" ], @@ -65,7 +64,6 @@ "webgl_shaders_ocean": [ "water" ], "webgl_shaders_sky": [ "sun" ], "webgl_shaders_tonemapping": [ "hrd" ], - "webgl_shading_physical": [ "pbr" ], "webgl_shadow_contact": [ "onBeforeCompile", "soft" ], "webgl_shadowmap_viewer": [ "directional", "spot" ], "webgl_skinning_simple": [ "animation" ], diff --git a/examples/textures/patterns/bright_squares256.png b/examples/textures/patterns/bright_squares256.png deleted file mode 100644 index cc9afcbd033b4ffc017e735e47687639b21a9b40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26698 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 diff --git a/examples/textures/patterns/circuit_pattern.png b/examples/textures/patterns/circuit_pattern.png deleted file mode 100644 index 95d721cb51cddfb48cb7462195503a234f503245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5417 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 NxjB0rDsnt|^?%o6Vvqm; diff --git a/examples/textures/patterns/readme.txt b/examples/textures/patterns/readme.txt deleted file mode 100644 index 315f922d238973..00000000000000 --- a/examples/textures/patterns/readme.txt +++ /dev/null @@ -1,6 +0,0 @@ -Texture "bright_squares256.png" from http://subtlepatterns.com/ - -Slightly modified to have more GPU friendly sizes. - -Licensed under a Creative Commons Attribution 3.0 Unported License: -http://creativecommons.org/licenses/by/3.0/ \ No newline at end of file diff --git a/examples/webgl_animation_cloth.html b/examples/webgl_animation_cloth.html deleted file mode 100644 index 023498849d3660..00000000000000 --- a/examples/webgl_animation_cloth.html +++ /dev/null @@ -1,629 +0,0 @@ - - - - three.js webgl - cloth simulation - - - - - - - -

    - - - - diff --git a/examples/webgl_morphtargets_face.html b/examples/webgl_morphtargets_face.html index 43cfb81a4b23b4..0825489cb91bd0 100644 --- a/examples/webgl_morphtargets_face.html +++ b/examples/webgl_morphtargets_face.html @@ -7,10 +7,7 @@ @@ -35,6 +32,8 @@ import { RoomEnvironment } from './jsm/environments/RoomEnvironment.js'; + import { GUI } from './jsm/libs/dat.gui.module.js'; + init(); function init() { @@ -77,22 +76,38 @@ mixer.clipAction( gltf.animations[ 0 ] ).play(); + // GUI + + const head = mesh.getObjectByName( 'mesh_2' ); + const influences = head.morphTargetInfluences; + + const gui = new GUI(); + gui.close(); + + for ( const [ key, value ] of Object.entries( head.morphTargetDictionary ) ) { + + gui.add( influences, value, 0, 1, 0.01 ) + .name( key.replace( 'blendShape1.', '' ) ) + .listen( influences ); + + } + } ); const environment = new RoomEnvironment(); const pmremGenerator = new THREE.PMREMGenerator( renderer ); - scene.background = new THREE.Color( 0xD5F7F7 ); + scene.background = new THREE.Color( 0x666666 ); scene.environment = pmremGenerator.fromScene( environment ).texture; const controls = new OrbitControls( camera, renderer.domElement ); + controls.enableDamping = true; controls.minDistance = 2.5; controls.maxDistance = 5; controls.minAzimuthAngle = - Math.PI / 2; controls.maxAzimuthAngle = Math.PI / 2; controls.maxPolarAngle = Math.PI / 1.8; - controls.target.set( 0, 0, - 0.2 ); - controls.enableDamping = true; + controls.target.set( 0, 0.15, - 0.2 ); const stats = new Stats(); container.appendChild( stats.dom ); diff --git a/examples/webgl_shading_physical.html b/examples/webgl_shading_physical.html deleted file mode 100644 index 108b370b6e3c06..00000000000000 --- a/examples/webgl_shading_physical.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - three.js webgl - physically based shading - - - - - - -
    - three.js - webgl physically based shading testbed -
    - - - - - diff --git a/examples/webgpu_sandbox.html b/examples/webgpu_sandbox.html index 4ca422f910c71f..95607c3b4308dd 100644 --- a/examples/webgpu_sandbox.html +++ b/examples/webgpu_sandbox.html @@ -80,7 +80,9 @@ const timerScaleNode = new Nodes.OperatorNode( '*', timerNode, new Nodes.Vector2Node( new THREE.Vector2( - 0.5, 0.1 ) ).setConst( true ) ); const animateUV = new Nodes.OperatorNode( '+', new Nodes.UVNode(), timerScaleNode ); - materialBox.colorNode = new Nodes.TextureNode( texture, animateUV ); + const textureNode = new Nodes.TextureNode( texture, animateUV ); + + materialBox.colorNode = new Nodes.MathNode( 'mix', textureNode, new Nodes.CheckerNode( animateUV ), new Nodes.FloatNode( .5) ); // test uv 2 //geometryBox.setAttribute( 'uv2', geometryBox.getAttribute( 'uv' ) ); diff --git a/package.json b/package.json index b480c5f35f376a..697548cc7f06d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "three", - "version": "0.132.2", + "version": "0.133.0", "description": "JavaScript 3D library", "main": "build/three.js", "module": "build/three.module.js", diff --git a/src/constants.js b/src/constants.js index 1efbda3a5105bf..16c3747c24413e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,4 +1,4 @@ -export const REVISION = '133dev'; +export const REVISION = '133'; export const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; export const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; export const CullFaceNone = 0; diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 43e0d94d4e557e..039fefedfe247b 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -113,8 +113,9 @@ class Object3D extends EventDispatcher { } - onBeforeRender() {} - onAfterRender() {} + onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} + + onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} applyMatrix4( matrix ) { diff --git a/src/objects/SkinnedMesh.js b/src/objects/SkinnedMesh.js index 9f9053e64735e4..376d7cabad456a 100644 --- a/src/objects/SkinnedMesh.js +++ b/src/objects/SkinnedMesh.js @@ -123,7 +123,7 @@ class SkinnedMesh extends Mesh { _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); - _basePosition.fromBufferAttribute( geometry.attributes.position, index ).applyMatrix4( this.bindMatrix ); + _basePosition.copy( target ).applyMatrix4( this.bindMatrix ); target.set( 0, 0, 0 ); diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index eddb9a8f929589..24138d2a9c26b9 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -737,7 +737,7 @@ function WebGLRenderer( parameters = {} ) { const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - const program = setProgram( camera, scene, material, object ); + const program = setProgram( camera, scene, geometry, material, object ); state.setMaterial( material, frontFaceCW ); @@ -769,12 +769,6 @@ function WebGLRenderer( parameters = {} ) { } - if ( geometry.morphAttributes.position !== undefined || geometry.morphAttributes.normal !== undefined ) { - - morphtargets.update( object, geometry, material, program ); - - } - bindingStates.setup( object, material, program, geometry, index ); let attribute; @@ -1340,7 +1334,7 @@ function WebGLRenderer( parameters = {} ) { if ( object.isImmediateRenderObject ) { - const program = setProgram( camera, scene, material, object ); + const program = setProgram( camera, scene, geometry, material, object ); state.setMaterial( material ); @@ -1505,7 +1499,7 @@ function WebGLRenderer( parameters = {} ) { } - function setProgram( camera, scene, material, object ) { + function setProgram( camera, scene, geometry, material, object ) { if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... @@ -1515,11 +1509,11 @@ function WebGLRenderer( parameters = {} ) { const environment = material.isMeshStandardMaterial ? scene.environment : null; const encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : _currentRenderTarget.texture.encoding; const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const vertexAlphas = material.vertexColors === true && !! object.geometry && !! object.geometry.attributes.color && object.geometry.attributes.color.itemSize === 4; - const vertexTangents = !! material.normalMap && !! object.geometry && !! object.geometry.attributes.tangent; - const morphTargets = !! object.geometry && !! object.geometry.morphAttributes.position; - const morphNormals = !! object.geometry && !! object.geometry.morphAttributes.normal; - const morphTargetsCount = ( !! object.geometry && !! object.geometry.morphAttributes.position ) ? object.geometry.morphAttributes.position.length : 0; + const vertexAlphas = material.vertexColors === true && !! geometry && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; + const vertexTangents = !! material.normalMap && !! geometry && !! geometry.attributes.tangent; + const morphTargets = !! geometry && !! geometry.morphAttributes.position; + const morphNormals = !! geometry && !! geometry.morphAttributes.normal; + const morphTargetsCount = ( !! geometry && !! geometry.morphAttributes.position ) ? geometry.morphAttributes.position.length : 0; const materialProperties = properties.get( material ); const lights = currentRenderState.state.lights; @@ -1717,9 +1711,9 @@ function WebGLRenderer( parameters = {} ) { } - // skinning uniforms must be set even if material didn't change - // auto-setting of texture unit for bone texture must go before other textures - // otherwise textures used for skinning can take over texture units reserved for other material textures + // skinning and morph target uniforms must be set even if material didn't change + // auto-setting of texture unit for bone and morph texture must go before other textures + // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures if ( object.isSkinnedMesh ) { @@ -1747,6 +1741,13 @@ function WebGLRenderer( parameters = {} ) { } + if ( !! geometry && ( geometry.morphAttributes.position !== undefined || geometry.morphAttributes.normal !== undefined ) ) { + + morphtargets.update( object, geometry, material, program ); + + } + + if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { materialProperties.receiveShadow = object.receiveShadow; diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index c079149403d391..5c00678f84bc9e 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -31,6 +31,7 @@ const exceptionList = [ 'webgl_loader_texture_lottie', // not sure why this fails 'webgl_loader_texture_pvrtc', // not supported in CI, useless 'webgl_materials_standard_nodes', // puppeteer does not support import maps yet + 'webgl_morphtargets_face', // To investigate... 'webgl_postprocessing_crossfade', // fails for some misterious reason 'webgl_raymarching_reflect', // exception for Github Actions 'webgl_test_memory2', // gives fatal error in puppeteer @@ -147,12 +148,12 @@ const pup = puppeteer.launch( { let endId = files.length; if ( 'CI' in process.env ) { - + const jobs = 8; - + beginId = Math.floor( parseInt( process.env.CI.slice( 0, 1 ) ) * files.length / jobs ); endId = Math.floor( ( parseInt( process.env.CI.slice( - 1 ) ) + 1 ) * files.length / jobs ); - + } for ( let id = beginId; id < endId; ++ id ) { From 0c7000ef32e5f53bf129808cb2fae3739c2e5433 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Tue, 5 Oct 2021 11:57:09 -0700 Subject: [PATCH 4/8] Address MrDoob's review comments --- src/renderers/WebGLMultisampleRenderTarget.js | 9 +++--- src/renderers/WebGLRenderTarget.js | 3 -- src/renderers/WebGLRenderer.js | 10 +++---- src/renderers/webgl/WebGLTextures.js | 28 +++++++++---------- src/renderers/webxr/WebXRManager.js | 4 +-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/renderers/WebGLMultisampleRenderTarget.js b/src/renderers/WebGLMultisampleRenderTarget.js index 8898923e5bbc7a..83ff547a33651c 100644 --- a/src/renderers/WebGLMultisampleRenderTarget.js +++ b/src/renderers/WebGLMultisampleRenderTarget.js @@ -8,8 +8,9 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { this.samples = 4; - this.useMultisampledRenderToTexture = ( options.useMultisampledRenderToTexture !== undefined ) ? options.useMultisampledRenderToTexture : false; - this.useMultisampledRenderbuffer = this.useMultisampledRenderToTexture === false; + this.ignoreDepthForMultisampleCopy = options.ignoreDepth !== undefined ? options.ignoreDepth : true; + this.useRenderToTexture = ( options.useMultisampledRenderToTexture !== undefined ) ? options.useMultisampledRenderToTexture : false; + this.useRenderbuffer = this.useRenderToTexture === false; } @@ -18,8 +19,8 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { super.copy.call( this, source ); this.samples = source.samples; - this.useMultisampledRenderToTexture = source.useMultisampledRenderToTexture; - this.useMultisampledRenderbuffer = source.useMultisampledRenderbuffer; + this.useRenderToTexture = source.useMultisampledRenderToTexture; + this.useRenderbuffer = source.useRenderbuffer; return this; diff --git a/src/renderers/WebGLRenderTarget.js b/src/renderers/WebGLRenderTarget.js index 0a7fe39e89c688..41ad23cccf91f4 100644 --- a/src/renderers/WebGLRenderTarget.js +++ b/src/renderers/WebGLRenderTarget.js @@ -35,9 +35,6 @@ class WebGLRenderTarget extends EventDispatcher { this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; - this.ignoreDepthForMultisampleCopy = options.ignoreDepth !== undefined ? options.ignoreDepth : true; - this.useMultisampledRenderToTexture = false; - this.useMultisampledRenderbuffer = false; } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 0ac87111c4c4b0..801e5d80bc3196 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1275,7 +1275,7 @@ function WebGLRenderer( parameters = {} ) { magFilter: NearestFilter, wrapS: ClampToEdgeWrapping, wrapT: ClampToEdgeWrapping, - useMultisampledRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) + useRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) } ); } @@ -1880,11 +1880,11 @@ function WebGLRenderer( parameters = {} ) { // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( renderTarget.useMultisampledRenderToTexture ) { + if ( renderTarget.useRenderToTexture ) { console.warn( 'render-to-texture extension was disabled because an external texture was provided' ); - renderTarget.useMultisampledRenderToTexture = false; - renderTarget.useMultisampledRenderbuffer = true; + renderTarget.useRenderToTexture = false; + renderTarget.useRenderbuffer = true; } @@ -1934,7 +1934,7 @@ function WebGLRenderer( parameters = {} ) { framebuffer = __webglFramebuffer[ activeCubeFace ]; isCube = true; - } else if ( renderTarget.useMultisampledRenderbuffer ) { + } else if ( renderTarget.useRenderbuffer ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 6e1171eaf389e9..cf12a42b59e15b 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -9,8 +9,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const maxCubemapSize = capabilities.maxCubemapSize; const maxTextureSize = capabilities.maxTextureSize; const maxSamples = capabilities.maxSamples; - const MultisampledRenderToTextureSupported = extensions.has( 'EXT_multisampled_render_to_texture' ); - const MultisampledRenderToTextureExtension = MultisampledRenderToTextureSupported ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; + const hasMultisampledRenderToTexture = extensions.has( 'EXT_multisampled_render_to_texture' ); + const MultisampledRenderToTextureExtension = hasMultisampledRenderToTexture ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; const _videoTextures = new WeakMap(); let _canvas; @@ -877,7 +877,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - if ( renderTarget.useMultisampledRenderToTexture ) { + if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); @@ -901,7 +901,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, let glInternalFormat = _gl.DEPTH_COMPONENT16; - if ( isMultisample || renderTarget.useMultisampledRenderToTexture ) { + if ( isMultisample || renderTarget.useRenderToTexture ) { const depthTexture = renderTarget.depthTexture; @@ -921,7 +921,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const samples = getRenderTargetSamples( renderTarget ); - if ( renderTarget.useMultisampledRenderToTexture ) { + if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); @@ -943,11 +943,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const samples = getRenderTargetSamples( renderTarget ); - if ( isMultisample && renderTarget.useMultisampledRenderbuffer ) { + if ( isMultisample && renderTarget.useRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - } else if ( renderTarget.useMultisampledRenderToTexture ) { + } else if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); @@ -970,11 +970,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding ); const samples = getRenderTargetSamples( renderTarget ); - if ( isMultisample && renderTarget.useMultisampledRenderbuffer ) { + if ( isMultisample && renderTarget.useRenderbuffer ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } else if ( renderTarget.useMultisampledRenderToTexture ) { + } else if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); @@ -1022,7 +1022,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( renderTarget.depthTexture.format === DepthFormat ) { - if ( renderTarget.useMultisampledRenderToTexture ) { + if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); @@ -1034,7 +1034,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - if ( renderTarget.useMultisampledRenderToTexture ) { + if ( renderTarget.useRenderToTexture ) { MultisampledRenderToTextureExtension.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); @@ -1191,7 +1191,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - } else if ( renderTarget.useMultisampledRenderbuffer ) { + } else if ( renderTarget.useRenderbuffer ) { if ( isWebGL2 ) { @@ -1346,7 +1346,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function updateMultisampleRenderTarget( renderTarget ) { - if ( renderTarget.useMultisampledRenderbuffer ) { + if ( renderTarget.useRenderbuffer ) { if ( isWebGL2 ) { @@ -1399,7 +1399,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function getRenderTargetSamples( renderTarget ) { - return ( isWebGL2 && ( renderTarget.useMultisampledRenderbuffer || renderTarget.useMultisampledRenderToTexture ) ) ? + return ( isWebGL2 && ( renderTarget.useRenderbuffer || renderTarget.useRenderToTexture ) ) ? Math.min( maxSamples, renderTarget.samples ) : 0; } diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index 31fb41868af8e5..e83cf55cf600e9 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -31,7 +31,7 @@ class WebXRManager extends EventDispatcher { let referenceSpace = null; let referenceSpaceType = 'local-floor'; - const MultisampledRenderToTextureSupported = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); + const hasMultisampledRenderToTexture = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); let pose = null; let glBinding = null; @@ -298,7 +298,7 @@ class WebXRManager extends EventDispatcher { depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), stencilBuffer: attributes.stencil, ignoreDepth: glProjLayer.ignoreDepthValues, - useMultisampledRenderToTexture: MultisampledRenderToTextureSupported, + useRenderToTexture: hasMultisampledRenderToTexture, } ); } else { From 5a2e3e613b747286140bc29bacb80f4a85f0d2e3 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Wed, 6 Oct 2021 14:58:22 -0700 Subject: [PATCH 5/8] Address MrDoob's review comments #2 --- src/renderers/WebGLMultisampleRenderTarget.js | 4 ++-- src/renderers/WebGLRenderer.js | 6 +++--- src/renderers/webxr/WebXRManager.js | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderers/WebGLMultisampleRenderTarget.js b/src/renderers/WebGLMultisampleRenderTarget.js index 83ff547a33651c..0d08ab6c1945cd 100644 --- a/src/renderers/WebGLMultisampleRenderTarget.js +++ b/src/renderers/WebGLMultisampleRenderTarget.js @@ -9,7 +9,7 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { this.samples = 4; this.ignoreDepthForMultisampleCopy = options.ignoreDepth !== undefined ? options.ignoreDepth : true; - this.useRenderToTexture = ( options.useMultisampledRenderToTexture !== undefined ) ? options.useMultisampledRenderToTexture : false; + this.useRenderToTexture = ( options.useRenderToTexture !== undefined ) ? options.useRenderToTexture : false; this.useRenderbuffer = this.useRenderToTexture === false; } @@ -19,7 +19,7 @@ class WebGLMultisampleRenderTarget extends WebGLRenderTarget { super.copy.call( this, source ); this.samples = source.samples; - this.useRenderToTexture = source.useMultisampledRenderToTexture; + this.useRenderToTexture = source.useRenderToTexture; this.useRenderbuffer = source.useRenderbuffer; return this; diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 801e5d80bc3196..e8c3127347a182 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1855,12 +1855,12 @@ function WebGLRenderer( parameters = {} ) { }; - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0, options = {} ) { + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0, defaultFramebuffer = undefined, options = {} ) { _currentRenderTarget = renderTarget; _currentActiveCubeFace = activeCubeFace; _currentActiveMipmapLevel = activeMipmapLevel; - const useDefaultFramebuffer = options.framebuffer === undefined; + const useDefaultFramebuffer = defaultFramebuffer === undefined; if ( renderTarget ) { @@ -1898,7 +1898,7 @@ function WebGLRenderer( parameters = {} ) { // We need to make sure to rebind the framebuffer. state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - renderTargetProperties.__webglFramebuffer = options.framebuffer; + renderTargetProperties.__webglFramebuffer = defaultFramebuffer; } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index e83cf55cf600e9..a110a95c8c44af 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -583,9 +583,7 @@ class WebXRManager extends EventDispatcher { newRenderTarget, 0, 0, - { - framebuffer: glBaseLayer.framebuffer - } ); + glBaseLayer.framebuffer ); } @@ -622,6 +620,7 @@ class WebXRManager extends EventDispatcher { newRenderTarget, 0, 0, + undefined, { colorTexture: glSubImage.colorTexture, depthTexture: glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture From e7b823b245751dba84bcd4cdbeb9529415fb7acf Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Mon, 11 Oct 2021 15:59:30 -0700 Subject: [PATCH 6/8] Updated to the new extension name --- src/renderers/WebGLRenderer.js | 2 +- src/renderers/webgl/WebGLExtensions.js | 2 +- src/renderers/webgl/WebGLTextures.js | 4 ++-- src/renderers/webxr/WebXRManager.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index e8c3127347a182..1ef7f0a9b74eda 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1275,7 +1275,7 @@ function WebGLRenderer( parameters = {} ) { magFilter: NearestFilter, wrapS: ClampToEdgeWrapping, wrapT: ClampToEdgeWrapping, - useRenderToTexture: extensions.has( 'EXT_multisampled_render_to_texture' ) + useRenderToTexture: extensions.has( 'WEBGL_multisampled_render_to_texture' ) } ); } diff --git a/src/renderers/webgl/WebGLExtensions.js b/src/renderers/webgl/WebGLExtensions.js index 0f3d8f861e644f..48a052f63aa485 100644 --- a/src/renderers/webgl/WebGLExtensions.js +++ b/src/renderers/webgl/WebGLExtensions.js @@ -65,12 +65,12 @@ function WebGLExtensions( gl ) { getExtension( 'OES_element_index_uint' ); getExtension( 'OES_vertex_array_object' ); getExtension( 'ANGLE_instanced_arrays' ); - getExtension( 'EXT_multisampled_render_to_texture' ); } getExtension( 'OES_texture_float_linear' ); getExtension( 'EXT_color_buffer_half_float' ); + getExtension( 'WEBGL_multisampled_render_to_texture' ); }, diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index cf12a42b59e15b..246ccd243fc370 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -9,8 +9,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const maxCubemapSize = capabilities.maxCubemapSize; const maxTextureSize = capabilities.maxTextureSize; const maxSamples = capabilities.maxSamples; - const hasMultisampledRenderToTexture = extensions.has( 'EXT_multisampled_render_to_texture' ); - const MultisampledRenderToTextureExtension = hasMultisampledRenderToTexture ? extensions.get( 'EXT_multisampled_render_to_texture' ) : undefined; + const hasMultisampledRenderToTexture = extensions.has( 'WEBGL_multisampled_render_to_texture' ); + const MultisampledRenderToTextureExtension = hasMultisampledRenderToTexture ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : undefined; const _videoTextures = new WeakMap(); let _canvas; diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index a110a95c8c44af..57d25fa3f81f54 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -31,7 +31,7 @@ class WebXRManager extends EventDispatcher { let referenceSpace = null; let referenceSpaceType = 'local-floor'; - const hasMultisampledRenderToTexture = renderer.extensions.has( 'EXT_multisampled_render_to_texture' ); + const hasMultisampledRenderToTexture = renderer.extensions.has( 'WEBGL_multisampled_render_to_texture' ); let pose = null; let glBinding = null; From 9aa7b2ffa79ceb33ae2abfbe2147d4d080c2cace Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Wed, 17 Nov 2021 20:25:07 -0800 Subject: [PATCH 7/8] Address MrDoob's comment --- src/renderers/webxr/WebXRManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index 57d25fa3f81f54..a5d0068e345bd5 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -241,7 +241,7 @@ class WebXRManager extends EventDispatcher { } - if ( ( session.renderState.layers === undefined ) || ( gl instanceof WebGLRenderingContext ) ) { + if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { const layerInit = { antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, From d16a6085ad632d46139f2de042d583d42d3d1deb Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Wed, 24 Nov 2021 15:58:40 -0800 Subject: [PATCH 8/8] Address MrDoob's other comment --- src/renderers/WebGLRenderer.js | 66 +++++++++++++++++------------ src/renderers/webxr/WebXRManager.js | 20 +++------ 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 9b209f9b82f1c9..ef140c98212c69 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1762,59 +1762,69 @@ function WebGLRenderer( parameters = {} ) { }; - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0, defaultFramebuffer = undefined, options = {} ) { + this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; - const useDefaultFramebuffer = defaultFramebuffer === undefined; + properties.get( renderTarget.texture ).__webglTexture = colorTexture; + properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; - if ( renderTarget ) { + const renderTargetProperties = properties.get( renderTarget ); + renderTargetProperties.__hasExternalTextures = true; - const renderTargetProperties = properties.get( renderTarget ); - let hasNewExternalTextures = false; + if ( renderTargetProperties.__hasExternalTextures ) { - if ( options.colorTexture !== undefined ) { + renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; - hasNewExternalTextures = true; - properties.get( renderTarget.texture ).__webglTexture = options.colorTexture; + if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { - renderTargetProperties.__autoAllocateDepthBuffer = options.depthTexture === undefined; + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth buffer. Disable use of the extension. + if ( renderTarget.useRenderToTexture ) { - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { + console.warn( 'render-to-texture extension was disabled because an external texture was provided' ); + renderTarget.useRenderToTexture = false; + renderTarget.useRenderbuffer = true; - properties.get( renderTarget.depthTexture ).__webglTexture = options.depthTexture; + } - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( renderTarget.useRenderToTexture ) { + } - console.warn( 'render-to-texture extension was disabled because an external texture was provided' ); - renderTarget.useRenderToTexture = false; - renderTarget.useRenderbuffer = true; + } - } + }; - } + this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - renderTargetProperties.__hasExternalTextures = true; + const renderTargetProperties = properties.get( renderTarget ); + renderTargetProperties.__webglFramebuffer = defaultFramebuffer; + renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - } + }; + + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + + _currentRenderTarget = renderTarget; + _currentActiveCubeFace = activeCubeFace; + _currentActiveMipmapLevel = activeMipmapLevel; + let useDefaultFramebuffer = true; + + if ( renderTarget ) { + + const renderTargetProperties = properties.get( renderTarget ); - if ( ! useDefaultFramebuffer ) { + if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { // We need to make sure to rebind the framebuffer. state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; + useDefaultFramebuffer = false; } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { textures.setupRenderTarget( renderTarget ); - } else if ( hasNewExternalTextures ) { + } else if ( renderTargetProperties.__hasExternalTextures ) { // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, options.colorTexture, options.depthTexture ); + textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); } diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index a5d0068e345bd5..b578538f2f8212 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -579,11 +579,8 @@ class WebXRManager extends EventDispatcher { if ( glBaseLayer !== null ) { - renderer.setRenderTarget( - newRenderTarget, - 0, - 0, - glBaseLayer.framebuffer ); + renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); + renderer.setRenderTarget( newRenderTarget ); } @@ -616,15 +613,12 @@ class WebXRManager extends EventDispatcher { // For side-by-side projection, we only produce a single texture for both eyes. if ( i === 0 ) { - renderer.setRenderTarget( + renderer.setRenderTargetTextures( newRenderTarget, - 0, - 0, - undefined, - { - colorTexture: glSubImage.colorTexture, - depthTexture: glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture - } ); + glSubImage.colorTexture, + glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); + + renderer.setRenderTarget( newRenderTarget ); }