This commit is contained in:
FMS-Cat
2021-03-14 02:26:59 +09:00
commit efac759e05
85 changed files with 10321 additions and 0 deletions

96
.eslintrc.js Normal file
View File

@@ -0,0 +1,96 @@
module.exports = {
"root": true,
"plugins": [
"sort-imports-es6-autofix",
"@typescript-eslint",
],
"env": {
"es6": true,
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2017
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"globals": {
"process": true // good ol' process.env
},
"rules": {
// basics
"@typescript-eslint/indent": [ "error", 2, { // indentation should be 2 spaces
"flatTernaryExpressions": true, // ternary should be performed in flat
"MemberExpression": 1 // member chain should be performed with an indent
} ], // it forces 2 spaces indentation
"linebreak-style": [ "error", "unix" ], // fuck you, CRLF
"quotes": [ "error", "single" ], // quotes must be single
"eqeqeq": [ "error", "smart" ], // fuck you, `==`
"max-len": [ "error", { // don't be too long, code
"code": 100,
"ignoreComments": true, // comments are okay
"ignoreStrings": true, // strings are okay
"ignoreTemplateLiterals": true, // templates are also okay
"ignoreRegExpLiterals": true, // regexs are also okay too
} ],
"sort-imports-es6-autofix/sort-imports-es6": [ "error" ], // imports have to be ordered
"eol-last": [ "error", "always" ], // eof newline is cool
// variables
"@typescript-eslint/no-unused-vars": [ "warn", { "argsIgnorePattern": "^_" } ], // draw yellow line under unused vars
// "no-undef": [ "warn" ], // draws yellow line under undefined vars // it doesn't work on typescript sometimes
"no-var": [ "error" ], // fuck you, var
"prefer-const": [ "error" ], // const is better than let
// omittables
"semi": [ "error", "always" ], // semicolon is required
"curly": [ "error" ], // it kills `if (foo) bar++;`
"arrow-parens": [ "error", "always" ], // it kills `arg => { func(); }`
// force spacing (I prefer super sparse code!)
"array-bracket-spacing": [ "error", "always" ], // it kills `[val1, val2]`
"arrow-spacing": [ "error", { "before": true, "after": true } ], // it kills `( arg )=>{ func(); }`
"block-spacing": [ "error", "always" ], // it kills `if ( cond ) {func();}`
"comma-spacing": [ "error", { "before": false, "after": true } ], // it kills `func( arg1,arg2 )`
"computed-property-spacing": [ "error", "always" ], // it kills `arr[i]`
"key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ], // it kills `{ key:val }`
"keyword-spacing": [ "error", { "before": true, "after": true } ], // it kills `}else{`
"object-curly-spacing": [ "error", "always" ], // it kills `{key: val}`
"semi-spacing": [ "error", { "before": false, "after": true } ], // it kills `func1();func2();`
"space-before-blocks": [ "error", "always" ], // it kills `if (cond){`
"space-in-parens": [ "error", "always" ], // it kills `func (arg)`
"space-infix-ops": [ "error" ], // it kills val1+val2
"space-unary-ops": [ "error", { "words": true, "nonwords": false, "overrides": { "++": true, "--": true } } ], // it kills `val++`
"spaced-comment": [ "error", "always" ], // it kills `//this is comment`
// ban spacing
"func-call-spacing": [ "error", "never" ], // no-trailing-spaces. yea.
"no-trailing-spaces": [ "error" ], // no-trailing-spaces. yea.
"no-whitespace-before-property": [ "error" ], // it kills `obj .key`
"space-before-function-paren": [ "error", { "anonymous": "never", "named": "never", "asyncArrow": "always" } ], // it kills `func ()`
// others
"no-eval": [ "off" ], // we need to go the evil way
"no-implied-eval": [ "warn" ], // ok don't
"no-console": [ "error", { allow: [ "info", "warn", "error" ] } ], // don't forget to remove `console.log` !
// typescript-specifics
"@typescript-eslint/no-explicit-any": [ "off" ], // yea
"@typescript-eslint/explicit-module-boundary-types": [ "off" ], // We are using explicit any on purpose because it's explicit, be permissive
"@typescript-eslint/no-inferrable-types": [ "off" ], // it's ok
"@typescript-eslint/no-non-null-assertion": [ "off" ], // bang is sometimes required
"@typescript-eslint/no-empty-interface": [ "off" ], // we need to perform mixins
"@typescript-eslint/explicit-function-return-type": [ "error", { "allowExpressions": true } ], // return type is required
"@typescript-eslint/explicit-member-accessibility": [ "error" ], // `public` / `private` for members and methods are required
}
};

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/dist/
/node_modules/
*.log

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# <placeholder>
## Build
- You will need [`shader_minifier.exe`](https://github.com/laurentlb/Shader_Minifier) in your PATH
```sh
yarn
yarn build
```

View File

@@ -0,0 +1,17 @@
const { getOptions } = require( 'loader-utils' );
const { AutomatonWithGUI } = require( '@fms-cat/automaton-with-gui' );
/**
* @param {string} content
*/
module.exports = function( content ) {
const options = getOptions( this );
let data = JSON.parse( content );
if ( options.minimize ) {
data = AutomatonWithGUI.minimizeData( data, { ...options } );
}
return `module.exports = ${ JSON.stringify( data ) }`;
};

View File

@@ -0,0 +1,107 @@
const { getOptions } = require( 'loader-utils' );
const cp = require( 'child_process' );
const tempy = require( 'tempy' );
const path = require( 'path' );
const fs = require( 'fs' );
const { promisify } = require( 'util' );
const exec = promisify( cp.exec );
/**
* @param {object} options
* @returns {string}
*/
function buildMinifierOptionsString( options ) {
let str = '';
if ( options.hlsl ) {
str += '--hlsl ';
}
str += '--format none ';
if ( typeof options.fieldNames === 'string' ) {
str += `--field-names ${ options.fieldNames } `;
}
if ( options.preserveExternals ) {
str += '--preserve-externals ';
}
if ( options.preserveAllGlobals ) {
str += '--preserve-all-globals ';
}
if ( options.noRenaming ) {
str += '--no-renaming ';
}
if ( Array.isArray( options.noRenamingList ) ) {
str += `--no-renaming-list ${ options.noRenamingList.join( ',' ) } `;
}
if ( options.noSequence ) {
str += '--no-sequence ';
}
if ( options.smoothstep ) {
str += '--smoothstep ';
}
return str;
}
/**
* @param {string} content
* @returns string
*/
function sanitizeContent( content ) {
return content.split( '\n' ).map( ( line ) => {
let lineSanitized = line;
// precision
// https://github.com/laurentlb/Shader_Minifier/issues/8
if ( /^\s*precision/m.exec( lineSanitized ) ) {
lineSanitized = `//[\n${ lineSanitized }\n//]`
}
return lineSanitized;
} ).join( '\n' );
}
/**
* @param {string} content
*/
module.exports = async function( content ) {
const callback = this.async();
const options = getOptions( this );
const minifierOptions = buildMinifierOptionsString( options );
const name = path.basename( this.resourcePath );
const contentSanitized = sanitizeContent( content );
const minified = await tempy.file.task( async ( pathOriginal ) => {
await fs.promises.writeFile( pathOriginal, contentSanitized, { encoding: 'utf8' } );
return await tempy.file.task( async ( pathMinified ) => {
const command = `shader_minifier.exe ${ pathOriginal } ${ minifierOptions }-o ${ pathMinified }`;
await exec( command ).catch( ( error ) => {
if ( error.stdout ) {
this.emitError( Error( error.stdout ) );
}
throw error;
} );
return await fs.promises.readFile( pathMinified, { encoding: 'utf8' } );
}, { name } );
}, { name } ).catch( ( error ) => {
callback( error );
} );
if ( minified ) {
callback( null, minified );
}
};

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "revision-2021",
"version": "0.0.1",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "webpack-cli serve --mode development",
"build": "webpack --mode production && ruby pnginator.rb ./dist/bundle.js ./dist/out.png.html"
},
"devDependencies": {
"@fms-cat/automaton": "^4.1.0",
"@fms-cat/automaton-fxs": "^4.1.0",
"@fms-cat/automaton-with-gui": "^4.1.1",
"@fms-cat/experimental": "^0.5.0",
"@fms-cat/glcat-ts": "^0.14.0",
"@types/webpack-env": "^1.16.0",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"eslint": "^7.21.0",
"glslify-loader": "^2.0.0",
"html-webpack-plugin": "^5.3.0",
"loader-utils": "^2.0.0",
"raw-loader": "^4.0.2",
"schema-utils": "^3.0.0",
"tempy": "^1.0.0",
"ts-loader": "^8.0.17",
"typescript": "^4.2.3",
"webpack": "^5.24.4",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
}
}

106
pnginator.rb Normal file
View File

@@ -0,0 +1,106 @@
#!/usr/bin/env ruby -w
# pnginator.rb: pack a .js file into a PNG image with an HTML payload;
# when saved with an .html extension and opened in a browser, the HTML extracts and executes
# the javascript.
# Usage: ruby pnginator.rb input.js output.png.html
# By Gasman <http://matt.west.co.tt/>
# from an original idea by Daeken: http://daeken.com/superpacking-js-demos
MAX_WIDTH = 4096
USE_PNGOUT = true
require 'zlib'
require 'tempfile'
input_filename, output_filename = ARGV
f = File.open(input_filename, 'rb')
js = f.read
f.close
if js.length < MAX_WIDTH
# js fits onto one pixel line
js += "\x00"
scanlines = [js]
width = js.length
height = 1
# Daeken's single-pixel-row bootstrap (requires js string to be reversed)
# (edit by Gasman: change eval to (1,eval) to force global evaluation and avoid massive slowdown)
# html = "<canvas id=q><img onload=for(p=q.width=#{width},(c=q.getContext('2d')).drawImage(this,0,e='');p;)e+=String.fromCharCode(c.getImageData(--p,0,1,1).data[0]);(1,eval)(e) src=#>"
# p01's single-pixel-row bootstrap (requires an 0x00 end marker on the js string)
# (edit by Gasman: move drawImage out of getImageData params (it returns undef, which is invalid) and change eval to (1,eval) to force global evaluation)
html = "<canvas id=c><img onload=with(c.getContext('2d'))for(p=e='';drawImage(this,p--,0),t=getImageData(0,0,1,1).data[0];)e+=String.fromCharCode(t);(1,eval)(e) src=#>"
else
js = "\x00" + js
width = MAX_WIDTH
# split js into scanlines of 'width' pixels; pad the last one with whitespace
scanlines = js.scan(/.{1,#{width}}/m).collect{|line| line.ljust(width, "\x00")}
height = scanlines.length
# p01's multiple-pixel-row bootstrap (requires a dummy first byte on the js string)
# (edit by Gasman: set explicit canvas width to support widths above 300; move drawImage out of getImageData params; change eval to (1,eval) to force global evaluation)
html = "<canvas id=c><img onload=for(w=c.width=#{width},a=c.getContext('2d'),a.drawImage(this,p=0,0),e='',d=a.getImageData(0,0,w,#{height}).data;t=d[p+=4];)e+=String.fromCharCode(t);(1,eval)(e) src=#>"
end
# prepend each scanline with 0x00 to indicate 'no filtering', then concat into one string
image_data = scanlines.collect{|line| "\x00" + line}.join
idat_chunk = Zlib::Deflate.deflate(image_data, 9) # 9 = maximum compression
def png_chunk(signature, data)
[data.length, signature, data, Zlib::crc32(signature + data)].pack("NA4A*N")
end
if USE_PNGOUT
# Create a valid (no format hacks) .png file to pass to pngout
f = Tempfile.open(['pnginator', '.png'])
begin
f.write("\x89PNG\x0d\x0a\x1a\x0a") # PNG file header
f.write(png_chunk("IHDR", [width, height, 8, 0, 0, 0, 0].pack("NNccccc")))
f.write(png_chunk("IDAT", idat_chunk))
f.write(png_chunk("IEND", ''))
f.close
system("pngout", f.path, "-c0", "-y")
# read file back and extract the IDAT chunk
f.open
f.read(8)
while !f.eof?
length, signature = f.read(8).unpack("NA4")
data = f.read(length)
crc = f.read(4)
if signature == "IDAT"
idat_chunk = data
break
end
end
ensure
f.close
f.unlink
end
end
File.open(output_filename, 'wb') do |f|
f.write("\x89PNG\x0d\x0a\x1a\x0a") # PNG file header
f.write(png_chunk("IHDR", [width, height, 8, 0, 0, 0, 0].pack("NNccccc")))
# a custom chunk containing the HTML payload; stated chunk length is 4 less than the actual length,
# leaving the final 4 bytes to take the place of the checksum
f.write([html.length - 4, "jawh", html].pack("NA4A*"))
# can safely omit the checksum of the IDAT chunk
# f.write([idat_chunk.length, "IDAT", idat_chunk, Zlib::crc32("IDAT" + idat_chunk)].pack("NA4A*N"))
f.write([idat_chunk.length, "IDAT", idat_chunk].pack("NA4A*"))
# can safely omit the IEND chunk
# f.write([0, "IEND", "", Zlib::crc32("IEND")].pack("NA4A*N"))
end

124
shader-minifier-tips.md Normal file
View File

@@ -0,0 +1,124 @@
# shader-minifier-tips.md
[shader_minifier](https://github.com/laurentlb/Shader_Minifier) is good. The behavior is kinda [jej](https://www.youtube.com/watch?v=Wjhhs6GcUx0) though.
This tips should cover all the tips you have to care when you are using shader_minifier.
## Precisions
It will be handled automatically in shader-minifier-loader.
It doesn't accept `precision <precision> <type>;` for some reasons.
There are a workaround that put `//[` and `//]` around the line (shoutouts to [davidar](https://github.com/laurentlb/Shader_Minifier/issues/8#issuecomment-401582088)):
```glsl
//[
precision highp float;
//]
```
### Error Examples
```
Parse error: Error in <path>: Ln: 5 Col: 17
precision highp float;
^
Expecting: '('
```
### Issues
- https://github.com/laurentlb/Shader_Minifier/issues/8
(issue)
## Preprocessor Branches
Preprocessor branching often triggers issues on minified output. Use with cares.
The branch below should be accepted without problems:
```glsl
vec3 func() {
vec3 ret;
#ifdef CONDITION
ret = vec3( 1.0 );
#else
ret = vec3( 0.0 );
#endif
return ret;
}
```
For more details, see these issues below.
### Error Examples
```
ERROR: 0:9: 'r' : undeclared identifier
ERROR: 0:9: 'constructor' : not enough data provided for construction
```
```
ERROR: 0:30: 'm' : variable expected
ERROR: 0:30: 'return' : function return is not matching type:
```
```
ERROR: 0:25: 'ifdef' : unexpected end of file found in conditional block
```
### Issues
- https://github.com/laurentlb/Shader_Minifier/issues/27
- https://github.com/laurentlb/Shader_Minifier/issues/28
## Overloading
I don't think overloading is not working properly for most of cases.
### Error Examples
```
ERROR: 0:11: 't' : no matching overloaded function found
ERROR: 0:11: '=' : dimension mismatch
ERROR: 0:11: 'assign' : cannot convert from 'highp 3-component vector of float' to 'highp float'
```
## Variable Declarations
Variable declarations with same types (especially, uniforms) should be done at once.
```glsl
// uh
uniform float uHoge;
uniform float uFuga;
uniform vec3 uFoo;
uniform float uBar;
// -> uniform float uHoge,uFuga;uniform vec3 uFoo;uniform float uBar;
// better
uniform float uHoge;
uniform float uFuga;
uniform float uBar;
uniform vec3 uFoo;
// -> uniform float uHoge,uFuga,uBar;uniform vec3 uFoo;
```
## Preprocessor Constants
Defining constants should be performed using `const` rather than `#define`.
```glsl
// no
const float PI = 3.14159265;
const float TAU = 6.283185307;
// -> const float PI = 3.14159265;
// const float TAU = 6.283185307;
// preferable
const float PI = 3.1415926535;
const float TAU = 6.283185307;
// -> const float e=3.14159,s=6.28319;
```
You might want to also avoid macros with arguments for the same reason.

186
src/Music.ts Normal file
View File

@@ -0,0 +1,186 @@
import { GLCat, GLCatBuffer, GLCatProgram, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
import { MUSIC_BPM, MUSIC_BUFFER_LENGTH } from './config';
import { Pool } from './utils/Pool';
import musicVert from './shaders/music.vert';
const discardFrag = '#version 300 es\nvoid main(){discard;}';
export class Music {
public isPlaying: boolean;
public time: number;
public deltaTime: number;
public audio: AudioContext;
public glCat: GLCat<WebGL2RenderingContext>;
private __program: GLCatProgram<WebGL2RenderingContext>;
private __bufferOff: GLCatBuffer<WebGL2RenderingContext>;
private __bufferTransformFeedbacks: [
GLCatBuffer<WebGL2RenderingContext>,
GLCatBuffer<WebGL2RenderingContext>
];
private __transformFeedback: GLCatTransformFeedback<WebGL2RenderingContext>;
private __prevAudioTime: number;
private __bufferPool: Pool<AudioBuffer>;
private __prevBufferSource: AudioBufferSourceNode | null = null;
constructor( glCat: GLCat<WebGL2RenderingContext>, audio: AudioContext ) {
this.glCat = glCat;
const { gl } = glCat;
this.audio = audio;
// == yoinked from wavenerd-deck ===============================================================
const bufferOffArray = new Array( MUSIC_BUFFER_LENGTH )
.fill( 0 )
.map( ( _, i ) => i );
this.__bufferOff = glCat.createBuffer();
this.__bufferOff.setVertexbuffer( new Float32Array( bufferOffArray ) );
this.__bufferTransformFeedbacks = [
glCat.createBuffer(),
glCat.createBuffer(),
];
this.__transformFeedback = glCat.createTransformFeedback();
this.__bufferTransformFeedbacks[ 0 ].setVertexbuffer(
MUSIC_BUFFER_LENGTH * 4,
gl.DYNAMIC_COPY
);
this.__bufferTransformFeedbacks[ 1 ].setVertexbuffer(
MUSIC_BUFFER_LENGTH * 4,
gl.DYNAMIC_COPY
);
this.__transformFeedback.bindBuffer( 0, this.__bufferTransformFeedbacks[ 0 ] );
this.__transformFeedback.bindBuffer( 1, this.__bufferTransformFeedbacks[ 1 ] );
// == program ==================================================================================
this.__program = glCat.lazyProgram(
musicVert,
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
);
// == audio ====================================================================================
this.__prevAudioTime = 0;
this.__bufferPool = new Pool( [
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
] );
// == I think these are gonna be required ======================================================
this.isPlaying = false;
this.time = 0.0;
this.deltaTime = 0.0;
// == hot hot hot hot hot ======================================================================
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept(
[
'./shaders/music.vert',
],
async () => {
const program = await this.glCat.lazyProgramAsync(
musicVert,
discardFrag,
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
).catch( ( error ) => {
console.error( error );
return null;
} );
if ( program ) {
this.__program.dispose( true );
this.__program = program;
}
}
);
}
}
}
public update(): void {
const { audio, isPlaying } = this;
const genTime = audio.currentTime;
this.deltaTime = genTime - this.__prevAudioTime;
if ( isPlaying ) {
this.time += this.deltaTime;
const buffer = this.__bufferPool.next();
if ( this.__program ) {
this.__prepareBuffer( buffer );
}
const bufferSource = audio.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect( audio.destination );
this.__prevBufferSource?.stop( audio.currentTime );
bufferSource.start( audio.currentTime, audio.currentTime - genTime );
this.__prevBufferSource = bufferSource;
} else {
this.__prevBufferSource?.stop( audio.currentTime );
this.__prevBufferSource = null;
}
this.__prevAudioTime = genTime;
}
private __prepareBuffer( buffer: AudioBuffer ): void {
const { glCat, time } = this;
const { gl } = this.glCat;
const program = this.__program;
const beatLength = 60.0 / MUSIC_BPM;
const barLength = 240.0 / MUSIC_BPM;
const sixteenBarLength = 3840.0 / MUSIC_BPM;
program.attribute( 'off', this.__bufferOff, 1 );
program.uniform1f( 'bpm', MUSIC_BPM );
program.uniform1f( '_deltaSample', 1.0 / this.audio.sampleRate );
program.uniform4f(
'timeLength',
beatLength,
barLength,
sixteenBarLength,
1E16
);
program.uniform4f(
'_timeHead',
time % beatLength,
time % barLength,
time % sixteenBarLength,
time
);
glCat.useProgram( program, () => {
glCat.bindTransformFeedback( this.__transformFeedback, () => {
gl.enable( gl.RASTERIZER_DISCARD );
gl.beginTransformFeedback( gl.POINTS );
gl.drawArrays( gl.POINTS, 0, MUSIC_BUFFER_LENGTH );
gl.endTransformFeedback();
gl.disable( gl.RASTERIZER_DISCARD );
} );
} );
gl.flush();
[ 0, 1 ].map( ( i ) => {
glCat.bindVertexBuffer( this.__bufferTransformFeedbacks[ i ], () => {
gl.getBufferSubData(
gl.ARRAY_BUFFER,
0,
buffer.getChannelData( i ),
0,
MUSIC_BUFFER_LENGTH
);
} );
} );
}
};

View File

@@ -0,0 +1 @@
export { hermitePatch } from '@fms-cat/automaton-fxs';

1
src/automaton.json Normal file
View File

@@ -0,0 +1 @@
{"version":"4.1.1","resolution":100,"curves":[{"nodes":[[0,1,0,0,0.16140350877192983],[1,0,-0.47719298245614045]]}],"channels":[["Glitch/amp",{"items":[{"length":1,"curve":0}]}]],"labels":{"zero":0},"guiSettings":{"snapTimeActive":true,"snapTimeInterval":0.1,"snapValueActive":true,"snapValueInterval":0.1,"snapBeatActive":false,"bpm":140,"beatOffset":0,"useBeatInGUI":false,"minimizedPrecisionTime":3,"minimizedPrecisionValue":3}}

6
src/automaton.json.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
import type { SerializedAutomatonWithGUI } from '@fms-cat/automaton-with-gui';
declare module './automaton.json' {
const data: SerializedAutomatonWithGUI;
export default data;
}

7
src/config-hot.ts Normal file
View File

@@ -0,0 +1,7 @@
export const
RTINSPECTOR_CAPTURE_NAME: string | null = null,
// RTINSPECTOR_CAPTURE_NAME: string | null = 'Bloom/swap1',
RTINSPECTOR_CAPTURE_INDEX = 0,
COMPONENT_UPDATE_BREAKPOINT: string | null = null,
// COMPONENT_UPDATE_BREAKPOINT: string | null = 'Bloom/quadDup3',
COMPONENT_DRAW_BREAKPOINT: string | null = 'Rings/meshRender';

7
src/config.ts Normal file
View File

@@ -0,0 +1,7 @@
export const
RANDOM_RESOLUTION = [ 64, 64 ],
STATIC_RANDOM_RESOLUTION = [ 2048, 2048 ],
AO_RESOLUTION_RATIO = 1.0,
RESOLUTION = [ 1280, 720 ],
MUSIC_BPM = 140,
MUSIC_BUFFER_LENGTH = 16384;

104
src/entities/Bloom.ts Normal file
View File

@@ -0,0 +1,104 @@
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import { Swap } from '@fms-cat/experimental';
import quadVert from '../shaders/quad.vert';
import bloomPreFrag from '../shaders/bloom-pre.frag';
import returnFrag from '../shaders/return.frag';
import bloomBlurFrag from '../shaders/bloom-blur.frag';
import bloomPostFrag from '../shaders/bloom-post.frag';
export interface BloomOptions {
input: GLCatTexture<WebGL2RenderingContext>;
target: RenderTarget;
}
export class Bloom {
public entity: Entity;
public constructor( options: BloomOptions ) {
this.entity = new Entity();
const swap = new Swap(
new BufferRenderTarget( {
width: options.target.width,
height: options.target.height,
name: process.env.DEV && 'Bloom/swap0',
} ),
new BufferRenderTarget( {
width: options.target.width,
height: options.target.height,
name: process.env.DEV && 'Bloom/swap1',
} ),
);
// -- pre ----------------------------------------------------------------------------------------
const materialBloomPre = new Material(
quadVert,
bloomPreFrag
);
materialBloomPre.addUniformTexture( 'sampler0', options.input );
this.entity.components.push( new Quad( {
target: swap.o,
material: materialBloomPre,
range: [ -1.0, -1.0, 0.0, 0.0 ],
name: process.env.DEV && 'Bloom/quadPre',
} ) );
swap.swap();
// -- dup ----------------------------------------------------------------------------------------
for ( let i = 0; i < 6; i ++ ) {
const material = new Material(
quadVert,
returnFrag
);
material.addUniformTexture( 'sampler0', swap.i.texture );
this.entity.components.push( new Quad( {
target: swap.o,
material,
range: i === 0 ? [ -1.0, -1.0, 1.0, 1.0 ] : [ 0.0, 0.0, 1.0, 1.0 ],
name: process.env.DEV && `Bloom/quadDup${ i }`,
} ) );
swap.swap();
}
// -- blur ---------------------------------------------------------------------------------------
for ( let i = 0; i < 2; i ++ ) {
const material = new Material(
quadVert,
bloomBlurFrag
);
material.addUniform( 'isVert', '1i', i );
material.addUniformTexture( 'sampler0', swap.i.texture );
this.entity.components.push( new Quad( {
target: swap.o,
material,
name: process.env.DEV && `Bloom/quadBlur${ i }`,
} ) );
swap.swap();
}
// -- post ---------------------------------------------------------------------------------------
const materialBloomPost = new Material(
quadVert,
bloomPostFrag
);
materialBloomPost.addUniformTexture( 'samplerDry', options.input );
materialBloomPost.addUniformTexture( 'samplerWet', swap.i.texture );
this.entity.components.push( new Quad( {
target: options.target,
material: materialBloomPost,
name: process.env.DEV && 'Bloom/quadPost',
} ) );
}
}

View File

@@ -0,0 +1,211 @@
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { DISPLAY } from '../heck/DISPLAY';
import { Entity } from '../heck/Entity';
import { Lambda } from '../heck/components/Lambda';
import { LightEntity } from './LightEntity';
import { Material } from '../heck/Material';
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import { AO_RESOLUTION_RATIO } from '../config';
import aoFrag from '../shaders/ao.frag';
import quadVert from '../shaders/quad.vert';
import shadingFrag from '../shaders/shading.frag';
export interface CameraEntityOptions {
root: Entity;
target: RenderTarget;
lights: LightEntity[];
textureRandom: GLCatTexture<WebGL2RenderingContext>;
}
export class CameraEntity {
private __root: Entity;
public get root(): Entity {
return this.__root;
}
private __camera: PerspectiveCamera;
public get camera(): PerspectiveCamera {
return this.__camera;
}
private __entity: Entity;
public get entity(): Entity {
return this.__entity;
}
public constructor( options: CameraEntityOptions ) {
this.__root = options.root;
this.__entity = new Entity();
const cameraTarget = new BufferRenderTarget( {
width: options.target.width,
height: options.target.height,
numBuffers: 4,
name: 'CameraEntity/cameraTarget',
} );
this.__camera = new PerspectiveCamera( {
scene: this.__root,
renderTarget: cameraTarget,
near: 0.1,
far: 20.0,
name: 'CameraEntity/camera',
} );
this.__entity.components.push( this.__camera );
const aoTarget = new BufferRenderTarget( {
width: AO_RESOLUTION_RATIO * options.target.width,
height: AO_RESOLUTION_RATIO * options.target.height,
name: 'CameraEntity/aoTarget',
} );
const aoMaterial = new Material(
quadVert,
aoFrag,
);
this.__entity.components.push( new Lambda( {
onUpdate: () => {
const cameraView = this.__entity.transform.matrix.inverse!;
aoMaterial.addUniformVector(
'cameraPV',
'Matrix4fv',
this.camera.projectionMatrix.multiply(
cameraView
).elements
);
},
visible: false,
name: process.env.DEV && 'CameraEntity/ao/setCameraUniforms',
} ) );
for ( let i = 0; i < 2; i ++ ) { // it doesn't need 2 and 3
aoMaterial.addUniformTexture(
'sampler' + i,
cameraTarget.getTexture( DISPLAY.gl.COLOR_ATTACHMENT0 + i )
);
}
aoMaterial.addUniformTexture( 'samplerRandom', options.textureRandom );
const aoQuad = new Quad( {
material: aoMaterial,
target: aoTarget,
name: process.env.DEV && 'CameraEntity/ao/quad',
} );
this.__entity.components.push( aoQuad );
const shadingMaterials = options.lights.map( ( light, iLight ) => {
const shadingMaterial = new Material(
quadVert,
shadingFrag,
{
defines: {
IS_FIRST_LIGHT: iLight === 0 ? 'true' : undefined
}
},
);
this.__entity.components.push( new Lambda( {
onUpdate: () => {
const cameraView = this.__entity.transform.matrix.inverse!;
shadingMaterial.addUniformVector(
'cameraView',
'Matrix4fv',
cameraView.elements
);
shadingMaterial.addUniformVector(
'cameraPV',
'Matrix4fv',
this.camera.projectionMatrix.multiply(
cameraView
).elements
);
shadingMaterial.addUniform(
'lightNearFar',
'2f',
light.camera.near,
light.camera.far
);
shadingMaterial.addUniform(
'cameraNearFar',
'2f',
this.camera.near,
this.camera.far
);
shadingMaterial.addUniform(
'cameraPos',
'3f',
...this.__entity.transform.position.elements
);
shadingMaterial.addUniform(
'lightPos',
'3f',
...light.entity.transform.position.elements
);
shadingMaterial.addUniform(
'lightColor',
'3f',
...light.color
);
shadingMaterial.addUniformVector(
'lightPV',
'Matrix4fv',
light.camera.projectionMatrix.multiply(
light.entity.transform.matrix.inverse!
).elements
);
},
visible: false,
name: process.env.DEV && 'CameraEntity/shading/setCameraUniforms',
} ) );
for ( let i = 0; i < 4; i ++ ) {
shadingMaterial.addUniformTexture(
'sampler' + i,
cameraTarget.getTexture( DISPLAY.gl.COLOR_ATTACHMENT0 + i )
);
}
shadingMaterial.blend = [ DISPLAY.gl.ONE, DISPLAY.gl.ONE ];
shadingMaterial.addUniformTexture( 'samplerAo', aoTarget.texture );
shadingMaterial.addUniformTexture( 'samplerShadow', light.shadowMap.texture );
shadingMaterial.addUniformTexture( 'samplerRandom', options.textureRandom );
const shadingQuad = new Quad( {
material: shadingMaterial,
target: options.target,
name: process.env.DEV && 'CameraEntity/shading/quad',
} );
shadingQuad.clear = iLight === 0 ? [] : false;
this.__entity.components.push( shadingQuad );
return shadingMaterial;
} );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/shading.frag', () => {
shadingMaterials.forEach( ( material ) => {
material.replaceShader( quadVert, shadingFrag );
} );
} );
}
}
}
}

