Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 57 additions & 33 deletions examples/jsm/objects/Sky.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,31 +181,35 @@ Sky.SkyShader = {
uniform float showSunDisc;
uniform float time;

// Cloud noise functions
float hash( vec2 p ) {
return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ) * 43758.5453123 );
// gradient at a lattice corner; sinless hash so every GPU produces the same clouds
vec2 gradient( vec2 i ) {
vec3 p = fract( i.xyx * vec3( 0.1031, 0.1030, 0.0973 ) );
p += dot( p, p.yzx + 33.33 );
return fract( ( p.xx + p.yz ) * p.zy ) * 2.0 - 1.0;
}

// 2D gradient noise: isotropic lobes like Perlin at value-noise cost
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 );
vec2 u = f * f * f * ( f * ( f * 6.0 - 15.0 ) + 10.0 ); // quintic fade
float a = dot( gradient( i ), f );
float b = dot( gradient( i + vec2( 1.0, 0.0 ) ), f - vec2( 1.0, 0.0 ) );
float c = dot( gradient( i + vec2( 0.0, 1.0 ) ), f - vec2( 0.0, 1.0 ) );
float d = dot( gradient( i + vec2( 1.0, 1.0 ) ), f - vec2( 1.0, 1.0 ) );
return mix( mix( a, b, u.x ), mix( c, d, u.x ), u.y ) * 1.6; // ~[-1,1]
}

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;
// fbm; per-octave drift makes clouds billow instead of scrolling as a rigid stamp
float fbm( vec2 p, float drift ) {
float result = 0.0;
float amplitude = 1.0;
for ( int i = 0; i < 4; i ++ ) {
result += amplitude * noise( p );
amplitude *= 0.5;
p = p * 2.0 + drift;
}
return value;
return result;
}

// constants for atmospheric scattering
Expand Down Expand Up @@ -282,30 +286,50 @@ Sky.SkyShader = {
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;
// Cloud density field
float evolve = time * cloudSpeed * 300.0;
float cloudNoise = clamp( fbm( cloudUV * 1000.0, evolve ) * 0.7 + 0.5, 0.0, 1.0 );

// Apply coverage threshold
float cloudMask = smoothstep( 1.0 - cloudCoverage, 1.0 - cloudCoverage + 0.3, cloudNoise );
// Large-scale coverage variation: clear gaps next to dense banks
float region = noise( cloudUV * 300.0 ) * 0.37 + 0.5;
float cov = clamp( cloudCoverage + ( region - 0.5 ) * 0.6, 0.0, 1.0 );

// Carve clouds where noise rises above the coverage level
float threshold = 1.0 - cov;
float cloudMask = smoothstep( threshold, threshold + 0.3, cloudNoise );

// Fade clouds near horizon (adjusted by elevation)
float horizonFade = smoothstep( 0.0, 0.1 + 0.2 * cloudElevation, direction.y );
float horizonFade = smoothstep( 0.0, 0.03 + 0.06 * 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 );
// Cloud lighting from the sky's own radiance
float dayFactor = smoothstep( -0.08, 0.3, vSunDirection.y );
vec3 sunColor = vSunE * Fex * 0.22 * 0.04; // 0.22 ~ albedo/pi, 0.04 = exposure; the aerial composite adds the eye-leg extinction
vec3 skyAmbient = Lin * 0.04 + vec3( 0.0, 0.0003, 0.00075 );

// Beer-powder self-shadow from the sampled density
float depth = max( 0.0, cloudNoise - threshold );
float beer = exp( depth * -4.0 );
float powder = 1.0 - beer * beer; // beer*beer == exp(-8*depth)
float shade = mix( 0.45, 1.0, clamp( beer * powder * 2.6, 0.0, 1.0 ) ); // 2.6 = 1/0.385, normalizes beer*powder peak to 1

// Henyey-Greenstein forward lobe ( g = 0.7 ): silver lining on rims toward the sun
float silver = clamp( 0.51 / pow( 1.49 - cosTheta * 1.4, 1.5 ), 0.0, 3.0 ); // 0.51=1-g^2, 1.49=1+g^2, 1.4=2g
float edge = cloudMask * ( 1.0 - cloudMask ) * 4.0;

vec3 cloudColor = skyAmbient + sunColor * shade;
cloudColor += sunColor * silver * edge * 0.6;
cloudColor *= max( dayFactor, 0.03 );

// Cloud opacity via Beer's law: density sets how solid the clouds get
float alpha = ( 1.0 - exp( depth * cloudDensity * -12.0 ) ) * horizonFade;

// 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;
// Occlude the sun disc/glow behind opaque cloud
texColor -= L0 * 0.04 * alpha;

// Blend clouds with sky
texColor = mix( texColor, cloudColor, cloudMask * cloudDensity );
// Composite through the atmosphere so distant clouds dissolve into haze
vec3 cloudAerial = mix( texColor, cloudColor, Fex );
texColor = mix( texColor, cloudAerial, alpha );

}

