From 270dbd96e4f011122320d668719985edf72601c9 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 28 Jan 2026 20:20:36 +0100 Subject: [PATCH 1/3] TSL: Avoid side effect of `getTextureLevel()` in normals. (#32877) --- src/nodes/accessors/Normal.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nodes/accessors/Normal.js b/src/nodes/accessors/Normal.js index a2c90cc24d9d54..9ba97f3f44a879 100644 --- a/src/nodes/accessors/Normal.js +++ b/src/nodes/accessors/Normal.js @@ -108,9 +108,9 @@ export const normalView = /*@__PURE__*/ ( Fn( ( builder ) => { } else { - // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + // Use custom context to avoid side effects from nodes overwriting getUV, getTextureLevel in the context (e.g. EnvironmentNode) - node = builder.context.setupNormal().context( { getUV: null } ); + node = builder.context.setupNormal().context( { getUV: null, getTextureLevel: null } ); } @@ -142,9 +142,9 @@ export const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context } } else { - // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + // Use custom context to avoid side effects from nodes overwriting getUV, getTextureLevel in the context (e.g. EnvironmentNode) - node = context.setupClearcoatNormal().context( { getUV: null } ); + node = context.setupClearcoatNormal().context( { getUV: null, getTextureLevel: null } ); } From 819cb9b2368aa86c0a29594c8ded29c69a017516 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 28 Jan 2026 20:21:24 +0100 Subject: [PATCH 2/3] WebGPURenderer: Export `CubeRenderTarget` and use it in examples. (#32871) --- examples/jsm/exporters/EXRExporter.js | 2 +- examples/webgpu_cubemap_dynamic.html | 2 +- examples/webgpu_lightprobe_cubecamera.html | 2 +- examples/webgpu_materials_envmaps_bpcem.html | 2 +- src/Three.Core.js | 1 - src/Three.WebGPU.js | 1 + src/Three.js | 1 + src/renderers/common/CubeRenderTarget.js | 56 +++++++++++++++++--- src/renderers/webgl-fallback/WebGLBackend.js | 2 +- 9 files changed, 57 insertions(+), 12 deletions(-) diff --git a/examples/jsm/exporters/EXRExporter.js b/examples/jsm/exporters/EXRExporter.js index da370a2a7fe0f2..fcc1d3f8b96074 100644 --- a/examples/jsm/exporters/EXRExporter.js +++ b/examples/jsm/exporters/EXRExporter.js @@ -90,7 +90,7 @@ function supportedRTT( renderTarget ) { } - if ( renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { + if ( renderTarget.isCubeRenderTarget || renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' ); diff --git a/examples/webgpu_cubemap_dynamic.html b/examples/webgpu_cubemap_dynamic.html index 1d8ff2cfabee77..15e02d5ae9ed2b 100644 --- a/examples/webgpu_cubemap_dynamic.html +++ b/examples/webgpu_cubemap_dynamic.html @@ -76,7 +76,7 @@ // - cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256 ); + cubeRenderTarget = new THREE.CubeRenderTarget( 256 ); cubeRenderTarget.texture.type = THREE.HalfFloatType; cubeRenderTarget.texture.minFilter = THREE.LinearMipmapLinearFilter; cubeRenderTarget.texture.magFilter = THREE.LinearFilter; diff --git a/examples/webgpu_lightprobe_cubecamera.html b/examples/webgpu_lightprobe_cubecamera.html index 73ebcab1964896..ba4b47169f5f8a 100644 --- a/examples/webgpu_lightprobe_cubecamera.html +++ b/examples/webgpu_lightprobe_cubecamera.html @@ -64,7 +64,7 @@ camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 ); camera.position.set( 0, 0, 30 ); - const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256 ); + const cubeRenderTarget = new THREE.CubeRenderTarget( 256 ); cubeCamera = new THREE.CubeCamera( 1, 1000, cubeRenderTarget ); diff --git a/examples/webgpu_materials_envmaps_bpcem.html b/examples/webgpu_materials_envmaps_bpcem.html index 9df692cd0e0c26..6df80d0ea84b6d 100644 --- a/examples/webgpu_materials_envmaps_bpcem.html +++ b/examples/webgpu_materials_envmaps_bpcem.html @@ -65,7 +65,7 @@ // cube camera for environment map - const renderTarget = new THREE.WebGLCubeRenderTarget( 512 ); + const renderTarget = new THREE.CubeRenderTarget( 512 ); renderTarget.texture.type = THREE.HalfFloatType; renderTarget.texture.minFilter = THREE.LinearMipmapLinearFilter; renderTarget.texture.magFilter = THREE.LinearFilter; diff --git a/src/Three.Core.js b/src/Three.Core.js index 2a7982a6b0a9a4..158f575912c4b0 100644 --- a/src/Three.Core.js +++ b/src/Three.Core.js @@ -3,7 +3,6 @@ import { warn } from './utils.js'; export { WebGLArrayRenderTarget } from './renderers/WebGLArrayRenderTarget.js'; export { WebGL3DRenderTarget } from './renderers/WebGL3DRenderTarget.js'; -export { WebGLCubeRenderTarget } from './renderers/WebGLCubeRenderTarget.js'; export { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js'; export { WebXRController } from './renderers/webxr/WebXRController.js'; export { FogExp2 } from './scenes/FogExp2.js'; diff --git a/src/Three.WebGPU.js b/src/Three.WebGPU.js index f6fedf52a4c95f..aef74112bb1727 100644 --- a/src/Three.WebGPU.js +++ b/src/Three.WebGPU.js @@ -10,6 +10,7 @@ export { default as RenderPipeline } from './renderers/common/RenderPipeline.js' export { default as PostProcessing } from './renderers/common/PostProcessing.js'; import * as RendererUtils from './renderers/common/RendererUtils.js'; export { RendererUtils }; +export { default as CubeRenderTarget } from './renderers/common/CubeRenderTarget.js'; export { default as StorageTexture } from './renderers/common/StorageTexture.js'; export { default as Storage3DTexture } from './renderers/common/Storage3DTexture.js'; export { default as StorageArrayTexture } from './renderers/common/StorageArrayTexture.js'; diff --git a/src/Three.js b/src/Three.js index ff492c75df53a9..b74685f9a473b3 100644 --- a/src/Three.js +++ b/src/Three.js @@ -1,6 +1,7 @@ export * from './Three.Core.js'; export { WebGLRenderer } from './renderers/WebGLRenderer.js'; +export { WebGLCubeRenderTarget } from './renderers/WebGLCubeRenderTarget.js'; export { ShaderLib } from './renderers/shaders/ShaderLib.js'; export { UniformsLib } from './renderers/shaders/UniformsLib.js'; export { UniformsUtils } from './renderers/shaders/UniformsUtils.js'; diff --git a/src/renderers/common/CubeRenderTarget.js b/src/renderers/common/CubeRenderTarget.js index e8931de831400b..596c8bdb7d1021 100644 --- a/src/renderers/common/CubeRenderTarget.js +++ b/src/renderers/common/CubeRenderTarget.js @@ -3,22 +3,21 @@ import { texture as TSL_Texture } from '../../nodes/accessors/TextureNode.js'; import { positionWorldDirection } from '../../nodes/accessors/Position.js'; import NodeMaterial from '../../materials/nodes/NodeMaterial.js'; -import { WebGLCubeRenderTarget } from '../../renderers/WebGLCubeRenderTarget.js'; +import { RenderTarget } from '../../core/RenderTarget.js'; import { Scene } from '../../scenes/Scene.js'; import { CubeCamera } from '../../cameras/CubeCamera.js'; import { BoxGeometry } from '../../geometries/BoxGeometry.js'; import { Mesh } from '../../objects/Mesh.js'; +import { CubeTexture } from '../../textures/CubeTexture.js'; import { BackSide, NoBlending, LinearFilter, LinearMipmapLinearFilter } from '../../constants.js'; -// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget - /** * This class represents a cube render target. It is a special version * of `WebGLCubeRenderTarget` which is compatible with `WebGPURenderer`. * - * @augments WebGLCubeRenderTarget + * @augments RenderTarget */ -class CubeRenderTarget extends WebGLCubeRenderTarget { +class CubeRenderTarget extends RenderTarget { /** * Constructs a new cube render target. @@ -28,7 +27,7 @@ class CubeRenderTarget extends WebGLCubeRenderTarget { */ constructor( size = 1, options = {} ) { - super( size, options ); + super( size, size, options ); /** * This flag can be used for type testing. @@ -39,6 +38,27 @@ class CubeRenderTarget extends WebGLCubeRenderTarget { */ this.isCubeRenderTarget = true; + const image = { width: size, height: size, depth: 1 }; + const images = [ image, image, image, image, image, image ]; + + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new CubeTexture( images ); + this._setTextureOptions( options ); + + // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) + // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, + // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. + + // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped + // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture + // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). + + this.texture.isRenderTargetTexture = true; + } /** @@ -98,6 +118,30 @@ class CubeRenderTarget extends WebGLCubeRenderTarget { } + /** + * Clears this cube render target. + * + * @param {Renderer} renderer - The renderer. + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + clear( renderer, color = true, depth = true, stencil = true ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + for ( let i = 0; i < 6; i ++ ) { + + renderer.setRenderTarget( this, i ); + + renderer.clear( color, depth, stencil ); + + } + + renderer.setRenderTarget( currentRenderTarget ); + + } + } export default CubeRenderTarget; diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index 5732177900fa8d..61aa4684be470a 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -2044,7 +2044,7 @@ class WebGLBackend extends Backend { const renderTargetContextData = this.get( renderTarget ); const { samples, depthBuffer, stencilBuffer } = renderTarget; - const isCube = renderTarget.isWebGLCubeRenderTarget === true; + const isCube = renderTarget.isCubeRenderTarget === true; const isRenderTarget3D = renderTarget.isRenderTarget3D === true; const isRenderTargetArray = renderTarget.depth > 1; const isXRRenderTarget = renderTarget.isXRRenderTarget === true; From b43e7641e2c6583b7e6eb43fe7b9be53d0b9aef6 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 28 Jan 2026 20:37:44 +0100 Subject: [PATCH 3/3] Line3: Fix closest point in `distanceSqToLine3()`. (#32878) --- src/math/Line3.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/math/Line3.js b/src/math/Line3.js index 1a81101cbe6e9c..df8377d7e9f61b 100644 --- a/src/math/Line3.js +++ b/src/math/Line3.js @@ -284,12 +284,10 @@ class Line3 { } - c1.copy( p1 ).add( _d1.multiplyScalar( s ) ); - c2.copy( p2 ).add( _d2.multiplyScalar( t ) ); + c1.copy( p1 ).addScaledVector( _d1, s ); + c2.copy( p2 ).addScaledVector( _d2, t ); - c1.sub( c2 ); - - return c1.dot( c1 ); + return c1.distanceToSquared( c2 ); }