浏览代码

Improved transparent rendering (#25819)

* improved transparent rendering

* updated for premultipliedAlpha: true

* revert premultiply

* transmission proof-of-concept

* update builds

* add dragon model

* CI fixes

* Allow CSS background to show through model

* Update builds

* merging two approaches

* update screenshot

* addressing feedback

* updating builds

* added comment

* added comment

* update screenshot

* revert files

---------

Co-authored-by: WestLangley <[email protected]>
Emmett Lalish 2 年之前
父节点
当前提交
f7dff100f5

二进制
examples/models/gltf/DragonAttenuation.glb


二进制
examples/screenshots/webgl_materials_physical_transmission.2.jpg


+ 323 - 0
examples/webgl_materials_physical_transmission.2.html

@@ -0,0 +1,323 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>threejs webgl - materials - transmission 2</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+
+		<style>
+			body {
+				background-color: #ffffff;
+			}
+
+			#table {
+			    margin-top: 100px;
+				border-collapse: collapse;
+				width: 100%;
+			}
+			#table td {
+			    margin: 0;
+			    padding: 0;
+			    font-size: 16px;
+			    text-align: center;
+			    vertical-align: center;
+			}
+			#table tr {
+			    height: 250px;
+			}
+
+			#block-ff0000 { background-color: #ff0000; color: white; }
+			#block-00ff00 { background-color: #00ff00; color: black; }
+			#block-0000ff { background-color: #0000ff; color: white; }
+			#block-000000 { background-color: #000000; color: black; }
+		</style>
+
+	</head>
+	<body>
+
+		<table id="table">
+			<tbody>
+				<tr>
+				<td id="block-ff0000">ff0000</td>
+				<td id="block-00ff00">00ff00</td>
+				<td id="block-0000ff">0000ff</td>
+				<td id="block-000000">000000</td>
+				</tr>
+			</tbody>
+		</table>
+
+		<div id="container"></div>
+		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">threejs</a> - Transmission 2</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+			const params = {
+				color: 0xffffff,
+				transmission: 1,
+				opacity: 1,
+				metalness: 0,
+				roughness: 0,
+				ior: 1.5,
+				thickness: 0.01,
+				attenuationColor: 0xffffff,
+				attenuationDistance: 1,
+				specularIntensity: 1,
+				specularColor: 0xffffff,
+				envMapIntensity: 1,
+				lightIntensity: 1,
+				exposure: 1
+			};
+
+			let camera, scene, renderer;
+
+			let mesh, material;
+
+			const hdrEquirect = new RGBELoader()
+				.setPath( 'textures/equirectangular/' )
+				.load( 'royal_esplanade_1k.hdr', function () {
+
+					hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
+
+					new GLTFLoader()
+						.setPath( 'models/gltf/' )
+						.load( 'DragonAttenuation.glb', function ( gltf ) {
+
+							gltf.scene.traverse( function ( child ) {
+
+								if ( child.isMesh && child.material.isMeshPhysicalMaterial ) {
+
+									mesh = child;
+									material = mesh.material;
+
+									let color = new THREE.Color();
+
+									params.color = color.copy( mesh.material.color ).getHex();
+									params.roughness = mesh.material.roughness;
+									params.metalness = mesh.material.metalness;
+
+									params.ior = mesh.material.ior;
+									params.specularIntensity = mesh.material.specularIntensity;
+									params.emissiveIntensity = mesh.material.emissiveIntensity;
+
+									params.transmission = mesh.material.transmission;
+									params.thickness = mesh.material.thickness;
+									params.attenuationColor = color.copy( mesh.material.attenuationColor ).getHex();
+									params.attenuationDistance = mesh.material.attenuationDistance;
+
+								}
+
+							} );
+
+							init();
+
+							scene.add( gltf.scene );
+
+							scene.environment = hdrEquirect;
+							//scene.background = hdrEquirect;
+	
+							render();
+
+						} );
+
+				} );
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+
+				// transparent background
+				renderer.setClearColor( 0x000000, 0 );
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				document.body.appendChild( renderer.domElement );
+
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = params.exposure;
+
+				// accommodate CSS table
+				renderer.domElement.style.position = 'absolute';
+				renderer.domElement.style.top = 0;
+
+				scene = new THREE.Scene();
+
+				const light = new THREE.DirectionalLight( 0xffffff, 1 );
+				light.position.set( 0, 1, 1 );
+				//scene.add( light );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( -10, 2, 0 );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+				controls.minDistance = 5;
+				controls.maxDistance = 20;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				//
+
+				const gui = new GUI();
+
+				gui.addColor( params, 'color' )
+					.onChange( function () {
+
+						material.color.set( params.color );
+						render();
+
+					} );
+
+				gui.add( params, 'transmission', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.transmission = params.transmission;
+						render();
+
+					} );
+
+				gui.add( params, 'opacity', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.opacity = params.opacity;
+						const transparent = params.opacity < 1;
+						if(transparent !== material.transparent) {
+							material.transparent = transparent;
+							material.needsUpdate = true;
+						}
+						render();
+
+					} );
+
+				gui.add( params, 'metalness', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.metalness = params.metalness;
+						render();
+
+					} );
+
+				gui.add( params, 'roughness', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.roughness = params.roughness;
+						render();
+
+					} );
+
+				gui.add( params, 'ior', 1, 2, 0.01 )
+					.onChange( function () {
+
+						material.ior = params.ior;
+						render();
+
+					} );
+
+				gui.add( params, 'thickness', 0, 5, 0.01 )
+					.onChange( function () {
+
+						material.thickness = params.thickness;
+						render();
+
+					} );
+
+				gui.addColor( params, 'attenuationColor' )
+					.name( 'attenuation color' )
+					.onChange( function () {
+
+						material.attenuationColor.set( params.attenuationColor );
+						render();
+
+					} );
+
+				gui.add( params, 'attenuationDistance', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.attenuationDistance = params.attenuationDistance;
+						render();
+
+					} );
+
+				gui.add( params, 'specularIntensity', 0, 1, 0.01 )
+					.onChange( function () {
+
+						material.specularIntensity = params.specularIntensity;
+						render();
+
+					} );
+
+				gui.addColor( params, 'specularColor' )
+					.onChange( function () {
+
+						material.specularColor.set( params.specularColor );
+						render();
+
+					} );
+
+				gui.add( params, 'envMapIntensity', 0, 1, 0.01 )
+					.name( 'envMap intensity' )
+					.onChange( function () {
+
+						material.envMapIntensity = params.envMapIntensity;
+						render();
+
+					} );
+
+				gui.add( params, 'exposure', 0, 1, 0.01 )
+					.onChange( function () {
+
+						renderer.toneMappingExposure = params.exposure;
+						render();
+
+					} );
+
+				gui.open();
+
+			}
+
+			function onWindowResize() {
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( width, height );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 11 - 0
src/renderers/WebGLRenderer.js

@@ -22,6 +22,7 @@ import {
 	UnsignedShort4444Type,
 	UnsignedShort5551Type
 } from '../constants.js';
+import { Color } from '../math/Color.js';
 import { Frustum } from '../math/Frustum.js';
 import { Matrix4 } from '../math/Matrix4.js';
 import { Vector3 } from '../math/Vector3.js';
@@ -174,6 +175,9 @@ class WebGLRenderer {
 		const _currentScissor = new Vector4();
 		let _currentScissorTest = null;
 
+		const _currentClearColor = new Color( 0x000000 );
+		let _currentClearAlpha = 0;
+
 		//
 
 		let _width = canvas.width;
@@ -1357,6 +1361,11 @@ class WebGLRenderer {
 
 			const currentRenderTarget = _this.getRenderTarget();
 			_this.setRenderTarget( _transmissionRenderTarget );
+
+			_this.getClearColor( _currentClearColor );
+			_currentClearAlpha = _this.getClearAlpha();
+			if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 );
+
 			_this.clear();
 
 			// Turn off the features which can affect the frag color for opaque objects pass.
@@ -1407,6 +1416,8 @@ class WebGLRenderer {
 
 			_this.setRenderTarget( currentRenderTarget );
 
+			_this.setClearColor( _currentClearColor, _currentClearAlpha );
+
 			_this.toneMapping = currentToneMapping;
 
 		}

+ 1 - 2
src/renderers/shaders/ShaderChunk/output_fragment.glsl.js

@@ -3,9 +3,8 @@ export default /* glsl */`
 diffuseColor.a = 1.0;
 #endif
 
-// https://github.com/mrdoob/three.js/pull/22425
 #ifdef USE_TRANSMISSION
-diffuseColor.a *= material.transmissionAlpha + 0.1;
+diffuseColor.a *= material.transmissionAlpha;
 #endif
 
 gl_FragColor = vec4( outgoingLight, diffuseColor.a );

+ 3 - 3
src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js

@@ -23,14 +23,14 @@ export default /* glsl */`
 	vec3 v = normalize( cameraPosition - pos );
 	vec3 n = inverseTransformDirection( normal, viewMatrix );
 
-	vec4 transmission = getIBLVolumeRefraction(
+	vec4 transmitted = getIBLVolumeRefraction(
 		n, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,
 		pos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,
 		material.attenuationColor, material.attenuationDistance );
 
-	material.transmissionAlpha = mix( material.transmissionAlpha, transmission.a, material.transmission );
+	material.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );
 
-	totalDiffuse = mix( totalDiffuse, transmission.rgb, material.transmission );
+	totalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );
 
 #endif
 `;

+ 10 - 5
src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js

@@ -149,19 +149,19 @@ export default /* glsl */`
 
 	}
 
-	vec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {
+	vec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {
 
 		if ( isinf( attenuationDistance ) ) {
 
 			// Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all.
-			return radiance;
+			return vec3( 1.0 );
 
 		} else {
 
 			// Compute light attenuation using Beer's law.
 			vec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;
 			vec3 transmittance = exp( - attenuationCoefficient * transmissionDistance ); // Beer's law
-			return transmittance * radiance;
+			return transmittance;
 
 		}
 
@@ -184,12 +184,17 @@ export default /* glsl */`
 		// Sample framebuffer to get pixel the refracted ray hits.
 		vec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
 
-		vec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );
+		vec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );
+		vec3 attenuatedColor = transmittance * transmittedLight.rgb;
 
 		// Get the specular component.
 		vec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );
 
-		return vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );
+		// As less light is transmitted, the opacity should be increased. This simple approximation does a decent job 
+		// of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color.
+		float transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;
+
+		return vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );
 
 	}
 #endif