Expand Down
97 changes: 61 additions & 36 deletions examples/jsm/objects/SkyMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
NodeMaterial
} from 'three/webgpu';

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';
import { Fn, float, floor, fract, vec2, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition, time, If, Loop } 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)
Expand Down Expand Up @@ -284,44 +284,48 @@ class SkyMesh extends Mesh {

const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ).toVar();

// Cloud noise functions
const hash = Fn( ( [ p ] ) => {
// gradient at a lattice corner; sinless hash so every GPU produces the same clouds
const gradient = Fn( ( [ i ] ) => {

return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ).mul( 43758.5453123 ) );
const p = fract( i.xyx.mul( vec3( 0.1031, 0.1030, 0.0973 ) ) ).toVar();
p.addAssign( dot( p, p.yzx.add( 33.33 ) ) );

return fract( p.xx.add( p.yz ).mul( p.zy ) ).mul( 2.0 ).sub( 1.0 );

} );

const noise = Fn( ( [ p_immutable ] ) => {
// 2D gradient noise: isotropic lobes like Perlin at value-noise cost
const noise = Fn( ( [ p ] ) => {

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 u = f.mul( f ).mul( f ).mul( f.mul( f.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) ); // quintic fade

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 ) ) );
const a = dot( gradient( i ), f );
const b = dot( gradient( i.add( vec2( 1.0, 0.0 ) ) ), f.sub( vec2( 1.0, 0.0 ) ) );
const c = dot( gradient( i.add( vec2( 0.0, 1.0 ) ) ), f.sub( vec2( 0.0, 1.0 ) ) );
const d = dot( gradient( i.add( vec2( 1.0, 1.0 ) ) ), f.sub( vec2( 1.0, 1.0 ) ) );

return mix( mix( a, b, ff.x ), mix( c, d, ff.x ), ff.y );
return mix( mix( a, b, u.x ), mix( c, d, u.x ), u.y ).mul( 1.6 ); // ~[-1,1]

} );

const fbm = Fn( ( [ p_immutable ] ) => {
// fbm; per-octave drift makes clouds billow instead of scrolling as a rigid stamp
const fbm = Fn( ( [ position, drift ] ) => {

const p = vec2( p_immutable ).toVar();
const value = float( 0.0 ).toVar();
const amplitude = float( 0.5 ).toVar();
const p = vec2( position ).toVar();
const result = float( 0.0 ).toVar();
const amplitude = float( 1.0 ).toVar();

Loop( 5, () => {
Loop( 4, () => {

value.addAssign( amplitude.mul( noise( p ) ) );
p.mulAssign( 2.0 );
result.addAssign( amplitude.mul( noise( p ) ) );
amplitude.mulAssign( 0.5 );
p.mulAssign( 2.0 ).addAssign( drift );

} );

return value;
return result;

} );

Expand All @@ -334,29 +338,50 @@ class SkyMesh extends Mesh {
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 ) );
// Cloud density field
const evolve = time.mul( this.cloudSpeed ).mul( 300.0 );
const cloudNoise = fbm( cloudUV.mul( 1000.0 ), evolve ).mul( 0.7 ).add( 0.5 ).clamp( 0.0, 1.0 ).toVar();

// Large-scale coverage variation: clear gaps next to dense banks
const region = noise( cloudUV.mul( 300.0 ) ).mul( 0.37 ).add( 0.5 );
const cov = clamp( this.cloudCoverage.add( region.sub( 0.5 ).mul( 0.6 ) ), 0.0, 1.0 );

// Apply coverage threshold
const cloudMask = smoothstep( sub( 1.0, this.cloudCoverage ), sub( 1.0, this.cloudCoverage ).add( 0.3 ), cloudNoise ).toVar();
// Carve clouds where noise rises above the coverage level
const threshold = sub( 1.0, cov ).toVar();
const cloudMask = smoothstep( threshold, threshold.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 );
const horizonFade = smoothstep( 0.0, add( 0.03, mul( 0.06, 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 ) );
// Cloud lighting from the sky's own radiance
const dayFactor = smoothstep( - 0.08, 0.3, vSunDirection.y );
const sunColor = vSunE.mul( Fex ).mul( 0.22 ).mul( 0.04 ).toVar(); // 0.22 ~ albedo/pi, 0.04 = exposure; the aerial composite adds the eye-leg extinction
const skyAmbient = Lin.mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) );

// Beer-powder self-shadow from the sampled density
const depth = max( 0.0, cloudNoise.sub( threshold ) ).toVar();
const beer = exp( depth.mul( - 4.0 ) ).toVar();
const powder = sub( 1.0, beer.mul( beer ) ); // beer*beer == exp(-8*depth)
const shade = mix( 0.45, 1.0, beer.mul( powder ).mul( 2.6 ).clamp( 0.0, 1.0 ) ); // 2.6 = 1/0.385, normalizes beer*powder peak to 1

// Henyey-Greenstein forward lobe ( g = 0.7 ): silver lining on rims toward the sun
const silver = float( 0.51 ).div( pow( sub( 1.49, cosTheta.mul( 1.4 ) ), 1.5 ) ).clamp( 0.0, 3.0 ); // 0.51=1-g^2, 1.49=1+g^2, 1.4=2g
const edge = cloudMask.mul( sub( 1.0, cloudMask ) ).mul( 4.0 );

const cloudColor = skyAmbient.add( sunColor.mul( shade ) ).toVar();
cloudColor.addAssign( sunColor.mul( silver ).mul( edge ).mul( 0.6 ) );
cloudColor.mulAssign( dayFactor.max( 0.03 ) );

// Cloud opacity via Beer's law: density sets how solid the clouds get
const alpha = sub( 1.0, exp( depth.mul( this.cloudDensity ).mul( - 12.0 ) ) ).mul( horizonFade ).toVar();

// 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 ) );
// Occlude the sun disc/glow behind opaque cloud
texColor.subAssign( L0.mul( 0.04 ).mul( alpha ) );

// Blend clouds with sky
texColor.assign( mix( texColor, cloudColor, cloudMask.mul( this.cloudDensity ) ) );
// Composite through the atmosphere so distant clouds dissolve into haze
const cloudAerial = mix( texColor, cloudColor, Fex );
texColor.assign( mix( texColor, cloudAerial, alpha ) );

} );