View File

@@ -0,0 +1,100 @@
import { BufferRenderTarget, BufferRenderTargetOptions } from '../heck/BufferRenderTarget';
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { Lambda } from '../heck/components/Lambda';
import { Material } from '../heck/Material';
import { Mesh } from '../heck/components/Mesh';
import { Quad } from '../heck/components/Quad';
import { Swap } from '@fms-cat/experimental';
export interface GPUParticlesOptions {
materialCompute: Material;
geometryRender: Geometry;
materialRender: Material;
computeWidth: number;
computeHeight: number;
computeNumBuffers: number;
namePrefix?: string;
}
export class GPUParticles {
private __entity: Entity;
public get entity(): Entity {
return this.__entity;
}
private __swapCompute: Swap<BufferRenderTarget>;
private __quadCompute: Quad;
private __meshRender: Mesh;
public get meshRender(): Mesh {
return this.__meshRender;
}
public get materialCompute(): Material {
return this.__quadCompute.material;
}
public get materialRender(): Material {
return this.__meshRender.material;
}
public constructor( options: GPUParticlesOptions ) {
this.__entity = new Entity();
const brtOptions: BufferRenderTargetOptions = {
width: options.computeWidth,
height: options.computeHeight,
numBuffers: options.computeNumBuffers,
};
this.__swapCompute = new Swap(
new BufferRenderTarget( {
...brtOptions,
name: process.env.DEV && `${ options.namePrefix }/swap0`,
} ),
new BufferRenderTarget( {
...brtOptions,
name: process.env.DEV && `${ options.namePrefix }/swap1`,
} ),
);
// -- swapper ----------------------------------------------------------------------------------
this.__entity.components.push( new Lambda( {
onUpdate: () => {
this.__swapCompute.swap();
this.materialCompute.addUniformTexture( 'samplerCompute', this.__swapCompute.i.texture );
this.__quadCompute.target = this.__swapCompute.o;
this.materialRender.addUniformTexture( 'samplerCompute', this.__swapCompute.o.texture );
},
visible: false,
name: process.env.DEV && `${ options.namePrefix }/swapper`,
} ) );
// -- compute ----------------------------------------------------------------------------------
this.__quadCompute = new Quad( {
target: this.__swapCompute.o,
material: options.materialCompute,
name: process.env.DEV && `${ options.namePrefix }/quadCompute`,
} );
this.__entity.components.push( this.__quadCompute );
// -- render -----------------------------------------------------------------------------------
this.__meshRender = new Mesh( {
geometry: options.geometryRender,
material: options.materialRender,
name: process.env.DEV && `${ options.namePrefix }/meshRender`,
} );
options.materialRender.addUniform(
'resolutionCompute',
'2f',
options.computeWidth,
options.computeHeight
);
this.__entity.components.push( this.__meshRender );
}
}

47
src/entities/Glitch.ts Normal file
View File

@@ -0,0 +1,47 @@
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import quadVert from '../shaders/quad.vert';
import glitchFrag from '../shaders/glitch.frag';
import { Automaton } from '@fms-cat/automaton';
export interface GlitchOptions {
input: GLCatTexture<WebGL2RenderingContext>;
target: RenderTarget;
automaton: Automaton;
}
export class Glitch {
public amp = 0.0;
public entity: Entity;
public material: Material;
public constructor( options: GlitchOptions ) {
this.entity = new Entity();
// -- quad -------------------------------------------------------------------------------------
this.material = new Material( quadVert, glitchFrag );
this.material.addUniformTexture( 'sampler0', options.input );
if ( module.hot ) {
module.hot.accept( '../shaders/glitch.frag', () => {
this.material.replaceShader( quadVert, glitchFrag );
} );
}
const quad = new Quad( {
target: options.target,
material: this.material,
name: process.env.DEV && 'Glitch/quad',
} );
this.entity.components.push( quad );
// -- update uniform ---------------------------------------------------------------------------
options.automaton.auto( 'Glitch/amp', ( { value } ) => {
this.material.addUniform( 'amp', '1f', value );
} );
}
}

137
src/entities/LightEntity.ts Normal file
View File

@@ -0,0 +1,137 @@
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { Entity } from '../heck/Entity';
import { Lambda } from '../heck/components/Lambda';
import { Material } from '../heck/Material';
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
import { Quad } from '../heck/components/Quad';
import { Swap } from '@fms-cat/experimental';
import posToDepthFrag from '../shaders/pos-to-depth.frag';
import quadVert from '../shaders/quad.vert';
import shadowBlurFrag from '../shaders/shadow-blur.frag';
export interface LightEntityOptions {
root: Entity;
shadowMapFov?: number;
shadowMapNear?: number;
shadowMapFar?: number;
shadowMapWidth?: number;
shadowMapHeight?: number;
namePrefix?: string;
}
export class LightEntity {
public color: [ number, number, number ] = [ 1.0, 1.0, 1.0 ];
private __root: Entity;
public get root(): Entity {
return this.__root;
}
private __shadowMapCamera: PerspectiveCamera;
public get camera(): PerspectiveCamera {
return this.__shadowMapCamera;
}
private __shadowMap: BufferRenderTarget;
public get shadowMap(): BufferRenderTarget {
return this.__shadowMap;
}
private __entity: Entity;
public get entity(): Entity {
return this.__entity;
}
public constructor( options: LightEntityOptions ) {
this.__root = options.root;
this.__entity = new Entity();
const swapOptions = {
width: options.shadowMapWidth || 1024,
height: options.shadowMapHeight || 1024
};
const swap = new Swap(
new BufferRenderTarget( {
...swapOptions,
name: process.env.DEV && `${ options.namePrefix }/swap0`,
} ),
new BufferRenderTarget( {
...swapOptions,
name: process.env.DEV && `${ options.namePrefix }/swap1`,
} )
);
// -- camera -----------------------------------------------------------------------------------
const fov = options.shadowMapFov || 45.0;
const near = options.shadowMapNear || 0.1;
const far = options.shadowMapFar || 100.0;
this.__shadowMapCamera = new PerspectiveCamera( {
fov,
near,
far,
renderTarget: swap.o,
scene: this.__root,
name: process.env.DEV && `${ options.namePrefix }/shadowMapCamera`,
} );
this.__shadowMapCamera.clear = [ 1.0, 1.0, 1.0, 1.0 ];
this.__entity.components.push( this.__shadowMapCamera );
this.__shadowMap = new BufferRenderTarget( {
width: options.shadowMapWidth || 2048,
height: options.shadowMapHeight || 2048,
name: process.env.DEV && `${ options.namePrefix }/shadowMap`,
} );
swap.swap();
// -- convert ----------------------------------------------------------------------------------
const materialConvert = new Material(
quadVert,
posToDepthFrag
);
materialConvert.addUniformTexture( 'sampler0', swap.i.texture );
this.__entity.components.push( new Lambda( {
onUpdate: () => {
materialConvert.addUniform( 'cameraPos', '3f', ...this.entity.transform.position.elements );
materialConvert.addUniform( 'cameraNearFar', '2f', this.camera.near, this.camera.far );
},
visible: false,
name: process.env.DEV && `${ options.namePrefix }/setCameraUniforms`,
} ) );
this.__entity.components.push( new Quad( {
target: swap.o,
material: materialConvert,
name: process.env.DEV && `${ options.namePrefix }/quadConvertPosToDepth`,
} ) );
swap.swap();
// -- blur ---------------------------------------------------------------------------------------
for ( let i = 0; i < 2; i ++ ) {
const material = new Material(
quadVert,
shadowBlurFrag
);
material.addUniform( 'isVert', '1i', i );
material.addUniformTexture( 'sampler0', swap.i.texture );
this.__entity.components.push( new Quad( {
target: i === 0 ? swap.o : this.__shadowMap,
material,
name: process.env.DEV && `${ options.namePrefix }/quadShadowBlur${ i }`,
} ) );
swap.swap();
}
}
}

42
src/entities/Post.ts Normal file
View File

@@ -0,0 +1,42 @@
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import postFrag from '../shaders/post.frag';
import quadVert from '../shaders/quad.vert';
export interface PostOptions {
input: GLCatTexture<WebGL2RenderingContext>;
target: RenderTarget;
}
export class Post {
public entity: Entity;
public constructor( options: PostOptions ) {
this.entity = new Entity();
this.entity.visible = false;
// -- post -------------------------------------------------------------------------------------
const material = new Material(
quadVert,
postFrag,
);
material.addUniformTexture( 'sampler0', options.input );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/post.frag', () => {
material.replaceShader( quadVert, postFrag );
} );
}
}
this.entity.components.push( new Quad( {
target: options.target,
material,
name: process.env.DEV && 'Post/quad',
} ) );
}
}

View File

