feature: IBL??

This commit is contained in:
FMS-Cat
2021-03-15 00:34:31 +09:00
parent 8e85e93163
commit 0630b728f1
12 changed files with 457 additions and 32 deletions

View File

@@ -17,6 +17,8 @@ export interface CameraEntityOptions {
root: Entity;
target: RenderTarget;
lights: LightEntity[];
textureIBLLUT: GLCatTexture<WebGL2RenderingContext>;
textureEnv: GLCatTexture<WebGL2RenderingContext>;
textureRandom: GLCatTexture<WebGL2RenderingContext>;
}
@@ -185,6 +187,8 @@ export class CameraEntity {
shadingMaterial.blend = [ gl.ONE, gl.ONE ];
shadingMaterial.addUniformTexture( 'samplerAo', aoTarget.texture );
shadingMaterial.addUniformTexture( 'samplerShadow', light.shadowMap.texture );
shadingMaterial.addUniformTexture( 'samplerIBLLUT', options.textureIBLLUT );
shadingMaterial.addUniformTexture( 'samplerEnv', options.textureEnv );
shadingMaterial.addUniformTexture( 'samplerRandom', options.textureRandom );
const shadingQuad = new Quad( {

View File

@@ -0,0 +1,81 @@
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import environmentMapFrag from '../shaders/environment-map.frag';
import quadVert from '../shaders/quad.vert';
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { Swap, Xorshift } from '@fms-cat/experimental';
import { Lambda } from '../heck/components/Lambda';
const WIDTH = 1024;
const HEIGHT = 512;
export class EnvironmentMap {
public entity: Entity;
public swap: Swap<BufferRenderTarget>;
public get texture(): GLCatTexture<WebGL2RenderingContext> {
return this.swap.o.texture;
}
public constructor() {
this.entity = new Entity();
this.entity.visible = false;
const rng = new Xorshift( 114514 );
// -- swap -------------------------------------------------------------------------------------
this.swap = new Swap(
new BufferRenderTarget( {
width: WIDTH,
height: HEIGHT,
name: process.env.DEV && 'EnvironmentMap/swap0',
} ),
new BufferRenderTarget( {
width: WIDTH,
height: HEIGHT,
name: process.env.DEV && 'EnvironmentMap/swap1',
} ),
);
// -- post -------------------------------------------------------------------------------------
const material = new Material(
quadVert,
environmentMapFrag,
);
material.addUniform( 'uniformSeed', '4f', rng.gen(), rng.gen(), rng.gen(), rng.gen() );
material.addUniformTexture( 'sampler0', this.swap.i.texture );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/environment-map.frag', () => {
material.replaceShader( quadVert, environmentMapFrag );
} );
}
}
const quad = new Quad( {
target: this.swap.o,
material,
name: process.env.DEV && 'EnvironmentMap/quad',
} );
// -- swapper ----------------------------------------------------------------------------------
this.entity.components.push( new Lambda( {
onUpdate: () => {
this.swap.swap();
material.addUniform( 'uniformSeed', '4f', rng.gen(), rng.gen(), rng.gen(), rng.gen() );
material.addUniformTexture( 'sampler0', this.swap.i.texture );
quad.target = this.swap.o;
},
visible: false,
name: process.env.DEV && 'EnvironmentMap/swapper',
} ) );
this.entity.components.push( quad );
}
}

80
src/entities/IBLLUT.ts Normal file
View File

@@ -0,0 +1,80 @@
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import iblLutFrag from '../shaders/ibl-lut.frag';
import quadVert from '../shaders/quad.vert';
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { Swap } from '@fms-cat/experimental';
import { Lambda } from '../heck/components/Lambda';
import { vdc } from '../utils/vdc';
const IBL_SIZE = 256;
export class IBLLUT {
public entity: Entity;
public swap: Swap<BufferRenderTarget>;
public get texture(): GLCatTexture<WebGL2RenderingContext> {
return this.swap.o.texture;
}
public constructor() {
this.entity = new Entity();
this.entity.visible = false;
// -- swap -------------------------------------------------------------------------------------
this.swap = new Swap(
new BufferRenderTarget( {
width: IBL_SIZE,
height: IBL_SIZE,
name: process.env.DEV && 'IBLLUT/swap0',
} ),
new BufferRenderTarget( {
width: IBL_SIZE,
height: IBL_SIZE,
name: process.env.DEV && 'IBLLUT/swap1',
} ),
);
// -- post -------------------------------------------------------------------------------------
let samples = 0.0;
const material = new Material(
quadVert,
iblLutFrag,
);
material.addUniform( 'samples', '1f', samples );
material.addUniform( 'vdc', '1f', vdc( samples, 2.0 ) );
material.addUniformTexture( 'sampler0', this.swap.i.texture );
const quad = new Quad( {
target: this.swap.o,
material,
name: process.env.DEV && 'IBLLUT/quad',
} );
// -- swapper ----------------------------------------------------------------------------------
this.entity.components.push( new Lambda( {
onUpdate: () => {
samples ++;
this.swap.swap();
if ( samples > 1024 ) {
this.entity.active = false;
} else {
material.addUniform( 'samples', '1f', samples );
material.addUniform( 'vdc', '1f', vdc( samples, 2.0 ) );
material.addUniformTexture( 'sampler0', this.swap.i.texture );
quad.target = this.swap.o;
}
},
visible: false,
name: process.env.DEV && 'IBLLUT/swapper',
} ) );
this.entity.components.push( quad );
}
}

