Skip to content

Sky: Add WebGPURenderer version. #29013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
"webgpu_skinning",
"webgpu_skinning_instancing",
"webgpu_skinning_points",
"webgpu_sky",
"webgpu_sprites",
"webgpu_storage_buffer",
"webgpu_texturegrad",
Expand Down
190 changes: 190 additions & 0 deletions examples/jsm/objects/SkyGPU.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {
BackSide,
BoxGeometry,
Mesh,
NodeMaterial,
Vector3
} from 'three';
import { float, tslFn, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varying, varyingProperty, vec4, uniform } from 'three/tsl';

/**
* Based on "A Practical Analytic Model for Daylight"
* aka The Preetham Model, the de facto standard analytic skydome model
* https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight
*
* First implemented by Simon Wallner
* http://simonwallner.at/project/atmospheric-scattering/
*
* Improved by Martin Upitis
* http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR
*
* Three.js integration by zz85 http://twitter.com/blurspline
*/

class Sky extends Mesh {

constructor() {

const material = new NodeMaterial();

super( new BoxGeometry( 1, 1, 1 ), material );

this.turbidity = uniform( 2 );
this.rayleigh = uniform( 1 );
this.mieCoefficient = uniform( 0.005 );
this.mieDirectionalG = uniform( 0.8 );
this.sunPosition = uniform( new Vector3() );
this.up = uniform( new Vector3( 0, 1, 0 ) );
this.cameraPosition = uniform( new Vector3() ).label( 'cameraPosition' ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) ); // TODO replace with cameraPosition from CameraNode

this.isSky = true;

const vertexNode = /*@__PURE__*/ tslFn( () => {

// constants for atmospheric scattering
const e = float( 2.71828182845904523536028747135266249775724709369995957 );
// const pi = float( 3.141592653589793238462643383279502884197169 );

// wavelength of used primaries, according to preetham
// const lambda = vec3( 680E-9, 550E-9, 450E-9 );
// this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function:
// (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn))
const totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );

// mie stuff
// K coefficient for the primaries
// const v = float( 4.0 );
// const K = vec3( 0.686, 0.678, 0.666 );
// MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K
const MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );

// earth shadow hack
// cutoffAngle = pi / 1.95;
const cutoffAngle = float( 1.6110731556870734 );
const steepness = float( 1.5 );
const EE = float( 1000.0 );

// varying sun position

const vSunDirection = normalize( this.sunPosition );
varyingProperty( 'vec3', 'vSunDirection' ).assign( vSunDirection );

// varying sun intensity

const angle = dot( vSunDirection, this.up );
const zenithAngleCos = clamp( angle, - 1, 1 );
const sunIntensity = EE.mul( max( 0.0, float( 1.0 ).sub( pow( e, cutoffAngle.sub( acos( zenithAngleCos ) ).div( steepness ).negate() ) ) ) );
varyingProperty( 'float', 'vSunE' ).assign( sunIntensity );

// varying sun fade

const vSunfade = float( 1.0 ).sub( clamp( float( 1.0 ).sub( exp( this.sunPosition.y.div( 450000.0 ) ) ), 0, 1 ) );
varyingProperty( 'float', 'vSunfade' ).assign( vSunfade );

// varying vBetaR

const rayleighCoefficient = this.rayleigh.sub( float( 1.0 ).mul( float( 1.0 ).sub( vSunfade ) ) );

// extinction (absorbtion + out scattering)
// rayleigh coefficients
varyingProperty( 'vec3', 'vBetaR' ).assign( totalRayleigh.mul( rayleighCoefficient ) );

// varying vBetaM

const c = float( 0.2 ).mul( this.turbidity ).mul( 10E-18 );
const totalMie = float( 0.434 ).mul( c ).mul( MieConst );

varyingProperty( 'vec3', 'vBetaM' ).assign( totalMie.mul( this.mieCoefficient ) );

// position

const position = modelViewProjection();
position.z.assign( position.w ); // set z to camera.far

return position;

} )();