@@ -0,0 +1,63 @@
import { Entity } from '../heck/Entity';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import returnFrag from '../shaders/return.frag';
import quadVert from '../shaders/quad.vert';
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
import { RTINSPECTOR_CAPTURE_INDEX, RTINSPECTOR_CAPTURE_NAME } from '../config-hot';
import { DISPLAY } from '../heck/DISPLAY';
export interface RTInspectorOptions {
target: RenderTarget;
}
export class RTInspector {
public entity: Entity;
public material: Material;
public constructor( options: RTInspectorOptions ) {
this.entity = new Entity();
this.material = new Material(
quadVert,
returnFrag,
);
this.entity.components.push( new Quad( {
target: options.target,
material: this.material,
name: process.env.DEV && 'RTInspector/quad',
ignoreBreakpoints: true,
} ) );
this.__updateTarget();
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../config-hot', () => {
this.__updateTarget();
} );
}
}
}
private __updateTarget(): void {
if ( RTINSPECTOR_CAPTURE_NAME != null ) {
const target = BufferRenderTarget.nameMap.get( RTINSPECTOR_CAPTURE_NAME ?? '' ) ?? null;
const attachment = DISPLAY.gl.COLOR_ATTACHMENT0 + ( RTINSPECTOR_CAPTURE_INDEX ?? 0 );
const texture = target?.getTexture( attachment );
if ( texture ) {
this.material.addUniformTexture( 'sampler0', texture );
this.entity.active = true;
return;
} else {
console.warn( `RTInspector: Cannot retrieve a render target texture, RTINSPECTOR_CAPTURE_NAME: ${ RTINSPECTOR_CAPTURE_NAME }, RTINSPECTOR_CAPTURE_INDEX: ${ RTINSPECTOR_CAPTURE_INDEX }` );
}
}
// fallback to not render it
this.entity.active = false;
}
}

102
src/entities/Raymarcher.ts Normal file
View File

@@ -0,0 +1,102 @@
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Mesh, MeshCull } from '../heck/components/Mesh';
import { TRIANGLE_STRIP_QUAD, Vector3 } from '@fms-cat/experimental';
import { DISPLAY } from '../heck/DISPLAY';
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { Material } from '../heck/Material';
import quadVert from '../shaders/quad.vert';
import raymarcherFrag from '../shaders/raymarcher.frag';
import { Lambda } from '../heck/components/Lambda';
export class Raymarcher {
private __mesh: Mesh;
private __geometry: Geometry;
private __material: Material;
public get material(): Material {
return this.__material;
}
private __entity: Entity;
public get entity(): Entity {
return this.__entity;
}
public constructor( options: {
textureRandom: GLCatTexture<WebGL2RenderingContext>;
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
} ) {
this.__entity = new Entity();
this.__entity.transform.position = new Vector3( [ 0.0, 0.0, 0.3 ] );
this.__entity.transform.scale = new Vector3( [ 16.0, 9.0, 1.0 ] ).scale( 0.15 );
this.__geometry = this.__createGeoemtry();
this.__material = this.__createMaterial();
this.__material.addUniform( 'range', '4f', -1.0, -1.0, 1.0, 1.0 );
this.__material.addUniformTexture( 'samplerRandom', options.textureRandom );
this.__material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
this.__entity.components.push( new Lambda( {
onDraw: ( event ) => {
this.__material.addUniform(
'cameraNearFar',
'2f',
event.camera.near,
event.camera.far
);
this.__material.addUniformVector(
'inversePV',
'Matrix4fv',
event.projectionMatrix.multiply( event.viewMatrix ).inverse!.elements
);
},
active: false,
name: process.env.DEV && 'Raymarcher/setCameraUniforms',
} ) );
this.__mesh = new Mesh( {
geometry: this.__geometry,
material: this.__material,
name: process.env.DEV && 'Raymarcher/mesh',
} );
this.__mesh.cull = MeshCull.None;
this.__entity.components.push( this.__mesh );
}
protected __createGeoemtry(): Geometry {
const geometry = new Geometry();
const bufferPos = DISPLAY.glCat.createBuffer();
bufferPos.setVertexbuffer( new Float32Array( TRIANGLE_STRIP_QUAD ) );
geometry.addAttribute( 'p', {
buffer: bufferPos,
size: 2,
type: DISPLAY.gl.FLOAT
} );
geometry.count = 4;
geometry.mode = DISPLAY.gl.TRIANGLE_STRIP;
return geometry;
}
protected __createMaterial(): Material {
const material = new Material( quadVert, raymarcherFrag );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/raymarcher.frag', () => {
material.replaceShader( quadVert, raymarcherFrag );
} );
}
}
return material;
}
}

32
src/entities/Return.ts Normal file
View File

@@ -0,0 +1,32 @@
import { Entity } from '../heck/Entity';
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { Material } from '../heck/Material';
import { Quad } from '../heck/components/Quad';
import { RenderTarget } from '../heck/RenderTarget';
import returnFrag from '../shaders/return.frag';
import quadVert from '../shaders/quad.vert';
export interface PostOptions {
input: GLCatTexture<WebGL2RenderingContext>;
target: RenderTarget;
}
export class Return {
public entity: Entity;
public constructor( options: PostOptions ) {
this.entity = new Entity();
// -- post -------------------------------------------------------------------------------------
const material = new Material(
quadVert,
returnFrag,
);
material.addUniformTexture( 'sampler0', options.input );
this.entity.components.push( new Quad( {
target: options.target,
material
} ) );
}
}

90
src/entities/Rings.ts Normal file
View File

@@ -0,0 +1,90 @@
import { Quaternion, Vector3 } from '@fms-cat/experimental';
import { genTorus } from '../geometries/genTorus';
import { Mesh } from '../heck/components/Mesh';
import { DISPLAY } from '../heck/DISPLAY';
import { Entity } from '../heck/Entity';
import { Geometry } from '../heck/Geometry';
import { InstancedGeometry } from '../heck/InstancedGeometry';
import { Material } from '../heck/Material';
import ringsVert from '../shaders/rings.vert';
import ringsFrag from '../shaders/rings.frag';
const PRIMCOUNT = 32;
export class Rings {
public mesh: Mesh;
public geometry: Geometry;
public material: Material;
public entity: Entity;
public constructor() {
this.entity = new Entity();
this.entity.transform.rotation = Quaternion.fromAxisAngle(
new Vector3( [ 1.0, 0.0, 0.0 ] ),
0.4,
).multiply( Quaternion.fromAxisAngle(
new Vector3( [ 0.0, 0.0, 1.0 ] ),
0.4,
) );
this.geometry = this.__createGeometry();
this.material = this.__createMaterial();
this.mesh = new Mesh( {
geometry: this.geometry,
material: this.material,
name: process.env.DEV && 'Rings/mesh',
} );
this.entity.components.push( this.mesh );
}
private __createGeometry(): Geometry {
const torus = genTorus( { segmentsRadial: 256 } );
const geometry = new InstancedGeometry();
geometry.addAttribute( 'position', torus.position );
geometry.addAttribute( 'normal', torus.normal );
geometry.setIndex( torus.index );
const arrayInstanceId = new Array( PRIMCOUNT ).fill( 0 ).map( ( _, i ) => i );
const bufferInstanceId = DISPLAY.glCat.createBuffer();
bufferInstanceId.setVertexbuffer( new Float32Array( arrayInstanceId ) );
geometry.addAttribute( 'instanceId', {
buffer: bufferInstanceId,
size: 1,
divisor: 1,
type: DISPLAY.gl.FLOAT
} );
geometry.count = torus.count;
geometry.primcount = PRIMCOUNT;
geometry.mode = torus.mode;
return geometry;
}
private __createMaterial(): Material {
const material = new Material( ringsVert, ringsFrag );
material.addUniform( 'inflate', '1f', 0.01 );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept(
[
'../shaders/rings.vert',
'../shaders/rings.frag',
],
() => {
material.replaceShader( ringsVert, ringsFrag );
},
);
}
}
return material;
}
}

View File

@@ -0,0 +1,152 @@
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { DISPLAY } from '../heck/DISPLAY';
import { Entity } from '../heck/Entity';
import { GPUParticles } from './GPUParticles';
import { Geometry } from '../heck/Geometry';
import { InstancedGeometry } from '../heck/InstancedGeometry';
import { Material } from '../heck/Material';
import { genOctahedron } from '../geometries/genOctahedron';
import quadVert from '../shaders/quad.vert';
import sphereParticleComputeFrag from '../shaders/sphere-particles-compute.frag';
import sphereParticleRenderFrag from '../shaders/sphere-particles-render.frag';
import sphereParticleRenderVert from '../shaders/sphere-particles-render.vert';
export interface SphereParticlesOptions {
particlesSqrt: number;
textureRandom: GLCatTexture<WebGL2RenderingContext>;
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
}
export class SphereParticles {
private static __ppp = 2;
public get entity(): Entity {
return this.__gpuParticles.entity;
}
private __gpuParticles: GPUParticles;
public get materialCompute(): Material {
return this.__gpuParticles.materialCompute;
}
public get materialRender(): Material {
return this.__gpuParticles.materialRender;
}
public constructor( options: SphereParticlesOptions ) {
this.__gpuParticles = new GPUParticles( {
materialCompute: this.__createMaterialCompute( options ),
geometryRender: this.__createGeometryRender( options ),
materialRender: this.__createMaterialRender( options ),
computeWidth: SphereParticles.__ppp * options.particlesSqrt,
computeHeight: options.particlesSqrt,
computeNumBuffers: 1,
namePrefix: process.env.DEV && 'SphereParticles',
} );
}
private __createMaterialCompute( options: SphereParticlesOptions ): Material {
const { particlesSqrt } = options;
const particles = particlesSqrt * particlesSqrt;
const material = new Material( quadVert, sphereParticleComputeFrag );
material.addUniform( 'particlesSqrt', '1f', particlesSqrt );
material.addUniform( 'particles', '1f', particles );
material.addUniform( 'ppp', '1f', SphereParticles.__ppp );
material.addUniformTexture( 'samplerRandom', options.textureRandom );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/sphere-particles-compute.frag', () => {
material.replaceShader( quadVert, sphereParticleComputeFrag );
} );
}
}
return material;
}
private __createGeometryRender( options: SphereParticlesOptions ): Geometry {
const { particlesSqrt } = options;
const particles = particlesSqrt * particlesSqrt;
const octahedron = genOctahedron( { radius: 1.0, div: 1 } );
const geometry = new InstancedGeometry();
geometry.addAttribute( 'position', octahedron.position );
geometry.addAttribute( 'normal', octahedron.normal );
geometry.setIndex( octahedron.index );
const bufferComputeUV = DISPLAY.glCat.createBuffer();
bufferComputeUV.setVertexbuffer( ( () => {
const ret = new Float32Array( particles * 2 );
for ( let iy = 0; iy < particlesSqrt; iy ++ ) {
for ( let ix = 0; ix < particlesSqrt; ix ++ ) {
const i = ix + iy * particlesSqrt;
const s = ( SphereParticles.__ppp * ix + 0.5 )
/ ( SphereParticles.__ppp * particlesSqrt );
const t = ( iy + 0.5 )
/ ( particlesSqrt );
ret[ i * 2 + 0 ] = s;
ret[ i * 2 + 1 ] = t;
}
}
return ret;
} )() );
geometry.addAttribute( 'computeUV', {
buffer: bufferComputeUV,
size: 2,
divisor: 1,
type: DISPLAY.gl.FLOAT
} );
geometry.count = octahedron.count;
geometry.mode = octahedron.mode;
geometry.primcount = options.particlesSqrt * options.particlesSqrt;
return geometry;
}
private __createMaterialRender( options: SphereParticlesOptions ): Material {
const material = new Material(
sphereParticleRenderVert,
sphereParticleRenderFrag,
{
defines: {
'USE_CLIP': 'true',
'USE_VERTEX_COLOR': 'true'
},
},
);
material.addUniform( 'colorVar', '1f', 0.1 );
material.addUniform( 'ppp', '1f', SphereParticles.__ppp );
material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/sphere-particles-render.vert', () => {
material.replaceShader(
sphereParticleRenderVert,
sphereParticleRenderFrag,
);
} );
}
}
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/sphere-particles-render.frag', () => {
material.replaceShader(
sphereParticleRenderVert,
sphereParticleRenderFrag,
);
} );
}
}
return material;
}
}

187
src/entities/Trails.ts Normal file
View File

@@ -0,0 +1,187 @@
import { GLCatTexture } from '@fms-cat/glcat-ts';
import { DISPLAY } from '../heck/DISPLAY';
import { Entity } from '../heck/Entity';
import { GPUParticles } from './GPUParticles';
import { Geometry } from '../heck/Geometry';
import { InstancedGeometry } from '../heck/InstancedGeometry';
import { Material } from '../heck/Material';
import quadVert from '../shaders/quad.vert';
import trailsComputeFrag from '../shaders/trails-compute.frag';
import trailsRenderFrag from '../shaders/trails-render.frag';
import trailsRenderVert from '../shaders/trails-render.vert';
export interface TrailsOptions {
trails: number;
trailLength: number;
textureRandom: GLCatTexture<WebGL2RenderingContext>;
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
}
export class Trails {
private static __ppp = 2;
public get entity(): Entity {
return this.__gpuParticles.entity;
}
private __gpuParticles: GPUParticles;
public get materialCompute(): Material {
return this.__gpuParticles.materialCompute;
}
public get materialRender(): Material {
return this.__gpuParticles.materialRender;
}
public constructor( options: TrailsOptions ) {
this.__gpuParticles = new GPUParticles( {
materialCompute: this.__createMaterialCompute( options ),
geometryRender: this.__createGeometryRender( options ),
materialRender: this.__createMaterialRender( options ),
computeWidth: Trails.__ppp * options.trailLength,
computeHeight: options.trails,
computeNumBuffers: 1,
namePrefix: process.env.DEV && 'Trails',
} );
}
private __createMaterialCompute( options: TrailsOptions ): Material {
const material = new Material( quadVert, trailsComputeFrag );
material.addUniform( 'trails', '1f', options.trails );
material.addUniform( 'trailLength', '1f', options.trailLength );
material.addUniform( 'ppp', '1f', Trails.__ppp );
material.addUniformTexture( 'samplerRandom', options.textureRandom );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/trails-compute.frag', () => {
material.replaceShader( quadVert, trailsComputeFrag );
} );
}
}
return material;
}
private __createGeometryRender( options: TrailsOptions ): Geometry {
const geometry = new InstancedGeometry();
const bufferComputeU = DISPLAY.glCat.createBuffer();
bufferComputeU.setVertexbuffer( ( () => {
const ret = new Float32Array( options.trailLength * 3 );
for ( let i = 0; i < options.trailLength; i ++ ) {
const u = ( Trails.__ppp * i + 0.5 ) / ( Trails.__ppp * options.trailLength );
ret[ i * 3 + 0 ] = u;
ret[ i * 3 + 1 ] = u;
ret[ i * 3 + 2 ] = u;
}
return ret;
} )() );
geometry.addAttribute( 'computeU', {
buffer: bufferComputeU,
size: 1,
type: DISPLAY.gl.FLOAT
} );
const bufferComputeV = DISPLAY.glCat.createBuffer();
bufferComputeV.setVertexbuffer( ( () => {
const ret = new Float32Array( options.trails );
for ( let i = 0; i < options.trails; i ++ ) {
ret[ i ] = ( i + 0.5 ) / options.trails;
}
return ret;
} )() );
geometry.addAttribute( 'computeV', {
buffer: bufferComputeV,
size: 1,
divisor: 1,
type: DISPLAY.gl.FLOAT
} );
const bufferTriIndex = DISPLAY.glCat.createBuffer();
bufferTriIndex.setVertexbuffer( ( () => {
const ret = new Float32Array( 3 * options.trailLength );
for ( let i = 0; i < options.trailLength; i ++ ) {
ret[ i * 3 + 0 ] = 0;
ret[ i * 3 + 1 ] = 1;
ret[ i * 3 + 2 ] = 2;
}
return ret;
} )() );
geometry.addAttribute( 'triIndex', {
buffer: bufferTriIndex,
size: 1,
type: DISPLAY.gl.FLOAT
} );
const indexBuffer = DISPLAY.glCat.createBuffer();
indexBuffer.setIndexbuffer( ( () => {
const ret = new Uint16Array( ( options.trailLength - 1 ) * 18 );
for ( let i = 0; i < options.trailLength - 1; i ++ ) {
for ( let j = 0; j < 3; j ++ ) {
const jn = ( j + 1 ) % 3;
ret[ i * 18 + j * 6 + 0 ] = i * 3 + j;
ret[ i * 18 + j * 6 + 1 ] = i * 3 + 3 + j;
ret[ i * 18 + j * 6 + 2 ] = i * 3 + 3 + jn;
ret[ i * 18 + j * 6 + 3 ] = i * 3 + j;
ret[ i * 18 + j * 6 + 4 ] = i * 3 + 3 + jn;
ret[ i * 18 + j * 6 + 5 ] = i * 3 + jn;
}
}
return ret;
} )() );
geometry.setIndex( {
buffer: indexBuffer,
type: DISPLAY.gl.UNSIGNED_SHORT
} );
geometry.count = ( options.trailLength - 1 ) * 18;
geometry.primcount = options.trails;
geometry.mode = DISPLAY.gl.TRIANGLES;
return geometry;
}
private __createMaterialRender( options: TrailsOptions ): Material {
const material = new Material(
trailsRenderVert,
trailsRenderFrag,
{
defines: {
'USE_CLIP': 'true',
'USE_VERTEX_COLOR': 'true'
}
},
);
material.addUniform( 'colorVar', '1f', 0.1 );
material.addUniform( 'ppp', '1f', Trails.__ppp );
material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
if ( process.env.DEV ) {
if ( module.hot ) {
module.hot.accept( '../shaders/trails-render.vert', () => {
material.replaceShader(
trailsRenderVert,
trailsRenderFrag,
);
} );
}
if ( module.hot ) {
module.hot.accept( '../shaders/trails-render.frag', () => {
material.replaceShader(
trailsRenderVert,
trailsRenderFrag,
);
} );
}
}
return material;
}
}

4
src/frag.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*.frag' {
const code: string;
export default code;
}

View File

@@ -0,0 +1,148 @@
import { GeometryAttribute, GeometryIndex } from '../heck/Geometry';
import { DISPLAY } from '../heck/DISPLAY';
interface ResultGenOctahedron {
position: GeometryAttribute;
normal: GeometryAttribute;
index: GeometryIndex;
count: number;
mode: GLenum;
}
export function genOctahedron( options: {
radius?: number;
div?: number;
} = {} ): ResultGenOctahedron {
const radius = options.radius ?? 1.0;
const div = options.div ?? 1;
const pos = [];
const nor = [];
const ind = [];
for ( let ii = 0; ii < 2; ii ++ ) { // side
for ( let ip = 0; ip < 4; ip ++ ) { // plane
for ( let iy = 0; iy < div + 1; iy ++ ) {
for ( let ix = 0; ix < iy + 1; ix ++ ) {
const lat0 = ( ii * 2.0 + iy / ( div + 1 ) ) * Math.PI / 2.0;
const lat1 = ( ii * 2.0 + ( iy + 1 ) / ( div + 1 ) ) * Math.PI / 2.0;
const lon0 = ( ii * 2.0 - 1.0 ) * ( ( ix - 1 ) / Math.max( 1, iy ) + ip ) * Math.PI / 2.0;
const lon1 = ( ii * 2.0 - 1.0 ) * ( ix / ( iy + 1 ) + ip ) * Math.PI / 2.0;
const lon2 = ( ii * 2.0 - 1.0 ) * ( ix / Math.max( 1, iy ) + ip ) * Math.PI / 2.0;
const lon3 = ( ii * 2.0 - 1.0 ) * ( ( ix + 1 ) / ( iy + 1 ) + ip ) * Math.PI / 2.0;
if ( ix !== 0 ) {
ind.push(
pos.length / 3,
pos.length / 3 + 1,
pos.length / 3 + 2
);
const x1 = radius * Math.sin( lat0 ) * Math.cos( lon0 );
const y1 = radius * Math.cos( lat0 );
const z1 = radius * Math.sin( lat0 ) * Math.sin( lon0 );
const x2 = radius * Math.sin( lat1 ) * Math.cos( lon1 );
const y2 = radius * Math.cos( lat1 );
const z2 = radius * Math.sin( lat1 ) * Math.sin( lon1 );
const x3 = radius * Math.sin( lat0 ) * Math.cos( lon2 );
const y3 = radius * Math.cos( lat0 );
const z3 = radius * Math.sin( lat0 ) * Math.sin( lon2 );
pos.push(
x1, y1, z1,
x2, y2, z2,
x3, y3, z3
);
{
const x = x1 + x2 + x3;
const y = y1 + y2 + y3;
const z = z1 + z2 + z3;
const l = Math.sqrt( x * x + y * y + z * z );
for ( let i = 0; i < 3; i ++ ) {
nor.push(
x / l,
y / l,
z / l
);
}
}
}
{
ind.push(
pos.length / 3,
pos.length / 3 + 1,
pos.length / 3 + 2
);
const x1 = radius * Math.sin( lat0 ) * Math.cos( lon2 );
const y1 = radius * Math.cos( lat0 );
const z1 = radius * Math.sin( lat0 ) * Math.sin( lon2 );
const x2 = radius * Math.sin( lat1 ) * Math.cos( lon1 );
const y2 = radius * Math.cos( lat1 );
const z2 = radius * Math.sin( lat1 ) * Math.sin( lon1 );
const x3 = radius * Math.sin( lat1 ) * Math.cos( lon3 );
const y3 = radius * Math.cos( lat1 );
const z3 = radius * Math.sin( lat1 ) * Math.sin( lon3 );
pos.push(
x1, y1, z1,
x2, y2, z2,
x3, y3, z3
);
{
const x = x1 + x2 + x3;
const y = y1 + y2 + y3;
const z = z1 + z2 + z3;
const l = Math.sqrt( x * x + y * y + z * z );
for ( let i = 0; i < 3; i ++ ) {
nor.push(
x / l,
y / l,
z / l
);
}
}
}
}
}
}
}
const position: GeometryAttribute = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.FLOAT,
size: 3
};
position.buffer.setVertexbuffer( new Float32Array( pos ) );
const normal: GeometryAttribute = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.FLOAT,
size: 3
};
normal.buffer.setVertexbuffer( new Float32Array( nor ) );
const index: GeometryIndex = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.UNSIGNED_SHORT
};
index.buffer.setIndexbuffer( new Uint16Array( ind ) );
return {
position,
normal,
index,
count: ind.length,
mode: DISPLAY.gl.TRIANGLES
};
}

