feature: ifs piston

This commit is contained in:
FMS-Cat
2021-03-28 02:18:22 +09:00
parent 99947c190c
commit b5664da350
13 changed files with 454 additions and 21 deletions

View File

@@ -1,3 +1,4 @@
import { gravity } from './gravity';
import { hermitePatch } from './hermitePatch';
import { repeat } from './repeat';
import { sine } from './sine';
@@ -5,9 +6,10 @@ import { transpose } from './transpose';
// quotes! prevent fx names from being mangled
const fxDefinitions = {
'sine': sine,
'repeat': repeat,
'gravity': gravity,
'hermitePatch': hermitePatch,
'repeat': repeat,
'sine': sine,
'transpose': transpose,
};

View File

@@ -0,0 +1,39 @@
import type { FxDefinition } from '@fms-cat/automaton';
export const gravity: FxDefinition = {
func( context ) {
const dt = context.deltaTime;
const v = context.value;
if ( context.init ) {
context.state.pos = v;
if ( context.params.preserve ) {
const dv = v - context.getValue( context.time - dt );
context.state.vel = dv / dt;
} else {
context.state.vel = 0.0;
}
}
const a = Math.sign( v - context.state.pos ) * context.params.a;
context.state.vel += a * dt;
context.state.pos += context.state.vel * dt;
if ( Math.sign( a ) !== Math.sign( v - context.state.pos ) ) {
context.state.vel *= -context.params.e;
context.state.pos = v + context.params.e * ( v - context.state.pos );
}
return context.state.pos;
}
};
if ( process.env.DEV ) {
gravity.name = 'Gravity';
gravity.description = 'Accelerate and bounce the curve.';
gravity.params = {
a: { name: 'Acceleration', type: 'float', default: 9.8 },
e: { name: 'Restitution', type: 'float', default: 0.5, min: 0.0 },
preserve: { name: 'Preserve Velocity', type: 'boolean', default: false }
};
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@ import { BufferRenderTarget, BufferRenderTargetOptions } from '../heck/BufferRen
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { Lambda } from '../heck/components/Lambda';
import { Material, MaterialMap, MaterialTag } from '../heck/Material';
import { Material, MaterialMap } from '../heck/Material';
import { Mesh } from '../heck/components/Mesh';
import { Quad } from '../heck/components/Quad';
import { Swap } from '@fms-cat/experimental';
@@ -12,7 +12,7 @@ import { objectValuesMap } from '../utils/objectEntriesMap';
export interface GPUParticlesOptions {
materialCompute: Material;
geometryRender: Geometry;
materialsRender: Partial<MaterialMap<MaterialTag>>;
materialsRender: MaterialMap;
computeWidth: number;
computeHeight: number;
computeNumBuffers: number;

73
src/entities/IFSPiston.ts Normal file
View File

@@ -0,0 +1,73 @@
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { Lambda } from '../heck/components/Lambda';
import { MaterialMap } from '../heck/Material';
import { Mesh, MeshCull } from '../heck/components/Mesh';
import { Quaternion, Vector3 } from '@fms-cat/experimental';
import { auto } from '../globals/automaton';
import { objectValuesMap } from '../utils/objectEntriesMap';
interface IFSPistonOptions {
group: number;
geometry: Geometry;
materials: MaterialMap;
}
export class IFSPiston extends Entity {
public constructor( { group, geometry, materials }: IFSPistonOptions ) {
super();
const entityCube = new Entity();
entityCube.transform.position = new Vector3( [ 0.0, 10.0, 0.0 ] );
this.children.push( entityCube );
// -- animation --------------------------------------------------------------------------------
const up = new Vector3( [ 0, 1, 0 ] );
auto( `IFSPistons/group${ group }/rot`, ( { value } ) => {
entityCube.transform.rotation = Quaternion.fromAxisAngle( up, 4.0 * Math.PI * value );
} );
// -- updater ----------------------------------------------------------------------------------
entityCube.components.push( new Lambda( {
onDraw: ( event ) => {
objectValuesMap( materials, ( material ) => {
if ( material == null ) { return; }
material.addUniform(
'ifsSeed',
'1f',
auto( `IFSPistons/group${ group }/rot` ) + 60.0 * group,
);
material.addUniform(
'cameraNearFar',
'2f',
event.camera.near,
event.camera.far
);
material.addUniformMatrixVector(
'inversePVM',
'Matrix4fv',
event.projectionMatrix
.multiply( event.viewMatrix )
.multiply( event.globalTransform.matrix )
.inverse!
.elements
);
} );
},
name: process.env.DEV && 'IFSPiston/updater',
} ) );
// -- mesh -------------------------------------------------------------------------------------
const mesh = new Mesh( {
geometry,
materials,
name: process.env.DEV && 'IFSPiston/mesh',
} );
mesh.cull = MeshCull.None;
entityCube.components.push( mesh );
}
}

View File

@@ -0,0 +1,87 @@
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { IFSPiston } from './IFSPiston';
import { Material } from '../heck/Material';
import { Vector3 } from '@fms-cat/experimental';
import { auto } from '../globals/automaton';
import { dummyRenderTarget, dummyRenderTargetFourDrawBuffers } from '../globals/dummyRenderTarget';
import { genCube } from '../geometries/genCube';
import { objectValuesMap } from '../utils/objectEntriesMap';
import { quatFromUnitVectors } from '../utils/quatFromUnitVectors';
import ifsPistonFrag from '../shaders/ifs-piston.frag';
import raymarchObjectVert from '../shaders/raymarch-object.vert';
export class IFSPistons extends Entity {
public constructor() {
super();
// -- geometry ---------------------------------------------------------------------------------
const cube = genCube( { dimension: [ 1.1, 10.1, 1.1 ] } );
const geometry = new Geometry();
geometry.vao.bindVertexbuffer( cube.position, 0, 3 );
geometry.vao.bindIndexbuffer( cube.index );
geometry.count = cube.count;
geometry.mode = cube.mode;
geometry.indexType = cube.indexType;
// -- materials --------------------------------------------------------------------------------
const deferred = new Material(
raymarchObjectVert,
ifsPistonFrag,
{
defines: [ 'DEFERRED 1' ],
initOptions: { geometry, target: dummyRenderTargetFourDrawBuffers },
},
);
const depth = new Material(
raymarchObjectVert,
ifsPistonFrag,
{
defines: [ 'SHADOW 1' ],
initOptions: { geometry, target: dummyRenderTarget }
},
);
const materials = { deferred, depth };
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/ifs-piston.frag', () => {
deferred.replaceShader( raymarchObjectVert, ifsPistonFrag );
depth.replaceShader( raymarchObjectVert, ifsPistonFrag );
} );
}
}
objectValuesMap( materials, ( material ) => {
material.addUniform( 'range', '4f', -1.0, -1.0, 1.0, 1.0 );
} );
// -- children pistons -------------------------------------------------------------------------
const up = new Vector3( [ 0, 1, 0 ] );
( [
[ new Vector3( [ 1, 1, 0 ] ).normalized, 0 ],
[ new Vector3( [ -1, -1, 0 ] ).normalized, 0 ],
[ new Vector3( [ -1, 1, 0 ] ).normalized, 1 ],
[ new Vector3( [ 1, -1, 0 ] ).normalized, 1 ],
] as [ Vector3, number ][] ).map( ( [ v, group ] ) => {
const piston = new IFSPiston( { group, geometry, materials } );
piston.transform.position = v.scale( 1.5 );
piston.transform.rotation = quatFromUnitVectors( up, v );
auto( `IFSPistons/group${ group }/pos`, ( { value } ) => {
piston.transform.position = v.scale( value );
} );
this.children.push( piston );
return piston;
} );
}
}

