<!-- NEW3 — TEST 3 · THE RIVER'S JOURNEY · part 433 -->
uniforms.probesMin.value.copy( volume.boundingBox.min );
					m_uniforms.probesMax.value.copy( volume.boundingBox.max );
					m_uniforms.probesResolution.value.copy( volume.resolution );

				}

				WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );

			}

			if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {

				WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );
				material.uniformsNeedUpdate = false;

			}

			if ( material.isSpriteMaterial ) {

				p_uniforms.setValue( _gl, 'center', object.center );

			}

			// common matrices

			p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
			p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
			p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );

			// UBOs

			if ( material.uniformsGroups !== undefined ) {

				const groups = material.uniformsGroups;

				for ( let i = 0, l = groups.length; i < l; i ++ ) {

					const group = groups[ i ];

					uniformsGroups.update( group, program );
					uniformsGroups.bind( group, program );

				}

			}

			return program;

		}

		// If uniforms are marked as clean, they don't need to be loaded to the GPU.

		function markUniformsLightsNeedsUpdate( uniforms, value ) {

			uniforms.ambientLightColor.needsUpdate = value;
			uniforms.lightProbe.needsUpdate = value;

			uniforms.sunLights.needsUpdate = value;
			uniforms.sunLightShadows.needsUpdate = value;
			uniforms.directionalLights.needsUpdate = value;
			uniforms.directionalLightShadows.needsUpdate = value;
			uniforms.pointLights.needsUpdate = value;
			uniforms.pointLightShadows.needsUpdate = value;
			uniforms.spotLights.needsUpdate = value;
			uniforms.spotLightShadows.needsUpdate = value;
			uniforms.rectAreaLights.needsUpdate = value;
			uniforms.hemisphereLights.needsUpdate = value;

		}

		function materialNeedsLights( material ) {

			return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
				material.isMeshStandardMaterial || material.isShadowMaterial ||
				( material.isShaderMaterial && material.lights === true );

		}

		/**
		 * Returns the active cube face.
		 *
		 * @return {number} The active cube face.
		 */
		this.getActiveCubeFace = function () {

			return _currentActiveCubeFace;

		};

		/**
		 * Returns the active mipmap level.
		 *
		 * @return {number} The active mipmap level.
		 */
		this.getActiveMipmapLevel = function () {

			return _currentActiveMipmapLevel;

		};

		/**
		 * Returns the active render target.
		 *
		 * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target
		 * is currently set.
		 */
		this.getRenderTarget = function () {

			return _currentRenderTarget;

		};

		this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) {

			const renderTargetProperties = properties.get( renderTarget );

			renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false;
			if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) {

				// 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.
				renderTargetProperties.__useRenderToTexture = false;

			}

			properties.get( renderTarget.texture ).__webglTexture = colorTexture;
			properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture;

			renderTargetProperties.__hasExternalTextures = true;

		};

		this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) {

			const renderTargetProperties = properties.get( renderTarget );
			renderTargetProperties.__webglFramebuffer = defaultFramebuffer;
			renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined;

		};

		/**
		 * Sets the active rendertarget.
		 *
		 * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given,
		 * the canvas is set as the active render target instead.
		 * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target.
		 * Indicates the z layer to render in to when using 3D or array render targets.
		 * @param {number} [activeMipmapLevel=0] - The active mipmap level.
		 */
		this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {

			_currentRenderTarget = renderTarget;
			_currentActiveCubeFace = activeCubeFace;
			_currentActiveMipmapLevel = activeMipmapLevel;

			let framebuffer = null;
			let isCube = false;
			let isRenderTarget3D = false;

			if ( renderTarget ) {

				const renderTargetProperties = properties.get( renderTarget );

				if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) {

					// Externally-managed fr