View File

@@ -0,0 +1,87 @@
import { GeometryAttribute, GeometryIndex } from '../heck/Geometry';
import { DISPLAY } from '../heck/DISPLAY';
interface ResultGenTorus {
position: GeometryAttribute;
normal: GeometryAttribute;
index: GeometryIndex;
count: number;
mode: GLenum;
}
export function genTorus( options?: {
radiusRadial?: number;
radiusTubular?: number;
segmentsRadial?: number;
segmentsTubular?: number;
} ): ResultGenTorus {
const radiusRadial = options?.radiusRadial ?? 1;
const radiusTubular = options?.radiusTubular ?? 0; // WHOA
const segmentsRadial = options?.segmentsRadial ?? 64;
const segmentsTubular = options?.segmentsTubular ?? 4;
const pos = [];
const nor = [];
const ind = [];
for ( let iRad = 0; iRad < segmentsRadial; iRad ++ ) {
const iRadn = ( iRad + 1 ) % segmentsRadial;
const tRad = 2.0 * Math.PI * iRad / segmentsRadial;
const xRad = Math.cos( tRad );
const yRad = Math.sin( tRad );
for ( let iTub = 0; iTub < segmentsTubular; iTub ++ ) {
const iTubn = ( iTub + 1 ) % segmentsTubular;
const tTub = 2.0 * Math.PI * iTub / segmentsTubular;
const xTub = Math.cos( tTub );
const yTub = Math.sin( tTub );
const nx = xRad * xTub;
const ny = yTub;
const nz = yRad * xTub;
pos.push(
radiusRadial * xRad + radiusTubular * nx,
0.0 + radiusTubular * ny,
radiusRadial * yRad + radiusTubular * nz
);
nor.push( nx, ny, nz );
ind.push(
segmentsTubular * iRad + iTub,
segmentsTubular * iRad + iTubn,
segmentsTubular * iRadn + iTubn,
segmentsTubular * iRad + iTub,
segmentsTubular * iRadn + iTubn,
segmentsTubular * iRadn + iTub,
);
}
}
const position: GeometryAttribute = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.FLOAT,
size: 3
};
position.buffer.setVertexbuffer( new Float32Array( pos ) );
const normal: GeometryAttribute = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.FLOAT,
size: 3
};
normal.buffer.setVertexbuffer( new Float32Array( nor ) );
const index: GeometryIndex = {
buffer: DISPLAY.glCat.createBuffer(),
type: DISPLAY.gl.UNSIGNED_SHORT
};
index.buffer.setIndexbuffer( new Uint16Array( ind ) );
return {
position,
normal,
index,
count: ind.length,
mode: DISPLAY.gl.TRIANGLES
};
}

View File

@@ -0,0 +1,88 @@
import { GLCatFramebuffer, GLCatTexture } from '@fms-cat/glcat-ts';
import { DISPLAY } from './DISPLAY';
import { RenderTarget } from './RenderTarget';
export interface BufferRenderTargetOptions {
width: number;
height: number;
numBuffers?: number;
isFloat?: boolean;
name?: string;
}
export class BufferRenderTarget extends RenderTarget {
public static nameMap = new Map<string, BufferRenderTarget>();
private readonly __framebuffer: GLCatFramebuffer<WebGL2RenderingContext>;
public get framebuffer(): GLCatFramebuffer<WebGL2RenderingContext> {
return this.__framebuffer;
}
private __width: number;
public get width(): number {
return this.__width;
}
private __height: number;
public get height(): number {
return this.__height;
}
private __numBuffers: number;
public get numBuffers(): number {
return this.__numBuffers;
}
public constructor( options: BufferRenderTargetOptions ) {
super();
this.__framebuffer = DISPLAY.glCat.lazyDrawbuffers(
options.width,
options.height,
options.numBuffers || 1,
{
isFloat: options.isFloat || true
}
);
this.__width = options.width;
this.__height = options.height;
this.__numBuffers = options.numBuffers || 1;
if ( process.env.DEV ) {
if ( options.name ) {
if ( BufferRenderTarget.nameMap.has( options.name ) ) {
console.warn( `Duplicated BufferRenderTarget name, ${ options.name }` );
return;
}
BufferRenderTarget.nameMap.set( options.name, this );
} else {
console.warn( 'BufferRenderTarget created without name' );
}
}
}
public get texture(): GLCatTexture<WebGL2RenderingContext> {
return this.__framebuffer.texture!;
}
public getTexture( attachment: number ): GLCatTexture<WebGL2RenderingContext> | null {
return this.__framebuffer.getTexture( attachment );
}
public bind(): void {
const { gl, glCat } = DISPLAY;
gl.bindFramebuffer( gl.FRAMEBUFFER, this.__framebuffer.raw );
glCat.drawBuffers( this.__numBuffers );
gl.viewport( 0, 0, this.width, this.height );
}
public dispose(): void {
this.__framebuffer.dispose();
}
}

View File

@@ -0,0 +1,19 @@
import { DISPLAY } from './DISPLAY';
import { RenderTarget } from './RenderTarget';
export class CanvasRenderTarget extends RenderTarget {
public get width(): number {
return DISPLAY.canvas.width;
}
public get height(): number {
return DISPLAY.canvas.height;
}
public bind(): void {
const { gl, glCat } = DISPLAY;
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
glCat.drawBuffers( [ gl.BACK ] );
gl.viewport( 0, 0, this.width, this.height );
}
}

17
src/heck/DISPLAY.ts Normal file
View File

@@ -0,0 +1,17 @@
import { RESOLUTION } from '../config';
import { GLCat } from '@fms-cat/glcat-ts';
const canvas = document.createElement( 'canvas' );
canvas.width = RESOLUTION[ 0 ];
canvas.height = RESOLUTION[ 1 ];
const gl = canvas.getContext( 'webgl2' )!;
gl.lineWidth( 1 );
const glCat = new GLCat( gl );
export const DISPLAY = {
canvas,
gl,
glCat
};

43
src/heck/Dog.ts Normal file
View File

@@ -0,0 +1,43 @@
import { Entity } from './Entity';
import { Transform } from './Transform';
import { Music } from '../Music';
import { Component } from './components/Component';
let ha = 0;
/**
* And what a WONDERFUL Dog they are!!
*/
export class Dog {
public root: Entity;
public music: Music;
public active: boolean;
private __frameCount: number = 0;
public constructor( music: Music ) {
this.root = new Entity();
this.music = music;
this.active = true;
const update = (): void => {
if ( this.active ) {
this.music.update();
this.root.update( {
frameCount: this.__frameCount ++,
time: this.music.time,
deltaTime: this.music.deltaTime,
globalTransform: new Transform(),
parent: null
} );
}
if ( process.env.DEV ) {
Component.resetUpdateBreakpoint();
}
requestAnimationFrame( update );
};
update();
}
}

90
src/heck/Entity.ts Normal file
View File

@@ -0,0 +1,90 @@
import { Camera } from './components/Camera';
import { Component } from './components/Component';
import { Matrix4 } from '@fms-cat/experimental';
import { RenderTarget } from './RenderTarget';
import { Transform } from './Transform';
export interface EntityUpdateEvent {
frameCount: number;
time: number;
deltaTime: number;
globalTransform: Transform;
parent: Entity | null;
}
export interface EntityDrawEvent {
frameCount: number;
time: number;
renderTarget: RenderTarget;
globalTransform: Transform;
viewMatrix: Matrix4;
projectionMatrix: Matrix4;
camera: Camera;
}
export class Entity {
public readonly transform = new Transform();
public active = true;
public visible = true;
public children: Entity[] = [];
public components: Component[] = [];
public update( event: EntityUpdateEvent ): void {
if ( !this.active ) { return; }
const globalTransform = event.globalTransform.multiply( this.transform );
this.components.forEach( ( component ) => {
component.update( {
frameCount: event.frameCount,
time: event.time,
deltaTime: event.deltaTime,
globalTransform,
entity: this
} );
} );
this.children.forEach( ( child ) => {
child.update( {
frameCount: event.frameCount,
time: event.time,
deltaTime: event.deltaTime,
globalTransform,
parent: this
} );
} );
}
public draw( event: EntityDrawEvent ): void {
if ( !this.visible ) { return; }
const globalTransform = event.globalTransform.multiply( this.transform );
this.components.forEach( ( component ) => {
component.draw( {
frameCount: event.frameCount,
time: event.time,
renderTarget: event.renderTarget,
globalTransform,
camera: event.camera,
viewMatrix: event.viewMatrix,
projectionMatrix: event.projectionMatrix,
entity: this
} );
} );
this.children.forEach( ( child ) => {
child.draw( {
frameCount: event.frameCount,
time: event.time,
renderTarget: event.renderTarget,
globalTransform,
viewMatrix: event.viewMatrix,
projectionMatrix: event.projectionMatrix,
camera: event.camera
} );
} );
}
}

118
src/heck/Geometry.ts Normal file
View File

@@ -0,0 +1,118 @@
import { GLCatBuffer, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
import { DISPLAY } from './DISPLAY';
import { Material } from './Material';
export interface GeometryAttribute {
buffer: GLCatBuffer<WebGL2RenderingContext>;
size: number;
divisor?: number;
type: GLenum;
stride?: number;
offset?: number;
}
export interface GeometryIndex {
buffer: GLCatBuffer<WebGL2RenderingContext>;
type: GLenum;
}
export class Geometry {
public static typeSizeMap = {
[ DISPLAY.gl.BYTE ]: 1,
[ DISPLAY.gl.UNSIGNED_BYTE ]: 1,
[ DISPLAY.gl.SHORT ]: 2,
[ DISPLAY.gl.UNSIGNED_SHORT ]: 2,
[ DISPLAY.gl.INT ]: 4,
[ DISPLAY.gl.UNSIGNED_INT ]: 4,
[ DISPLAY.gl.HALF_FLOAT ]: 2,
[ DISPLAY.gl.FLOAT ]: 4,
};
public transformFeedback?: GLCatTransformFeedback<WebGL2RenderingContext> | null;
protected __attributes: {
[ name: string ]: GeometryAttribute;
} = {};
protected __index: GeometryIndex | null = null;
public mode: GLenum = /* GL_TRIANGLES */ 4;
public first = 0;
public count = 0;
public primcount: number | null = null;
public addAttribute( name: string, attribute: GeometryAttribute ): void {
this.__attributes[ name ] = attribute;
}
public removeAttribute( name: string, alsoDisposeBuffer = true ): void {
if ( alsoDisposeBuffer ) {
this.__attributes[ name ].buffer.dispose();
}
delete this.__attributes[ name ];
}
public setIndex( index: GeometryIndex | null ): void {
this.__index = index;
}
public assignBuffers( material: Material ): void {
const program = material.program;
Object.entries( this.__attributes ).forEach( ( [ name, attr ] ) => {
program.attribute(
name,
attr.buffer,
attr.size,
attr.divisor,
attr.type,
attr.stride,
attr.offset
);
} );
}
public draw(): void {
const { gl, glCat } = DISPLAY;
if ( process.env.DEV ) {
if ( this.count === 0 ) {
console.warn( 'You attempt to draw a geometry that count is 0' );
return;
}
}
if ( this.transformFeedback ) {
glCat.bindTransformFeedback( this.transformFeedback, () => {
gl.beginTransformFeedback( this.mode );
this.drawElementsOrArrays();
gl.endTransformFeedback();
} );
} else {
this.drawElementsOrArrays();
}
}
public drawElementsOrArrays(): void {
const { gl, glCat } = DISPLAY;
if ( this.__index ) {
glCat.bindIndexBuffer( this.__index.buffer, () => {
gl.drawElements(
this.mode,
this.count,
this.__index!.type,
this.first * Geometry.typeSizeMap[ this.__index!.type ]
);
} );
} else {
gl.drawArrays( this.mode, this.first, this.count );
}
}
public disposeBuffers(): void {
Object.values( this.__attributes ).forEach( ( attr ) => {
attr.buffer.dispose();
} );
}
}

View File

@@ -0,0 +1,36 @@
import { DISPLAY } from './DISPLAY';
import { Geometry } from './Geometry';
export class InstancedGeometry extends Geometry {
public primcount: number = 0;
public draw(): void {
const { gl } = DISPLAY;
if ( process.env.DEV ) {
if ( this.count === 0 ) {
console.warn( 'You attempt to draw an instanced geometry that count is 0' );
return;
}
if ( this.primcount === 0 ) {
console.warn( 'You attempt to draw an instanced geometry that primcount is 0' );
return;
}
}
if ( this.__index ) {
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.__index.buffer.raw );
gl.drawElementsInstanced(
this.mode,
this.count,
this.__index.type,
this.first * InstancedGeometry.typeSizeMap[ this.__index.type ],
this.primcount
);
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
} else {
gl.drawArraysInstanced( this.mode, this.first, this.count, this.primcount );
}
}
}

161
src/heck/Material.ts Normal file
View File

@@ -0,0 +1,161 @@
import { GLCatProgram, GLCatProgramLinkOptions, GLCatProgramUniformType, GLCatTexture } from '@fms-cat/glcat-ts';
import { DISPLAY } from './DISPLAY';
import { SHADERPOOL } from './ShaderPool';
export class Material {
protected __linkOptions: GLCatProgramLinkOptions;
protected __defines: {
[ name: string ]: ( string | undefined );
};
protected __uniforms: {
[ name: string ]: {
type: GLCatProgramUniformType;
value: number[];
};
} = {};
protected __uniformVectors: {
[ name: string ]: {
type: GLCatProgramUniformType;
value: Float32List | Int32List;
};
} = {};
protected __uniformTextures: {
[ name: string ]: {
texture: GLCatTexture<WebGL2RenderingContext> | null;
};
} = {};
private __vert: string;
public get vert(): string {
return this.__vert;
}
public get vertWithDefines(): string {
return this.__withDefines( this.vert );
}
private __frag: string;
public get frag(): string {
return this.__frag;
}
public get fragWithDefines(): string {
return this.__withDefines( this.frag );
}
public get program(): GLCatProgram<WebGL2RenderingContext> {
return SHADERPOOL.getProgram(
this,
this.vertWithDefines,
this.fragWithDefines,
);
}
public blend: [ GLenum, GLenum ] = [ DISPLAY.gl.ONE, DISPLAY.gl.ZERO ];
public constructor(
vert: string,
frag: string,
options?: {
defines?: { [ key: string ]: ( string | undefined ) },
linkOptions?: GLCatProgramLinkOptions,
},
) {
this.__vert = vert;
this.__frag = frag;
this.__linkOptions = options?.linkOptions ?? {};
this.__defines = options?.defines ?? {};
}
public addUniform( name: string, type: GLCatProgramUniformType, ...value: number[] ): void {
this.__uniforms[ name ] = { type, value };
}
public addUniformVector(
name: string,
type: GLCatProgramUniformType,
value: Float32List | Int32List
): void {
this.__uniformVectors[ name ] = { type, value };
}
public addUniformTexture( name: string, texture: GLCatTexture<WebGL2RenderingContext> | null ): void {
this.__uniformTextures[ name ] = { texture };
}
public setUniforms(): void {
const program = this.program;
Object.entries( this.__uniforms ).forEach( ( [ name, { type, value } ] ) => {
program.uniform( name, type, ...value );
} );
Object.entries( this.__uniformVectors ).forEach( ( [ name, { type, value } ] ) => {
program.uniformVector( name, type, value );
} );
Object.entries( this.__uniformTextures ).forEach( ( [ name, { texture } ] ) => {
program.uniformTexture( name, texture );
} );
}
public setBlendMode(): void {
const { gl } = DISPLAY;
gl.blendFunc( ...this.blend );
}
public async replaceShader(
vert: string,
frag: string,
options?: {
defines?: { [ key: string ]: ( string | undefined ) },
linkOptions?: GLCatProgramLinkOptions,
},
): Promise<void> {
if ( options?.defines ) {
this.__defines = { ...options.defines };
}
const program = await SHADERPOOL.getProgramAsync(
this,
this.__withDefines( vert ),
this.__withDefines( frag ),
options?.linkOptions,
).catch( ( e ) => {
console.error( e );
} );
if ( program ) {
const prevVert = this.vertWithDefines;
const prevFrag = this.fragWithDefines;
this.__vert = vert;
this.__frag = frag;
SHADERPOOL.discardProgram( this, prevVert, prevFrag );
}
}
protected __withDefines( code: string ): string {
if ( !code.startsWith( '#version' ) ) {
return code;
}
const lines = code.split( '\n' );
Object.entries( this.__defines ).map( ( [ key, value ] ) => {
if ( value != null ) {
lines.splice( 1, 0, `#define ${key} ${value}` );
}
} );
return lines.join( '\n' );
}
}

7
src/heck/RenderTarget.ts Normal file
View File

@@ -0,0 +1,7 @@
export abstract class RenderTarget {
public abstract get width(): number;
public abstract get height(): number;
public abstract bind(): void;
}

115
src/heck/ShaderPool.ts Normal file
View File

@@ -0,0 +1,115 @@
import { DISPLAY } from './DISPLAY';
import { GLCatProgram, GLCatProgramLinkOptions } from '@fms-cat/glcat-ts';
import { Material } from './Material';
export class ShaderPool<TUser> {
private __programMap: Map<string, GLCatProgram<WebGL2RenderingContext>> = new Map();
private __ongoingPromises: Map<string, Promise<GLCatProgram<WebGL2RenderingContext>>> = new Map();
private __programUsersMap: Map<GLCatProgram<WebGL2RenderingContext>, Set<TUser>> = new Map();
public getProgram(
user: TUser,
vert: string,
frag: string,
options?: GLCatProgramLinkOptions,
): GLCatProgram<WebGL2RenderingContext> {
let program = this.__programMap.get( vert + frag );
if ( !program ) {
if ( process.env.DEV ) {
try {
program = DISPLAY.glCat.lazyProgram( vert, frag, options );
} catch ( e ) {
console.error( user );
throw e;
}
} else {
program = DISPLAY.glCat.lazyProgram( vert, frag, options );
}
this.__programMap.set( vert + frag, program );
}
this.__setUser( user, program );
return program;
}
public async getProgramAsync(
user: TUser,
vert: string,
frag: string,
options?: GLCatProgramLinkOptions
): Promise<GLCatProgram<WebGL2RenderingContext>> {
let program = this.__programMap.get( vert + frag );
if ( !program ) {
let promise = this.__ongoingPromises.get( vert + frag );
if ( !promise ) {
if ( process.env.DEV ) {
promise = DISPLAY.glCat.lazyProgramAsync( vert, frag, options ).catch( ( e ) => {
console.error( user );
throw e;
} );
} else {
promise = DISPLAY.glCat.lazyProgramAsync( vert, frag, options );
}
promise.then( ( program ) => {
this.__programMap.set( vert + frag, program );
this.__ongoingPromises.delete( vert + frag );
} );
this.__ongoingPromises.set( vert + frag, promise );
}
program = await promise;
}
this.__setUser( user, program );
return program;
}
public discardProgram(
user: TUser,
vert: string,
frag: string,
): void {
const program = this.__programMap.get( vert + frag )!;
this.__deleteUser( user, program );
if ( this.__countUsers( program ) === 0 ) {
program.dispose( true );
this.__programMap.delete( vert + frag );
}
}
private __setUser( user: TUser, program: GLCatProgram<WebGL2RenderingContext> ): void {
let users = this.__programUsersMap.get( program );
if ( !users ) {
users = new Set();
this.__programUsersMap.set( program, users );
}
if ( !users.has( user ) ) {
users.add( user );
}
}
private __deleteUser( user: TUser, program: GLCatProgram<WebGL2RenderingContext> ): void {
const users = this.__programUsersMap.get( program )!;
if ( !users.has( user ) ) {
throw new Error( 'Attempt to delete an user of the program but the specified user is not an owner' );
}
users.delete( user );
}
private __countUsers( program: GLCatProgram<WebGL2RenderingContext> ): number {
const users = this.__programUsersMap.get( program )!;
return users.size;
}
}
export const SHADERPOOL = new ShaderPool<Material>();