View File

@@ -10,7 +10,11 @@ interface ResultGenCube {
indexType: GLenum;
}
export function genCube(): ResultGenCube {
export function genCube( options?: {
dimension?: [ number, number, number ]
} ): ResultGenCube {
const dimension = options?.dimension ?? [ 1, 1, 1 ];
const pos: number[] = [];
const nor: number[] = [];
const ind: number[] = [];
@@ -29,7 +33,7 @@ export function genCube(): ResultGenCube {
];
for ( let i = 0; i < 6; i ++ ) {
const func = ( v: number[] ): number[] => {
const rotate = ( v: number[] ): number[] => {
const vt: number[] = [];
if ( i < 4 ) {
@@ -47,8 +51,16 @@ export function genCube(): ResultGenCube {
return vt;
};
pos.push( ...p.map( func ).flat() );
nor.push( ...n.map( func ).flat() );
const scale = ( v: number[] ): number[] => {
return [
v[ 0 ] * dimension[ 0 ],
v[ 1 ] * dimension[ 1 ],
v[ 2 ] * dimension[ 2 ],
];
};
pos.push( ...p.map( rotate ).map( scale ).flat() );
nor.push( ...n.map( rotate ).flat() );
ind.push( ...[ 0, 1, 3, 0, 3, 2 ].map( ( v ) => v + 4 * i ) );
}

View File

@@ -10,7 +10,7 @@ export type MaterialTag =
| 'forward'
| 'depth';
export type MaterialMap<T extends MaterialTag = MaterialTag> = { [ tag in T ]: Material };
export type MaterialMap = { [ tag in MaterialTag ]?: Material };
export interface MaterialInitOptions {
target: RenderTarget;

View File

@@ -1,6 +1,6 @@
import { Component, ComponentDrawEvent, ComponentOptions } from './Component';
import { Geometry } from '../Geometry';
import { MaterialMap, MaterialTag } from '../Material';
import { MaterialMap } from '../Material';
import { glCat } from '../../globals/canvas';
export enum MeshCull {
@@ -18,12 +18,12 @@ const meshCullMap = {
export interface MeshOptions extends ComponentOptions {
geometry: Geometry;
materials: Partial<MaterialMap<MaterialTag>>;
materials: MaterialMap;
}
export class Mesh extends Component {
public geometry: Geometry;
public materials: Partial<MaterialMap<MaterialTag>>;
public materials: MaterialMap;
public cull: MeshCull = MeshCull.Back;
@@ -64,7 +64,7 @@ export class Mesh extends Component {
program.uniform( 'cameraPos', '3f', ...event.cameraTransform.position.elements );
program.uniform( 'cameraNearFar', '2f', event.camera.near, event.camera.far );
program.uniformMatrixVector( 'normalMatrix', 'Matrix4fv', event.globalTransform.matrix.inverse!.transpose.elements );
program.uniformMatrixVector( 'normalMatrix', 'Matrix4fv', event.globalTransform.matrix.inverse!.elements, true );
program.uniformMatrixVector( 'modelMatrix', 'Matrix4fv', event.globalTransform.matrix.elements );
program.uniformMatrixVector( 'viewMatrix', 'Matrix4fv', event.viewMatrix.elements );
program.uniformMatrixVector( 'projectionMatrix', 'Matrix4fv', event.projectionMatrix.elements );

View File

@@ -6,6 +6,7 @@ import { canvas } from './globals/canvas';
import { dog } from './scene';
import { getCheckboxActive, getDivCanvasContainer } from './globals/dom';
import { music } from './globals/music';
import type { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
// == dom ==========================================================================================
document.body.style.margin = '0';
@@ -54,7 +55,7 @@ if ( process.env.DEV ) {
music.isPlaying = false;
checkboxActive.checked = false;
} else if ( event.key === ' ' ) {
music.isPlaying = !music.isPlaying;
( automaton as AutomatonWithGUI ).togglePlay();
} else if ( event.key === 'ArrowLeft' ) {
music.time -= 480.0 / MUSIC_BPM;
automaton.reset();

196
src/shaders/ifs-piston.frag Normal file
View File

@@ -0,0 +1,196 @@
#version 300 es
precision highp float;
#define fs(i) (fract(sin((i)*114.514)*1919.810))
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
const int MARCH_ITER = 90;
const int MTL_UNLIT = 1;
const int MTL_PBR = 2;
const int MTL_GRADIENT = 3;
const int MTL_IRIDESCENT = 4;
const float PI = 3.14159265;
const float TAU = PI * 2.0;
#ifdef DEFERRED
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
#endif
in vec4 vPosition;
#ifdef SHADOW
out vec4 fragColor;
#endif
uniform float deformAmp;
uniform float deformFreq;
uniform float deformTime;
uniform float time;
uniform float ifsSeed;
uniform vec2 resolution;
uniform vec2 cameraNearFar;
uniform vec3 cameraPos;
uniform mat4 normalMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 inversePVM;
uniform sampler2D samplerRandom;
uniform sampler2D samplerRandomStatic;
uniform sampler2D samplerCapture;
vec3 divideByW( vec4 v ) {
return v.xyz / v.w;
}
// https://www.iquilezles.org/www/articles/smin/smin.htm
float smin( float a, float b, float k ) {
float h = max( k - abs( a - b ), 0.0 ) / k;
return min( a, b ) - h * h * h * k * ( 1.0 / 6.0 );
}
mat2 rot2d( float t ) {
float c = cos( t );
float s = sin( t );
return mat2( c, -s, s, c );
}
#pragma glslify: orthBasis = require( ./modules/orthBasis );
#pragma glslify: cyclicNoise = require( ./modules/cyclicNoise );
vec3 ifs( vec3 p, vec3 r, vec3 t ) {
vec3 s = t;
mat3 bas = orthBasis( r );
for ( int i = 0; i < 6; i ++ ) {
p = abs( p ) - abs( s ) * pow( 1.7, -float( i ) );
s = bas * s;
p.xy = p.x < p.y ? p.yx : p.xy;
p.yz = p.y < p.z ? p.zy : p.yz;
}
return p;
}
float box( vec3 p, vec3 s ) {
vec3 d = abs( p ) - s;
return min( 0.0, max( d.x, max( d.y, d.z ) ) ) + length( max( vec3( 0.0 ), d ) );
}
vec4 map( vec3 p ) {
p.y += 10.0;
float d1, d2;
{
float clampbox = box( p - vec3( 0.0, 10.0, 0.0 ), vec3( 1.0, 10.0, 1.0 ) - 0.1 );
vec3 r = mix(
fs( vec3( 4.7, 2.2, 8.3 ) + floor( ifsSeed ) ),
fs( vec3( 4.7, 2.2, 8.3 ) + floor( ifsSeed + 1.0 ) ),
fract( ifsSeed )
);
vec3 t = 0.1 * vec3( 3.0, 2.3, 3.5 );
vec3 pt = ifs( p, r, t );
pt = mod( pt - 0.1, 0.2 ) - 0.1;
d1 = max( box( pt, vec3( 0.02 ) ), clampbox );
}
{
float clampbox = box( p - vec3( 0.0, 10.0, 0.0 ), vec3( 1.0, 10.0, 1.0 ) );
vec3 r = mix(
fs( vec3( 5.3, 1.1, 2.9 ) + floor( ifsSeed ) ),
fs( vec3( 5.3, 1.1, 2.9 ) + floor( ifsSeed + 1.0 ) ),
fract( ifsSeed )
);
vec3 t = 0.2 * vec3( 3.0, 2.3, 3.5 );
vec3 pt = ifs( p, r, t );
pt = mod( pt - 0.1, 0.2 ) - 0.1;
d2 = max( box( pt, vec3( 0.07 ) ), clampbox );
}
return d1 < d2 ? vec4( d1, 1, 0, 0 ) : vec4( d2, 2, 0, 0 );
}
vec3 normalFunc( vec3 p, float dd ) {
vec2 d = vec2( 0.0, dd );
return normalize( vec3(
map( p + d.yxx ).x - map( p - d.yxx ).x,
map( p + d.xyx ).x - map( p - d.xyx ).x,
map( p + d.xxy ).x - map( p - d.xxy ).x
) );
}
void main() {
vec2 p = ( gl_FragCoord.xy * 2.0 - resolution ) / resolution.y;
vec3 rayOri = divideByW( inversePVM * vec4( p, 0.0, 1.0 ) );
vec3 farPos = divideByW( inversePVM * vec4( p, 1.0, 1.0 ) );
vec3 rayDir = normalize( farPos - rayOri );
float rayLen = length( vPosition.xyz - cameraPos );
vec3 rayPos = rayOri + rayDir * rayLen;
vec4 isect;
for ( int i = 0; i < MARCH_ITER; i ++ ) {
isect = map( rayPos );
rayLen += 0.5 * isect.x;
rayPos = rayOri + rayDir * rayLen;
if ( abs( isect.x ) < 1E-3 ) { break; }
if ( rayLen > cameraNearFar.y ) { break; }
}
if ( 0.01 < isect.x ) {
discard;
}
vec3 modelNormal = ( normalMatrix * vec4( normalFunc( rayPos, 1E-3 ), 1.0 ) ).xyz;
vec4 modelPos = modelMatrix * vec4( rayPos, 1.0 );
vec4 projPos = projectionMatrix * viewMatrix * modelPos; // terrible
float depth = projPos.z / projPos.w;
gl_FragDepth = 0.5 + 0.5 * depth;
#ifdef DEFERRED
fragPosition = vec4( modelPos.xyz, depth );
fragNormal = vec4( modelNormal, 1.0 );
if ( isect.y == 2.0 ) {
vec3 noise = cyclicNoise( 6.0 * rayPos );
vec3 noiseDetail = cyclicNoise( vec3( 38.0, 1.0, 1.0 ) * ( orthBasis( vec3( 1 ) ) * rayPos ) );
float roughness = (
0.6 +
0.1 * noise.x +
0.2 * smoothstep( -0.2, 0.4, noise.y ) * ( 0.8 + 0.2 * sin( 17.0 * noiseDetail.x ) )
);
fragColor = vec4( vec3( 0.4 ), 1.0 );
fragWTF = vec4( vec3( roughness, 0.9, 0.0 ), MTL_PBR );
} else if ( isect.y == 1.0 ) {
fragColor = vec4( vec3( 1.0 ), 1.0 );
fragWTF = vec4( vec3( 0.3, 0.1, 0.0 ), MTL_PBR );
}
#endif
#ifdef SHADOW
float shadowDepth = linearstep(
cameraNearFar.x,
cameraNearFar.y,
length( cameraPos - rayPos )
);
fragColor = vec4( shadowDepth, shadowDepth * shadowDepth, shadowDepth, 1.0 );
#endif
}

View File

@@ -34,6 +34,8 @@ uniform float time;
uniform vec2 resolution;
uniform vec2 cameraNearFar;
uniform vec3 cameraPos;
uniform mat4 normalMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 inversePVM;
@@ -130,17 +132,17 @@ void main() {
discard;
}
vec3 normal = normalFunc( rayPos, 1E-2 );
vec4 color = vec4( 0.4, 0.7, 0.9, 1.0 );
vec3 modelNormal = ( normalMatrix * vec4( normalFunc( rayPos, 1E-2 ), 1.0 ) ).xyz;
vec4 projPos = projectionMatrix * viewMatrix * vec4( rayPos, 1.0 ); // terrible
vec4 modelPos = modelMatrix * vec4( rayPos, 1.0 );
vec4 projPos = projectionMatrix * viewMatrix * modelPos; // terrible
float depth = projPos.z / projPos.w;
gl_FragDepth = 0.5 + 0.5 * depth;
#ifdef DEFERRED
fragPosition = vec4( rayPos, depth );
fragNormal = vec4( normal, 1.0 );
fragColor = color;
fragPosition = vec4( modelPos.xyz, depth );
fragNormal = vec4( modelNormal, 1.0 );
fragColor = vec4( 0.4, 0.7, 0.9, 1.0 );
fragWTF = vec4( vec3( 0.9, 0.2, 0.0 ), MTL_PBR );
#endif

View File

@@ -0,0 +1,21 @@
import { Quaternion, Vector3 } from '@fms-cat/experimental';
// https://github.com/mrdoob/three.js/blob/94f043c4e105eb73236529231388402da2b07cba/src/math/Quaternion.js#L362
export function quatFromUnitVectors( a: Vector3, b: Vector3 ): Quaternion {
const r = a.dot( b ) + 1.0;
if ( r < 1E-4 ) {
if ( Math.abs( a.x ) > Math.abs( a.z ) ) {
return new Quaternion( [ -a.y, a.x, 0.0, r ] ).normalized;
} else {
return new Quaternion( [ 0.0, -a.z, a.y, r ] ).normalized;
}
} else {
return new Quaternion( [
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
r,
] ).normalized;
}
}