ソースを参照

Grounded Skybox (#27448)

* initial change

* mrdoob approves

* updated example

* update screenshot
Emmett Lalish 1 年間 前
コミット
36f9019186

+ 0 - 172
examples/jsm/objects/GroundProjectedSkybox.js

@@ -1,172 +0,0 @@
-import { Mesh, IcosahedronGeometry, ShaderMaterial, DoubleSide } from 'three';
-
-/**
- * Ground projected env map adapted from @react-three/drei.
- * https://github.com/pmndrs/drei/blob/master/src/core/Environment.tsx
- */
-class GroundProjectedSkybox extends Mesh {
-
-	constructor( texture, options = {} ) {
-
-		const isCubeMap = texture.isCubeTexture;
-
-		const defines = [
-			isCubeMap ? '#define ENVMAP_TYPE_CUBE' : ''
-		];
-
-		const vertexShader = /* glsl */ `
-			varying vec3 vWorldPosition;
-
-			void main() {
-
-				vec4 worldPosition = ( modelMatrix * vec4( position, 1.0 ) );
-				vWorldPosition = worldPosition.xyz;
-
-				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
-			}
-			`;
-		const fragmentShader = defines.join( '\n' ) + /* glsl */ `
-
-				varying vec3 vWorldPosition;
-
-				uniform float radius;
-				uniform float height;
-				uniform float angle;
-
-				#ifdef ENVMAP_TYPE_CUBE
-
-					uniform samplerCube map;
-
-				#else
-
-					uniform sampler2D map;
-
-				#endif
-
-				// From: https://www.shadertoy.com/view/4tsBD7
-				float diskIntersectWithBackFaceCulling( vec3 ro, vec3 rd, vec3 c, vec3 n, float r ) 
-				{
-
-					float d = dot ( rd, n );
-
-					if( d > 0.0 ) { return 1e6; }
-
-					vec3 o = ro - c;
-					float t = - dot( n, o ) / d;
-					vec3 q = o + rd * t;
-
-					return ( dot( q, q ) < r * r ) ? t : 1e6;
-
-				}
-
-				// From: https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
-				float sphereIntersect( vec3 ro, vec3 rd, vec3 ce, float ra ) {
-
-					vec3 oc = ro - ce;
-					float b = dot( oc, rd );
-					float c = dot( oc, oc ) - ra * ra;
-					float h = b * b - c;
-
-					if( h < 0.0 ) { return -1.0; }
-
-					h = sqrt( h );
-
-					return - b + h;
-
-				}
-
-				vec3 project() {
-
-					vec3 p = normalize( vWorldPosition );
-					vec3 camPos = cameraPosition;
-					camPos.y -= height;
-
-					float intersection = sphereIntersect( camPos, p, vec3( 0.0 ), radius );
-					if( intersection > 0.0 ) {
-
-						vec3 h = vec3( 0.0, - height, 0.0 );
-						float intersection2 = diskIntersectWithBackFaceCulling( camPos, p, h, vec3( 0.0, 1.0, 0.0 ), radius );
-						p = ( camPos + min( intersection, intersection2 ) * p ) / radius;
-
-					} else {
-
-						p = vec3( 0.0, 1.0, 0.0 );
-
-					}
-
-					return p;
-
-				}
-
-				#include <common>
-
-				void main() {
-
-					vec3 projectedWorldPosition = project();
-
-					#ifdef ENVMAP_TYPE_CUBE
-
-						vec3 outcolor = textureCube( map, projectedWorldPosition ).rgb;
-
-					#else
-
-						vec3 direction = normalize( projectedWorldPosition );
-						vec2 uv = equirectUv( direction );
-						vec3 outcolor = texture2D( map, uv ).rgb;
-
-					#endif
-
-					gl_FragColor = vec4( outcolor, 1.0 );
-
-					#include <tonemapping_fragment>
-					#include <colorspace_fragment>
-
-				}
-				`;
-
-		const uniforms = {
-			map: { value: texture },
-			height: { value: options.height || 15 },
-			radius: { value: options.radius || 100 },
-		};
-
-		const geometry = new IcosahedronGeometry( 1, 16 );
-		const material = new ShaderMaterial( {
-			uniforms,
-			fragmentShader,
-			vertexShader,
-			side: DoubleSide,
-		} );
-
-		super( geometry, material );
-
-	}
-
-	set radius( radius ) {
-
-		this.material.uniforms.radius.value = radius;
-
-	}
-
-	get radius() {
-
-		return this.material.uniforms.radius.value;
-
-	}
-
-	set height( height ) {
-
-		this.material.uniforms.height.value = height;
-
-	}
-
-	get height() {
-
-		return this.material.uniforms.height.value;
-
-	}
-
-}
-
-export { GroundProjectedSkybox };

+ 49 - 0
examples/jsm/objects/GroundedSkybox.js

@@ -0,0 +1,49 @@
+import { BackSide, Mesh, MeshBasicMaterial, SphereGeometry, Vector3 } from 'three';
+
+/**
+ * A ground-projected skybox. The height is how far the camera that took the photo was above the ground - 
+ * a larger value will magnify the downward part of the image. By default the object is centered at the camera, 
+ * so it is often helpful to set skybox.position.y = height to put the ground at the origin. Set the radius 
+ * large enough to ensure your user's camera stays inside.
+ */
+
+class GroundedSkybox extends Mesh {
+
+	constructor( map, height, radius, resolution = 128 ) {
+
+		if ( height <= 0 || radius <= 0 || resolution <= 0 ) {
+
+			throw new Error( 'GroundedSkybox height, radius, and resolution must be positive.' );
+
+		}
+
+		const geometry = new SphereGeometry( radius, 2 * resolution, resolution );
+
+		const pos = geometry.getAttribute( 'position' );
+		const tmp = new Vector3();
+
+		for ( let i = 0; i < pos.count; ++ i ) {
+
+			tmp.fromBufferAttribute( pos, i );
+			if ( tmp.y < 0 ) {
+
+				// Smooth out the transition from flat floor to sphere:
+				const y1 = - height * 3 / 2;
+				const f =
+						tmp.y < y1 ? - height / tmp.y : ( 1 - tmp.y * tmp.y / ( 3 * y1 * y1 ) );
+				tmp.multiplyScalar( f );
+				tmp.toArray( pos.array, 3 * i );
+
+			}
+
+		}
+
+		pos.needsUpdate = true;
+
+		super( geometry, new MeshBasicMaterial( { map, side: BackSide } ) );
+
+	}
+
+}
+
+export { GroundedSkybox };

BIN
examples/screenshots/webgl_materials_envmaps_groundprojected.jpg


+ 6 - 17
examples/webgl_materials_envmaps_groundprojected.html

@@ -14,7 +14,7 @@
 	<body>
 		<div id="container"></div>
 		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener">threejs</a> - Ground projected environment mapping. By <a href="https://twitter.com/CantBeFaraz" target="_blank" rel="noopener">Faraz Shaikh</a>.<br>
+			<a href="https://threejs.org" target="_blank" rel="noopener">threejs</a> - Ground projected environment mapping.<br>
 			Ferrari 458 Italia model by <a href="https://sketchfab.com/models/57bf6cc56931426e87494f554df1dab6" target="_blank" rel="noopener">vicent091036</a><br>
 			<a href="https://polyhaven.com/a/blouberg_sunrise_2" target="_blank" rel="noopener">Blouberg Sunrise 2</a> by <a href="https://gregzaal.com/" target="_blank" rel="noopener">Greg Zaal</a>
 		</div>
@@ -31,16 +31,15 @@
 		<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 { GroundProjectedSkybox } from 'three/addons/objects/GroundProjectedSkybox.js';
+			import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
 			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
 			const params = {
-				height: 20,
-				radius: 440
+				height: 15,
+				radius: 100
 			};
 
 			let camera, scene, renderer, skybox;
@@ -64,8 +63,8 @@
 				const envMap = await hdrLoader.loadAsync( 'textures/equirectangular/blouberg_sunrise_2_1k.hdr' );
 				envMap.mapping = THREE.EquirectangularReflectionMapping;
 
-				skybox = new GroundProjectedSkybox( envMap );
-				skybox.scale.setScalar( 100 );
+				skybox = new GroundedSkybox( envMap, params.height, params.radius );
+				skybox.position.y = params.height - 0.01;
 				scene.add( skybox );
 
 				scene.environment = envMap;
@@ -115,7 +114,6 @@
 						} )
 					);
 					mesh.rotation.x = - Math.PI / 2;
-					mesh.renderOrder = 2;
 					carModel.add( mesh );
 
 					scene.add( carModel );
@@ -142,12 +140,6 @@
 				controls.enablePan = false;
 				controls.update();
 
-				const gui = new GUI();
-				gui.add( params, 'height', 20, 50, 0.1 ).name( 'Skybox height' ).onChange( render );
-				gui.add( params, 'radius', 200, 600, 0.1 ).name( 'Skybox radius' ).onChange( render );
-
-				//
-
 				document.body.appendChild( renderer.domElement );
 				window.addEventListener( 'resize', onWindowResize );
 
@@ -168,9 +160,6 @@
 
 				renderer.render( scene, camera );
 
-				skybox.radius = params.radius;
-				skybox.height = params.height;
-
 			}
 		</script>
 	</body>