From 61a94dc7ba9a6ad1d608bd42ffe01d4b0261de9a Mon Sep 17 00:00:00 2001 From: rodri Date: Tue, 3 Mar 2026 21:38:28 +0000 Subject: [PATCH 1/7] add more controls to debug --- examples/webgl_shaders_ocean.html | 81 +++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/examples/webgl_shaders_ocean.html b/examples/webgl_shaders_ocean.html index 7f9df7a91e1047..7cb019336e2156 100644 --- a/examples/webgl_shaders_ocean.html +++ b/examples/webgl_shaders_ocean.html @@ -36,7 +36,15 @@ let container, stats; let camera, scene, renderer; - let controls, water, sun, sky, mesh, bloomPass; + let controls, water, sun, sky, mesh, bloomPass, debugLight; + + const toggles = { + sky: false, + water: false, + bloom: false, + clouds: false, + debugLight: true + }; init(); @@ -114,7 +122,7 @@ skyUniforms[ 'cloudElevation' ].value = 0.5; const parameters = { - elevation: 2, + elevation: 30, azimuth: 180, exposure: 0.1 }; @@ -149,11 +157,16 @@ // const geometry = new THREE.BoxGeometry( 30, 30, 30 ); - const material = new THREE.MeshStandardMaterial( { roughness: 0 } ); + const material = new THREE.MeshStandardMaterial( { roughness: 1 } ); mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); + // Debug light + + debugLight = new THREE.DirectionalLight( 0xffffff, 1 ); + debugLight.position.set( 100, 100, 100 ); + // controls = new OrbitControls( camera, renderer.domElement ); @@ -170,9 +183,34 @@ // GUI + let savedCloudCoverage = skyUniforms.cloudCoverage.value; + + // Apply initial scene state from toggles + sky.visible = toggles.sky; + if ( ! toggles.sky ) scene.environment = null; + water.visible = toggles.water; + bloomPass.enabled = toggles.bloom; + if ( toggles.debugLight ) scene.add( debugLight ); + if ( ! toggles.clouds ) skyUniforms.cloudCoverage.value = 0; + const gui = new GUI(); const folderSky = gui.addFolder( 'Sky' ); + folderSky.add( toggles, 'sky' ).name( 'visible' ).onChange( value => { + + sky.visible = value; + + if ( value ) { + + updateSun(); + + } else { + + scene.environment = null; + + } + + } ); folderSky.add( parameters, 'elevation', 0, 90, 0.1 ).onChange( updateSun ); folderSky.add( parameters, 'azimuth', - 180, 180, 0.1 ).onChange( updateSun ); folderSky.add( parameters, 'exposure', 0, 1, 0.0001 ).onChange( function ( value ) { @@ -185,21 +223,58 @@ const waterUniforms = water.material.uniforms; const folderWater = gui.addFolder( 'Water' ); + folderWater.add( toggles, 'water' ).name( 'visible' ).onChange( value => { water.visible = value; } ); folderWater.add( waterUniforms.distortionScale, 'value', 0, 8, 0.1 ).name( 'distortionScale' ); folderWater.add( waterUniforms.size, 'value', 0.1, 10, 0.1 ).name( 'size' ); folderWater.open(); const folderBloom = gui.addFolder( 'Bloom' ); + folderBloom.add( toggles, 'bloom' ).name( 'enabled' ).onChange( value => { bloomPass.enabled = value; } ); folderBloom.add( bloomPass, 'strength', 0, 3, 0.01 ); folderBloom.add( bloomPass, 'radius', 0, 1, 0.01 ); folderBloom.open(); const folderClouds = gui.addFolder( 'Clouds' ); + folderClouds.add( toggles, 'clouds' ).name( 'visible' ).onChange( value => { + + if ( value ) { + + skyUniforms.cloudCoverage.value = savedCloudCoverage; + + } else { + + savedCloudCoverage = skyUniforms.cloudCoverage.value; + skyUniforms.cloudCoverage.value = 0; + + } + + } ); folderClouds.add( skyUniforms.cloudCoverage, 'value', 0, 1, 0.01 ).name( 'coverage' ); folderClouds.add( skyUniforms.cloudDensity, 'value', 0, 1, 0.01 ).name( 'density' ); folderClouds.add( skyUniforms.cloudElevation, 'value', 0, 1, 0.01 ).name( 'elevation' ); folderClouds.open(); + const folderDebugLight = gui.addFolder( 'Debug Light' ); + folderDebugLight.add( toggles, 'debugLight' ).name( 'enabled' ).onChange( value => { + + if ( value ) { + + scene.add( debugLight ); + + } else { + + scene.remove( debugLight ); + + } + + } ); + folderDebugLight.add( debugLight, 'intensity', 0, 10, 5 ); + folderDebugLight.addColor( debugLight, 'color' ); + folderDebugLight.add( debugLight.position, 'x', - 200, 200, 1 ).name( 'pos X' ); + folderDebugLight.add( debugLight.position, 'y', - 200, 200, 1 ).name( 'pos Y' ); + folderDebugLight.add( debugLight.position, 'z', - 200, 200, 1 ).name( 'pos Z' ); + folderDebugLight.open(); + // window.addEventListener( 'resize', onWindowResize ); From 55614585b32fbde7fb350d5c97eae15318f766f5 Mon Sep 17 00:00:00 2001 From: rodri Date: Tue, 3 Mar 2026 21:55:57 +0000 Subject: [PATCH 2/7] add names --- examples/webgl_shaders_ocean.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/webgl_shaders_ocean.html b/examples/webgl_shaders_ocean.html index 7cb019336e2156..1f1cb8d90080fc 100644 --- a/examples/webgl_shaders_ocean.html +++ b/examples/webgl_shaders_ocean.html @@ -82,6 +82,7 @@ // Water const waterGeometry = new THREE.PlaneGeometry( 10000, 10000 ); + waterGeometry.name = 'waterGeometry'; water = new Water( waterGeometry, @@ -101,6 +102,8 @@ } ); + water.name = 'water'; + water.material.name = 'waterMaterial'; water.rotation.x = - Math.PI / 2; scene.add( water ); @@ -108,6 +111,8 @@ // Skybox sky = new Sky(); + sky.name = 'sky'; + sky.material.name = 'skyMaterial'; sky.scale.setScalar( 10000 ); scene.add( sky ); @@ -148,6 +153,7 @@ renderTarget = pmremGenerator.fromScene( sceneEnv ); scene.add( sky ); + renderTarget.texture.name = 'skyEnvironment'; scene.environment = renderTarget.texture; } @@ -157,14 +163,18 @@ // const geometry = new THREE.BoxGeometry( 30, 30, 30 ); + geometry.name = 'boxGeometry'; const material = new THREE.MeshStandardMaterial( { roughness: 1 } ); + material.name = 'boxMaterial'; mesh = new THREE.Mesh( geometry, material ); + mesh.name = 'box'; scene.add( mesh ); // Debug light debugLight = new THREE.DirectionalLight( 0xffffff, 1 ); + debugLight.name = 'debugLight'; debugLight.position.set( 100, 100, 100 ); // From 9f8dc61d7cf781b0e68ce7dfada1f2676d41653c Mon Sep 17 00:00:00 2001 From: Rodri Date: Tue, 3 Mar 2026 22:28:52 +0000 Subject: [PATCH 3/7] extras/PMREMGenerator: fall back to FloatType when EXT_color_buffer_half_float is unsupported --- src/extras/PMREMGenerator.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extras/PMREMGenerator.js b/src/extras/PMREMGenerator.js index 05ac5d6eae2db8..6cfef467be847e 100644 --- a/src/extras/PMREMGenerator.js +++ b/src/extras/PMREMGenerator.js @@ -7,6 +7,7 @@ import { NoBlending, RGBAFormat, HalfFloatType, + FloatType, BackSide, LinearSRGBColorSpace } from '../constants.js'; @@ -288,11 +289,14 @@ class PMREMGenerator { const width = 3 * Math.max( this._cubeSize, 16 * 7 ); const height = 4 * this._cubeSize; + const extensions = this._renderer.extensions; + const type = extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : FloatType; + const params = { magFilter: LinearFilter, minFilter: LinearFilter, generateMipmaps: false, - type: HalfFloatType, + type: type, format: RGBAFormat, colorSpace: LinearSRGBColorSpace, depthBuffer: false From 15cd9d3cdd19c0177b767e374ba8b2d04f5d1000 Mon Sep 17 00:00:00 2001 From: Rodri Date: Tue, 3 Mar 2026 22:44:18 +0000 Subject: [PATCH 4/7] jsm/objects/Water: fall back to FloatType when EXT_color_buffer_half_float is unsupported --- examples/jsm/objects/Water.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/jsm/objects/Water.js b/examples/jsm/objects/Water.js index 8575cb9c99291e..8c78c6962c5cd1 100644 --- a/examples/jsm/objects/Water.js +++ b/examples/jsm/objects/Water.js @@ -1,6 +1,7 @@ import { Color, FrontSide, + FloatType, HalfFloatType, Matrix4, Mesh, @@ -85,7 +86,7 @@ class Water extends Mesh { const mirrorCamera = new PerspectiveCamera(); - const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: HalfFloatType } ); + let renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: HalfFloatType } ); const mirrorShader = { @@ -237,6 +238,15 @@ class Water extends Mesh { scope.onBeforeRender = function ( renderer, scene, camera ) { + // Lazily fall back to FloatType if EXT_color_buffer_half_float is unsupported (e.g. some mobile GPUs) + if ( renderTarget.texture.type === HalfFloatType && ! renderer.extensions.has( 'EXT_color_buffer_half_float' ) ) { + + renderTarget.dispose(); + renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: FloatType } ); + material.uniforms[ 'mirrorSampler' ].value = renderTarget.texture; + + } + mirrorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); From e331e97cbb8117f261c7220e339f42aa4c3f0efe Mon Sep 17 00:00:00 2001 From: Rodri Date: Tue, 3 Mar 2026 22:44:18 +0000 Subject: [PATCH 5/7] examples/webgl_shaders_ocean: enable water and disable debug light by default --- examples/webgl_shaders_ocean.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/webgl_shaders_ocean.html b/examples/webgl_shaders_ocean.html index 1f1cb8d90080fc..7b54f9a26432bd 100644 --- a/examples/webgl_shaders_ocean.html +++ b/examples/webgl_shaders_ocean.html @@ -39,11 +39,11 @@ let controls, water, sun, sky, mesh, bloomPass, debugLight; const toggles = { - sky: false, - water: false, + sky: true, + water: true, bloom: false, clouds: false, - debugLight: true + debugLight: false }; init(); From 12f3b410505eb89a082cabeb2d44ac67a2c1e07a Mon Sep 17 00:00:00 2001 From: Rodri Date: Tue, 3 Mar 2026 22:46:06 +0000 Subject: [PATCH 6/7] examples/webgl_shaders_ocean: revert debug scaffolding --- examples/webgl_shaders_ocean.html | 91 +------------------------------ 1 file changed, 3 insertions(+), 88 deletions(-) diff --git a/examples/webgl_shaders_ocean.html b/examples/webgl_shaders_ocean.html index 7b54f9a26432bd..7f9df7a91e1047 100644 --- a/examples/webgl_shaders_ocean.html +++ b/examples/webgl_shaders_ocean.html @@ -36,15 +36,7 @@ let container, stats; let camera, scene, renderer; - let controls, water, sun, sky, mesh, bloomPass, debugLight; - - const toggles = { - sky: true, - water: true, - bloom: false, - clouds: false, - debugLight: false - }; + let controls, water, sun, sky, mesh, bloomPass; init(); @@ -82,7 +74,6 @@ // Water const waterGeometry = new THREE.PlaneGeometry( 10000, 10000 ); - waterGeometry.name = 'waterGeometry'; water = new Water( waterGeometry, @@ -102,8 +93,6 @@ } ); - water.name = 'water'; - water.material.name = 'waterMaterial'; water.rotation.x = - Math.PI / 2; scene.add( water ); @@ -111,8 +100,6 @@ // Skybox sky = new Sky(); - sky.name = 'sky'; - sky.material.name = 'skyMaterial'; sky.scale.setScalar( 10000 ); scene.add( sky ); @@ -127,7 +114,7 @@ skyUniforms[ 'cloudElevation' ].value = 0.5; const parameters = { - elevation: 30, + elevation: 2, azimuth: 180, exposure: 0.1 }; @@ -153,7 +140,6 @@ renderTarget = pmremGenerator.fromScene( sceneEnv ); scene.add( sky ); - renderTarget.texture.name = 'skyEnvironment'; scene.environment = renderTarget.texture; } @@ -163,20 +149,11 @@ // const geometry = new THREE.BoxGeometry( 30, 30, 30 ); - geometry.name = 'boxGeometry'; - const material = new THREE.MeshStandardMaterial( { roughness: 1 } ); - material.name = 'boxMaterial'; + const material = new THREE.MeshStandardMaterial( { roughness: 0 } ); mesh = new THREE.Mesh( geometry, material ); - mesh.name = 'box'; scene.add( mesh ); - // Debug light - - debugLight = new THREE.DirectionalLight( 0xffffff, 1 ); - debugLight.name = 'debugLight'; - debugLight.position.set( 100, 100, 100 ); - // controls = new OrbitControls( camera, renderer.domElement ); @@ -193,34 +170,9 @@ // GUI - let savedCloudCoverage = skyUniforms.cloudCoverage.value; - - // Apply initial scene state from toggles - sky.visible = toggles.sky; - if ( ! toggles.sky ) scene.environment = null; - water.visible = toggles.water; - bloomPass.enabled = toggles.bloom; - if ( toggles.debugLight ) scene.add( debugLight ); - if ( ! toggles.clouds ) skyUniforms.cloudCoverage.value = 0; - const gui = new GUI(); const folderSky = gui.addFolder( 'Sky' ); - folderSky.add( toggles, 'sky' ).name( 'visible' ).onChange( value => { - - sky.visible = value; - - if ( value ) { - - updateSun(); - - } else { - - scene.environment = null; - - } - - } ); folderSky.add( parameters, 'elevation', 0, 90, 0.1 ).onChange( updateSun ); folderSky.add( parameters, 'azimuth', - 180, 180, 0.1 ).onChange( updateSun ); folderSky.add( parameters, 'exposure', 0, 1, 0.0001 ).onChange( function ( value ) { @@ -233,58 +185,21 @@ const waterUniforms = water.material.uniforms; const folderWater = gui.addFolder( 'Water' ); - folderWater.add( toggles, 'water' ).name( 'visible' ).onChange( value => { water.visible = value; } ); folderWater.add( waterUniforms.distortionScale, 'value', 0, 8, 0.1 ).name( 'distortionScale' ); folderWater.add( waterUniforms.size, 'value', 0.1, 10, 0.1 ).name( 'size' ); folderWater.open(); const folderBloom = gui.addFolder( 'Bloom' ); - folderBloom.add( toggles, 'bloom' ).name( 'enabled' ).onChange( value => { bloomPass.enabled = value; } ); folderBloom.add( bloomPass, 'strength', 0, 3, 0.01 ); folderBloom.add( bloomPass, 'radius', 0, 1, 0.01 ); folderBloom.open(); const folderClouds = gui.addFolder( 'Clouds' ); - folderClouds.add( toggles, 'clouds' ).name( 'visible' ).onChange( value => { - - if ( value ) { - - skyUniforms.cloudCoverage.value = savedCloudCoverage; - - } else { - - savedCloudCoverage = skyUniforms.cloudCoverage.value; - skyUniforms.cloudCoverage.value = 0; - - } - - } ); folderClouds.add( skyUniforms.cloudCoverage, 'value', 0, 1, 0.01 ).name( 'coverage' ); folderClouds.add( skyUniforms.cloudDensity, 'value', 0, 1, 0.01 ).name( 'density' ); folderClouds.add( skyUniforms.cloudElevation, 'value', 0, 1, 0.01 ).name( 'elevation' ); folderClouds.open(); - const folderDebugLight = gui.addFolder( 'Debug Light' ); - folderDebugLight.add( toggles, 'debugLight' ).name( 'enabled' ).onChange( value => { - - if ( value ) { - - scene.add( debugLight ); - - } else { - - scene.remove( debugLight ); - - } - - } ); - folderDebugLight.add( debugLight, 'intensity', 0, 10, 5 ); - folderDebugLight.addColor( debugLight, 'color' ); - folderDebugLight.add( debugLight.position, 'x', - 200, 200, 1 ).name( 'pos X' ); - folderDebugLight.add( debugLight.position, 'y', - 200, 200, 1 ).name( 'pos Y' ); - folderDebugLight.add( debugLight.position, 'z', - 200, 200, 1 ).name( 'pos Z' ); - folderDebugLight.open(); - // window.addEventListener( 'resize', onWindowResize ); From 406268b15f9ec1107a9eda18c4116af72e4f3860 Mon Sep 17 00:00:00 2001 From: Rodri Date: Wed, 4 Mar 2026 17:47:08 +0000 Subject: [PATCH 7/7] WebGLTextures: auto-downgrade HalfFloatType render targets on unsupported devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When EXT_color_buffer_half_float is unavailable, setupRenderTarget now automatically falls back to FloatType (if EXT_color_buffer_float is present) or UnsignedByteType, emitting a one-time console warning. This covers Water, PMREMGenerator, shadow maps, post-processing passes, and all other WebGL render targets uniformly. Revert per-component patches in Water.js and PMREMGenerator.js — now redundant. --- examples/jsm/objects/Water.js | 12 +----------- src/extras/PMREMGenerator.js | 6 +----- src/renderers/webgl/WebGLTextures.js | 29 +++++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/jsm/objects/Water.js b/examples/jsm/objects/Water.js index 8c78c6962c5cd1..8575cb9c99291e 100644 --- a/examples/jsm/objects/Water.js +++ b/examples/jsm/objects/Water.js @@ -1,7 +1,6 @@ import { Color, FrontSide, - FloatType, HalfFloatType, Matrix4, Mesh, @@ -86,7 +85,7 @@ class Water extends Mesh { const mirrorCamera = new PerspectiveCamera(); - let renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: HalfFloatType } ); + const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: HalfFloatType } ); const mirrorShader = { @@ -238,15 +237,6 @@ class Water extends Mesh { scope.onBeforeRender = function ( renderer, scene, camera ) { - // Lazily fall back to FloatType if EXT_color_buffer_half_float is unsupported (e.g. some mobile GPUs) - if ( renderTarget.texture.type === HalfFloatType && ! renderer.extensions.has( 'EXT_color_buffer_half_float' ) ) { - - renderTarget.dispose(); - renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { type: FloatType } ); - material.uniforms[ 'mirrorSampler' ].value = renderTarget.texture; - - } - mirrorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); diff --git a/src/extras/PMREMGenerator.js b/src/extras/PMREMGenerator.js index 6cfef467be847e..05ac5d6eae2db8 100644 --- a/src/extras/PMREMGenerator.js +++ b/src/extras/PMREMGenerator.js @@ -7,7 +7,6 @@ import { NoBlending, RGBAFormat, HalfFloatType, - FloatType, BackSide, LinearSRGBColorSpace } from '../constants.js'; @@ -289,14 +288,11 @@ class PMREMGenerator { const width = 3 * Math.max( this._cubeSize, 16 * 7 ); const height = 4 * this._cubeSize; - const extensions = this._renderer.extensions; - const type = extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : FloatType; - const params = { magFilter: LinearFilter, minFilter: LinearFilter, generateMipmaps: false, - type: type, + type: HalfFloatType, format: RGBAFormat, colorSpace: LinearSRGBColorSpace, depthBuffer: false diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 85c2bcad042999..02bf79f735dce8 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -1,4 +1,4 @@ -import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, RGBAFormat, DepthFormat, DepthStencilFormat, UnsignedIntType, FloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, UnsignedByteType, NoColorSpace, LinearSRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, SRGBTransfer, LinearTransfer, UnsignedShortType, UnsignedInt248Type } from '../../constants.js'; +import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, RGBAFormat, DepthFormat, DepthStencilFormat, UnsignedIntType, HalfFloatType, FloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, UnsignedByteType, NoColorSpace, LinearSRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, SRGBTransfer, LinearTransfer, UnsignedShortType, UnsignedInt248Type } from '../../constants.js'; import { createElementNS, warn, error } from '../../utils.js'; import { ColorManagement } from '../../math/ColorManagement.js'; import { Vector2 } from '../../math/Vector2.js'; @@ -13,6 +13,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const _videoTextures = new WeakMap(); let _canvas; + let _halfFloatFallbackWarned = false; + const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, @@ -1907,6 +1909,31 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // Set up GL resources for the render target function setupRenderTarget( renderTarget ) { + // Auto-fallback: HalfFloatType render targets require EXT_color_buffer_half_float. + // When unavailable, cascade to FloatType (requires EXT_color_buffer_float) or UnsignedByteType. + if ( ! extensions.has( 'EXT_color_buffer_half_float' ) ) { + + const fallbackType = extensions.has( 'EXT_color_buffer_float' ) ? FloatType : UnsignedByteType; + + for ( const texture of renderTarget.textures ) { + + if ( texture.type === HalfFloatType ) { + + texture.type = fallbackType; + + if ( ! _halfFloatFallbackWarned ) { + + warn( 'THREE.WebGLTextures: HalfFloatType render target is not supported on this device; falling back to ' + ( fallbackType === FloatType ? 'FloatType' : 'UnsignedByteType' ) + '.' ); + _halfFloatFallbackWarned = true; + + } + + } + + } + + } + const texture = renderTarget.texture; const renderTargetProperties = properties.get( renderTarget );