64
src/heck/Transform.ts Normal file
View File

@@ -0,0 +1,64 @@
import { Matrix4, Quaternion, Vector3 } from '@fms-cat/experimental';
export class Transform {
protected __position: Vector3 = Vector3.zero;
public get position(): Vector3 {
return this.__position;
}
public set position( vector: Vector3 ) {
this.__position = vector;
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
}
protected __rotation: Quaternion = Quaternion.identity;
public get rotation(): Quaternion {
return this.__rotation;
}
public set rotation( quaternion: Quaternion ) {
this.__rotation = quaternion;
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
}
protected __scale: Vector3 = Vector3.one;
public get scale(): Vector3 {
return this.__scale;
}
public set scale( vector: Vector3 ) {
this.__scale = vector;
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
}
protected __matrix: Matrix4 = Matrix4.identity;
public get matrix(): Matrix4 {
return this.__matrix;
}
public set matrix( matrix: Matrix4 ) {
this.__matrix = matrix;
const decomposed = this.__matrix.decompose();
this.__position = decomposed.position;
this.__rotation = decomposed.rotation;
this.__scale = decomposed.scale;
}
public lookAt( position: Vector3, target?: Vector3, up?: Vector3, roll?: number ): void {
this.matrix = Matrix4.lookAt( position, target, up, roll );
}
public multiply( transform: Transform ): Transform {
const result = new Transform();
result.matrix = this.matrix.multiply( transform.matrix );
return result;
}
}

View File

@@ -0,0 +1,76 @@
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
import { DISPLAY } from '../DISPLAY';
import { Entity } from '../Entity';
import { Matrix4 } from '@fms-cat/experimental';
import { RenderTarget } from '../RenderTarget';
import { Transform } from '../Transform';
export interface CameraOptions extends ComponentOptions {
renderTarget?: RenderTarget;
projectionMatrix: Matrix4;
scene?: Entity;
clear?: Array<number | undefined> | false;
}
export abstract class Camera extends Component {
protected __projectionMatrix: Matrix4;
public get projectionMatrix(): Matrix4 {
return this.__projectionMatrix;
}
public renderTarget?: RenderTarget;
public scene?: Entity;
public clear: Array<number | undefined> | false = [];
public abstract get near(): number;
public abstract get far(): number;
public constructor( options: CameraOptions ) {
super( options );
this.visible = false;
this.renderTarget = options.renderTarget;
this.scene = options.scene;
this.__projectionMatrix = options.projectionMatrix;
if ( options.clear !== undefined ) { this.clear = options.clear; }
}
protected __updateImpl( event: ComponentUpdateEvent ): void {
const { renderTarget, scene } = this;
if ( !renderTarget ) {
throw new Error( process.env.DEV && 'You must assign a renderTarget to the Camera' );
}
if ( !scene ) {
throw new Error( process.env.DEV && 'You must assign a scene to the Camera' );
}
const viewMatrix = event.globalTransform.matrix.inverse!;
renderTarget.bind();
if ( this.clear ) {
DISPLAY.glCat.clear( ...this.clear );
}
scene.draw( {
frameCount: event.frameCount,
time: event.time,
renderTarget: renderTarget,
globalTransform: new Transform(),
viewMatrix,
projectionMatrix: this.__projectionMatrix,
camera: this
} );
if ( process.env.DEV ) {
Component.resetDrawBreakpoint();
}
}
}

View File

@@ -0,0 +1,189 @@
import { Camera } from './Camera';
import { Entity } from '../Entity';
import { Matrix4 } from '@fms-cat/experimental';
import { RenderTarget } from '../RenderTarget';
import { Transform } from '../Transform';
import { COMPONENT_DRAW_BREAKPOINT, COMPONENT_UPDATE_BREAKPOINT } from '../../config-hot';
export interface ComponentUpdateEvent {
frameCount: number;
time: number;
deltaTime: number;
globalTransform: Transform;
entity: Entity;
}
export interface ComponentDrawEvent {
frameCount: number;
time: number;
camera: Camera;
renderTarget: RenderTarget;
globalTransform: Transform;
viewMatrix: Matrix4;
projectionMatrix: Matrix4;
entity: Entity;
}
export interface ComponentOptions {
active?: boolean;
visible?: boolean;
name?: string;
ignoreBreakpoints?: boolean;
}
export class Component {
public static nameMap = new Map<string, Component>();
private static __updateHaveReachedBreakpoint = false;
private static __drawHaveReachedBreakpoint = false;
public static resetUpdateBreakpoint(): void {
if ( process.env.DEV ) {
if ( window.divComponentsUpdate ) {
if ( window.divComponentsUpdate.innerHTML !== window.strComponentsUpdate ) {
window.divComponentsUpdate.innerHTML = window.strComponentsUpdate ?? '';
}
}
window.strComponentsUpdate = '== update ==';
this.__updateHaveReachedBreakpoint = false;
}
}
public static resetDrawBreakpoint(): void {
if ( process.env.DEV ) {
if ( window.divComponentsDraw ) {
if ( window.divComponentsDraw.innerHTML !== window.strComponentsDraw ) {
window.divComponentsDraw.innerHTML = window.strComponentsDraw ?? '';
}
}
window.strComponentsDraw = '== draw ==';
this.__drawHaveReachedBreakpoint = false;
}
}
protected __lastUpdateFrame = 0;
public active: boolean;
public visible: boolean;
private __name?: string;
public get name(): string | undefined {
return this.__name;
}
public set name( name: string | undefined ) {
if ( process.env.DEV ) {
// remove previous one from the nameMap
if ( this.__name != null ) {
Component.nameMap.delete( this.__name );
}
this.__name = name;
// set the current one to the nameMap
if ( name != null ) {
if ( Component.nameMap.has( name ) ) {
console.warn( `Duplicated Component name, ${ name }` );
return;
}
Component.nameMap.set( name, this );
}
}
}
public ignoreBreakpoints?: boolean;
public constructor( options?: ComponentOptions ) {
this.active = options?.active ?? true;
this.visible = options?.visible ?? true;
if ( process.env.DEV ) {
this.name = options?.name;
this.ignoreBreakpoints = options?.ignoreBreakpoints;
if ( !this.name ) {
console.warn( 'Component created without name' );
}
}
}
public update( event: ComponentUpdateEvent ): void {
if ( !this.active ) { return; }
if ( this.__lastUpdateFrame === event.frameCount ) { return; }
this.__lastUpdateFrame = event.frameCount;
if ( process.env.DEV ) {
if ( Component.__updateHaveReachedBreakpoint && !this.ignoreBreakpoints ) {
return;
}
if ( window.strComponentsUpdate != null ) {
window.strComponentsUpdate += `\n${ this.name }`;
}
}
this.__updateImpl( event );
if ( process.env.DEV ) {
if ( COMPONENT_UPDATE_BREAKPOINT != null && COMPONENT_UPDATE_BREAKPOINT === this.name ) {
Component.__updateHaveReachedBreakpoint = true;
}
}
}
protected __updateImpl( event: ComponentUpdateEvent ): void { // eslint-disable-line
// do nothing
}
public draw( event: ComponentDrawEvent ): void {
if ( !this.visible ) { return; }
if ( process.env.DEV ) {
if ( window.strComponentsDraw != null ) {
window.strComponentsDraw += `\n${ this.name }`;
}
if ( Component.__drawHaveReachedBreakpoint && !this.ignoreBreakpoints ) {
return;
}
}
this.__drawImpl( event );
if ( process.env.DEV ) {
if ( COMPONENT_DRAW_BREAKPOINT != null && COMPONENT_DRAW_BREAKPOINT === this.name ) {
Component.__drawHaveReachedBreakpoint = true;
}
}
}
protected __drawImpl( event: ComponentDrawEvent ): void { // eslint-disable-line
// do nothing
}
}
if ( process.env.DEV ) {
const checkBreakpointNames = () => {
if (
COMPONENT_UPDATE_BREAKPOINT != null &&
Component.nameMap.get( COMPONENT_UPDATE_BREAKPOINT ?? '' ) == null
) {
console.warn( `Component: Cannot retrieve a component, COMPONENT_UPDATE_BREAKPOINT: ${ COMPONENT_UPDATE_BREAKPOINT }` );
}
if (
COMPONENT_DRAW_BREAKPOINT != null &&
Component.nameMap.get( COMPONENT_DRAW_BREAKPOINT ?? '' ) == null
) {
console.warn( `Component: Cannot retrieve a component, COMPONENT_DRAW_BREAKPOINT: ${ COMPONENT_DRAW_BREAKPOINT }` );
}
};
checkBreakpointNames();
if ( module.hot ) {
module.hot.accept( '../../config-hot', () => {
checkBreakpointNames();
} );
}
}

View File

@@ -0,0 +1,26 @@
import { Component, ComponentDrawEvent, ComponentOptions, ComponentUpdateEvent } from './Component';
export interface LambdaOptions extends ComponentOptions {
onUpdate?: ( event: ComponentUpdateEvent ) => void;
onDraw?: ( event: ComponentDrawEvent ) => void;
}
export class Lambda extends Component {
public onUpdate?: ( event: ComponentUpdateEvent ) => void;
public onDraw?: ( event: ComponentDrawEvent ) => void;
public constructor( options?: LambdaOptions ) {
super( options );
this.onUpdate = options?.onUpdate;
this.onDraw = options?.onDraw;
}
protected __updateImpl( event: ComponentUpdateEvent ): void {
this.onUpdate && this.onUpdate( event );
}
protected __drawImpl( event: ComponentDrawEvent ): void {
this.onDraw && this.onDraw( event );
}
}

View File

@@ -0,0 +1,18 @@
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
export class LogTransform extends Component {
public constructor( options?: ComponentOptions ) {
super( options );
this.visible = false;
}
protected __updateImpl( event: ComponentUpdateEvent ): void {
console.info( `
Position: ${ event.globalTransform.position }
Rotation: ${ event.globalTransform.rotation }
Scale: ${ event.globalTransform.scale }
Matrix: ${ event.globalTransform.matrix }
` );
}
}

View File

@@ -0,0 +1,70 @@
import { Component, ComponentDrawEvent, ComponentOptions } from './Component';
import { DISPLAY } from '../DISPLAY';
import { Geometry } from '../Geometry';
import { Material } from '../Material';
export enum MeshCull {
None,
Front,
Back,
Both
}
const meshCullMap = {
[ MeshCull.Front ]: /* GL_FRONT */ 1028,
[ MeshCull.Back ]: /* GL_BACK */ 1029,
[ MeshCull.Both ]: /* GL_FRONT_AND_BACK */ 1032
};
export interface MeshOptions extends ComponentOptions {
geometry: Geometry;
material: Material;
}
export class Mesh extends Component {
public geometry: Geometry;
public material: Material;
public cull: MeshCull = MeshCull.Back;
public constructor( options: MeshOptions ) {
super( options );
this.active = false;
this.geometry = options.geometry;
this.material = options.material;
}
protected __drawImpl( event: ComponentDrawEvent ): void {
const glCat = DISPLAY.glCat;
const gl = glCat.renderingContext;
const program = this.material.program;
glCat.useProgram( program );
this.material.setBlendMode();
if ( this.cull === MeshCull.None ) {
gl.disable( gl.CULL_FACE );
} else {
gl.enable( gl.CULL_FACE );
gl.cullFace( meshCullMap[ this.cull ] );
}
this.geometry.assignBuffers( this.material );
this.material.setUniforms();
program.uniform1f( 'time', event.time );
program.uniform1f( 'frameCount', event.frameCount );
program.uniform2f( 'resolution', event.renderTarget.width, event.renderTarget.height );
program.uniformMatrix4fv( 'normalMatrix', event.globalTransform.matrix.inverse!.transpose.elements );
program.uniformMatrix4fv( 'modelMatrix', event.globalTransform.matrix.elements );
program.uniformMatrix4fv( 'viewMatrix', event.viewMatrix.elements );
program.uniformMatrix4fv( 'projectionMatrix', event.projectionMatrix.elements );
this.geometry.draw();
}
}

View File

@@ -0,0 +1,47 @@
import { Camera } from './Camera';
import { Entity } from '../Entity';
import { Matrix4 } from '@fms-cat/experimental';
import { RenderTarget } from '../RenderTarget';
import { ComponentOptions } from './Component';
export interface PerspectiveCameraOptions extends ComponentOptions {
renderTarget?: RenderTarget;
near?: number;
far?: number;
fov?: number;
scene?: Entity;
clear?: Array<number | undefined> | false;
}
export class PerspectiveCamera extends Camera {
private __near: number;
public get near(): number {
return this.__near;
}
private __far: number;
public get far(): number {
return this.__far;
}
public constructor( options: PerspectiveCameraOptions ) {
const projectionMatrix = Matrix4.perspective(
options.fov || 45.0,
options.near || 0.01,
options.far || 100.0,
);
super( {
...options,
projectionMatrix,
renderTarget: options.renderTarget,
scene: options.scene,
clear: options.clear
} );
this.__near = options.near || 0.01;
this.__far = options.far || 100.0;
}
}

View File

@@ -0,0 +1,74 @@
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
import { DISPLAY } from '../DISPLAY';
import { Geometry } from '../Geometry';
import { Material } from '../Material';
import { RenderTarget } from '../RenderTarget';
import { TRIANGLE_STRIP_QUAD } from '@fms-cat/experimental';
const quadBuffer = DISPLAY.glCat.createBuffer();
quadBuffer.setVertexbuffer( new Float32Array( TRIANGLE_STRIP_QUAD ) );
const quadGeometry = new Geometry();
quadGeometry.addAttribute( 'p', {
buffer: quadBuffer,
size: 2,
type: /* GL_FLOAT */ 5126
} );
quadGeometry.count = 4;
quadGeometry.mode = /* GL_TRIANGLE_STRIP */ 5;
export interface QuadOptions extends ComponentOptions {
material: Material;
target: RenderTarget;
range?: [ number, number, number, number ];
clear?: Array<number | undefined> | false;
}
/**
* Renders a fullscreen quad.
*/
export class Quad extends Component {
public material: Material;
public target: RenderTarget;
public range: [ number, number, number, number ] = [ -1.0, -1.0, 1.0, 1.0 ];
public clear: Array<number | undefined> | false = false;
public constructor( options: QuadOptions ) {
super( options );
this.visible = false;
this.material = options.material;
this.target = options.target;
if ( options.range !== undefined ) { this.range = options.range; }
if ( options.clear !== undefined ) { this.clear = options.clear; }
}
protected __updateImpl( event: ComponentUpdateEvent ): void {
const { glCat } = DISPLAY;
glCat.useProgram( this.material.program );
this.target.bind();
this.material.setBlendMode();
if ( this.clear ) {
glCat.clear( ...this.clear );
}
quadGeometry.assignBuffers( this.material );
this.material.setUniforms();
const program = this.material.program;
program.uniform1f( 'time', event.time );
program.uniform1f( 'deltaTime', event.deltaTime );
program.uniform1f( 'frameCount', event.frameCount );
program.uniform2f( 'resolution', this.target.width, this.target.height );
program.uniform4f( 'range', ...this.range );
quadGeometry.draw();
}
}

354
src/main.ts Normal file
View File