Expand Down
Binary file modified examples/screenshots/webgl_animation_keyframes.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgl_lightprobes_sponza.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgl_shaders_ocean.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgl_shaders_sky.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_custom_fog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_generator_building.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_generator_city.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_lightprobes_sponza.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_ocean.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/screenshots/webgpu_sky.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 18 additions & 5 deletions examples/webgl_shaders_sky.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

let sky, sun;

let sphere, cubeCamera;

init();

function initSky() {
Expand All @@ -54,9 +56,9 @@
rayleigh: 3,
mieCoefficient: 0.005,
mieDirectionalG: 0.7,
elevation: 2,
azimuth: 180,
exposure: renderer.toneMappingExposure,
elevation: 65,
azimuth: 0,
exposure: 0.05,
cloudCoverage: 0.4,
cloudDensity: 0.4,
cloudElevation: 0.5,
Expand Down Expand Up @@ -113,8 +115,14 @@

scene = new THREE.Scene();

const helper = new THREE.GridHelper( 10000, 2, 0xffffff, 0xffffff );
scene.add( helper );
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { type: THREE.HalfFloatType } );
cubeCamera = new THREE.CubeCamera( 1, 1000, cubeRenderTarget );

sphere = new THREE.Mesh(
new THREE.SphereGeometry( 400, 64, 32 ),
new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget.texture } )
);
scene.add( sphere );

renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
Expand Down Expand Up @@ -147,6 +155,11 @@
function animate() {

sky.material.uniforms[ 'time' ].value = performance.now() * 0.001;

sphere.visible = false;
cubeCamera.update( renderer, scene );
sphere.visible = true;

renderer.render( scene, camera );

}
Expand Down
21 changes: 18 additions & 3 deletions examples/webgpu_sky.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

let sky, sun;

let sphere, cubeCamera;

init();

function initSky() {
Expand All @@ -66,9 +68,9 @@
rayleigh: 3,
mieCoefficient: 0.005,
mieDirectionalG: 0.7,
elevation: 2,
azimuth: 180,
exposure: renderer.toneMappingExposure,
elevation: 65,
azimuth: 0,
exposure: 0.05,
cloudCoverage: 0.4,
cloudDensity: 0.4,
cloudElevation: 0.5,
Expand Down Expand Up @@ -124,6 +126,15 @@

scene = new THREE.Scene();

const cubeRenderTarget = new THREE.CubeRenderTarget( 256, { type: THREE.HalfFloatType } );
cubeCamera = new THREE.CubeCamera( 1, 1000, cubeRenderTarget );

sphere = new THREE.Mesh(
new THREE.SphereGeometry( 400, 64, 32 ),
new THREE.MeshBasicNodeMaterial( { envMap: cubeRenderTarget.texture } )
);
scene.add( sphere );

renderer = new THREE.WebGPURenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
Expand Down Expand Up @@ -155,6 +166,10 @@

function animate() {

sphere.visible = false;
cubeCamera.update( renderer, scene );
sphere.visible = true;

renderer.render( scene, camera );

}
Expand Down
8 changes: 4 additions & 4 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1692,7 +1692,7 @@ class WebGLRenderer {

if ( _this.sortObjects === true ) {

currentRenderList.sort( _opaqueSort, _transparentSort, camera.reversedDepth );
currentRenderList.sort( _opaqueSort, _transparentSort );

}

Expand Down Expand Up @@ -1882,7 +1882,7 @@ class WebGLRenderer {

if ( material.visible ) {

currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null );
currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera );

}

Expand Down Expand Up @@ -1926,15 +1926,15 @@ class WebGLRenderer {

if ( groupMaterial && groupMaterial.visible ) {

currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group );
currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, camera );

}

}

} else if ( material.visible ) {

currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null );
currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera );

}

Expand Down
Loading
Loading