feat(3d): integrate ZJB scene with project context
Generic Container CI/CD / test-build-publish (push) Successful in 17s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 18s

This commit is contained in:
2026-09-11 18:49:55 +08:00
parent 3ebe3328aa
commit 35b6819459
76 changed files with 109136 additions and 22 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,182 @@
import {
BackSide,
BoxGeometry,
InstancedMesh,
Mesh,
MeshLambertMaterial,
MeshStandardMaterial,
PointLight,
Scene,
Object3D,
} from 'three';
/**
* This class represents a scene with a basic room setup that can be used as
* input for {@link PMREMGenerator#fromScene}. The resulting PMREM represents the room's
* lighting and can be used for Image Based Lighting by assigning it to {@link Scene#environment}
* or directly as an environment map to PBR materials.
*
* The implementation is based on the [EnvironmentScene](https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts)
* component from the `model-viewer` project.
*
* ```js
* const environment = new RoomEnvironment();
* const pmremGenerator = new THREE.PMREMGenerator( renderer );
*
* const envMap = pmremGenerator.fromScene( environment ).texture;
* scene.environment = envMap;
* ```
*
* @augments Scene
* @three_import import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
*/
class RoomEnvironment extends Scene {
constructor() {
super();
const geometry = new BoxGeometry();
geometry.deleteAttribute( 'uv' );
const roomMaterial = new MeshStandardMaterial( { side: BackSide } );
const boxMaterial = new MeshStandardMaterial();
const mainLight = new PointLight( 0xffffff, 900, 28, 2 );
mainLight.position.set( 0.418, 16.199, 0.300 );
this.add( mainLight );
const room = new Mesh( geometry, roomMaterial );
room.position.set( - 0.757, 13.219, 0.717 );
room.scale.set( 31.713, 28.305, 28.591 );
this.add( room );
const boxes = new InstancedMesh( geometry, boxMaterial, 6 );
const transform = new Object3D();
// box1
transform.position.set( - 10.906, 2.009, 1.846 );
transform.rotation.set( 0, - 0.195, 0 );
transform.scale.set( 2.328, 7.905, 4.651 );
transform.updateMatrix();
boxes.setMatrixAt( 0, transform.matrix );
// box2
transform.position.set( - 5.607, - 0.754, - 0.758 );
transform.rotation.set( 0, 0.994, 0 );
transform.scale.set( 1.970, 1.534, 3.955 );
transform.updateMatrix();
boxes.setMatrixAt( 1, transform.matrix );
// box3
transform.position.set( 6.167, 0.857, 7.803 );
transform.rotation.set( 0, 0.561, 0 );
transform.scale.set( 3.927, 6.285, 3.687 );
transform.updateMatrix();
boxes.setMatrixAt( 2, transform.matrix );
// box4
transform.position.set( - 2.017, 0.018, 6.124 );
transform.rotation.set( 0, 0.333, 0 );
transform.scale.set( 2.002, 4.566, 2.064 );
transform.updateMatrix();
boxes.setMatrixAt( 3, transform.matrix );
// box5
transform.position.set( 2.291, - 0.756, - 2.621 );
transform.rotation.set( 0, - 0.286, 0 );
transform.scale.set( 1.546, 1.552, 1.496 );
transform.updateMatrix();
boxes.setMatrixAt( 4, transform.matrix );
// box6
transform.position.set( - 2.193, - 0.369, - 5.547 );
transform.rotation.set( 0, 0.516, 0 );
transform.scale.set( 3.875, 3.487, 2.986 );
transform.updateMatrix();
boxes.setMatrixAt( 5, transform.matrix );
this.add( boxes );
// -x right
const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
light1.position.set( - 16.116, 14.37, 8.208 );
light1.scale.set( 0.1, 2.428, 2.739 );
this.add( light1 );
// -x left
const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
light2.position.set( - 16.109, 18.021, - 8.207 );
light2.scale.set( 0.1, 2.425, 2.751 );
this.add( light2 );
// +x
const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) );
light3.position.set( 14.904, 12.198, - 1.832 );
light3.scale.set( 0.15, 4.265, 6.331 );
this.add( light3 );
// +z
const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) );
light4.position.set( - 0.462, 8.89, 14.520 );
light4.scale.set( 4.38, 5.441, 0.088 );
this.add( light4 );
// -z
const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) );
light5.position.set( 3.235, 11.486, - 12.541 );
light5.scale.set( 2.5, 2.0, 0.1 );
this.add( light5 );
// +y
const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) );
light6.position.set( 0.0, 20.0, 0.0 );
light6.scale.set( 1.0, 0.1, 1.0 );
this.add( light6 );
}
/**
* Frees internal resources. This method should be called
* when the environment is no longer required.
*/
dispose() {
const resources = new Set();
this.traverse( ( object ) => {
if ( object.isMesh ) {
resources.add( object.geometry );
resources.add( object.material );
}
} );
for ( const resource of resources ) {
resource.dispose();
}
}
}
function createAreaLightMaterial( intensity ) {
// create an emissive-only material. see #31348
const material = new MeshLambertMaterial( {
color: 0x000000,
emissive: 0xffffff,
emissiveIntensity: intensity
} );
return material;
}
export { RoomEnvironment };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,470 @@
/**
* A utility class providing noise functions.
*
* The code is based on [Simplex noise demystified]{@link https://web.archive.org/web/20210210162332/http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf}
* by Stefan Gustavson, 2005.
*
* @three_import import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
*/
class SimplexNoise {
/**
* Constructs a new simplex noise object.
*
* @param {Object} [r=Math] - A math utility class that holds a `random()` method. This makes it
* possible to pass in custom random number generator.
*/
constructor( r = Math ) {
this.grad3 = [[ 1, 1, 0 ], [ - 1, 1, 0 ], [ 1, - 1, 0 ], [ - 1, - 1, 0 ],
[ 1, 0, 1 ], [ - 1, 0, 1 ], [ 1, 0, - 1 ], [ - 1, 0, - 1 ],
[ 0, 1, 1 ], [ 0, - 1, 1 ], [ 0, 1, - 1 ], [ 0, - 1, - 1 ]];
this.grad4 = [[ 0, 1, 1, 1 ], [ 0, 1, 1, - 1 ], [ 0, 1, - 1, 1 ], [ 0, 1, - 1, - 1 ],
[ 0, - 1, 1, 1 ], [ 0, - 1, 1, - 1 ], [ 0, - 1, - 1, 1 ], [ 0, - 1, - 1, - 1 ],
[ 1, 0, 1, 1 ], [ 1, 0, 1, - 1 ], [ 1, 0, - 1, 1 ], [ 1, 0, - 1, - 1 ],
[ - 1, 0, 1, 1 ], [ - 1, 0, 1, - 1 ], [ - 1, 0, - 1, 1 ], [ - 1, 0, - 1, - 1 ],
[ 1, 1, 0, 1 ], [ 1, 1, 0, - 1 ], [ 1, - 1, 0, 1 ], [ 1, - 1, 0, - 1 ],
[ - 1, 1, 0, 1 ], [ - 1, 1, 0, - 1 ], [ - 1, - 1, 0, 1 ], [ - 1, - 1, 0, - 1 ],
[ 1, 1, 1, 0 ], [ 1, 1, - 1, 0 ], [ 1, - 1, 1, 0 ], [ 1, - 1, - 1, 0 ],
[ - 1, 1, 1, 0 ], [ - 1, 1, - 1, 0 ], [ - 1, - 1, 1, 0 ], [ - 1, - 1, - 1, 0 ]];
this.p = [];
for ( let i = 0; i < 256; i ++ ) {
this.p[ i ] = Math.floor( r.random() * 256 );
}
// To remove the need for index wrapping, double the permutation table length
this.perm = [];
for ( let i = 0; i < 512; i ++ ) {
this.perm[ i ] = this.p[ i & 255 ];
}
// A lookup table to traverse the simplex around a given point in 4D.
// Details can be found where this table is used, in the 4D noise method.
this.simplex = [
[ 0, 1, 2, 3 ], [ 0, 1, 3, 2 ], [ 0, 0, 0, 0 ], [ 0, 2, 3, 1 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 1, 2, 3, 0 ],
[ 0, 2, 1, 3 ], [ 0, 0, 0, 0 ], [ 0, 3, 1, 2 ], [ 0, 3, 2, 1 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 1, 3, 2, 0 ],
[ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ],
[ 1, 2, 0, 3 ], [ 0, 0, 0, 0 ], [ 1, 3, 0, 2 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 2, 3, 0, 1 ], [ 2, 3, 1, 0 ],
[ 1, 0, 2, 3 ], [ 1, 0, 3, 2 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 2, 0, 3, 1 ], [ 0, 0, 0, 0 ], [ 2, 1, 3, 0 ],
[ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ],
[ 2, 0, 1, 3 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 3, 0, 1, 2 ], [ 3, 0, 2, 1 ], [ 0, 0, 0, 0 ], [ 3, 1, 2, 0 ],
[ 2, 1, 0, 3 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 3, 1, 0, 2 ], [ 0, 0, 0, 0 ], [ 3, 2, 0, 1 ], [ 3, 2, 1, 0 ]];
}
/**
* A 2D simplex noise method.
*
* @param {number} xin - The x coordinate.
* @param {number} yin - The y coordinate.
* @return {number} The noise value.
*/
noise( xin, yin ) {
let n0; // Noise contributions from the three corners
let n1;
let n2;
// Skew the input space to determine which simplex cell we're in
const F2 = 0.5 * ( Math.sqrt( 3.0 ) - 1.0 );
const s = ( xin + yin ) * F2; // Hairy factor for 2D
const i = Math.floor( xin + s );
const j = Math.floor( yin + s );
const G2 = ( 3.0 - Math.sqrt( 3.0 ) ) / 6.0;
const t = ( i + j ) * G2;
const X0 = i - t; // Unskew the cell origin back to (x,y) space
const Y0 = j - t;
const x0 = xin - X0; // The x,y distances from the cell origin
const y0 = yin - Y0;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
let i1; // Offsets for second (middle) corner of simplex in (i,j) coords
let j1;
if ( x0 > y0 ) {
i1 = 1; j1 = 0;
// lower triangle, XY order: (0,0)->(1,0)->(1,1)
} else {
i1 = 0; j1 = 1;
} // upper triangle, YX order: (0,0)->(0,1)->(1,1)
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
const y1 = y0 - j1 + G2;
const x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
const y2 = y0 - 1.0 + 2.0 * G2;
// Work out the hashed gradient indices of the three simplex corners
const ii = i & 255;
const jj = j & 255;
const gi0 = this.perm[ ii + this.perm[ jj ] ] % 12;
const gi1 = this.perm[ ii + i1 + this.perm[ jj + j1 ] ] % 12;
const gi2 = this.perm[ ii + 1 + this.perm[ jj + 1 ] ] % 12;
// Calculate the contribution from the three corners
let t0 = 0.5 - x0 * x0 - y0 * y0;
if ( t0 < 0 ) n0 = 0.0;
else {
t0 *= t0;
n0 = t0 * t0 * this._dot( this.grad3[ gi0 ], x0, y0 ); // (x,y) of grad3 used for 2D gradient
}
let t1 = 0.5 - x1 * x1 - y1 * y1;
if ( t1 < 0 ) n1 = 0.0;
else {
t1 *= t1;
n1 = t1 * t1 * this._dot( this.grad3[ gi1 ], x1, y1 );
}
let t2 = 0.5 - x2 * x2 - y2 * y2;
if ( t2 < 0 ) n2 = 0.0;
else {
t2 *= t2;
n2 = t2 * t2 * this._dot( this.grad3[ gi2 ], x2, y2 );
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 70.0 * ( n0 + n1 + n2 );
}
/**
* A 3D simplex noise method.
*
* @param {number} xin - The x coordinate.
* @param {number} yin - The y coordinate.
* @param {number} zin - The z coordinate.
* @return {number} The noise value.
*/
noise3d( xin, yin, zin ) {
let n0; // Noise contributions from the four corners
let n1;
let n2;
let n3;
// Skew the input space to determine which simplex cell we're in
const F3 = 1.0 / 3.0;
const s = ( xin + yin + zin ) * F3; // Very nice and simple skew factor for 3D
const i = Math.floor( xin + s );
const j = Math.floor( yin + s );
const k = Math.floor( zin + s );
const G3 = 1.0 / 6.0; // Very nice and simple unskew factor, too
const t = ( i + j + k ) * G3;
const X0 = i - t; // Unskew the cell origin back to (x,y,z) space
const Y0 = j - t;
const Z0 = k - t;
const x0 = xin - X0; // The x,y,z distances from the cell origin
const y0 = yin - Y0;
const z0 = zin - Z0;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
let i1; // Offsets for second corner of simplex in (i,j,k) coords
let j1;
let k1;
let i2; // Offsets for third corner of simplex in (i,j,k) coords
let j2;
let k2;
if ( x0 >= y0 ) {
if ( y0 >= z0 ) {
i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
// X Y Z order
} else if ( x0 >= z0 ) {
i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1;
// X Z Y order
} else {
i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1;
} // Z X Y order
} else { // x0<y0
if ( y0 < z0 ) {
i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1;
// Z Y X order
} else if ( x0 < z0 ) {
i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1;
// Y Z X order
} else {
i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
} // Y X Z order
}
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
// c = 1/6.
const x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
const y1 = y0 - j1 + G3;
const z1 = z0 - k1 + G3;
const x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords
const y2 = y0 - j2 + 2.0 * G3;
const z2 = z0 - k2 + 2.0 * G3;
const x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords
const y3 = y0 - 1.0 + 3.0 * G3;
const z3 = z0 - 1.0 + 3.0 * G3;
// Work out the hashed gradient indices of the four simplex corners
const ii = i & 255;
const jj = j & 255;
const kk = k & 255;
const gi0 = this.perm[ ii + this.perm[ jj + this.perm[ kk ] ] ] % 12;
const gi1 = this.perm[ ii + i1 + this.perm[ jj + j1 + this.perm[ kk + k1 ] ] ] % 12;
const gi2 = this.perm[ ii + i2 + this.perm[ jj + j2 + this.perm[ kk + k2 ] ] ] % 12;
const gi3 = this.perm[ ii + 1 + this.perm[ jj + 1 + this.perm[ kk + 1 ] ] ] % 12;
// Calculate the contribution from the four corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if ( t0 < 0 ) n0 = 0.0;
else {
t0 *= t0;
n0 = t0 * t0 * this._dot3( this.grad3[ gi0 ], x0, y0, z0 );
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if ( t1 < 0 ) n1 = 0.0;
else {
t1 *= t1;
n1 = t1 * t1 * this._dot3( this.grad3[ gi1 ], x1, y1, z1 );
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if ( t2 < 0 ) n2 = 0.0;
else {
t2 *= t2;
n2 = t2 * t2 * this._dot3( this.grad3[ gi2 ], x2, y2, z2 );
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if ( t3 < 0 ) n3 = 0.0;
else {
t3 *= t3;
n3 = t3 * t3 * this._dot3( this.grad3[ gi3 ], x3, y3, z3 );
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to stay just inside [-1,1]
return 32.0 * ( n0 + n1 + n2 + n3 );
}
/**
* A 4D simplex noise method.
*
* @param {number} x - The x coordinate.
* @param {number} y - The y coordinate.
* @param {number} z - The z coordinate.
* @param {number} w - The w coordinate.
* @return {number} The noise value.
*/
noise4d( x, y, z, w ) {
// For faster and easier lookups
const grad4 = this.grad4;
const simplex = this.simplex;
const perm = this.perm;
// The skewing and unskewing factors are hairy again for the 4D case
const F4 = ( Math.sqrt( 5.0 ) - 1.0 ) / 4.0;
const G4 = ( 5.0 - Math.sqrt( 5.0 ) ) / 20.0;
let n0; // Noise contributions from the five corners
let n1;
let n2;
let n3;
let n4;
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
const s = ( x + y + z + w ) * F4; // Factor for 4D skewing
const i = Math.floor( x + s );
const j = Math.floor( y + s );
const k = Math.floor( z + s );
const l = Math.floor( w + s );
const t = ( i + j + k + l ) * G4; // Factor for 4D unskewing
const X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
const Y0 = j - t;
const Z0 = k - t;
const W0 = l - t;
const x0 = x - X0; // The x,y,z,w distances from the cell origin
const y0 = y - Y0;
const z0 = z - Z0;
const w0 = w - W0;
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
// To find out which of the 24 possible simplices we're in, we need to
// determine the magnitude ordering of x0, y0, z0 and w0.
// The method below is a good way of finding the ordering of x,y,z,w and
// then find the correct traversal order for the simplex were in.
// First, six pair-wise comparisons are performed between each possible pair
// of the four coordinates, and the results are used to add up binary bits
// for an integer index.
const c1 = ( x0 > y0 ) ? 32 : 0;
const c2 = ( x0 > z0 ) ? 16 : 0;
const c3 = ( y0 > z0 ) ? 8 : 0;
const c4 = ( x0 > w0 ) ? 4 : 0;
const c5 = ( y0 > w0 ) ? 2 : 0;
const c6 = ( z0 > w0 ) ? 1 : 0;
const c = c1 + c2 + c3 + c4 + c5 + c6;
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
// impossible. Only the 24 indices which have non-zero entries make any sense.
// We use a thresholding to set the coordinates in turn from the largest magnitude.
// The number 3 in the "simplex" array is at the position of the largest coordinate.
const i1 = simplex[ c ][ 0 ] >= 3 ? 1 : 0;
const j1 = simplex[ c ][ 1 ] >= 3 ? 1 : 0;
const k1 = simplex[ c ][ 2 ] >= 3 ? 1 : 0;
const l1 = simplex[ c ][ 3 ] >= 3 ? 1 : 0;
// The number 2 in the "simplex" array is at the second largest coordinate.
const i2 = simplex[ c ][ 0 ] >= 2 ? 1 : 0;
const j2 = simplex[ c ][ 1 ] >= 2 ? 1 : 0;
const k2 = simplex[ c ][ 2 ] >= 2 ? 1 : 0;
const l2 = simplex[ c ][ 3 ] >= 2 ? 1 : 0;
// The number 1 in the "simplex" array is at the second smallest coordinate.
const i3 = simplex[ c ][ 0 ] >= 1 ? 1 : 0;
const j3 = simplex[ c ][ 1 ] >= 1 ? 1 : 0;
const k3 = simplex[ c ][ 2 ] >= 1 ? 1 : 0;
const l3 = simplex[ c ][ 3 ] >= 1 ? 1 : 0;
// The fifth corner has all coordinate offsets = 1, so no need to look that up.
const x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
const y1 = y0 - j1 + G4;
const z1 = z0 - k1 + G4;
const w1 = w0 - l1 + G4;
const x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords
const y2 = y0 - j2 + 2.0 * G4;
const z2 = z0 - k2 + 2.0 * G4;
const w2 = w0 - l2 + 2.0 * G4;
const x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords
const y3 = y0 - j3 + 3.0 * G4;
const z3 = z0 - k3 + 3.0 * G4;
const w3 = w0 - l3 + 3.0 * G4;
const x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords
const y4 = y0 - 1.0 + 4.0 * G4;
const z4 = z0 - 1.0 + 4.0 * G4;
const w4 = w0 - 1.0 + 4.0 * G4;
// Work out the hashed gradient indices of the five simplex corners
const ii = i & 255;
const jj = j & 255;
const kk = k & 255;
const ll = l & 255;
const gi0 = perm[ ii + perm[ jj + perm[ kk + perm[ ll ] ] ] ] % 32;
const gi1 = perm[ ii + i1 + perm[ jj + j1 + perm[ kk + k1 + perm[ ll + l1 ] ] ] ] % 32;
const gi2 = perm[ ii + i2 + perm[ jj + j2 + perm[ kk + k2 + perm[ ll + l2 ] ] ] ] % 32;
const gi3 = perm[ ii + i3 + perm[ jj + j3 + perm[ kk + k3 + perm[ ll + l3 ] ] ] ] % 32;
const gi4 = perm[ ii + 1 + perm[ jj + 1 + perm[ kk + 1 + perm[ ll + 1 ] ] ] ] % 32;
// Calculate the contribution from the five corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
if ( t0 < 0 ) n0 = 0.0;
else {
t0 *= t0;
n0 = t0 * t0 * this._dot4( grad4[ gi0 ], x0, y0, z0, w0 );
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
if ( t1 < 0 ) n1 = 0.0;
else {
t1 *= t1;
n1 = t1 * t1 * this._dot4( grad4[ gi1 ], x1, y1, z1, w1 );
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
if ( t2 < 0 ) n2 = 0.0;
else {
t2 *= t2;
n2 = t2 * t2 * this._dot4( grad4[ gi2 ], x2, y2, z2, w2 );
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
if ( t3 < 0 ) n3 = 0.0;
else {
t3 *= t3;
n3 = t3 * t3 * this._dot4( grad4[ gi3 ], x3, y3, z3, w3 );
}
let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
if ( t4 < 0 ) n4 = 0.0;
else {
t4 *= t4;
n4 = t4 * t4 * this._dot4( grad4[ gi4 ], x4, y4, z4, w4 );
}
// Sum up and scale the result to cover the range [-1,1]
return 27.0 * ( n0 + n1 + n2 + n3 + n4 );
}
// private
_dot( g, x, y ) {
return g[ 0 ] * x + g[ 1 ] * y;
}
_dot3( g, x, y, z ) {
return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z;
}
_dot4( g, x, y, z, w ) {
return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z + g[ 3 ] * w;
}
}
export { SimplexNoise };
@@ -0,0 +1,363 @@
import {
Clock,
HalfFloatType,
NoBlending,
Vector2,
WebGLRenderTarget
} from 'three';
import { CopyShader } from '../shaders/CopyShader.js';
import { ShaderPass } from './ShaderPass.js';
import { ClearMaskPass, MaskPass } from './MaskPass.js';
/**
* Used to implement post-processing effects in three.js.
* The class manages a chain of post-processing passes to produce the final visual result.
* Post-processing passes are executed in order of their addition/insertion.
* The last pass is automatically rendered to screen.
*
* This module can only be used with {@link WebGLRenderer}.
*
* ```js
* const composer = new EffectComposer( renderer );
*
* // adding some passes
* const renderPass = new RenderPass( scene, camera );
* composer.addPass( renderPass );
*
* const glitchPass = new GlitchPass();
* composer.addPass( glitchPass );
*
* const outputPass = new OutputPass()
* composer.addPass( outputPass );
*
* function animate() {
*
* composer.render(); // instead of renderer.render()
*
* }
* ```
*
* @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
*/
class EffectComposer {
/**
* Constructs a new effect composer.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will
* be used as the internal read and write buffers. If not given, the composer creates
* the buffers automatically.
*/
constructor( renderer, renderTarget ) {
/**
* The renderer.
*
* @type {WebGLRenderer}
*/
this.renderer = renderer;
this._pixelRatio = renderer.getPixelRatio();
if ( renderTarget === undefined ) {
const size = renderer.getSize( new Vector2() );
this._width = size.width;
this._height = size.height;
renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
renderTarget.texture.name = 'EffectComposer.rt1';
} else {
this._width = renderTarget.width;
this._height = renderTarget.height;
}
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.renderTarget2.texture.name = 'EffectComposer.rt2';
/**
* A reference to the internal write buffer. Passes usually write
* their result into this buffer.
*
* @type {WebGLRenderTarget}
*/
this.writeBuffer = this.renderTarget1;
/**
* A reference to the internal read buffer. Passes usually read
* the previous render result from this buffer.
*
* @type {WebGLRenderTarget}
*/
this.readBuffer = this.renderTarget2;
/**
* Whether the final pass is rendered to the screen (default framebuffer) or not.
*
* @type {boolean}
* @default true
*/
this.renderToScreen = true;
/**
* An array representing the (ordered) chain of post-processing passes.
*
* @type {Array<Pass>}
*/
this.passes = [];
/**
* A copy pass used for internal swap operations.
*
* @private
* @type {ShaderPass}
*/
this.copyPass = new ShaderPass( CopyShader );
this.copyPass.material.blending = NoBlending;
/**
* The internal clock for managing time data.
*
* @private
* @type {Clock}
*/
this.clock = new Clock();
}
/**
* Swaps the internal read/write buffers.
*/
swapBuffers() {
const tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
}
/**
* Adds the given pass to the pass chain.
*
* @param {Pass} pass - The pass to add.
*/
addPass( pass ) {
this.passes.push( pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
/**
* Inserts the given pass at a given index.
*
* @param {Pass} pass - The pass to insert.
* @param {number} index - The index into the pass chain.
*/
insertPass( pass, index ) {
this.passes.splice( index, 0, pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
/**
* Removes the given pass from the pass chain.
*
* @param {Pass} pass - The pass to remove.
*/
removePass( pass ) {
const index = this.passes.indexOf( pass );
if ( index !== - 1 ) {
this.passes.splice( index, 1 );
}
}
/**
* Returns `true` if the pass for the given index is the last enabled pass in the pass chain.
*
* @param {number} passIndex - The pass index.
* @return {boolean} Whether the pass for the given index is the last pass in the pass chain.
*/
isLastEnabledPass( passIndex ) {
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
if ( this.passes[ i ].enabled ) {
return false;
}
}
return true;
}
/**
* Executes all enabled post-processing passes in order to produce the final frame.
*
* @param {number} deltaTime - The delta time in seconds. If not given, the composer computes
* its own time delta value.
*/
render( deltaTime ) {
// deltaTime value is in seconds
if ( deltaTime === undefined ) {
deltaTime = this.clock.getDelta();
}
const currentRenderTarget = this.renderer.getRenderTarget();
let maskActive = false;
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
const pass = this.passes[ i ];
if ( pass.enabled === false ) continue;
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
if ( pass.needsSwap ) {
if ( maskActive ) {
const context = this.renderer.getContext();
const stencil = this.renderer.state.buffers.stencil;
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
}
this.swapBuffers();
}
if ( MaskPass !== undefined ) {
if ( pass instanceof MaskPass ) {
maskActive = true;
} else if ( pass instanceof ClearMaskPass ) {
maskActive = false;
}
}
}
this.renderer.setRenderTarget( currentRenderTarget );
}
/**
* Resets the internal state of the EffectComposer.
*
* @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like
* the one from the constructor. If set, it is used to setup the read and write buffers.
*/
reset( renderTarget ) {
if ( renderTarget === undefined ) {
const size = this.renderer.getSize( new Vector2() );
this._pixelRatio = this.renderer.getPixelRatio();
this._width = size.width;
this._height = size.height;
renderTarget = this.renderTarget1.clone();
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
}
/**
* Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize},
* this method honors the current pixel ration.
*
* @param {number} width - The width in logical pixels.
* @param {number} height - The height in logical pixels.
*/
setSize( width, height ) {
this._width = width;
this._height = height;
const effectiveWidth = this._width * this._pixelRatio;
const effectiveHeight = this._height * this._pixelRatio;
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
for ( let i = 0; i < this.passes.length; i ++ ) {
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
}
}
/**
* Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output.
* Setting the pixel ratio will automatically resize the composer.
*
* @param {number} pixelRatio - The pixel ratio to set.
*/
setPixelRatio( pixelRatio ) {
this._pixelRatio = pixelRatio;
this.setSize( this._width, this._height );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the composer is no longer used in your app.
*/
dispose() {
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.copyPass.dispose();
}
}
export { EffectComposer };
@@ -0,0 +1,195 @@
import { Pass } from './Pass.js';
/**
* This pass can be used to define a mask during post processing.
* Meaning only areas of subsequent post processing are affected
* which lie in the masking area of this pass. Internally, the masking
* is implemented with the stencil buffer.
*
* ```js
* const maskPass = new MaskPass( scene, camera );
* composer.addPass( maskPass );
* ```
*
* @augments Pass
* @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js';
*/
class MaskPass extends Pass {
/**
* Constructs a new mask pass.
*
* @param {Scene} scene - The 3D objects in this scene will define the mask.
* @param {Camera} camera - The camera.
*/
constructor( scene, camera ) {
super();
/**
* The scene that defines the mask.
*
* @type {Scene}
*/
this.scene = scene;
/**
* The camera.
*
* @type {Camera}
*/
this.camera = camera;
/**
* Overwritten to perform a clear operation by default.
*
* @type {boolean}
* @default true
*/
this.clear = true;
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
/**
* Whether to inverse the mask or not.
*
* @type {boolean}
* @default false
*/
this.inverse = false;
}
/**
* Performs a mask pass with the configured scene and camera.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const context = renderer.getContext();
const state = renderer.state;
// don't update color or depth
state.buffers.color.setMask( false );
state.buffers.depth.setMask( false );
// lock buffers
state.buffers.color.setLocked( true );
state.buffers.depth.setLocked( true );
// set up stencil
let writeValue, clearValue;
if ( this.inverse ) {
writeValue = 0;
clearValue = 1;
} else {
writeValue = 1;
clearValue = 0;
}
state.buffers.stencil.setTest( true );
state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE );
state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff );
state.buffers.stencil.setClear( clearValue );
state.buffers.stencil.setLocked( true );
// draw into the stencil buffer
renderer.setRenderTarget( readBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
// unlock color and depth buffer and make them writable for subsequent rendering/clearing
state.buffers.color.setLocked( false );
state.buffers.depth.setLocked( false );
state.buffers.color.setMask( true );
state.buffers.depth.setMask( true );
// only render where stencil is set to 1
state.buffers.stencil.setLocked( false );
state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP );
state.buffers.stencil.setLocked( true );
}
}
/**
* This pass can be used to clear a mask previously defined with {@link MaskPass}.
*
* ```js
* const clearPass = new ClearMaskPass();
* composer.addPass( clearPass );
* ```
*
* @augments Pass
*/
class ClearMaskPass extends Pass {
/**
* Constructs a new clear mask pass.
*/
constructor() {
super();
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
}
/**
* Performs the clear of the currently defined mask.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
renderer.state.buffers.stencil.setLocked( false );
renderer.state.buffers.stencil.setTest( false );
}
}
export { MaskPass, ClearMaskPass };
@@ -0,0 +1,139 @@
import {
ColorManagement,
RawShaderMaterial,
UniformsUtils,
LinearToneMapping,
ReinhardToneMapping,
CineonToneMapping,
AgXToneMapping,
ACESFilmicToneMapping,
NeutralToneMapping,
CustomToneMapping,
SRGBTransfer
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { OutputShader } from '../shaders/OutputShader.js';
/**
* This pass is responsible for including tone mapping and color space conversion
* into your pass chain. In most cases, this pass should be included at the end
* of each pass chain. If a pass requires sRGB input (e.g. like FXAA), the pass
* must follow `OutputPass` in the pass chain.
*
* The tone mapping and color space settings are extracted from the renderer.
*
* ```js
* const outputPass = new OutputPass();
* composer.addPass( outputPass );
* ```
*
* @augments Pass
* @three_import import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
*/
class OutputPass extends Pass {
/**
* Constructs a new output pass.
*/
constructor() {
super();
/**
* The pass uniforms.
*
* @type {Object}
*/
this.uniforms = UniformsUtils.clone( OutputShader.uniforms );
/**
* The pass material.
*
* @type {RawShaderMaterial}
*/
this.material = new RawShaderMaterial( {
name: OutputShader.name,
uniforms: this.uniforms,
vertexShader: OutputShader.vertexShader,
fragmentShader: OutputShader.fragmentShader
} );
// internals
this._fsQuad = new FullScreenQuad( this.material );
this._outputColorSpace = null;
this._toneMapping = null;
}
/**
* Performs the output pass.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) {
this.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
this.uniforms[ 'toneMappingExposure' ].value = renderer.toneMappingExposure;
// rebuild defines if required
if ( this._outputColorSpace !== renderer.outputColorSpace || this._toneMapping !== renderer.toneMapping ) {
this._outputColorSpace = renderer.outputColorSpace;
this._toneMapping = renderer.toneMapping;
this.material.defines = {};
if ( ColorManagement.getTransfer( this._outputColorSpace ) === SRGBTransfer ) this.material.defines.SRGB_TRANSFER = '';
if ( this._toneMapping === LinearToneMapping ) this.material.defines.LINEAR_TONE_MAPPING = '';
else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = '';
else if ( this._toneMapping === CineonToneMapping ) this.material.defines.CINEON_TONE_MAPPING = '';
else if ( this._toneMapping === ACESFilmicToneMapping ) this.material.defines.ACES_FILMIC_TONE_MAPPING = '';
else if ( this._toneMapping === AgXToneMapping ) this.material.defines.AGX_TONE_MAPPING = '';
else if ( this._toneMapping === NeutralToneMapping ) this.material.defines.NEUTRAL_TONE_MAPPING = '';
else if ( this._toneMapping === CustomToneMapping ) this.material.defines.CUSTOM_TONE_MAPPING = '';
this.material.needsUpdate = true;
}
//
if ( this.renderToScreen === true ) {
renderer.setRenderTarget( null );
this._fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
this._fsQuad.render( renderer );
}
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*/
dispose() {
this.material.dispose();
this._fsQuad.dispose();
}
}
export { OutputPass };
@@ -0,0 +1,191 @@
import {
BufferGeometry,
Float32BufferAttribute,
OrthographicCamera,
Mesh
} from 'three';
/**
* Abstract base class for all post processing passes.
*
* This module is only relevant for post processing with {@link WebGLRenderer}.
*
* @abstract
* @three_import import { Pass } from 'three/addons/postprocessing/Pass.js';
*/
class Pass {
/**
* Constructs a new pass.
*/
constructor() {
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isPass = true;
/**
* If set to `true`, the pass is processed by the composer.
*
* @type {boolean}
* @default true
*/
this.enabled = true;
/**
* If set to `true`, the pass indicates to swap read and write buffer after rendering.
*
* @type {boolean}
* @default true
*/
this.needsSwap = true;
/**
* If set to `true`, the pass clears its buffer before rendering
*
* @type {boolean}
* @default false
*/
this.clear = false;
/**
* If set to `true`, the result of the pass is rendered to screen. The last pass in the composers
* pass chain gets automatically rendered to screen, no matter how this property is configured.
*
* @type {boolean}
* @default false
*/
this.renderToScreen = false;
}
/**
* Sets the size of the pass.
*
* @abstract
* @param {number} width - The width to set.
* @param {number} height - The height to set.
*/
setSize( /* width, height */ ) {}
/**
* This method holds the render logic of a pass. It must be implemented in all derived classes.
*
* @abstract
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*
* @abstract
*/
dispose() {}
}
// Helper for passes that need to fill the viewport with a single quad.
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
// https://github.com/mrdoob/three.js/pull/21358
class FullscreenTriangleGeometry extends BufferGeometry {
constructor() {
super();
this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
}
}
const _geometry = new FullscreenTriangleGeometry();
/**
* This module is a helper for passes which need to render a full
* screen effect which is quite common in context of post processing.
*
* The intended usage is to reuse a single full screen quad for rendering
* subsequent passes by just reassigning the `material` reference.
*
* This module can only be used with {@link WebGLRenderer}.
*
* @augments Mesh
* @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
*/
class FullScreenQuad {
/**
* Constructs a new full screen quad.
*
* @param {?Material} material - The material to render te full screen quad with.
*/
constructor( material ) {
this._mesh = new Mesh( _geometry, material );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the instance is no longer used in your app.
*/
dispose() {
this._mesh.geometry.dispose();
}
/**
* Renders the full screen quad.
*
* @param {WebGLRenderer} renderer - The renderer.
*/
render( renderer ) {
renderer.render( this._mesh, _camera );
}
/**
* The quad's material.
*
* @type {?Material}
*/
get material() {
return this._mesh.material;
}
set material( value ) {
this._mesh.material = value;
}
}
export { Pass, FullScreenQuad };
@@ -0,0 +1,183 @@
import {
Color
} from 'three';
import { Pass } from './Pass.js';
/**
* This class represents a render pass. It takes a camera and a scene and produces
* a beauty pass for subsequent post processing effects.
*
* ```js
* const renderPass = new RenderPass( scene, camera );
* composer.addPass( renderPass );
* ```
*
* @augments Pass
* @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
*/
class RenderPass extends Pass {
/**
* Constructs a new render pass.
*
* @param {Scene} scene - The scene to render.
* @param {Camera} camera - The camera.
* @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used
* for all objects in the scene.
* @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass.
* @param {?number} [clearAlpha=null] - The clear alpha of the render pass.
*/
constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) {
super();
/**
* The scene to render.
*
* @type {Scene}
*/
this.scene = scene;
/**
* The camera.
*
* @type {Camera}
*/
this.camera = camera;
/**
* The override material. If set, this material is used
* for all objects in the scene.
*
* @type {?Material}
* @default null
*/
this.overrideMaterial = overrideMaterial;
/**
* The clear color of the render pass.
*
* @type {?(number|Color|string)}
* @default null
*/
this.clearColor = clearColor;
/**
* The clear alpha of the render pass.
*
* @type {?number}
* @default null
*/
this.clearAlpha = clearAlpha;
/**
* Overwritten to perform a clear operation by default.
*
* @type {boolean}
* @default true
*/
this.clear = true;
/**
* If set to `true`, only the depth can be cleared when `clear` is to `false`.
*
* @type {boolean}
* @default false
*/
this.clearDepth = false;
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
this._oldClearColor = new Color();
}
/**
* Performs a beauty pass with the configured scene and camera.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
let oldClearAlpha, oldOverrideMaterial;
if ( this.overrideMaterial !== null ) {
oldOverrideMaterial = this.scene.overrideMaterial;
this.scene.overrideMaterial = this.overrideMaterial;
}
if ( this.clearColor !== null ) {
renderer.getClearColor( this._oldClearColor );
renderer.setClearColor( this.clearColor, renderer.getClearAlpha() );
}
if ( this.clearAlpha !== null ) {
oldClearAlpha = renderer.getClearAlpha();
renderer.setClearAlpha( this.clearAlpha );
}
if ( this.clearDepth == true ) {
renderer.clearDepth();
}
renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );
if ( this.clear === true ) {
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
}
renderer.render( this.scene, this.camera );
// restore
if ( this.clearColor !== null ) {
renderer.setClearColor( this._oldClearColor );
}
if ( this.clearAlpha !== null ) {
renderer.setClearAlpha( oldClearAlpha );
}
if ( this.overrideMaterial !== null ) {
this.scene.overrideMaterial = oldOverrideMaterial;
}
renderer.autoClear = oldAutoClear;
}
}
export { RenderPass };
@@ -0,0 +1,527 @@
import {
AddEquation,
Color,
CustomBlending,
DataTexture,
DepthTexture,
DstAlphaFactor,
DstColorFactor,
FloatType,
HalfFloatType,
MathUtils,
MeshNormalMaterial,
NearestFilter,
NoBlending,
RedFormat,
DepthStencilFormat,
UnsignedInt248Type,
RepeatWrapping,
ShaderMaterial,
UniformsUtils,
Vector3,
WebGLRenderTarget,
ZeroFactor
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
import { SimplexNoise } from '../math/SimplexNoise.js';
import { SSAOBlurShader, SSAODepthShader, SSAOShader } from '../shaders/SSAOShader.js';
import { CopyShader } from '../shaders/CopyShader.js';
/**
* A pass for a basic SSAO effect.
*
* {@link SAOPass} and {@link GTAPass} produce a more advanced AO but are also
* more expensive.
*
* ```js
* const ssaoPass = new SSAOPass( scene, camera, width, height );
* composer.addPass( ssaoPass );
* ```
*
* @augments Pass
* @three_import import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
*/
class SSAOPass extends Pass {
/**
* Constructs a new SSAO pass.
*
* @param {Scene} scene - The scene to compute the AO for.
* @param {Camera} camera - The camera.
* @param {number} [width=512] - The width of the effect.
* @param {number} [height=512] - The height of the effect.
* @param {number} [kernelSize=32] - The kernel size.
*/
constructor( scene, camera, width = 512, height = 512, kernelSize = 32 ) {
super();
/**
* The width of the effect.
*
* @type {number}
* @default 512
*/
this.width = width;
/**
* The height of the effect.
*
* @type {number}
* @default 512
*/
this.height = height;
/**
* Overwritten to perform a clear operation by default.
*
* @type {boolean}
* @default true
*/
this.clear = true;
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
/**
* The camera.
*
* @type {Camera}
*/
this.camera = camera;
/**
* The scene to render the AO for.
*
* @type {Scene}
*/
this.scene = scene;
/**
* The kernel radius controls how wide the
* AO spreads.
*
* @type {number}
* @default 8
*/
this.kernelRadius = 8;
this.kernel = [];
this.noiseTexture = null;
/**
* The output configuration.
*
* @type {number}
* @default 0
*/
this.output = 0;
/**
* Defines the minimum distance that should be
* affected by the AO.
*
* @type {number}
* @default 0.005
*/
this.minDistance = 0.005;
/**
* Defines the maximum distance that should be
* affected by the AO.
*
* @type {number}
* @default 0.1
*/
this.maxDistance = 0.1;
this._visibilityCache = [];
//
this._generateSampleKernel( kernelSize );
this._generateRandomKernelRotations();
// depth texture
const depthTexture = new DepthTexture();
depthTexture.format = DepthStencilFormat;
depthTexture.type = UnsignedInt248Type;
// normal render target with depth buffer
this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, {
minFilter: NearestFilter,
magFilter: NearestFilter,
type: HalfFloatType,
depthTexture: depthTexture
} );
// ssao render target
this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } );
this.blurRenderTarget = this.ssaoRenderTarget.clone();
// ssao material
this.ssaoMaterial = new ShaderMaterial( {
defines: Object.assign( {}, SSAOShader.defines ),
uniforms: UniformsUtils.clone( SSAOShader.uniforms ),
vertexShader: SSAOShader.vertexShader,
fragmentShader: SSAOShader.fragmentShader,
blending: NoBlending
} );
this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize;
this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture;
this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture;
this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture;
this.ssaoMaterial.uniforms[ 'kernel' ].value = this.kernel;
this.ssaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
this.ssaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
this.ssaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height );
this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse );
// normal material
this.normalMaterial = new MeshNormalMaterial();
this.normalMaterial.blending = NoBlending;
// blur material
this.blurMaterial = new ShaderMaterial( {
defines: Object.assign( {}, SSAOBlurShader.defines ),
uniforms: UniformsUtils.clone( SSAOBlurShader.uniforms ),
vertexShader: SSAOBlurShader.vertexShader,
fragmentShader: SSAOBlurShader.fragmentShader
} );
this.blurMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture;
this.blurMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height );
// material for rendering the depth
this.depthRenderMaterial = new ShaderMaterial( {
defines: Object.assign( {}, SSAODepthShader.defines ),
uniforms: UniformsUtils.clone( SSAODepthShader.uniforms ),
vertexShader: SSAODepthShader.vertexShader,
fragmentShader: SSAODepthShader.fragmentShader,
blending: NoBlending
} );
this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture;
this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
// material for rendering the content of a render target
this.copyMaterial = new ShaderMaterial( {
uniforms: UniformsUtils.clone( CopyShader.uniforms ),
vertexShader: CopyShader.vertexShader,
fragmentShader: CopyShader.fragmentShader,
transparent: true,
depthTest: false,
depthWrite: false,
blendSrc: DstColorFactor,
blendDst: ZeroFactor,
blendEquation: AddEquation,
blendSrcAlpha: DstAlphaFactor,
blendDstAlpha: ZeroFactor,
blendEquationAlpha: AddEquation
} );
// internals
this._fsQuad = new FullScreenQuad( null );
this._originalClearColor = new Color();
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*/
dispose() {
// dispose render targets
this.normalRenderTarget.dispose();
this.ssaoRenderTarget.dispose();
this.blurRenderTarget.dispose();
// dispose materials
this.normalMaterial.dispose();
this.blurMaterial.dispose();
this.copyMaterial.dispose();
this.depthRenderMaterial.dispose();
// dispose full screen quad
this._fsQuad.dispose();
}
/**
* Performs the SSAO pass.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
// render normals and depth (honor only meshes, points and lines do not contribute to SSAO)
this._overrideVisibility();
this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 );
this._restoreVisibility();
// render SSAO
this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius;
this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance;
this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance;
this._renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget );
// render blur
this._renderPass( renderer, this.blurMaterial, this.blurRenderTarget );
// output result to screen
switch ( this.output ) {
case SSAOPass.OUTPUT.SSAO:
this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture;
this.copyMaterial.blending = NoBlending;
this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer );
break;
case SSAOPass.OUTPUT.Blur:
this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture;
this.copyMaterial.blending = NoBlending;
this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer );
break;
case SSAOPass.OUTPUT.Depth:
this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : readBuffer );
break;
case SSAOPass.OUTPUT.Normal:
this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture;
this.copyMaterial.blending = NoBlending;
this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer );
break;
case SSAOPass.OUTPUT.Default:
this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture;
this.copyMaterial.blending = CustomBlending;
this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer );
break;
default:
console.warn( 'THREE.SSAOPass: Unknown output type.' );
}
}
/**
* Sets the size of the pass.
*
* @param {number} width - The width to set.
* @param {number} height - The height to set.
*/
setSize( width, height ) {
this.width = width;
this.height = height;
this.ssaoRenderTarget.setSize( width, height );
this.normalRenderTarget.setSize( width, height );
this.blurRenderTarget.setSize( width, height );
this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height );
this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse );
this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height );
}
// internals
_renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) {
// save original state
renderer.getClearColor( this._originalClearColor );
const originalClearAlpha = renderer.getClearAlpha();
const originalAutoClear = renderer.autoClear;
renderer.setRenderTarget( renderTarget );
// setup pass state
renderer.autoClear = false;
if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) {
renderer.setClearColor( clearColor );
renderer.setClearAlpha( clearAlpha || 0.0 );
renderer.clear();
}
this._fsQuad.material = passMaterial;
this._fsQuad.render( renderer );
// restore original state
renderer.autoClear = originalAutoClear;
renderer.setClearColor( this._originalClearColor );
renderer.setClearAlpha( originalClearAlpha );
}
_renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) {
renderer.getClearColor( this._originalClearColor );
const originalClearAlpha = renderer.getClearAlpha();
const originalAutoClear = renderer.autoClear;
renderer.setRenderTarget( renderTarget );
renderer.autoClear = false;
clearColor = overrideMaterial.clearColor || clearColor;
clearAlpha = overrideMaterial.clearAlpha || clearAlpha;
if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) {
renderer.setClearColor( clearColor );
renderer.setClearAlpha( clearAlpha || 0.0 );
renderer.clear();
}
this.scene.overrideMaterial = overrideMaterial;
renderer.render( this.scene, this.camera );
this.scene.overrideMaterial = null;
// restore original state
renderer.autoClear = originalAutoClear;
renderer.setClearColor( this._originalClearColor );
renderer.setClearAlpha( originalClearAlpha );
}
_generateSampleKernel( kernelSize ) {
const kernel = this.kernel;
for ( let i = 0; i < kernelSize; i ++ ) {
const sample = new Vector3();
sample.x = ( Math.random() * 2 ) - 1;
sample.y = ( Math.random() * 2 ) - 1;
sample.z = Math.random();
sample.normalize();
let scale = i / kernelSize;
scale = MathUtils.lerp( 0.1, 1, scale * scale );
sample.multiplyScalar( scale );
kernel.push( sample );
}
}
_generateRandomKernelRotations() {
const width = 4, height = 4;
const simplex = new SimplexNoise();
const size = width * height;
const data = new Float32Array( size );
for ( let i = 0; i < size; i ++ ) {
const x = ( Math.random() * 2 ) - 1;
const y = ( Math.random() * 2 ) - 1;
const z = 0;
data[ i ] = simplex.noise3d( x, y, z );
}
this.noiseTexture = new DataTexture( data, width, height, RedFormat, FloatType );
this.noiseTexture.wrapS = RepeatWrapping;
this.noiseTexture.wrapT = RepeatWrapping;
this.noiseTexture.needsUpdate = true;
}
_overrideVisibility() {
const scene = this.scene;
const cache = this._visibilityCache;
scene.traverse( function ( object ) {
if ( ( object.isPoints || object.isLine || object.isLine2 ) && object.visible ) {
object.visible = false;
cache.push( object );
}
} );
}
_restoreVisibility() {
const cache = this._visibilityCache;
for ( let i = 0; i < cache.length; i ++ ) {
cache[ i ].visible = true;
}
cache.length = 0;
}
}
SSAOPass.OUTPUT = {
'Default': 0,
'SSAO': 1,
'Blur': 2,
'Depth': 3,
'Normal': 4
};
export { SSAOPass };
@@ -0,0 +1,135 @@
import {
ShaderMaterial,
UniformsUtils
} from 'three';
import { Pass, FullScreenQuad } from './Pass.js';
/**
* This pass can be used to create a post processing effect
* with a raw GLSL shader object. Useful for implementing custom
* effects.
*
* ```js
* const fxaaPass = new ShaderPass( FXAAShader );
* composer.addPass( fxaaPass );
* ```
*
* @augments Pass
* @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
*/
class ShaderPass extends Pass {
/**
* Constructs a new shader pass.
*
* @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as
* defines and uniforms. It's also valid to pass a custom shader material.
* @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample
* the read buffer.
*/
constructor( shader, textureID = 'tDiffuse' ) {
super();
/**
* The name of the texture uniform that should sample the read buffer.
*
* @type {string}
* @default 'tDiffuse'
*/
this.textureID = textureID;
/**
* The pass uniforms.
*
* @type {?Object}
*/
this.uniforms = null;
/**
* The pass material.
*
* @type {?ShaderMaterial}
*/
this.material = null;
if ( shader instanceof ShaderMaterial ) {
this.uniforms = shader.uniforms;
this.material = shader;
} else if ( shader ) {
this.uniforms = UniformsUtils.clone( shader.uniforms );
this.material = new ShaderMaterial( {
name: ( shader.name !== undefined ) ? shader.name : 'unspecified',
defines: Object.assign( {}, shader.defines ),
uniforms: this.uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
}
// internals
this._fsQuad = new FullScreenQuad( this.material );
}
/**
* Performs the shader pass.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
if ( this.uniforms[ this.textureID ] ) {
this.uniforms[ this.textureID ].value = readBuffer.texture;
}
this._fsQuad.material = this.material;
if ( this.renderToScreen ) {
renderer.setRenderTarget( null );
this._fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
this._fsQuad.render( renderer );
}
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*/
dispose() {
this.material.dispose();
this._fsQuad.dispose();
}
}
export { ShaderPass };
@@ -0,0 +1,52 @@
/**
* @module CopyShader
* @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js';
*/
/**
* Full-screen copy shader pass.
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const CopyShader = {
name: 'CopyShader',
uniforms: {
'tDiffuse': { value: null },
'opacity': { value: 1.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
gl_FragColor = opacity * texel;
}`
};
export { CopyShader };
@@ -0,0 +1,298 @@
import {
Vector2
} from 'three';
/**
* @module FXAAShader
* @three_import import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
*/
/**
* FXAA algorithm from NVIDIA, C# implementation by Jasper Flick, GLSL port by Dave Hoskins.
*
* References:
* - {@link http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf}.
* - {@link https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/}.
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const FXAAShader = {
name: 'FXAAShader',
uniforms: {
'tDiffuse': { value: null },
'resolution': { value: new Vector2( 1 / 1024, 1 / 512 ) }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform vec2 resolution;
varying vec2 vUv;
#define EDGE_STEP_COUNT 6
#define EDGE_GUESS 8.0
#define EDGE_STEPS 1.0, 1.5, 2.0, 2.0, 2.0, 4.0
const float edgeSteps[EDGE_STEP_COUNT] = float[EDGE_STEP_COUNT]( EDGE_STEPS );
float _ContrastThreshold = 0.0312;
float _RelativeThreshold = 0.063;
float _SubpixelBlending = 1.0;
vec4 Sample( sampler2D tex2D, vec2 uv ) {
return texture( tex2D, uv );
}
float SampleLuminance( sampler2D tex2D, vec2 uv ) {
return dot( Sample( tex2D, uv ).rgb, vec3( 0.3, 0.59, 0.11 ) );
}
float SampleLuminance( sampler2D tex2D, vec2 texSize, vec2 uv, float uOffset, float vOffset ) {
uv += texSize * vec2(uOffset, vOffset);
return SampleLuminance(tex2D, uv);
}
struct LuminanceData {
float m, n, e, s, w;
float ne, nw, se, sw;
float highest, lowest, contrast;
};
LuminanceData SampleLuminanceNeighborhood( sampler2D tex2D, vec2 texSize, vec2 uv ) {
LuminanceData l;
l.m = SampleLuminance( tex2D, uv );
l.n = SampleLuminance( tex2D, texSize, uv, 0.0, 1.0 );
l.e = SampleLuminance( tex2D, texSize, uv, 1.0, 0.0 );
l.s = SampleLuminance( tex2D, texSize, uv, 0.0, -1.0 );
l.w = SampleLuminance( tex2D, texSize, uv, -1.0, 0.0 );
l.ne = SampleLuminance( tex2D, texSize, uv, 1.0, 1.0 );
l.nw = SampleLuminance( tex2D, texSize, uv, -1.0, 1.0 );
l.se = SampleLuminance( tex2D, texSize, uv, 1.0, -1.0 );
l.sw = SampleLuminance( tex2D, texSize, uv, -1.0, -1.0 );
l.highest = max( max( max( max( l.n, l.e ), l.s ), l.w ), l.m );
l.lowest = min( min( min( min( l.n, l.e ), l.s ), l.w ), l.m );
l.contrast = l.highest - l.lowest;
return l;
}
bool ShouldSkipPixel( LuminanceData l ) {
float threshold = max( _ContrastThreshold, _RelativeThreshold * l.highest );
return l.contrast < threshold;
}
float DeterminePixelBlendFactor( LuminanceData l ) {
float f = 2.0 * ( l.n + l.e + l.s + l.w );
f += l.ne + l.nw + l.se + l.sw;
f *= 1.0 / 12.0;
f = abs( f - l.m );
f = clamp( f / l.contrast, 0.0, 1.0 );
float blendFactor = smoothstep( 0.0, 1.0, f );
return blendFactor * blendFactor * _SubpixelBlending;
}
struct EdgeData {
bool isHorizontal;
float pixelStep;
float oppositeLuminance, gradient;
};
EdgeData DetermineEdge( vec2 texSize, LuminanceData l ) {
EdgeData e;
float horizontal =
abs( l.n + l.s - 2.0 * l.m ) * 2.0 +
abs( l.ne + l.se - 2.0 * l.e ) +
abs( l.nw + l.sw - 2.0 * l.w );
float vertical =
abs( l.e + l.w - 2.0 * l.m ) * 2.0 +
abs( l.ne + l.nw - 2.0 * l.n ) +
abs( l.se + l.sw - 2.0 * l.s );
e.isHorizontal = horizontal >= vertical;
float pLuminance = e.isHorizontal ? l.n : l.e;
float nLuminance = e.isHorizontal ? l.s : l.w;
float pGradient = abs( pLuminance - l.m );
float nGradient = abs( nLuminance - l.m );
e.pixelStep = e.isHorizontal ? texSize.y : texSize.x;
if (pGradient < nGradient) {
e.pixelStep = -e.pixelStep;
e.oppositeLuminance = nLuminance;
e.gradient = nGradient;
} else {
e.oppositeLuminance = pLuminance;
e.gradient = pGradient;
}
return e;
}
float DetermineEdgeBlendFactor( sampler2D tex2D, vec2 texSize, LuminanceData l, EdgeData e, vec2 uv ) {
vec2 uvEdge = uv;
vec2 edgeStep;
if (e.isHorizontal) {
uvEdge.y += e.pixelStep * 0.5;
edgeStep = vec2( texSize.x, 0.0 );
} else {
uvEdge.x += e.pixelStep * 0.5;
edgeStep = vec2( 0.0, texSize.y );
}
float edgeLuminance = ( l.m + e.oppositeLuminance ) * 0.5;
float gradientThreshold = e.gradient * 0.25;
vec2 puv = uvEdge + edgeStep * edgeSteps[0];
float pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance;
bool pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold;
for ( int i = 1; i < EDGE_STEP_COUNT && !pAtEnd; i++ ) {
puv += edgeStep * edgeSteps[i];
pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance;
pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold;
}
if ( !pAtEnd ) {
puv += edgeStep * EDGE_GUESS;
}
vec2 nuv = uvEdge - edgeStep * edgeSteps[0];
float nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance;
bool nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold;
for ( int i = 1; i < EDGE_STEP_COUNT && !nAtEnd; i++ ) {
nuv -= edgeStep * edgeSteps[i];
nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance;
nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold;
}
if ( !nAtEnd ) {
nuv -= edgeStep * EDGE_GUESS;
}
float pDistance, nDistance;
if ( e.isHorizontal ) {
pDistance = puv.x - uv.x;
nDistance = uv.x - nuv.x;
} else {
pDistance = puv.y - uv.y;
nDistance = uv.y - nuv.y;
}
float shortestDistance;
bool deltaSign;
if ( pDistance <= nDistance ) {
shortestDistance = pDistance;
deltaSign = pLuminanceDelta >= 0.0;
} else {
shortestDistance = nDistance;
deltaSign = nLuminanceDelta >= 0.0;
}
if ( deltaSign == ( l.m - edgeLuminance >= 0.0 ) ) {
return 0.0;
}
return 0.5 - shortestDistance / ( pDistance + nDistance );
}
vec4 ApplyFXAA( sampler2D tex2D, vec2 texSize, vec2 uv ) {
LuminanceData luminance = SampleLuminanceNeighborhood( tex2D, texSize, uv );
if ( ShouldSkipPixel( luminance ) ) {
return Sample( tex2D, uv );
}
float pixelBlend = DeterminePixelBlendFactor( luminance );
EdgeData edge = DetermineEdge( texSize, luminance );
float edgeBlend = DetermineEdgeBlendFactor( tex2D, texSize, luminance, edge, uv );
float finalBlend = max( pixelBlend, edgeBlend );
if (edge.isHorizontal) {
uv.y += edge.pixelStep * finalBlend;
} else {
uv.x += edge.pixelStep * finalBlend;
}
return Sample( tex2D, uv );
}
void main() {
gl_FragColor = ApplyFXAA( tDiffuse, resolution.xy, vUv );
}`
};
export { FXAAShader };
@@ -0,0 +1,103 @@
/**
* @module OutputShader
* @three_import import { OutputShader } from 'three/addons/shaders/OutputShader.js';
*/
/**
* Performs tone mapping and color space conversion for
* FX workflows.
*
* Used by {@link OutputPass}.
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const OutputShader = {
name: 'OutputShader',
uniforms: {
'tDiffuse': { value: null },
'toneMappingExposure': { value: 1 }
},
vertexShader: /* glsl */`
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
precision highp float;
uniform sampler2D tDiffuse;
#include <tonemapping_pars_fragment>
#include <colorspace_pars_fragment>
varying vec2 vUv;
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
// tone mapping
#ifdef LINEAR_TONE_MAPPING
gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb );
#elif defined( REINHARD_TONE_MAPPING )
gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb );
#elif defined( CINEON_TONE_MAPPING )
gl_FragColor.rgb = CineonToneMapping( gl_FragColor.rgb );
#elif defined( ACES_FILMIC_TONE_MAPPING )
gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb );
#elif defined( AGX_TONE_MAPPING )
gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb );
#elif defined( NEUTRAL_TONE_MAPPING )
gl_FragColor.rgb = NeutralToneMapping( gl_FragColor.rgb );
#elif defined( CUSTOM_TONE_MAPPING )
gl_FragColor.rgb = CustomToneMapping( gl_FragColor.rgb );
#endif
// color space
#ifdef SRGB_TRANSFER
gl_FragColor = sRGBTransferOETF( gl_FragColor );
#endif
}`
};
export { OutputShader };
@@ -0,0 +1,321 @@
import {
Matrix4,
Vector2
} from 'three';
/**
* @module SSAOShader
* @three_import import { SSAOShader } from 'three/addons/shaders/SSAOShader.js';
*/
/**
* SSAO shader.
*
* References:
* - {@link http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html}
* - {@link https://learnopengl.com/Advanced-Lighting/SSAO}
* - {@link https://github.com/McNopper/OpenGL/blob/master/Example28/shader/ssao.frag.glsl}
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const SSAOShader = {
name: 'SSAOShader',
defines: {
'PERSPECTIVE_CAMERA': 1,
'KERNEL_SIZE': 32
},
uniforms: {
'tNormal': { value: null },
'tDepth': { value: null },
'tNoise': { value: null },
'kernel': { value: null },
'cameraNear': { value: null },
'cameraFar': { value: null },
'resolution': { value: new Vector2() },
'cameraProjectionMatrix': { value: new Matrix4() },
'cameraInverseProjectionMatrix': { value: new Matrix4() },
'kernelRadius': { value: 8 },
'minDistance': { value: 0.005 },
'maxDistance': { value: 0.05 },
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform highp sampler2D tNormal;
uniform highp sampler2D tDepth;
uniform sampler2D tNoise;
uniform vec3 kernel[ KERNEL_SIZE ];
uniform vec2 resolution;
uniform float cameraNear;
uniform float cameraFar;
uniform mat4 cameraProjectionMatrix;
uniform mat4 cameraInverseProjectionMatrix;
uniform float kernelRadius;
uniform float minDistance; // avoid artifacts caused by neighbour fragments with minimal depth difference
uniform float maxDistance; // avoid the influence of fragments which are too far away
varying vec2 vUv;
#include <packing>
float getDepth( const in vec2 screenPosition ) {
return texture2D( tDepth, screenPosition ).x;
}
float getLinearDepth( const in vec2 screenPosition ) {
#if PERSPECTIVE_CAMERA == 1
float fragCoordZ = texture2D( tDepth, screenPosition ).x;
float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
#else
return texture2D( tDepth, screenPosition ).x;
#endif
}
float getViewZ( const in float depth ) {
#if PERSPECTIVE_CAMERA == 1
return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
#else
return orthographicDepthToViewZ( depth, cameraNear, cameraFar );
#endif
}
vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) {
float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3];
vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 );
clipPosition *= clipW; // unprojection.
return ( cameraInverseProjectionMatrix * clipPosition ).xyz;
}
vec3 getViewNormal( const in vec2 screenPosition ) {
return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz );
}
void main() {
float depth = getDepth( vUv );
if ( depth == 1.0 ) {
gl_FragColor = vec4( 1.0 ); // don't influence background
} else {
float viewZ = getViewZ( depth );
vec3 viewPosition = getViewPosition( vUv, depth, viewZ );
vec3 viewNormal = getViewNormal( vUv );
vec2 noiseScale = vec2( resolution.x / 4.0, resolution.y / 4.0 );
vec3 random = vec3( texture2D( tNoise, vUv * noiseScale ).r );
// compute matrix used to reorient a kernel vector
vec3 tangent = normalize( random - viewNormal * dot( random, viewNormal ) );
vec3 bitangent = cross( viewNormal, tangent );
mat3 kernelMatrix = mat3( tangent, bitangent, viewNormal );
float occlusion = 0.0;
for ( int i = 0; i < KERNEL_SIZE; i ++ ) {
vec3 sampleVector = kernelMatrix * kernel[ i ]; // reorient sample vector in view space
vec3 samplePoint = viewPosition + ( sampleVector * kernelRadius ); // calculate sample point
vec4 samplePointNDC = cameraProjectionMatrix * vec4( samplePoint, 1.0 ); // project point and calculate NDC
samplePointNDC /= samplePointNDC.w;
vec2 samplePointUv = samplePointNDC.xy * 0.5 + 0.5; // compute uv coordinates
float realDepth = getLinearDepth( samplePointUv ); // get linear depth from depth texture
float sampleDepth = viewZToOrthographicDepth( samplePoint.z, cameraNear, cameraFar ); // compute linear depth of the sample view Z value
float delta = sampleDepth - realDepth;
if ( delta > minDistance && delta < maxDistance ) { // if fragment is before sample point, increase occlusion
occlusion += 1.0;
}
}
occlusion = clamp( occlusion / float( KERNEL_SIZE ), 0.0, 1.0 );
gl_FragColor = vec4( vec3( 1.0 - occlusion ), 1.0 );
}
}`
};
/**
* SSAO depth shader.
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const SSAODepthShader = {
name: 'SSAODepthShader',
defines: {
'PERSPECTIVE_CAMERA': 1
},
uniforms: {
'tDepth': { value: null },
'cameraNear': { value: null },
'cameraFar': { value: null },
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`uniform sampler2D tDepth;
uniform float cameraNear;
uniform float cameraFar;
varying vec2 vUv;
#include <packing>
float getLinearDepth( const in vec2 screenPosition ) {
#if PERSPECTIVE_CAMERA == 1
float fragCoordZ = texture2D( tDepth, screenPosition ).x;
float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
#else
return texture2D( tDepth, screenPosition ).x;
#endif
}
void main() {
float depth = getLinearDepth( vUv );
gl_FragColor = vec4( vec3( 1.0 - depth ), 1.0 );
}`
};
/**
* SSAO blur shader.
*
* @constant
* @type {Object}
*/
const SSAOBlurShader = {
name: 'SSAOBlurShader',
uniforms: {
'tDiffuse': { value: null },
'resolution': { value: new Vector2() }
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`uniform sampler2D tDiffuse;
uniform vec2 resolution;
varying vec2 vUv;
void main() {
vec2 texelSize = ( 1.0 / resolution );
float result = 0.0;
for ( int i = - 2; i <= 2; i ++ ) {
for ( int j = - 2; j <= 2; j ++ ) {
vec2 offset = ( vec2( float( i ), float( j ) ) ) * texelSize;
result += texture2D( tDiffuse, vUv + offset ).r;
}
}
gl_FragColor = vec4( vec3( result / ( 5.0 * 5.0 ) ), 1.0 );
}`
};
export { SSAOShader, SSAODepthShader, SSAOBlurShader };
File diff suppressed because it is too large Load Diff