View File

@@ -24,6 +24,8 @@ import { RTInspector } from './entities/RTInspector';
import { Component } from './heck/components/Component';
import { FlickyParticles } from './entities/FlickyParticles';
import { PixelSorter } from './entities/PixelSorter';
import { IBLLUT } from './entities/IBLLUT';
import { EnvironmentMap } from './entities/EnvironmentMap';
// == music ========================================================================================
const audio = new AudioContext();
@@ -193,6 +195,13 @@ dog.root.components.push( new Lambda( {
name: process.env.DEV && 'main/update',
} ) );
// -- bake -----------------------------------------------------------------------------------------
const ibllut = new IBLLUT();
dog.root.children.push( ibllut.entity );
const environmentMap = new EnvironmentMap();
dog.root.children.push( environmentMap.entity );
// -- "objects" ------------------------------------------------------------------------------------
const sphereParticles = new SphereParticles( {
particlesSqrt: 256,
@@ -201,13 +210,13 @@ const sphereParticles = new SphereParticles( {
} );
dog.root.children.push( sphereParticles.entity );
const trails = new Trails( {
trails: 4096,
trailLength: 64,
textureRandom: randomTexture.texture,
textureRandomStatic: randomTextureStatic.texture
} );
dog.root.children.push( trails.entity );
// const trails = new Trails( {
// trails: 4096,
// trailLength: 64,
// textureRandom: randomTexture.texture,
// textureRandomStatic: randomTextureStatic.texture
// } );
// dog.root.children.push( trails.entity );
const rings = new Rings();
dog.root.children.push( rings.entity );
@@ -219,11 +228,11 @@ const flickyParticles = new FlickyParticles( {
} );
dog.root.children.push( flickyParticles.entity );
const raymarcher = new Raymarcher( {
textureRandom: randomTexture.texture,
textureRandomStatic: randomTextureStatic.texture
} );
dog.root.children.push( raymarcher.entity );
// const raymarcher = new Raymarcher( {
// textureRandom: randomTexture.texture,
// textureRandomStatic: randomTextureStatic.texture
// } );
// dog.root.children.push( raymarcher.entity );
// -- things that is not an "object" ---------------------------------------------------------------
const swapOptions = {
@@ -249,7 +258,7 @@ const light = new LightEntity( {
shadowMapFar: 20.0,
namePrefix: process.env.DEV && 'light1',
} );
light.color = [ 60.0, 60.0, 60.0 ];
light.color = [ 40.0, 40.0, 40.0 ];
light.entity.transform.lookAt( new Vector3( [ -1.0, 2.0, 8.0 ] ) );
dog.root.children.push( light.entity );
@@ -271,6 +280,8 @@ const camera = new CameraEntity( {
light,
// light2
],
textureIBLLUT: ibllut.texture,
textureEnv: environmentMap.texture,
textureRandom: randomTexture.texture
} );
camera.camera.clear = [ 0.0, 0.0, 0.0, 0.0 ];

View File

@@ -9,12 +9,16 @@ out vec4 fragColor;
uniform sampler2D samplerDry;
uniform sampler2D samplerWet;
vec4 sampleLOD( vec2 uv, float lv ) {
float p = pow( 0.5, float( lv ) );
vec2 uvt = mix( vec2( 1.0 - p ), vec2( 1.0 - 0.5 * p ), uv );
return texture( samplerWet, uvt );
}
void main() {
fragColor = texture( samplerDry, vUv );
for ( int i = 0; i < 5; i ++ ) {
float fuck = pow( 0.5, float( i ) );
vec2 suv = mix( vec2( 1.0 - fuck ), vec2( 1.0 - 0.5 * fuck ), vUv );
fragColor += texture( samplerWet, suv );
fragColor += sampleLOD( vUv, float( i ) );
}
fragColor.xyz = max( vec3( 0.0 ), fragColor.xyz );
}

View File

@@ -0,0 +1,119 @@
#version 300 es
// https://learnopengl.com/PBR/IBL/Specular-IBL
precision highp float;
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
#pragma glslify: prng = require( ./-prng );
const int SAMPLES = 16;
const float UV_MARGIN = 0.9;
const float PI = 3.14159265;
const float TAU = 6.283185307;
in vec2 vUv;
out vec4 fragColor;
uniform float head;
uniform vec2 resolution;
uniform vec4 uniformSeed;
uniform sampler2D sampler0;
vec4 seed;
float vdc( float i, float base ) {
float r = 0.0;
float denom = 1.0;
for ( int j = 0; j < 32; j ++ ) {
denom *= base;
r += mod( i, base ) / denom;
i = floor( i / base );
if ( i <= 0.0 ) { break; }
}
return r;
}
vec3 ImportanceSampleGGX( vec2 Xi, float roughness, vec3 N ) {
float a = roughness * roughness;
float phi = TAU * Xi.x;
float cosTheta = roughness < 0.0 // negative roughness to usa lambert ???
? asin( sqrt( Xi.y ) )
: sqrt( ( 1.0 - Xi.y ) / ( 1.0 + ( a * a - 1.0 ) * Xi.y ) );
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
// from spherical coordinates to cartesian coordinates
vec3 H = vec3(
cos( phi ) * sinTheta,
sin( phi ) * sinTheta,
cosTheta
);
// from tangent-space vector to world-space sample vector
vec3 up = abs( N.y ) < 0.999 ? vec3( 0.0, 1.0, 0.0 ) : vec3( 1.0, 0.0, 0.0 );
vec3 tangent = normalize( cross( up, N ) );
vec3 bitangent = cross( N, tangent );
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize( sampleVec );
}
vec3 haha( vec3 L ) {
bool circ = dot( L, normalize( vec3( 1.0, 3.0, 3.0 ) ) ) > 0.9;
vec3 c = circ ? 0.01 * vec3( 0.1, 0.1, 1.0 ) : vec3( 0.0 );
bool ring = abs( dot( L, vec3( 0.0, 1.0, 0.0 ) ) ) < 0.1;
c += ring ? 10.0 * vec3( 0.1, 1.0, 0.3 ) : vec3( 0.0 );
return c;
// return 0.5 + 0.5 * L;
}
void main() {
vec2 halfTexel = 1.0 / resolution; // * 0.5;
float lv = ceil( -log( 1.0 - min( vUv.x, vUv.y ) ) / log( 2.0 ) );
float p = pow( 0.5, lv );
vec2 uv00 = floor( vUv / p ) * p;
vec2 uv11 = uv00 + p;
vec2 uv = clamp( vUv, uv00 + halfTexel, uv11 - halfTexel );
uv = linearstep( uv00, uv11, uv );
uv = ( uv - 0.5 ) / UV_MARGIN + 0.5;
vec3 tex = texture( sampler0, vUv ).xyz;
float roughness = lv * 0.2;
float a = TAU * uv.x;
float b = PI * ( uv.y - 0.5 );
vec3 N = vec3( sin( a ) * cos( b ), -sin( b ), -cos( a ) * cos( b ) );
vec3 R = N;
vec3 V = R;
seed = uniformSeed + N.xyzx;
vec4 col = vec4( 0.0 );
for ( int i = 0; i < SAMPLES; i ++ ) {
vec2 Xi = vec2( prng( seed ), prng( seed ) );
vec3 H = ImportanceSampleGGX( Xi, roughness, N );
vec3 L = normalize( 2.0 * dot( V, H ) * H - V );
float NoL = dot( N, L );
if ( NoL > 0.0 ) {
col += vec4( haha( L ), 1.0 ) * NoL;
}
}
col.xyz = col.w <= 0.001 ? vec3( 0.0 ) : ( col.xyz / col.w );
tex.xyz = mix( tex.xyz, col.xyz, 1.0 / 16.0 );
fragColor = vec4( tex, 1.0 );
}

90
src/shaders/ibl-lut.frag Normal file
View File

@@ -0,0 +1,90 @@
#version 300 es
precision highp float;
const float TAU = 6.283185307;
in vec2 vUv;
out vec4 fragColor;
uniform float samples;
uniform float vdc;
uniform sampler2D sampler0;
vec3 ImportanceSampleGGX( vec2 Xi, float roughness, vec3 N ) {
float a = roughness * roughness;
float phi = TAU * Xi.x;
float cosTheta = sqrt( ( 1.0 - Xi.y ) / ( 1.0 + ( a * a - 1.0 ) * Xi.y ) );
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
// from spherical coordinates to cartesian coordinates
vec3 H = vec3(
cos( phi ) * sinTheta,
sin( phi ) * sinTheta,
cosTheta
);
// from tangent-space vector to world-space sample vector
vec3 up = abs( N.y ) < 0.999 ? vec3( 0.0, 1.0, 0.0 ) : vec3( 1.0, 0.0, 0.0 );
vec3 tangent = normalize( cross( up, N ) );
vec3 bitangent = cross( N, tangent );
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize( sampleVec );
}
float GeometrySchlickGGX( float NdotV, float roughness ) {
float a = roughness;
float k = ( a * a ) / 2.0;
float nom = NdotV;
float denom = NdotV * ( 1.0 - k ) + k;
return nom / denom;
}
float GeometrySmith( float roughness, float NoV, float NoL ) {
float ggx2 = GeometrySchlickGGX( NoV, roughness );
float ggx1 = GeometrySchlickGGX( NoL, roughness );
return ggx1 * ggx2;
}
// https://github.com/HectorMF/BRDFGenerator/blob/master/BRDFGenerator/BRDFGenerator.cpp
vec2 IntegrateBRDF( float NdotV, float roughness ) {
vec3 V = vec3( sqrt( 1.0 - NdotV * NdotV ), 0.0, NdotV );
vec3 N = vec3( 0.0, 0.0, 1.0 );
vec2 Xi = vec2( samples / 1024.0, vdc );
vec3 H = ImportanceSampleGGX( Xi, roughness, N );
vec3 L = normalize( 2.0 * dot( V, H ) * H - V );
float NoL = max( L.z, 0.0 );
float NoH = max( H.z, 0.0 );
float VoH = max( dot( V, H ), 0.0 );
float NoV = max( dot( N, V ), 0.0 );
if ( NoL > 0.0 ) {
float G = GeometrySmith( roughness, NoV, NoL );
float G_Vis = ( G * VoH ) / ( NoH * NoV );
float Fc = pow( 1.0 - VoH, 5.0 );
return vec2( ( 1.0 - Fc ) * G_Vis, Fc * G_Vis );
}
return vec2( 0.0 );
}
void main() {
vec2 tex = texture( sampler0, vUv ).xy;
float NdotV = vUv.y;
float roughness = vUv.x;
tex = mix( tex, IntegrateBRDF( NdotV, roughness ), 1.0 / samples );
fragColor = vec4( tex, 0.0, 1.0 );
}

View File

@@ -70,7 +70,7 @@ void main() {
}
vec3 normal = normalFunc( rayPos, 1E-4 );
vec4 color = vec4( 0.1, 0.2, 0.4, 1.0 );
vec4 color = vec4( 0.4, 0.7, 0.9, 1.0 );
vec4 projPos = projectionMatrix * viewMatrix * vec4( rayPos, 1.0 ); // terrible
float depth = projPos.z / projPos.w;
@@ -79,5 +79,5 @@ void main() {
fragPosition = vec4( rayPos, depth );
fragNormal = vec4( normal, 1.0 );
fragColor = color;
fragWTF = vec4( vec3( 2.0, 0.9, 0.9 ), MTL_IRIDESCENT );
fragWTF = vec4( vec3( 0.8, 0.8, 0.0 ), MTL_PBR );
}

View File

@@ -8,6 +8,7 @@ const int MTL_PBR = 2;
const int MTL_GRADIENT = 3;
const int MTL_IRIDESCENT = 4;
const int AO_ITER = 8;
const float ENV_UV_MARGIN = 0.9;
const float AO_BIAS = 0.0;
const float AO_RADIUS = 0.5;
const float PI = 3.14159265359;
@@ -36,7 +37,7 @@ uniform sampler2D sampler1; // normal.xyz (yes, this is not good)
uniform sampler2D sampler2; // color.rgba (what is a though????)
uniform sampler2D sampler3; // materialParams.xyz, materialId
uniform sampler2D samplerShadow;
uniform sampler2D samplerBRDFLUT;
uniform sampler2D samplerIBLLUT;
uniform sampler2D samplerEnv;
uniform sampler2D samplerAo;
uniform sampler2D samplerRandom;
@@ -86,6 +87,21 @@ vec3 blurpleGradient( float t ) {
);
}
vec4 sampleEnvNearest( vec2 uv, float lv ) {
float p = pow( 0.5, float( lv ) );
vec2 uvt = ENV_UV_MARGIN * ( uv - 0.5 ) + 0.5;
uvt = mix( vec2( 1.0 - p ), vec2( 1.0 - 0.5 * p ), uvt );
return texture( samplerEnv, uvt );
}
vec4 sampleEnvLinear( vec2 uv, float lv ) {
return mix(
sampleEnvNearest( uv, floor( lv ) ),
sampleEnvNearest( uv, floor( lv + 1.0 ) ),
fract( lv )
);
}
// == structs ======================================================================================
struct Isect {
vec2 screenUv;
@@ -188,16 +204,18 @@ vec3 shadePBR( Isect isect, AngularInfo aI ) {
vec3 color = shade;
#ifdef IS_FIRST_LIGHT
// vec3 refl = reflect( aI.V, isect.normal );
// vec2 envCoord = vec2(
// 0.5 + atan( refl.z, refl.x ) / TAU,
// 0.5 + atan( refl.y, length( refl.zx ) ) / PI
// );
// vec2 brdf = texture( samplerBRDFLUT, vec2( aI.dotNV, 1.0 - roughness ) ).xy;
vec3 refl = reflect( aI.V, isect.normal );
vec2 envUv = vec2(
0.5 + atan( refl.x, -refl.z ) / TAU,
0.5 + atan( refl.y, length( refl.zx ) ) / PI
);
// vec3 texEnv = 0.2 * pow( texture( samplerEnv, envCoord ).rgb, vec3( 2.2 ) );
// color += PI * texEnv * ( brdf.x * F0 + brdf.y );
// reflective ibl
vec2 brdf = texture( samplerIBLLUT, vec2( aI.dotNV, 1.0 - roughness ) ).xy;
vec3 texEnv = 0.2 * sampleEnvLinear( envUv, 5.0 * roughness ).rgb;
color += PI * ao * texEnv * ( brdf.x * F0 + brdf.y );
// emissive
color += emissive * aI.dotNV * isect.albedo;
#endif // IS_FIRST_LIGHT

View File

@@ -38,5 +38,5 @@ void main() {
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vec4( vColor.xyz, 1.0 );
fragWTF = vec4( vec3( 0.8, 0.8, 0.0 ), MTL_PBR );
fragWTF = vec4( vec3( 0.4, 0.4, 0.0 ), MTL_PBR );
}

View File

@@ -83,15 +83,15 @@ void main() {
size *= sin( PI * saturate( vLife ) );
vec3 shape = position * size;
shape.yz = rotate2D( 7.0 * vPosition.x ) * shape.yz;
shape.zx = rotate2D( 7.0 * vPosition.y ) * shape.zx;
shape.yz = rotate2D( 7.0 * ( vPosition.x + vDice.z ) ) * shape.yz;
shape.zx = rotate2D( 7.0 * ( vPosition.y + vDice.w ) ) * shape.zx;
vPosition.xyz += shape;
// == compute normals ============================================================================
vNormal = ( normalMatrix * vec4( normal, 1.0 ) ).xyz;
vNormal.yz = rotate2D( 7.0 * vPosition.x ) * vNormal.yz;
vNormal.zx = rotate2D( 7.0 * vPosition.y ) * vNormal.zx;
vNormal.yz = rotate2D( 7.0 * ( vPosition.x + vDice.z ) ) * vNormal.yz;
vNormal.zx = rotate2D( 7.0 * ( vPosition.y + vDice.w ) ) * vNormal.zx;
// == send the vertex position ===================================================================
vPosition = modelMatrix * vPosition;

18
src/utils/vdc.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* Generate a number using Van der Corput sequence.
* e.g. vdc(i, 2) = 1/2, 1/4, 3/4, 1/8, 5/8, 3/8, 7/8, 1/16, ...
* @param i Index of the sequence
* @param base Base of the sequence
*/
export function vdc( i: number, base: number ) {
let r = 0;
let denom = 1;
while ( 0 < i ) {
denom *= base;
r += ( i % base ) / denom;
i = Math.floor( i / base );
}
return r;
}