소스 검색

Sky/SkyMesh: Added procedural clouds. (#32682)

mrdoob 2 일 전
부모
커밋
f848b7508a

+ 76 - 1
examples/jsm/objects/Sky.js

@@ -72,7 +72,13 @@ Sky.SkyShader = {
 		'mieCoefficient': { value: 0.005 },
 		'mieDirectionalG': { value: 0.8 },
 		'sunPosition': { value: new Vector3() },
-		'up': { value: new Vector3( 0, 1, 0 ) }
+		'up': { value: new Vector3( 0, 1, 0 ) },
+		'cloudScale': { value: 0.0002 },
+		'cloudSpeed': { value: 0.0001 },
+		'cloudCoverage': { value: 0.4 },
+		'cloudDensity': { value: 0.4 },
+		'cloudElevation': { value: 0.5 },
+		'time': { value: 0.0 }
 	},
 
 	vertexShader: /* glsl */`
@@ -156,6 +162,39 @@ Sky.SkyShader = {
 
 		uniform float mieDirectionalG;
 		uniform vec3 up;
+		uniform float cloudScale;
+		uniform float cloudSpeed;
+		uniform float cloudCoverage;
+		uniform float cloudDensity;
+		uniform float cloudElevation;
+		uniform float time;
+
+		// Cloud noise functions
+		float hash( vec2 p ) {
+			return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ) * 43758.5453123 );
+		}
+
+		float noise( vec2 p ) {
+			vec2 i = floor( p );
+			vec2 f = fract( p );
+			f = f * f * ( 3.0 - 2.0 * f );
+			float a = hash( i );
+			float b = hash( i + vec2( 1.0, 0.0 ) );
+			float c = hash( i + vec2( 0.0, 1.0 ) );
+			float d = hash( i + vec2( 1.0, 1.0 ) );
+			return mix( mix( a, b, f.x ), mix( c, d, f.x ), f.y );
+		}
+
+		float fbm( vec2 p ) {
+			float value = 0.0;
+			float amplitude = 0.5;
+			for ( int i = 0; i < 5; i ++ ) {
+				value += amplitude * noise( p );
+				p *= 2.0;
+				amplitude *= 0.5;
+			}
+			return value;
+		}
 
 		// constants for atmospheric scattering
 		const float pi = 3.141592653589793238462643383279502884197169;
@@ -222,6 +261,42 @@ Sky.SkyShader = {
 
 			vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );
 
+			// Clouds
+			if ( direction.y > 0.0 && cloudCoverage > 0.0 ) {
+
+				// Project to cloud plane (higher elevation = clouds appear lower/closer)
+				float elevation = mix( 1.0, 0.1, cloudElevation );
+				vec2 cloudUV = direction.xz / ( direction.y * elevation );
+				cloudUV *= cloudScale;
+				cloudUV += time * cloudSpeed;
+
+				// Multi-octave noise for fluffy clouds
+				float cloudNoise = fbm( cloudUV * 1000.0 );
+				cloudNoise += 0.5 * fbm( cloudUV * 2000.0 + 3.7 );
+				cloudNoise = cloudNoise * 0.5 + 0.5;
+
+				// Apply coverage threshold
+				float cloudMask = smoothstep( 1.0 - cloudCoverage, 1.0 - cloudCoverage + 0.3, cloudNoise );
+
+				// Fade clouds near horizon (adjusted by elevation)
+				float horizonFade = smoothstep( 0.0, 0.1 + 0.2 * cloudElevation, direction.y );
+				cloudMask *= horizonFade;
+
+				// Cloud lighting based on sun position
+				float sunInfluence = dot( direction, vSunDirection ) * 0.5 + 0.5;
+				float daylight = max( 0.0, vSunDirection.y * 2.0 );
+
+				// Base cloud color affected by atmosphere
+				vec3 atmosphereColor = Lin * 0.04;
+				vec3 cloudColor = mix( vec3( 0.3 ), vec3( 1.0 ), daylight );
+				cloudColor = mix( cloudColor, atmosphereColor + vec3( 1.0 ), sunInfluence * 0.5 );
+				cloudColor *= vSunE * 0.00002;
+
+				// Blend clouds with sky
+				texColor = mix( texColor, cloudColor, cloudMask * cloudDensity );
+
+			}
+
 			gl_FragColor = vec4( texColor, 1.0 );
 
 			#include <tonemapping_fragment>

+ 113 - 2
examples/jsm/objects/SkyMesh.js

@@ -6,7 +6,7 @@ import {
 	NodeMaterial
 } from 'three/webgpu';
 
-import { Fn, float, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition } from 'three/tsl';
+import { Fn, float, vec2, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition, fract, floor, sin, time, Loop, If } from 'three/tsl';
 
 /**
  * Represents a skydome for scene backgrounds. Based on [A Practical Analytic Model for Daylight](https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight)
@@ -82,6 +82,41 @@ class SkyMesh extends Mesh {
 		 */
 		this.upUniform = uniform( new Vector3( 0, 1, 0 ) );
 
+		/**
+		 * The cloud scale uniform.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.cloudScale = uniform( 0.0002 );
+
+		/**
+		 * The cloud speed uniform.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.cloudSpeed = uniform( 0.0001 );
+
+		/**
+		 * The cloud coverage uniform.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.cloudCoverage = uniform( 0.4 );
+
+		/**
+		 * The cloud density uniform.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.cloudDensity = uniform( 0.4 );
+
+		/**
+		 * The cloud elevation uniform.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.cloudElevation = uniform( 0.5 );
+
 		/**
 		 * This flag can be used for type testing.
 		 *
@@ -230,7 +265,83 @@ class SkyMesh extends Mesh {
 			const sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos.add( 0.00002 ), cosTheta );
 			L0.addAssign( vSunE.mul( 19000.0 ).mul( Fex ).mul( sundisk ) );
 
-			const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) );
+			const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ).toVar();
+
+			// Cloud noise functions
+			const hash = Fn( ( [ p ] ) => {
+
+				return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ).mul( 43758.5453123 ) );
+
+			} );
+
+			const noise = Fn( ( [ p_immutable ] ) => {
+
+				const p = vec2( p_immutable ).toVar();
+				const i = floor( p );
+				const f = fract( p );
+				const ff = f.mul( f ).mul( sub( 3.0, f.mul( 2.0 ) ) );
+
+				const a = hash( i );
+				const b = hash( add( i, vec2( 1.0, 0.0 ) ) );
+				const c = hash( add( i, vec2( 0.0, 1.0 ) ) );
+				const d = hash( add( i, vec2( 1.0, 1.0 ) ) );
+
+				return mix( mix( a, b, ff.x ), mix( c, d, ff.x ), ff.y );
+
+			} );
+
+			const fbm = Fn( ( [ p_immutable ] ) => {
+
+				const p = vec2( p_immutable ).toVar();
+				const value = float( 0.0 ).toVar();
+				const amplitude = float( 0.5 ).toVar();
+
+				Loop( 5, () => {
+
+					value.addAssign( amplitude.mul( noise( p ) ) );
+					p.mulAssign( 2.0 );
+					amplitude.mulAssign( 0.5 );
+
+				} );
+
+				return value;
+
+			} );
+
+			// Clouds
+			If( direction.y.greaterThan( 0.0 ).and( this.cloudCoverage.greaterThan( 0.0 ) ), () => {
+
+				// Project to cloud plane (higher elevation = clouds appear lower/closer)
+				const elevation = mix( 1.0, 0.1, this.cloudElevation );
+				const cloudUV = direction.xz.div( direction.y.mul( elevation ) ).toVar();
+				cloudUV.mulAssign( this.cloudScale );
+				cloudUV.addAssign( time.mul( this.cloudSpeed ) );
+
+				// Multi-octave noise for fluffy clouds
+				const cloudNoise = fbm( cloudUV.mul( 1000.0 ) ).add( fbm( cloudUV.mul( 2000.0 ).add( 3.7 ) ).mul( 0.5 ) ).toVar();
+				cloudNoise.assign( cloudNoise.mul( 0.5 ).add( 0.5 ) );
+
+				// Apply coverage threshold
+				const cloudMask = smoothstep( sub( 1.0, this.cloudCoverage ), sub( 1.0, this.cloudCoverage ).add( 0.3 ), cloudNoise ).toVar();
+
+				// Fade clouds near horizon (adjusted by elevation)
+				const horizonFade = smoothstep( 0.0, add( 0.1, mul( 0.2, this.cloudElevation ) ), direction.y );
+				cloudMask.mulAssign( horizonFade );
+
+				// Cloud lighting based on sun position
+				const sunInfluence = dot( direction, vSunDirection ).mul( 0.5 ).add( 0.5 );
+				const daylight = max( 0.0, vSunDirection.y.mul( 2.0 ) );
+
+				// Base cloud color affected by atmosphere
+				const atmosphereColor = Lin.mul( 0.04 );
+				const cloudColor = mix( vec3( 0.3 ), vec3( 1.0 ), daylight ).toVar();
+				cloudColor.assign( mix( cloudColor, atmosphereColor.add( vec3( 1.0 ) ), sunInfluence.mul( 0.5 ) ) );
+				cloudColor.mulAssign( vSunE.mul( 0.00002 ) );
+
+				// Blend clouds with sky
+				texColor.assign( mix( texColor, cloudColor, cloudMask.mul( this.cloudDensity ) ) );
+
+			} );
 
 			return vec4( texColor, 1.0 );
 

BIN
examples/screenshots/webgl_shaders_ocean.jpg


BIN
examples/screenshots/webgl_shaders_sky.jpg


BIN
examples/screenshots/webgpu_ocean.jpg


BIN
examples/screenshots/webgpu_sky.jpg


+ 12 - 2
examples/webgl_shaders_ocean.html

@@ -36,7 +36,7 @@
 
 			let container, stats;
 			let camera, scene, renderer;
-			let controls, water, sun, mesh, bloomPass;
+			let controls, water, sun, sky, mesh, bloomPass;
 
 			init();
 
@@ -99,7 +99,7 @@
 
 				// Skybox
 
-				const sky = new Sky();
+				sky = new Sky();
 				sky.scale.setScalar( 10000 );
 				scene.add( sky );
 
@@ -109,6 +109,9 @@
 				skyUniforms[ 'rayleigh' ].value = 2;
 				skyUniforms[ 'mieCoefficient' ].value = 0.005;
 				skyUniforms[ 'mieDirectionalG' ].value = 0.8;
+				skyUniforms[ 'cloudCoverage' ].value = 0.4;
+				skyUniforms[ 'cloudDensity' ].value = 0.5;
+				skyUniforms[ 'cloudElevation' ].value = 0.5;
 
 				const parameters = {
 					elevation: 2,
@@ -191,6 +194,12 @@
 				folderBloom.add( bloomPass, 'radius', 0, 1, 0.01 );
 				folderBloom.open();
 
+				const folderClouds = gui.addFolder( 'Clouds' );
+				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();
+
 				//
 
 				window.addEventListener( 'resize', onWindowResize );
@@ -222,6 +231,7 @@
 				mesh.rotation.z = time * 0.51;
 
 				water.material.uniforms[ 'time' ].value += 1.0 / 60.0;
+				sky.material.uniforms[ 'time' ].value = time;
 
 				renderer.render( scene, camera );
 

+ 15 - 7
examples/webgl_shaders_sky.html

@@ -33,7 +33,6 @@
 			let sky, sun;
 
 			init();
-			render();
 
 			function initSky() {
 
@@ -53,7 +52,10 @@
 					mieDirectionalG: 0.7,
 					elevation: 2,
 					azimuth: 180,
-					exposure: renderer.toneMappingExposure
+					exposure: renderer.toneMappingExposure,
+					cloudCoverage: 0.4,
+					cloudDensity: 0.4,
+					cloudElevation: 0.5
 				};
 
 				function guiChanged() {
@@ -63,6 +65,9 @@
 					uniforms[ 'rayleigh' ].value = effectController.rayleigh;
 					uniforms[ 'mieCoefficient' ].value = effectController.mieCoefficient;
 					uniforms[ 'mieDirectionalG' ].value = effectController.mieDirectionalG;
+					uniforms[ 'cloudCoverage' ].value = effectController.cloudCoverage;
+					uniforms[ 'cloudDensity' ].value = effectController.cloudDensity;
+					uniforms[ 'cloudElevation' ].value = effectController.cloudElevation;
 
 					const phi = THREE.MathUtils.degToRad( 90 - effectController.elevation );
 					const theta = THREE.MathUtils.degToRad( effectController.azimuth );
@@ -72,7 +77,6 @@
 					uniforms[ 'sunPosition' ].value.copy( sun );
 
 					renderer.toneMappingExposure = effectController.exposure;
-					renderer.render( scene, camera );
 
 				}
 
@@ -86,6 +90,11 @@
 				gui.add( effectController, 'azimuth', - 180, 180, 0.1 ).onChange( guiChanged );
 				gui.add( effectController, 'exposure', 0, 1, 0.0001 ).onChange( guiChanged );
 
+				const folderClouds = gui.addFolder( 'Clouds' );
+				folderClouds.add( effectController, 'cloudCoverage', 0, 1, 0.01 ).name( 'coverage' ).onChange( guiChanged );
+				folderClouds.add( effectController, 'cloudDensity', 0, 1, 0.01 ).name( 'density' ).onChange( guiChanged );
+				folderClouds.add( effectController, 'cloudElevation', 0, 1, 0.01 ).name( 'elevation' ).onChange( guiChanged );
+
 				guiChanged();
 
 			}
@@ -103,12 +112,12 @@
 				renderer = new THREE.WebGLRenderer();
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
 				renderer.toneMapping = THREE.ACESFilmicToneMapping;
 				renderer.toneMappingExposure = 0.5;
 				document.body.appendChild( renderer.domElement );
 
 				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.addEventListener( 'change', render );
 				//controls.maxPolarAngle = Math.PI / 2;
 				controls.enableZoom = false;
 				controls.enablePan = false;
@@ -126,12 +135,11 @@
 
 				renderer.setSize( window.innerWidth, window.innerHeight );
 
-				render();
-
 			}
 
-			function render() {
+			function animate() {
 
+				sky.material.uniforms[ 'time' ].value = performance.now() * 0.001;
 				renderer.render( scene, camera );
 
 			}

+ 10 - 2
examples/webgpu_ocean.html

@@ -47,7 +47,7 @@
 
 			let container;
 			let camera, scene, renderer, postProcessing;
-			let controls, water, sun, mesh, bloomPass;
+			let controls, water, sun, sky, mesh, bloomPass;
 
 			init();
 
@@ -115,7 +115,7 @@
 
 				// Skybox
 
-				const sky = new SkyMesh();
+				sky = new SkyMesh();
 				sky.scale.setScalar( 10000 );
 				scene.add( sky );
 
@@ -123,6 +123,9 @@
 				sky.rayleigh.value = 2;
 				sky.mieCoefficient.value = 0.005;
 				sky.mieDirectionalG.value = 0.8;
+				sky.cloudCoverage.value = 0.4;
+				sky.cloudDensity.value = 0.5;
+				sky.cloudElevation.value = 0.5;
 
 				const parameters = {
 					elevation: 2,
@@ -195,6 +198,11 @@
 				folderBloom.add( bloomPass.strength, 'value', 0, 3, 0.01 ).name( 'strength' );
 				folderBloom.add( bloomPass.radius, 'value', 0, 1, 0.01 ).name( 'radius' );
 
+				const folderClouds = gui.addFolder( 'Clouds' );
+				folderClouds.add( sky.cloudCoverage, 'value', 0, 1, 0.01 ).name( 'coverage' );
+				folderClouds.add( sky.cloudDensity, 'value', 0, 1, 0.01 ).name( 'density' );
+				folderClouds.add( sky.cloudElevation, 'value', 0, 1, 0.01 ).name( 'elevation' );
+
 				//
 
 				window.addEventListener( 'resize', onWindowResize );

+ 12 - 1
examples/webgpu_sky.html

@@ -64,7 +64,10 @@
 					mieDirectionalG: 0.7,
 					elevation: 2,
 					azimuth: 180,
-					exposure: renderer.toneMappingExposure
+					exposure: renderer.toneMappingExposure,
+					cloudCoverage: 0.4,
+					cloudDensity: 0.4,
+					cloudElevation: 0.5
 				};
 
 				function guiChanged() {
@@ -73,6 +76,9 @@
 					sky.rayleigh.value = effectController.rayleigh;
 					sky.mieCoefficient.value = effectController.mieCoefficient;
 					sky.mieDirectionalG.value = effectController.mieDirectionalG;
+					sky.cloudCoverage.value = effectController.cloudCoverage;
+					sky.cloudDensity.value = effectController.cloudDensity;
+					sky.cloudElevation.value = effectController.cloudElevation;
 
 					const phi = THREE.MathUtils.degToRad( 90 - effectController.elevation );
 					const theta = THREE.MathUtils.degToRad( effectController.azimuth );
@@ -95,6 +101,11 @@
 				gui.add( effectController, 'azimuth', - 180, 180, 0.1 ).onChange( guiChanged );
 				gui.add( effectController, 'exposure', 0, 1, 0.0001 ).onChange( guiChanged );
 
+				const folderClouds = gui.addFolder( 'Clouds' );
+				folderClouds.add( effectController, 'cloudCoverage', 0, 1, 0.01 ).name( 'coverage' ).onChange( guiChanged );
+				folderClouds.add( effectController, 'cloudDensity', 0, 1, 0.01 ).name( 'density' ).onChange( guiChanged );
+				folderClouds.add( effectController, 'cloudElevation', 0, 1, 0.01 ).name( 'elevation' ).onChange( guiChanged );
+
 				guiChanged();
 
 			}

粤ICP备19079148号