@@ -0,0 +1,354 @@
import { Automaton } from '@fms-cat/automaton';
import { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
import * as automatonFxs from './automaton-fxs/automatonFxs';
import { Music } from './Music';
import automatonData from './automaton.json';
import { DISPLAY } from './heck/DISPLAY';
import { Dog } from './heck/Dog';
import { CanvasRenderTarget } from './heck/CanvasRenderTarget';
import { Lambda } from './heck/components/Lambda';
import { RandomTexture } from './utils/RandomTexture';
import { RANDOM_RESOLUTION, STATIC_RANDOM_RESOLUTION } from './config';
import { SphereParticles } from './entities/SphereParticles';
import { Swap, Vector3 } from '@fms-cat/experimental';
import { BufferRenderTarget } from './heck/BufferRenderTarget';
import { CameraEntity } from './entities/CameraEntity';
import { LightEntity } from './entities/LightEntity';
import { Bloom } from './entities/Bloom';
import { Post } from './entities/Post';
import { Raymarcher } from './entities/Raymarcher';
import { Trails } from './entities/Trails';
import { Glitch } from './entities/Glitch';
import { Rings } from './entities/Rings';
import { RTInspector } from './entities/RTInspector';
import { Component } from './heck/components/Component';
// == gl ===========================================================================================
const { canvas, glCat } = DISPLAY;
// == music ========================================================================================
const audio = new AudioContext();
const music = new Music( glCat, audio );
if ( process.env.DEV ) {
music.isPlaying = true;
}
// == dom ==========================================================================================
document.body.style.margin = '0';
document.body.style.padding = '0';
if ( process.env.DEV ) {
document.body.style.background = '#000';
document.body.style.width = '100%';
document.body.appendChild( canvas );
canvas.style.left = '0';
canvas.style.top = '0';
canvas.style.width = 'calc( 100% - 240px )';
window.divPath = document.createElement( 'div' );
document.body.appendChild( window.divPath );
window.divPath.style.position = 'fixed';
window.divPath.style.textAlign = 'right';
window.divPath.style.right = '8px';
window.divPath.style.bottom = '248px';
window.divPath.style.textShadow = '1px 1px 1px #ffffff';
window.divAutomaton = document.createElement( 'div' );
document.body.appendChild( window.divAutomaton );
window.divAutomaton.style.position = 'fixed';
window.divAutomaton.style.width = '100%';
window.divAutomaton.style.height = '240px';
window.divAutomaton.style.right = '0';
window.divAutomaton.style.bottom = '0';
window.checkActive = document.createElement( 'input' );
document.body.appendChild( window.checkActive );
window.checkActive.type = 'checkbox';
window.checkActive.checked = true;
window.checkActive.style.position = 'fixed';
window.checkActive.style.left = '8px';
window.checkActive.style.bottom = '248px';
window.strComponentsUpdate = '';
window.divComponentsUpdate = document.createElement( 'div' );
document.body.appendChild( window.divComponentsUpdate );
window.divComponentsUpdate.style.whiteSpace = 'pre-wrap';
window.divComponentsUpdate.style.color = '#ffffff';
window.divComponentsUpdate.style.font = '500 10px Wt-Position-Mono';
window.divComponentsUpdate.style.position = 'fixed';
window.divComponentsUpdate.style.padding = '0';
window.divComponentsUpdate.style.boxSizing = 'border-box';
window.divComponentsUpdate.style.width = '240px';
window.divComponentsUpdate.style.height = 'calc( ( 100% - 240px ) * 0.5 )';
window.divComponentsUpdate.style.right = '0';
window.divComponentsUpdate.style.top = '0';
window.divComponentsUpdate.style.overflow = 'scroll';
window.strComponentsDraw = '';
window.divComponentsDraw = document.createElement( 'div' );
document.body.appendChild( window.divComponentsDraw );
window.divComponentsDraw.style.whiteSpace = 'pre-wrap';
window.divComponentsDraw.style.color = '#ffffff';
window.divComponentsDraw.style.font = '500 10px Wt-Position-Mono';
window.divComponentsDraw.style.position = 'fixed';
window.divComponentsDraw.style.padding = '0';
window.divComponentsDraw.style.boxSizing = 'border-box';
window.divComponentsDraw.style.width = '240px';
window.divComponentsDraw.style.height = 'calc( ( 100% - 240px ) * 0.5 )';
window.divComponentsDraw.style.right = '0';
window.divComponentsDraw.style.top = 'calc( ( 100% - 240px ) * 0.5 )';
window.divComponentsDraw.style.overflow = 'scroll';
} else {
canvas.style.position = 'fixed';
canvas.style.left = '0';
canvas.style.top = '0';
document.body.style.width = canvas.style.width = '100%';
document.body.style.height = canvas.style.height = '100%';
const button = document.createElement( 'a' );
document.body.appendChild( button );
button.innerHTML = 'click me!';
button.onclick = () => {
document.body.appendChild( canvas );
music.isPlaying = true;
document.body.requestFullscreen();
};
}
// == automaton ====================================================================================
let totalFrame = 0;
let isInitialFrame = true;
let automaton: Automaton;
if ( process.env.DEV ) {
// this cast smells so bad
// https://github.com/FMS-Cat/automaton/issues/121
const automatonWithGUI = new AutomatonWithGUI(
automatonData,
{
gui: window.divAutomaton,
isPlaying: true,
fxDefinitions: automatonFxs,
}
);
automatonWithGUI.on( 'play', () => { music.isPlaying = true; } );
automatonWithGUI.on( 'pause', () => { music.isPlaying = false; } );
automatonWithGUI.on( 'seek', ( { time } ) => {
music.time = Math.max( 0.0, time );
automaton.reset();
} );
if ( module.hot ) {
module.hot.accept( './automaton.json', () => {
// we probably don't need this feature for now...
// See: https://github.com/FMS-Cat/automaton/issues/120
// automatonWithGUI.deserialize( automatonData );
} );
}
automaton = automatonWithGUI;
} else {
// this cast smells so bad
// https://github.com/FMS-Cat/automaton/issues/121
automaton = new Automaton(
automatonData,
{
fxDefinitions: automatonFxs
}
);
}
// == random texture ===============================================================================
const randomTexture = new RandomTexture(
glCat,
RANDOM_RESOLUTION[ 0 ],
RANDOM_RESOLUTION[ 1 ]
);
randomTexture.update();
const randomTextureStatic = new RandomTexture(
glCat,
STATIC_RANDOM_RESOLUTION[ 0 ],
STATIC_RANDOM_RESOLUTION[ 1 ]
);
randomTextureStatic.update();
// == scene ========================================================================================
const dog = new Dog( music );
const canvasRenderTarget = new CanvasRenderTarget();
// Mr. Update Everything
dog.root.components.push( new Lambda( {
onUpdate: () => {
totalFrame ++;
isInitialFrame = false;
randomTexture.update();
automaton.update( music.time );
},
visible: false,
name: process.env.DEV && 'main/update',
} ) );
// -- "objects" ------------------------------------------------------------------------------------
const sphereParticles = new SphereParticles( {
particlesSqrt: 256,
textureRandom: randomTexture.texture,
textureRandomStatic: randomTextureStatic.texture
} );
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 rings = new Rings();
dog.root.children.push( rings.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 = {
width: canvasRenderTarget.width,
height: canvasRenderTarget.height
};
const swap = new Swap(
new BufferRenderTarget( {
...swapOptions,
name: process.env.DEV && 'main/postSwap0',
} ),
new BufferRenderTarget( {
...swapOptions,
name: process.env.DEV && 'main/postSwap1',
} ),
);
const light = new LightEntity( {
root: dog.root,
shadowMapFov: 90.0,
shadowMapNear: 1.0,
shadowMapFar: 20.0,
namePrefix: process.env.DEV && 'light1',
} );
light.color = [ 60.0, 60.0, 60.0 ];
light.entity.transform.lookAt( new Vector3( [ -1.0, 2.0, 8.0 ] ) );
dog.root.children.push( light.entity );
const light2 = new LightEntity( {
root: dog.root,
shadowMapFov: 90.0,
shadowMapNear: 1.0,
shadowMapFar: 20.0,
namePrefix: process.env.DEV && 'light2',
} );
light2.color = [ 50.0, 30.0, 40.0 ];
light2.entity.transform.lookAt( new Vector3( [ -4.0, -2.0, 6.0 ] ) );
dog.root.children.push( light2.entity );
const camera = new CameraEntity( {
root: dog.root,
target: swap.o,
lights: [
light,
// light2
],
textureRandom: randomTexture.texture
} );
camera.camera.clear = [ 0.0, 0.0, 0.0, 0.0 ];
camera.entity.components.unshift( new Lambda( {
onUpdate: ( event ) => {
const t1 = 0.02 * Math.sin( event.time );
const s1 = Math.sin( t1 );
const c1 = Math.cos( t1 );
const t2 = 0.02 * Math.cos( event.time );
const s2 = Math.sin( t2 );
const c2 = Math.cos( t2 );
const r = 5.0;
camera.entity.transform.lookAt( new Vector3( [
r * c1 * s2,
r * s1,
r * c1 * c2
] ) );
},
visible: false,
name: process.env.DEV && 'main/updateCamera',
} ) );
dog.root.children.push( camera.entity );
swap.swap();
const bloom = new Bloom( {
input: swap.i.texture,
target: swap.o
} );
dog.root.children.push( bloom.entity );
swap.swap();
const glitch = new Glitch( {
input: swap.i.texture,
target: swap.o,
automaton,
} );
dog.root.children.push( glitch.entity );
swap.swap();
const post = new Post( {
input: swap.i.texture,
target: canvasRenderTarget
} );
dog.root.children.push( post.entity );
if ( process.env.DEV ) {
const rtInspector = new RTInspector( {
target: canvasRenderTarget
} );
dog.root.children.push( rtInspector.entity );
}
// -- keyboards ------------------------------------------------------------------------------------
if ( process.env.DEV ) {
window.addEventListener( 'keydown', ( event ) => {
if ( event.key === 'Escape' ) { // panic button
dog.root.active = false;
music.isPlaying = false;
window.checkActive!.checked = false;
}
} );
window.checkActive!.addEventListener( 'input', ( event: any ) => {
dog.root.active = event.target.checked;
music.isPlaying = event.target.checked;
} );
}
if ( !process.env.DEV ) {
window.addEventListener( 'keydown', ( event ) => {
if ( event.key === 'Escape' ) { // panic button
dog.root.active = false;
music.isPlaying = false;
}
} );
}
// -- wenis ----------------------------------------------------------------------------------------
if ( process.env.DEV ) {
console.info( Component.nameMap );
console.info( BufferRenderTarget.nameMap );
}

View File

@@ -0,0 +1,54 @@
float fractSin( float i ) {
return fract( sin( i ) * 1846.42 );
}
// 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 );
}
vec3 ifs( vec3 p, vec3 r, vec3 t ) {
vec3 s = t;
for ( int i = 0; i < 5; i ++ ) {
p = abs( p ) - abs( s ) * pow( 0.5, float( i ) );
s.yz = rot2d( r.x ) * s.yz;
s.zx = rot2d( r.y ) * s.zx;
s.xy = rot2d( r.z ) * s.xy;
p.xy = p.x < p.y ? p.yx : p.xy;
p.yz = p.y < p.z ? p.zy : p.yz;
p.xz = p.x < p.z ? p.zx : p.xz;
}
return p;
}
float box( vec3 p, vec3 d ) {
vec3 absp = abs( p );
return max( ( absp.x - d.x ), max( ( absp.y - d.y ), ( absp.z - d.z ) ) );
}
float distFunc( vec3 p, float time ) {
float dist = 1E9;
for ( int i = 0; i < 5; i ++ ) {
float fi = float( i );
vec3 trans = 0.5 * vec3(
sin( 6.0 * fractSin( fi * 2.874 ) + time * ( 1.0 + fractSin( fi * 2.271 ) ) ),
sin( 6.0 * fractSin( fi * 4.512 ) + time * ( 1.0 + fractSin( fi * 1.271 ) ) ),
sin( 6.0 * fractSin( fi * 3.112 ) + time * ( 1.0 + fractSin( fi * 3.271 ) ) )
);
dist = smin( dist, length( p - trans ) - 0.5, 1.0 );
}
return dist;
}
#pragma glslify: export(distFunc)

18
src/shaders/-prng.glsl Normal file
View File

@@ -0,0 +1,18 @@
float GPURnd(inout vec4 n)
{
// Based on the post http://gpgpu.org/forums/viewtopic.php?t=2591&sid=17051481b9f78fb49fba5b98a5e0f1f3
// (The page no longer exists as of March 17th, 2015. Please let me know if you see why this code works.)
const vec4 q = vec4( 1225.0, 1585.0, 2457.0, 2098.0);
const vec4 r = vec4( 1112.0, 367.0, 92.0, 265.0);
const vec4 a = vec4( 3423.0, 2646.0, 1707.0, 1999.0);
const vec4 m = vec4(4194287.0, 4194277.0, 4194191.0, 4194167.0);
vec4 beta = floor(n / q);
vec4 p = a * (n - beta * q) - beta * r;
beta = (sign(-p) + vec4(1.0)) * vec4(0.5) * m;
n = (p + beta);
return fract(dot(n / m, vec4(1.0, -1.0, 1.0, -1.0)));
}
#pragma glslify: export(GPURnd)

127
src/shaders/-simplex4d.glsl Normal file
View File

@@ -0,0 +1,127 @@
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec4 v4Mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0; }
float fMod289(float x) {
return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 v4Permute(vec4 x) {
return v4Mod289(((x*34.0)+1.0)*x);
}
float fPermute(float x) {
return fMod289(((x*34.0)+1.0)*x);
}
vec4 v4TaylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float fTaylorInvSqrt(float r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
vec4 grad4(float j, vec4 ip)
{
const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
vec4 p,s;
p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
s = vec4(lessThan(p, vec4(0.0)));
p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
return p;
}
float snoise(vec4 v)
{
const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4
0.276393202250021, // 2 * G4
0.414589803375032, // 3 * G4
-0.447213595499958); // -1 + 4 * G4
// First corner
vec4 i = floor(v + dot(v, vec4(0.309016994374947451)) ); // (sqrt(5) - 1)/4
vec4 x0 = v - i + dot(i, C.xxxx);
// Other corners
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
vec4 i0;
vec3 isX = step( x0.yzw, x0.xxx );
vec3 isYZ = step( x0.zww, x0.yyz );
// i0.x = dot( isX, vec3( 1.0 ) );
i0.x = isX.x + isX.y + isX.z;
i0.yzw = 1.0 - isX;
// i0.y += dot( isYZ.xy, vec2( 1.0 ) );
i0.y += isYZ.x + isYZ.y;
i0.zw += 1.0 - isYZ.xy;
i0.z += isYZ.z;
i0.w += 1.0 - isYZ.z;
// i0 now contains the unique values 0,1,2,3 in each channel
vec4 i3 = clamp( i0, 0.0, 1.0 );
vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
// x0 = x0 - 0.0 + 0.0 * C.xxxx
// x1 = x0 - i1 + 1.0 * C.xxxx
// x2 = x0 - i2 + 2.0 * C.xxxx
// x3 = x0 - i3 + 3.0 * C.xxxx
// x4 = x0 - 1.0 + 4.0 * C.xxxx
vec4 x1 = x0 - i1 + C.xxxx;
vec4 x2 = x0 - i2 + C.yyyy;
vec4 x3 = x0 - i3 + C.zzzz;
vec4 x4 = x0 + C.wwww;
// Permutations
i = v4Mod289(i);
float j0 = fPermute( fPermute( fPermute( fPermute(i.w) + i.z) + i.y) + i.x);
vec4 j1 = v4Permute( v4Permute( v4Permute( v4Permute (
i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
+ i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
+ i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
+ i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
vec4 p0 = grad4(j0, ip);
vec4 p1 = grad4(j1.x, ip);
vec4 p2 = grad4(j1.y, ip);
vec4 p3 = grad4(j1.z, ip);
vec4 p4 = grad4(j1.w, ip);
// Normalise gradients
vec4 norm = v4TaylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
p4 *= fTaylorInvSqrt(dot(p4,p4));
// Mix contributions from the five corners
vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);
m0 = m0 * m0;
m1 = m1 * m1;
return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
+ dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
}
#pragma glslify: export(snoise)

78
src/shaders/ao.frag Normal file
View File

@@ -0,0 +1,78 @@
#version 300 es
precision highp float;
const int AO_ITER = 16;
const float AO_BIAS = 0.0;
const float AO_RADIUS = 0.5;
const float PI = 3.14159265359;
const float TAU = 6.28318530718;
const float EPSILON = 1E-3;
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
in vec2 vUv;
out vec4 fragColor;
uniform vec2 resolution;
uniform mat4 cameraPV;
uniform sampler2D sampler0; // position.xyz, depth
uniform sampler2D sampler1; // normal.xyz
uniform sampler2D samplerRandom;
// == commons ======================================================================================
#pragma glslify: prng = require( ./-prng );
vec3 randomDirection( inout vec4 seed ) {
float phi16_ = TAU * prng( seed );
float theta = acos( -1.0 + 2.0 * prng( seed ) );
return vec3(
sin( theta ) * sin( phi16_ ),
cos( theta ),
sin( theta ) * cos( phi16_ )
);
}
// == features =====================================================================================
float ambientOcclusion( vec2 uv, vec3 position, vec3 normal ) {
float ao = 0.0;
vec4 seed = texture( samplerRandom, uv );
prng( seed );
for ( int i = 0; i < AO_ITER; i ++ ) {
vec3 dir = randomDirection( seed ) * prng( seed );
if ( dot( dir, normal ) < 0.0 ) { dir = -dir; }
vec4 screenPos = cameraPV * vec4( position + dir * AO_RADIUS, 1.0 );
screenPos.x *= resolution.y / resolution.x;
vec2 screenUv = screenPos.xy / screenPos.w * 0.5 + 0.5;
vec4 s0 = texture( sampler0, screenUv );
vec3 dDir = s0.xyz - position;
if ( length( dDir ) < 1E-2 ) {
ao += 1.0;
} else {
float dNor = dot( normalize( normal ), normalize( dDir ) );
ao += 1.0 - saturate( dNor - AO_BIAS ) / ( length( dDir ) + 1.0 );
}
}
ao = ao / float( AO_ITER );
return ao;
}
// == main procedure ===============================================================================
void main() {
vec4 tex0 = texture( sampler0, vUv );
vec4 tex1 = texture( sampler1, vUv );
vec3 position = tex0.xyz;
vec3 normal = tex1.xyz;
float ao = ambientOcclusion( vUv, position, normal );
fragColor = vec4( vec3( ao ), 1.0 );
}

View File

@@ -0,0 +1,35 @@
#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform bool isVert;
uniform vec2 resolution;
uniform sampler2D sampler0;
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 clampMin = floor( vUv / p ) * p + halfTexel;
vec2 clampMax = clampMin + p - 2.0 * halfTexel;
vec2 bv = halfTexel * ( isVert ? vec2( 0.0, 1.0 ) : vec2( 1.0, 0.0 ) );
vec4 sum = vec4( 0.0 );
sum += 0.2270270270 * texture( sampler0, vUv );
vec2 suv = clamp( vUv - bv * 1.3846153846, clampMin, clampMax );
sum += 0.3162162162 * texture( sampler0, suv );
suv = clamp( vUv + bv * 1.3846153846, clampMin, clampMax );
sum += 0.3162162162 * texture( sampler0, suv );
suv = clamp( vUv - bv * 3.2307692308, clampMin, clampMax );
sum += 0.0702702703 * texture( sampler0, suv );
suv = clamp( vUv + bv * 3.2307692308, clampMin, clampMax );
sum += 0.0702702703 * texture( sampler0, suv );
fragColor = vec4( sum.xyz, 1.0 );
}

View File

@@ -0,0 +1,20 @@
#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D samplerDry;
uniform sampler2D samplerWet;
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.xyz = max( vec3( 0.0 ), fragColor.xyz );
}

View File

@@ -0,0 +1,16 @@
#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D sampler0;
void main() {
fragColor = vec4(
max( vec3( 0.0 ), ( texture( sampler0, vUv ).xyz - 1.0 ) * 1.0 ),
1.0
);
}

2
src/shaders/discard.frag Normal file
View File

@@ -0,0 +1,2 @@
#version 300 es
void main(){discard;}

63
src/shaders/glitch.frag Normal file
View File

@@ -0,0 +1,63 @@
#version 300 es
precision highp float;
const int BARREL_ITER = 10;
#define saturate(i) clamp(i,0.,1.)
#define lofi(i,m) (floor((i)/(m))*(m))
in vec2 vUv;
out vec4 fragColor;
uniform float time;
uniform float amp;
uniform float barrelAmp;
uniform float barrelOffset;
uniform vec2 resolution;
uniform sampler2D sampler0;
// == common =======================================================================================
float fractSin( float v ) {
return fract( 17.351 * sin( 27.119 * v ) );
}
// == glitch =======================================================================================
vec2 displace( vec2 uv, float threshold ) {
float seed = fractSin( lofi( uv.y, 0.0625 ) + fractSin( lofi( uv.x, 0.25 ) ) );
if ( seed < threshold ) { return vec2( 0.0 ); }
vec2 d = vec2( 0.0 );
seed = fractSin( seed );
d.x = seed - 0.5;
return d;
}
// == fetch ========================================================================================
vec4 fetch( vec2 uv ) {
vec2 uvt = saturate( uv );
vec4 color = texture( sampler0, uvt );
return color;
}
// == main procedure ===============================================================================
void main() {
vec2 uv = vUv.xy;
vec2 d = vec2( 0.0 );
for ( int i = 0; i < 3; i ++ ) {
float p = pow( 2.4, float( i ) );
float thr = 1.0 - pow( amp, 6.0 );
thr = thr * pow( thr, float( i ) );
d += displace( uv * p + 50.0 * fractSin( 0.1 * time ), thr ) * 0.4 / p;
}
vec4 col = vec4( 0.0 );
col += fetch( uv + d * 0.60 ) * vec4( 1.0, 0.0, 0.0, 1.0 );
col += fetch( uv + d * 0.90 ) * vec4( 0.0, 1.0, 0.0, 1.0 );
col += fetch( uv + d * 1.20 ) * vec4( 0.0, 0.0, 1.0, 1.0 );
fragColor = col;
}

47
src/shaders/music.vert Normal file
View File

@@ -0,0 +1,47 @@
#version 300 es
precision highp float;
const float PI = 3.14159265359;
const float TAU = 6.28318530718;
uniform float bpm;
uniform float sampleRate;
uniform float _deltaSample;
uniform vec4 timeLength;
uniform vec4 _timeHead;
in float off;
out float outL;
out float outR;
float kick( float t ) {
if ( t < 0.0 ) { return 0.0; }
float attack = 4.0;
return exp( -4.0 * t ) * sin( TAU * (
50.0 * t - attack * ( exp( -40.0 * t ) + exp( -10.0 * t ) )
) );
}
vec2 mainAudio( vec4 time ) {
vec2 dest = vec2( 0.0 );
float tKick = time.x; // time.x = a beat
float aKick = kick( tKick );
dest += 0.0 * aKick;
return dest;
}
void main() {
vec2 out2 = mainAudio( mod( _timeHead + off * _deltaSample, timeLength ) );
outL = out2.x;
outR = out2.y;
}

23
src/shaders/normal.frag Normal file
View File

@@ -0,0 +1,23 @@
#version 300 es
precision highp float;
const int MTL_UNLIT = 1;
in float vLife;
in vec4 vPosition;
in vec3 vNormal;
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
uniform float time;
void main() {
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vec4( 0.5 + 0.5 * vNormal, 1.0 );
fragWTF = vec4( vec3( 0.0, 0.0, 0.0 ), MTL_UNLIT );
}

View File

@@ -0,0 +1,40 @@
#version 300 es
in vec3 position;
in vec3 normal;
in vec2 uv;
out vec4 vPosition;
out vec3 vNormal;
out vec2 vUv;
#ifdef USE_VERTEX_COLOR
in vec4 color;
out vec4 vColor;
#endif
uniform float inflate;
uniform vec2 resolution;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
// ------
void main() {
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
vPosition = modelMatrix * vec4( position + inflate * normal, 1.0 );
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
outPos.x *= resolution.y / resolution.x;
gl_Position = outPos;
vPosition.w = outPos.z / outPos.w;
#ifdef USE_VERTEX_COLOR
vColor = color;
#endif
vUv = uv;
}

39
src/shaders/object.vert Normal file
View File

@@ -0,0 +1,39 @@
#version 300 es
in vec3 position;
in vec3 normal;
in vec2 uv;
out vec4 vPosition;
out vec3 vNormal;
out vec2 vUv;
#ifdef USE_VERTEX_COLOR
in vec4 color;
out vec4 vColor;
#endif
uniform vec2 resolution;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
// ------
void main() {
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
vPosition = modelMatrix * vec4( position, 1.0 );
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
outPos.x *= resolution.y / resolution.x;
gl_Position = outPos;
vPosition.w = outPos.z / outPos.w;
#ifdef USE_VERTEX_COLOR
vColor = color;
#endif
vUv = uv;
}

View File

@@ -0,0 +1,24 @@
#version 300 es
precision highp float;
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
in vec2 vUv;
out vec4 fragColor;
uniform vec2 cameraNearFar;
uniform vec3 cameraPos;
uniform sampler2D sampler0;
void main() {
vec4 tex = texture( sampler0, vUv );
float depth = linearstep(
cameraNearFar.x,
cameraNearFar.y,
length( cameraPos - tex.xyz )
);
fragColor = vec4( depth, depth * depth, depth, 1.0 );
}

66
src/shaders/post.frag Normal file
View File

@@ -0,0 +1,66 @@
#version 300 es
precision highp float;
const int BARREL_ITER = 10;
const float BARREL_OFFSET = 0.05;
const float BARREL_AMP = 0.05;
const float HUGE = 9E16;
const float PI = 3.14159265;
#define saturate(i) clamp(i,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
#define lofi(i,m) (floor((i)/(m))*(m))
in vec2 vUv;
out vec4 fragColor;
uniform float time;
uniform vec2 resolution;
uniform sampler2D sampler0;
vec3 colorMap( vec3 i ) {
return vec3(
smoothstep( 0.0, 1.0, i.r ),
i.g,
0.1 + 0.8 * i.b
);
}
vec3 barrel( float amp, vec2 uv ) {
float corn = length( vec2( 0.5 ) );
float a = min( 3.0 * sqrt( amp ), corn * PI );
float zoom = corn / ( tan( corn * a ) + corn );
vec2 p = saturate(
( uv + normalize( uv - 0.5 ) * tan( length( uv - 0.5 ) * a ) ) * zoom +
0.5 * ( 1.0 - zoom )
);
return texture( sampler0, vec2( p.x, p.y ) ).xyz;
}
void main() {
vec2 uv = vUv;
vec2 p = ( uv * resolution * 2.0 - resolution ) / resolution.y;
float vig = 1.0 - length( p ) * 0.2;
vec3 tex = vec3( 0.0 );
for ( int i = 0; i < BARREL_ITER; i ++ ) {
float fi = ( float( i ) + 0.5 ) / float( BARREL_ITER );
vec3 a = saturate( vec3(
1.0 - 3.0 * abs( 1.0 / 6.0 - fi ),
1.0 - 3.0 * abs( 1.0 / 2.0 - fi ),
1.0 - 3.0 * abs( 5.0 / 6.0 - fi )
) ) / float( BARREL_ITER ) * 4.0;
tex += a * barrel( BARREL_OFFSET + BARREL_AMP * fi, uv );
}
tex = mix( vec3( 0.0 ), tex, vig );
vec3 col = pow( saturate( tex.xyz ), vec3( 0.4545 ) );
col.x = linearstep( 0.0, 1.2, col.x + 0.2 * uv.y );
col = colorMap( col );
fragColor = vec4( col, 1.0 );
}

12
src/shaders/quad.vert Normal file
View File

@@ -0,0 +1,12 @@
#version 300 es
in vec2 p;
out vec2 vUv;
uniform vec4 range;
void main() {
vUv = 0.5 + 0.5 * p;
gl_Position = vec4( mix( range.xy, range.zw, vUv ), 0.0, 1.0 );
}

View File

@@ -0,0 +1,83 @@
#version 300 es
precision highp float;
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
const int MARCH_ITER = 50;
const int MTL_UNLIT = 1;
const int MTL_PBR = 2;
const int MTL_GRADIENT = 3;
const int MTL_IRIDESCENT = 4;
in vec2 vUv;
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
uniform float time;
uniform vec2 resolution;
uniform vec2 cameraNearFar;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 inversePV;
uniform sampler2D samplerRandom;
uniform sampler2D samplerRandomStatic;
uniform sampler2D samplerCapture;
vec3 divideByW( vec4 v ) {
return v.xyz / v.w;
}
#pragma glslify: distFunc = require( ./-distFunc );
float fDistFunc( vec3 p ) {
return distFunc( p, time );
}
vec3 normalFunc( vec3 p, float dd ) {
vec2 d = vec2( 0.0, dd );
return normalize( vec3(
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
) );
}
void main() {
vec2 p = vUv * 2.0 - 1.0;
p.x *= resolution.x / resolution.y;
vec3 rayOri = divideByW( inversePV * vec4( p, 0.0, 1.0 ) );
vec3 farPos = divideByW( inversePV * vec4( p, 1.0, 1.0 ) );
vec3 rayDir = normalize( farPos - rayOri );
float rayLen = cameraNearFar.x;
vec3 rayPos = rayOri + rayDir * rayLen;
float dist;
for ( int i = 0; i < MARCH_ITER; i ++ ) {
dist = distFunc( rayPos, time );
rayLen += 0.7 * dist;
rayPos = rayOri + rayDir * rayLen;
if ( abs( dist ) < 1E-3 ) { break; }
}
if ( 0.01 < dist ) {
discard;
}
vec3 normal = normalFunc( rayPos, 1E-4 );
vec4 color = vec4( 0.1, 0.2, 0.4, 1.0 );
vec4 projPos = projectionMatrix * viewMatrix * vec4( rayPos, 1.0 ); // terrible
float depth = projPos.z / projPos.w;
gl_FragDepth = 0.5 + 0.5 * depth;
fragPosition = vec4( rayPos, depth );
fragNormal = vec4( normal, 1.0 );
fragColor = color;
fragWTF = vec4( vec3( 2.0, 0.9, 0.9 ), MTL_IRIDESCENT );
}

13
src/shaders/return.frag Normal file
View File

@@ -0,0 +1,13 @@
#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D sampler0;
void main() {
fragColor = texture( sampler0, vUv );
}

23
src/shaders/rings.frag Normal file
View File

@@ -0,0 +1,23 @@
#version 300 es
precision highp float;
const int MTL_UNLIT = 1;
in float vLife;
in vec4 vPosition;
in vec3 vNormal;
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
uniform float time;
void main() {
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vec4( 0.6, 1.0, 4.0, 1.0 );
fragWTF = vec4( vec3( 0.0, 0.0, 0.0 ), MTL_UNLIT );
}

65
src/shaders/rings.vert Normal file
View File

@@ -0,0 +1,65 @@
#version 300 es
const float TAU = 6.283185307;
in float instanceId;
in vec3 position;
in vec3 normal;
in vec2 uv;
out vec4 vPosition;
out vec3 vNormal;
out vec2 vUv;
#ifdef USE_VERTEX_COLOR
in vec4 color;
out vec4 vColor;
#endif
uniform float time;
uniform vec2 resolution;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
float seed;
mat2 rotate2D( float t ) {
return mat2( cos( t ), sin( t ), -sin( t ), cos( t ) );
}
float fs( float s ) {
return fract( sin( s * 114.514 ) * 1919.810 );
}
float random() {
seed = fs( seed );
return seed;
}
void main() {
seed = instanceId;
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
vPosition = vec4( mix( 2.0, 2.7, random() ) * position, 1.0 );
vPosition.xyz += mix( 0.002, 0.005, random() ) * normal;
vPosition.y += sin( random() * time + random() * vPosition.x + TAU * random() ) * 0.2 * random();
vPosition.y += sin( random() * time + random() * vPosition.z + TAU * random() ) * 0.2 * random();
vPosition.xy = rotate2D( 0.2 * ( random() - 0.5 ) ) * vPosition.xy;
vPosition.yz = rotate2D( 0.2 * ( random() - 0.5 ) ) * vPosition.yz;
vPosition = modelMatrix * vPosition;
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
outPos.x *= resolution.y / resolution.x;
gl_Position = outPos;
vPosition.w = outPos.z / outPos.w;
#ifdef USE_VERTEX_COLOR
vColor = color;
#endif
vUv = uv;
}

275
src/shaders/shading.frag Normal file
View File

@@ -0,0 +1,275 @@
#version 300 es
precision highp float;
const int MTL_NONE = 0;
const int MTL_UNLIT = 1;
const int MTL_PBR = 2;
const int MTL_GRADIENT = 3;
const int MTL_IRIDESCENT = 4;
const int AO_ITER = 8;
const float AO_BIAS = 0.0;
const float AO_RADIUS = 0.5;
const float PI = 3.14159265359;
const float TAU = 6.28318530718;
const float EPSILON = 1E-3;
const vec3 BLACK = vec3( 0.0 );
const vec3 DIELECTRIC_SPECULAR = vec3( 0.04 );
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
in vec2 vUv;
out vec4 fragColor;
uniform vec2 lightNearFar;
uniform vec2 cameraNearFar;
uniform vec3 cameraPos;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform mat4 lightPV;
uniform mat4 cameraView;
uniform mat4 cameraPV;
uniform sampler2D sampler0; // position.xyz, depth
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 samplerEnv;
uniform sampler2D samplerAo;
uniform sampler2D samplerRandom;
// == commons ======================================================================================
#pragma glslify: prng = require( ./-prng );
vec3 catColor( float _p ) {
return 0.5 + 0.5 * vec3(
cos( _p ),
cos( _p + PI / 3.0 * 4.0 ),
cos( _p + PI / 3.0 * 2.0 )
);
}
vec3 randomSphere( inout vec4 seed ) {
vec3 v;
for ( int i = 0; i < 10; i ++ ) {
v = vec3(
prng( seed ),
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
if ( length( v ) < 1.0 ) { break; }
}
return v;
}
vec3 blurpleGradient( float t ) {
vec3 colorA = vec3( 0.01, 0.04, 0.2 );
vec3 colorB = vec3( 0.02, 0.3, 0.9 );
vec3 colorC = vec3( 0.9, 0.01, 0.6 );
vec3 colorD = vec3( 0.5, 0.02, 0.02 );
return mix(
colorA,
mix(
colorB,
mix(
colorC,
colorD,
linearstep( 0.67, 1.0, t )
),
linearstep( 0.33, 0.67, t )
),
linearstep( 0.0, 0.33, t )
);
}
// == structs ======================================================================================
struct Isect {
vec2 screenUv;
vec3 albedo;
vec3 position;
float depth;
vec3 normal;
int materialId;
vec3 materialParams;
};
struct AngularInfo {
vec3 V;
vec3 L;
vec3 H;
float dotNV;
float dotNL;
float dotNH;
float dotVH;
};
AngularInfo genAngularInfo( Isect isect ) {
AngularInfo aI;
aI.V = normalize( cameraPos - isect.position );
aI.L = normalize( lightPos - isect.position );
aI.H = normalize( aI.V + aI.L );
aI.dotNV = clamp( dot( isect.normal, aI.V ), EPSILON, 1.0 );
aI.dotNL = clamp( dot( isect.normal, aI.L ), EPSILON, 1.0 );
aI.dotNH = clamp( dot( isect.normal, aI.H ), EPSILON, 1.0 );
aI.dotVH = clamp( dot( aI.V, aI.H ), EPSILON, 1.0 );
return aI;
}
// == features =====================================================================================
float castShadow( Isect isect, AngularInfo aI ) {
float depth = linearstep( lightNearFar.x, lightNearFar.y, length( isect.position - lightPos ) );
float bias = 0.0001 + 0.0001 * ( 1.0 - aI.dotNL );
depth -= bias;
vec4 proj = lightPV * vec4( isect.position, 1.0 );
vec2 uv = proj.xy / proj.w * 0.5 + 0.5;
vec4 tex = texture( samplerShadow, uv );
float edgeClip = smoothstep( 0.4, 0.5, max( abs( uv.x - 0.5 ), abs( uv.y - 0.5 ) ) );
float variance = saturate( tex.y - tex.x * tex.x );
float md = depth - tex.x;
float p = linearstep( 0.2, 1.0, variance / ( variance + md * md ) );
float softShadow = md < 0.0 ? 1.0 : p;
return mix(
softShadow,
1.0,
edgeClip
);
}
float calcDepth( vec3 pos ) {
float dist = length( cameraPos - pos );
float near = cameraNearFar.x;
float far = cameraNearFar.y;
return linearstep( near, far, dist );
}
// == shading functions ============================================================================
vec3 shadePBR( Isect isect, AngularInfo aI ) {
// ref: https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/master/src/shaders/metallic-roughness.frag
float roughness = isect.materialParams.x;
float metallic = isect.materialParams.y;
float emissive = isect.materialParams.z;
float shadow = castShadow( isect, aI );
shadow = mix( 1.0, shadow, 0.8 );
float ao = texture( samplerAo, isect.screenUv ).x;
shadow *= ao;
float lenL = length( isect.position - lightPos );
float decay = 1.0 / ( lenL * lenL );
vec3 F0 = mix( DIELECTRIC_SPECULAR, isect.albedo, metallic );
vec3 F = F0 + ( 1.0 - F0 ) * pow( 1.0 - aI.dotVH, 5.0 );
float roughnessSq = roughness * roughness;
float GGXV = aI.dotNL * sqrt( aI.dotNV * aI.dotNV * ( 1.0 - roughnessSq ) + roughnessSq );
float GGXL = aI.dotNV * sqrt( aI.dotNL * aI.dotNL * ( 1.0 - roughnessSq ) + roughnessSq );
float GGX = GGXV + GGXL;
float Vis = ( 0.0 < GGX ) ? ( 0.5 / GGX ) : 0.0;
float f = ( aI.dotNH * roughnessSq - aI.dotNH ) * aI.dotNH + 1.0;
float D = roughnessSq / ( PI * f * f );
vec3 diffuse = max( vec3( 0.0 ), ( 1.0 - F ) * ( isect.albedo / PI ) );
vec3 spec = max( vec3( 0.0 ), F * Vis * D );
vec3 shade = PI * lightColor * decay * shadow * aI.dotNL * ( diffuse + spec );
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 texEnv = 0.2 * pow( texture( samplerEnv, envCoord ).rgb, vec3( 2.2 ) );
// color += PI * texEnv * ( brdf.x * F0 + brdf.y );
color += emissive * aI.dotNV * isect.albedo;
#endif // IS_FIRST_LIGHT
return color;
}
vec3 shadeGradient( Isect isect ) {
vec3 ret;
#ifdef IS_FIRST_LIGHT
float shade = isect.normal.y;
ret = blurpleGradient( 0.5 + 0.5 * shade );
#else // IS_FIRST_LIGHT
ret = vec3( 0.0 );
#endif // IS_FIRST_LIGHT
return ret;
}
// == main procedure ===============================================================================
void main() {
vec4 tex0 = texture( sampler0, vUv );
vec4 tex1 = texture( sampler1, vUv );
vec4 tex2 = texture( sampler2, vUv );
vec4 tex3 = texture( sampler3, vUv );
Isect isect;
isect.screenUv = vUv;
isect.position = tex0.xyz;
isect.depth = tex0.w;
isect.normal = tex1.xyz;
isect.albedo = tex2.rgb;
isect.materialId = int( tex3.w + 0.5 );
isect.materialParams = tex3.xyz;
vec3 color = vec3( 0.0 );
if ( isect.materialId == MTL_NONE ) {
// do nothing
} else if ( isect.materialId == MTL_UNLIT ) {
#ifdef IS_FIRST_LIGHT
color = isect.albedo;
#endif
} else if ( isect.materialId == MTL_PBR ) {
AngularInfo aI = genAngularInfo( isect );
color = shadePBR( isect, aI );
} else if ( isect.materialId == MTL_GRADIENT ) {
color = shadeGradient( isect );
} else if ( isect.materialId == MTL_IRIDESCENT ) {
AngularInfo aI = genAngularInfo( isect );
isect.albedo *= mix(
vec3( 1.0 ),
catColor( isect.materialParams.x * aI.dotNV ),
isect.materialParams.y
);
isect.materialParams = vec3( 0.1, isect.materialParams.z, 0.0 );
color = shadePBR( isect, aI );
}
#ifdef IS_FIRST_LIGHT
// color = 0.5 + 0.5 * isect.normal;
// color = vec3( calcDepth( tex0.xyz ) );
// color = vec3( 0.8 ) * ( 1.0 - texture( samplerAo, isect.screenUv ).xyz );
#endif
// xfdA = shadeGradient( isect );
fragColor = vec4( color, 1.0 );
// fragColor.xyz *= smoothstep( 1.0, 0.7, calcDepth( tex0.xyz ) );
}

View File

@@ -0,0 +1,38 @@
#version 300 es
precision highp float;
const int SAMPLES = 30;
const float PI = 3.14159265;
const float MUL_THR = 1E-4;
#define saturate(i) clamp(i,0.,1.)
in vec2 vUv;
out vec4 fragColor;
uniform bool isVert;
uniform vec2 resolution;
uniform sampler2D sampler0;
float gaussian( float _x, float _v ) {
return 1.0 / sqrt( 2.0 * PI * _v ) * exp( - _x * _x / 2.0 / _v );
}
void main() {
vec2 texel = 1.0 / resolution;
vec2 bv = texel * ( isVert ? vec2( 0.0, 1.0 ) : vec2( 1.0, 0.0 ) );
vec4 sum = vec4( 0.0 );
vec4 tex = texture( sampler0, vUv );
sum += 0.29411764705882354 * tex;
vec2 suv = vUv - bv * 1.3333333333333333;
sum += 0.35294117647058826 * texture( sampler0, suv );
suv = vUv + bv * 1.3333333333333333;
sum += 0.35294117647058826 * texture( sampler0, suv );
fragColor = vec4( sum.xy, tex.z, 1.0 );
}

View File

@@ -0,0 +1,181 @@
#version 300 es
precision highp float;
const float PARTICLE_LIFE_LENGTH = 1.0;
const float HUGE = 9E16;
const float PI = 3.14159265;
const float TAU = 6.283185307;
#define saturate(i) clamp(i,0.,1.)
#define lofi(i,m) (floor((i)/(m))*(m))
#define lofir(i,m) (floor((i)/(m)+.5)*(m))
out vec4 fragColor;
uniform bool init;
uniform float time;
uniform float beat;
uniform float particlesSqrt;
uniform float ppp;
uniform float totalFrame;
uniform float deltaTime;
uniform float noiseScale;
uniform float noisePhase;
uniform vec2 resolution;
uniform sampler2D samplerCompute;
uniform sampler2D samplerRandom;
// uniform float velScale;
// uniform float genRate;
// ------
vec2 uvInvT( vec2 _uv ) {
return vec2( 0.0, 1.0 ) + vec2( 1.0, -1.0 ) * _uv;
}
// ------
#pragma glslify: distFunc = require( ./-distFunc );
float fDistFunc( vec3 p ) {
return distFunc( p, time );
}
vec3 normalFunc( vec3 p, float dd ) {
vec2 d = vec2( 0.0, dd );
return normalize( vec3(
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
) );
}
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
float fractSin( float i ) {
return fract( sin( i ) * 1846.42 );
}
vec4 sampleRandom( vec2 _uv ) {
return texture( samplerRandom, _uv );
}
#pragma glslify: prng = require( ./-prng );
#pragma glslify: noise = require( ./-simplex4d );
vec3 randomSphere( inout vec4 seed ) {
vec3 v;
for ( int i = 0; i < 10; i ++ ) {
v = vec3(
prng( seed ),
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
if ( length( v ) < 1.0 ) { break; }
}
return v;
}
vec2 randomCircle( inout vec4 seed ) {
vec2 v;
for ( int i = 0; i < 10; i ++ ) {
v = vec2(
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
if ( length( v ) < 1.0 ) { break; }
}
return v;
}
vec3 randomBox( inout vec4 seed ) {
vec3 v;
v = vec3(
prng( seed ),
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
return v;
}
// ------
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec2 puv = vec2( ( floor( gl_FragCoord.x / ppp ) * ppp + 0.5 ) / resolution.x, uv.y );
float pixId = mod( gl_FragCoord.x, ppp );
vec2 dpix = vec2( 1.0 ) / resolution;
float dt = deltaTime;
// == prepare some vars ==========================================================================
vec4 seed = texture( samplerRandom, puv );
prng( seed );
vec4 tex0 = texture( samplerCompute, puv );
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
vec3 pos = tex0.xyz;
float life = tex0.w;
vec3 vel = tex1.xyz;
float timing = mix(
0.0,
PARTICLE_LIFE_LENGTH,
( floor( puv.x * particlesSqrt ) / particlesSqrt + floor( puv.y * particlesSqrt ) ) / particlesSqrt
);
timing += lofi( time, PARTICLE_LIFE_LENGTH );
if ( time - deltaTime + PARTICLE_LIFE_LENGTH < timing ) {
timing -= PARTICLE_LIFE_LENGTH;
}
// == initialize particles =======================================================================
if (
time - deltaTime < timing && timing <= time
) {
dt = time - timing;
pos = 0.5 * randomSphere( seed );
vel = 1.0 * randomSphere( seed );
life = 1.0;
} else {
// do nothing
// if you want to remove init frag from the particle, do at here
}
// == update particles ===========================================================================
// distFunc
float dist = fDistFunc( pos.xyz ) - 0.2;
vec3 nor = normalFunc( pos.xyz, 1E-4 );
vel -= dt * 100.0 * dist * nor;
// spin around center
vel.zx += dt * 20.0 * vec2( -1.0, 1.0 ) * normalize( nor.xz );
// noise field
vel += 40.0 * vec3(
noise( vec4( pos.xyz, 1.485 + sin( time * 0.1 ) + noisePhase ) ),
noise( vec4( pos.xyz, 3.485 + sin( time * 0.1 ) + noisePhase ) ),
noise( vec4( pos.xyz, 5.485 + sin( time * 0.1 ) + noisePhase ) )
) * dt;
// resistance
vel *= exp( -10.0 * dt );
vec3 v = vel;
float vmax = max( abs( v.x ), max( abs( v.y ), abs( v.z ) ) );
pos += vel * dt;
life -= dt / PARTICLE_LIFE_LENGTH;
fragColor = (
pixId < 1.0 ? vec4( pos, life ) :
vec4( vel, 1.0 )
);
}

View File

@@ -0,0 +1,42 @@
#version 300 es
precision highp float;
const int MTL_UNLIT = 1;
const int MTL_PBR = 2;
const int MTL_IRIDESCENT = 4;
const float PI = 3.14159265;
const float TAU = 6.28318531;
#define saturate(i) clamp(i,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
// == varings / uniforms ===========================================================================
in float vLife;
in vec4 vPosition;
in vec3 vNormal;
in vec4 vColor;
in vec4 vRandom;
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
uniform float time;
// == common =======================================================================================
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
// == main procedure ===============================================================================
void main() {
if ( vColor.a < 0.0 ) { discard; }
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vec4( vColor.xyz, 1.0 );
fragWTF = vec4( vec3( 0.5, 0.5, 0.0 ), MTL_PBR );
}

View File

@@ -0,0 +1,108 @@
#version 300 es
const int MODE_RECT = 0;
const int MODE_GRID = 1;
const int MODE_CIRCLE = 2;
const int MODE_CHAR = 3;
const int MODE_BUTTON = 4;
const int MODE_ICON = 5;
const int MODES = 6;
const float HUGE = 9E16;
const float PI = 3.14159265;
const float TAU = 6.283185307;
#define saturate(i) clamp(i,0.,1.)
#define lofi(i,m) (floor((i)/(m))*(m))
#define lofir(i,m) (floor((i+0.5)/(m))*(m))
// -------------------------------------------------------------------------------------------------
in vec2 computeUV;
in vec3 position;
in vec3 normal;
out float vLife;
out vec2 vUv;
out vec3 vNormal;
out vec4 vPosition;
out vec4 vColor;
out vec4 vDice;
uniform bool isShadow;
uniform float ppp;
uniform float trailShaker;
uniform float colorVar;
uniform float colorOffset;
uniform vec2 resolution;
uniform vec2 resolutionCompute;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
uniform sampler2D samplerCompute;
uniform sampler2D samplerRandomStatic;
// -------------------------------------------------------------------------------------------------
vec3 catColor( float _p ) {
return 0.5 + 0.5 * vec3(
cos( _p ),
cos( _p + PI / 3.0 * 4.0 ),
cos( _p + PI / 3.0 * 2.0 )
);
}
vec4 random( vec2 _uv ) {
return texture( samplerRandomStatic, _uv );
}
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
// -------------------------------------------------------------------------------------------------
void main() {
vec2 puv = vec2( computeUV );
vec2 dppix = vec2( 1.0 ) / resolutionCompute;
// == fetch texture ==============================================================================
vec4 tex0 = texture( samplerCompute, puv );
vec4 tex1 = texture( samplerCompute, puv + dppix * vec2( 1.0, 0.0 ) );
// == assign varying variables ===================================================================
vDice = random( puv.xy * 182.92 );
vColor.xyz = 0.8 * mix( catColor( 2.0 + 40.0 * vDice.x ), vec3( 0.9 ), 0.0 );
vColor.xyz = vec3( 0.2 );
vLife = tex0.w;
// == compute size ===============================================================================
vPosition = vec4( tex0.xyz, 1.0 );
float size = vDice.x * 0.08;
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;
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;
// == send the vertex position ===================================================================
vPosition = modelMatrix * vPosition;
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
outPos.x *= resolution.y / resolution.x;
gl_Position = outPos;
vPosition.w = outPos.z / outPos.w;
// gl_PointSize = resolution.y * size / outPos.z;
}

View File

@@ -0,0 +1,218 @@
#version 300 es
precision highp float;
const float PARTICLE_LIFE_LENGTH = 5.0;
const float HUGE = 9E16;
const float PI = 3.14159265;
const float TAU = 6.283185307;
#define saturate(i) clamp(i,0.,1.)
#define lofi(i,m) (floor((i)/(m))*(m))
#define lofir(i,m) (floor((i)/(m)+.5)*(m))
// ------
out vec4 fragColor;
uniform float time;
uniform float beat;
uniform float trails;
uniform float trailLength;
uniform float ppp;
uniform float totalFrame;
uniform bool init;
uniform float deltaTime;
uniform vec2 resolution;
uniform sampler2D samplerCompute;
uniform sampler2D samplerRandom;
uniform float noiseScale;
uniform float noisePhase;
// uniform float velScale;
// uniform float genRate;
// ------
#pragma glslify: distFunc = require( ./-distFunc );
float fDistFunc( vec3 p ) {
return distFunc( p, time );
}
vec3 normalFunc( vec3 p, float dd ) {
vec2 d = vec2( 0.0, dd );
return normalize( vec3(
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
) );
}
vec2 uvInvT( vec2 _uv ) {
return vec2( 0.0, 1.0 ) + vec2( 1.0, -1.0 ) * _uv;
}
// ------
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
float fractSin( float i ) {
return fract( sin( i ) * 1846.42 );
}
vec4 sampleRandom( vec2 _uv ) {
return texture( samplerRandom, _uv );
}
#pragma glslify: prng = require( ./-prng );
#pragma glslify: noise = require( ./-simplex4d );
vec3 randomSphere( inout vec4 seed ) {
vec3 v;
for ( int i = 0; i < 10; i ++ ) {
v = vec3(
prng( seed ),
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
if ( length( v ) < 1.0 ) { break; }
}
return v;
}
vec2 randomCircle( inout vec4 seed ) {
vec2 v;
for ( int i = 0; i < 10; i ++ ) {
v = vec2(
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
if ( length( v ) < 1.0 ) { break; }
}
return v;
}
vec3 randomBox( inout vec4 seed ) {
vec3 v;
v = vec3(
prng( seed ),
prng( seed ),
prng( seed )
) * 2.0 - 1.0;
return v;
}
float uneune( float i, float p ) {
return sin( TAU * (
fractSin( i ) + floor( 1.0 + 4.0 * fractSin( i + 54.12 ) ) * p
) );
}
vec3 uneune3( float i, float p ) {
return vec3( uneune( i, p ), uneune( i + 11.87, p ), uneune( i + 21.92, p ) );
}
// ------
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec2 puv = vec2( ( floor( gl_FragCoord.x / ppp ) * ppp + 0.5 ) / resolution.x, uv.y );
float pixId = mod( gl_FragCoord.x, ppp );
vec2 dpix = vec2( 1.0 ) / resolution;
float dt = deltaTime;
// == if it is not head of particles =============================================================
if ( ppp < gl_FragCoord.x ) {
puv.x -= ppp / resolution.x;
vec4 tex0 = texture( samplerCompute, puv );
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
tex0.w = saturate( tex0.w - 1.0 / trailLength ); // decrease the life
fragColor = (
pixId < 1.0 ? tex0 :
tex1
);
return;
}
// == prepare some vars ==========================================================================
vec4 seed = texture( samplerRandom, puv );
prng( seed );
vec4 tex0 = texture( samplerCompute, puv );
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
vec3 pos = tex0.xyz;
float life = tex0.w;
vec3 vel = tex1.xyz;
float jumpFlag = tex1.w;
float timing = mix( 0.0, PARTICLE_LIFE_LENGTH, floor( puv.y * trails ) / trails );
timing += lofi( time, PARTICLE_LIFE_LENGTH );
if ( time - deltaTime + PARTICLE_LIFE_LENGTH < timing ) {
timing -= PARTICLE_LIFE_LENGTH;
}
// == initialize particles =======================================================================
if (
time - deltaTime < timing && timing <= time
) {
dt = time - timing;
pos = 1.0 * randomSphere( seed );
vel = 1.0 * randomSphere( seed );
life = 1.0;
jumpFlag = 1.0;
} else {
jumpFlag = 0.0; // remove jumping flag
}
// == update particles ===========================================================================
// distFunc
float dist = fDistFunc( pos.xyz ) - 0.2;
vec3 nor = normalFunc( pos.xyz, 1E-4 );
vel -= dt * 100.0 * dist * nor;
// spin around center
vel.zx += dt * 20.0 * vec2( -1.0, 1.0 ) * normalize( nor.xz );
// noise field
vel += 40.0 * vec3(
noise( vec4( pos.xyz, 1.485 + sin( time * 0.1 ) + noisePhase ) ),
noise( vec4( pos.xyz, 3.485 + sin( time * 0.1 ) + noisePhase ) ),
noise( vec4( pos.xyz, 5.485 + sin( time * 0.1 ) + noisePhase ) )
) * dt;
// resistance
vel *= exp( -10.0 * dt );
// vel.z += 10.0 * dt;
vec3 v = vel;
// float vmax = max( abs( v.x ), max( abs( v.y ), abs( v.z ) ) );
// v *= (
// abs( v.x ) == vmax ? vec3( 1.0, 0.0, 0.0 ) :
// abs( v.y ) == vmax ? vec3( 0.0, 1.0, 0.0 ) :
// vec3( 0.0, 0.0, 1.0 )
// );
// pos.xyz += velScale * v * dt;
pos += v * dt;
life -= dt / PARTICLE_LIFE_LENGTH;
fragColor = (
pixId < 1.0 ? vec4( pos, life ) :
vec4( vel, jumpFlag )
);
}

View File

@@ -0,0 +1,44 @@
#version 300 es
precision highp float;
const int MTL_UNLIT = 1;
const int MTL_PBR = 2;
const float PI = 3.14159265;
const float TAU = 6.28318531;
#define saturate(i) clamp(i,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
// == varings / uniforms ===========================================================================
in vec4 vPosition;
in vec3 vNormal;
in vec4 vColor;
in float vLife;
in vec4 vRandom;
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
uniform float time;
// == common =======================================================================================
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
// == main procedure ===============================================================================
void main() {
if ( vColor.a < 0.0 ) { discard; }
float emissive = 4.0;
// emissive *= 0.5 + 0.5 * sin( TAU * vRandom.z + 20.0 * time );
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vColor;
fragWTF = vec4( vec3( 0.9, 0.9, emissive ), MTL_PBR );
}

View File

@@ -0,0 +1,123 @@
#version 300 es
const float HUGE = 9E16;
const float PI = 3.14159265;
const float TAU = 6.283185307;
#define saturate(x) clamp(x,0.,1.)
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
#define lofi(i,m) (floor((i)/(m))*(m))
#define lofir(i,m) (floor((i+0.5)/(m))*(m))
// -------------------------------------------------------------------------------------------------
in float computeU;
in float computeV;
in float triIndex;
out float vLife;
out vec3 vNormal;
out vec4 vPosition;
out vec4 vColor;
out vec4 vRandom;
uniform bool isShadow;
uniform float ppp;
uniform float trailShaker;
uniform float colorVar;
uniform float colorOffset;
uniform vec2 resolution;
uniform vec2 resolutionCompute;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
uniform sampler2D samplerCompute;
uniform sampler2D samplerRandomStatic;
// -------------------------------------------------------------------------------------------------
vec3 blurpleGradient( float t ) {
vec3 colorA = vec3( 0.01, 0.04, 0.2 );
vec3 colorB = vec3( 0.02, 0.3, 0.9 );
vec3 colorC = vec3( 0.9, 0.01, 0.6 );
vec3 colorD = vec3( 0.5, 0.02, 0.02 );
return mix(
colorA,
mix(
colorB,
mix(
colorC,
colorD,
linearstep( 0.67, 1.0, t )
),
linearstep( 0.33, 0.67, t )
),
linearstep( 0.0, 0.33, t )
);
}
vec3 catColor( float _p ) {
return 0.5 + 0.5 * vec3(
cos( _p ),
cos( _p + PI / 3.0 * 4.0 ),
cos( _p + PI / 3.0 * 2.0 )
);
}
vec4 random( vec2 _uv ) {
return texture( samplerRandomStatic, _uv );
}
mat2 rotate2D( float _t ) {
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
}
// -------------------------------------------------------------------------------------------------
void main() {
vec2 puv = vec2( computeU, computeV );
vec2 dppix = vec2( 1.0 ) / resolutionCompute;
// == fetch texture ==============================================================================
vec4 pos = texture( samplerCompute, puv );
vec4 vel = texture( samplerCompute, puv + dppix * vec2( 1.0, 0.0 ) );
vec4 velp = texture( samplerCompute, puv + dppix * vec2( -ppp + 1.0, 0.0 ) );
// == assign varying variables ===================================================================
vLife = pos.w;
vRandom = random( puv.yy * 182.92 );
vColor.xyz = (
vRandom.y < 0.8
? pow( catColor( TAU * ( ( vRandom.x * 2.0 - 1.0 ) * colorVar + 0.6 + colorOffset ) ), vec3( 2.0 ) )
: vec3( 0.4 )
);
vColor.xyz = blurpleGradient( vLife );
// vColor.xyz = catColor( 3.0 + 4.0 * vLife );
vColor.w = ( velp.w < 0.5 && vel.w < 0.5 && 0.0 < vLife ) ? 1.0 : -1.0;
// == compute size and direction =================================================================
float size = 0.005;
size *= 1.0 + pow( vRandom.w, 2.0 );
// size *= max( 0.0, sin( PI * vLife ) );
vec3 dir = normalize( vel.xyz );
vec3 sid = normalize( cross( dir, vec3( 0.0, 1.0, 0.0 ) ) );
vec3 top = normalize( cross( sid, dir ) );
float theta = triIndex / 3.0 * TAU + vLife * 1.0;
vec2 tri = vec2( sin( theta ), cos( theta ) );
vNormal = ( normalMatrix * vec4( tri.x * sid + tri.y * top, 1.0 ) ).xyz;
pos.xyz += size * vNormal;
vPosition = modelMatrix * vec4( pos.xyz, 1.0 );
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
outPos.x *= resolution.y / resolution.x;
gl_Position = outPos;
vPosition.w = outPos.z / outPos.w;
// gl_PointSize = resolution.y * size / outPos.z;
}

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

@@ -0,0 +1,18 @@
export class Pool<T> {
public array: T[];
private __index = 0;
public get current(): T {
return this.array[ this.__index ];
}
public constructor( array: T[] ) {
this.array = array;
}
public next(): T {
this.__index = ( this.__index + 1 ) % this.array.length;
return this.current;
}
}

View File

@@ -0,0 +1,54 @@
import { GLCat, GLCatTexture } from '@fms-cat/glcat-ts';
import { Xorshift } from '@fms-cat/experimental';
import { DISPLAY } from '../heck/DISPLAY';
export class RandomTexture {
private __texture: GLCatTexture<WebGL2RenderingContext>;
private __array: Uint8Array;
private __rng: Xorshift;
private __width: number;
private __height: number;
public constructor(
glCat: GLCat<WebGL2RenderingContext>,
width: number,
height: number,
) {
this.__width = width;
this.__height = height;
this.__rng = new Xorshift();
this.__array = new Uint8Array( width * height * 4 );
this.__texture = glCat.createTexture()!;
this.__texture.textureWrap( DISPLAY.gl.REPEAT );
}
public get texture(): GLCatTexture<WebGL2RenderingContext> {
return this.__texture;
}
public dispose(): void {
this.__texture.dispose();
}
public resize( width: number, height: number, seed?: number ): void {
this.__width = width;
this.__height = height;
this.__array = new Uint8Array( width * height * 4 );
this.update( seed );
}
public update( seed?: number ): void {
if ( seed ) { this.__rng.seed = seed; }
for ( let i = 0; i < this.__array.length; i ++ ) {
this.__array[ i ] = Math.floor( this.__rng.gen() * 256.0 );
}
this.__texture.setTextureFromArray(
this.__width,
this.__height,
this.__array
);
}
}

View File

@@ -0,0 +1,5 @@
export function createDefineString( defines: { [ identifier: string ]: string | undefined } ): string {
return Object.entries( defines ).map( ( [ identifier, value ] ) => (
value ? `#define ${identifier} ${value}\n` : ''
) ).join( '' );
}

3
src/utils/mod.ts Normal file
View File

@@ -0,0 +1,3 @@
export function mod( value: number, divisor: number ): number {
return value - Math.floor( value / divisor ) * divisor;
}

4
src/vert.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*.vert' {
const code: string;
export default code;
}

9
src/window.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
interface Window {
divPath?: HTMLDivElement;
divAutomaton?: HTMLDivElement;
checkActive?: HTMLInputElement;
strComponentsUpdate?: string;
divComponentsUpdate?: HTMLDivElement;
strComponentsDraw?: string;
divComponentsDraw?: HTMLDivElement;
}

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"moduleResolution": "node",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": false,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
]
}

84
webpack.config.js Normal file
View File

@@ -0,0 +1,84 @@
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const HtmlWebpackPlugin = require( 'html-webpack-plugin' );
const packageJson = require( './package.json' );
const path = require( 'path' );
const webpack = require( 'webpack' );
module.exports = ( env, argv ) => {
const VERSION = packageJson.version;
const DEV = argv.mode === 'development';
// console.info( `Webpack: Building ${ packageJson.name } v${ VERSION } under ${ argv.mode } settings...` );
return {
entry: path.resolve( __dirname, 'src/main.ts' ),
output: {
path: path.join( __dirname, 'dist' ),
filename: 'bundle.js',
},
resolve: {
extensions: [ '.js', '.ts' ],
},
module: {
rules: [
{
test: /automaton\.json$/,
use: [
{
loader: path.resolve( __dirname, './loaders/automaton-json-loader.js' ),
options: {
minimize: DEV ? false : {
precisionTime: 3,
precisionValue: 3,
}
}
},
],
type: 'javascript/auto',
},
{
test: /\.(glsl|frag|vert)$/,
use: [
'raw-loader',
// shader minifier is kinda jej
// want to try it works well in development phase
{
loader: path.resolve( __dirname, './loaders/shader-minifier-loader.js' ),
options: {
preserveExternals: true,
},
},
'glslify-loader',
],
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
'ts-loader',
]
},
],
},
optimization: {
minimize: !DEV,
moduleIds: DEV ? 'named' : undefined,
usedExports: !DEV,
},
devServer: {
inline: true,
hot: true
},
devtool: DEV ? 'inline-source-map' : 'source-map',
plugins: [
new webpack.DefinePlugin( {
'process.env': {
DEV,
VERSION: `"${ VERSION }"`
},
} ),
new HtmlWebpackPlugin(),
],
};
};

4431
yarn.lock Normal file

File diff suppressed because it is too large Load Diff