const fragmentNode = /*@__PURE__*/ tslFn( () => {

const vSunDirection = varying( vec3(), 'vSunDirection' );
const vSunE = varying( float(), 'vSunE' );
const vSunfade = varying( float(), 'vSunfade' );
const vBetaR = varying( vec3(), 'vBetaR' );
const vBetaM = varying( vec3(), 'vBetaM' );

// constants for atmospheric scattering
const pi = float( 3.141592653589793238462643383279502884197169 );

// optical length at zenith for molecules
const rayleighZenithLength = float( 8.4E3 );
const mieZenithLength = float( 1.25E3 );
// 66 arc seconds -> degrees, and the cosine of that
const sunAngularDiameterCos = float( 0.999956676946448443553574619906976478926848692873900859324 );

// 3.0 / ( 16.0 * pi )
const THREE_OVER_SIXTEENPI = float( 0.05968310365946075 );
// 1.0 / ( 4.0 * pi )
const ONE_OVER_FOURPI = float( 0.07957747154594767 );

//

const direction = normalize( positionWorld.sub( this.cameraPosition ) );

// optical length
// cutoff angle at 90 to avoid singularity in next formula.
const zenithAngle = acos( max( 0.0, dot( this.up, direction ) ) );
const inverse = float( 1.0 ).div( cos( zenithAngle ).add( float( 0.15 ).mul( pow( float( 93.885 ).sub( zenithAngle.mul( 180.0 ).div( pi ) ), - 1.253 ) ) ) );
const sR = rayleighZenithLength.mul( inverse );
const sM = mieZenithLength.mul( inverse );

// combined extinction factor
const Fex = exp( mul( vBetaR, sR ).add( mul( vBetaM, sM ) ).negate() );

// in scattering
const cosTheta = dot( direction, vSunDirection );

// betaRTheta

const c = cosTheta.mul( 0.5 ).add( 0.5 );
const rPhase = THREE_OVER_SIXTEENPI.mul( float( 1.0 ).add( pow( c, 2.0 ) ) );
const betaRTheta = vBetaR.mul( rPhase );

// betaMTheta

const g2 = pow( this.mieDirectionalG, 2.0 );
const inv = float( 1.0 ).div( pow( float( 1.0 ).sub( float( 2.0 ).mul( this.mieDirectionalG ).mul( cosTheta ) ).add( g2 ), 1.5 ) );
const mPhase = ONE_OVER_FOURPI.mul( float( 1.0 ).sub( g2 ) ).mul( inv );
const betaMTheta = vBetaM.mul( mPhase );

const Lin = pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( sub( 1.0, Fex ) ), vec3( 1.5 ) );
Lin.mulAssign( mix( vec3( 1.0 ), pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( Fex ), vec3( 1.0 / 2.0 ) ), clamp( pow( sub( 1.0, dot( this.up, vSunDirection ) ), 5.0 ), 0.0, 1.0 ) ) );

// nightsky

const L0 = vec3( 0.1 ).mul( Fex );

// composition + solar disc
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 retColor = pow( texColor, vec3( float( 1.0 ).div( float( 1.2 ).add( vSunfade.mul( 1.2 ) ) ) ) );

return vec4( retColor, 1.0 );

} )();

material.normals = false;
material.side = BackSide;
material.depthWrite = false;

material.vertexNode = vertexNode;
material.fragmentNode = fragmentNode;

}

}

export { Sky };
136 changes: 136 additions & 0 deletions examples/webgpu_sky.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - sky</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">
</head>
<body>

<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - sky + sun shader
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/tsl": "../build/three.webgpu.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 { Sky } from 'three/addons/objects/SkyGPU.js';

let camera, scene, renderer;

let sky, sun;

init();

function initSky() {

// Add Sky
sky = new Sky();
sky.scale.setScalar( 450000 );
scene.add( sky );

sun = new THREE.Vector3();

/// GUI

const effectController = {
turbidity: 10,
rayleigh: 3,
mieCoefficient: 0.005,
mieDirectionalG: 0.7,
elevation: 2,
azimuth: 180,
exposure: renderer.toneMappingExposure
};

function guiChanged() {

sky.turbidity.value = effectController.turbidity;
sky.rayleigh.value = effectController.rayleigh;
sky.mieCoefficient.value = effectController.mieCoefficient;
sky.mieDirectionalG.value = effectController.mieDirectionalG;

const phi = THREE.MathUtils.degToRad( 90 - effectController.elevation );
const theta = THREE.MathUtils.degToRad( effectController.azimuth );

sun.setFromSphericalCoords( 1, phi, theta );

sky.sunPosition.value.copy( sun );

renderer.toneMappingExposure = effectController.exposure;

}

const gui = new GUI();

gui.add( effectController, 'turbidity', 0.0, 20.0, 0.1 ).onChange( guiChanged );
gui.add( effectController, 'rayleigh', 0.0, 4, 0.001 ).onChange( guiChanged );
gui.add( effectController, 'mieCoefficient', 0.0, 0.1, 0.001 ).onChange( guiChanged );
gui.add( effectController, 'mieDirectionalG', 0.0, 1, 0.001 ).onChange( guiChanged );
gui.add( effectController, 'elevation', 0, 90, 0.1 ).onChange( guiChanged );
gui.add( effectController, 'azimuth', - 180, 180, 0.1 ).onChange( guiChanged );
gui.add( effectController, 'exposure', 0, 1, 0.0001 ).onChange( guiChanged );

guiChanged();

}

function init() {

camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 100, 2000000 );
camera.position.set( 0, 100, 2000 );

scene = new THREE.Scene();

renderer = new THREE.WebGPURenderer();
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.maxPolarAngle = Math.PI / 2;
controls.enableZoom = false;
controls.enablePan = false;

initSky();

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

renderer.render( scene, camera );

}

</script>

</body>

</html>
Loading