Fix lights

This commit is contained in:
schmelczerandras 2020-09-20 19:23:22 +02:00
parent 13843f87a8
commit e44be0c5ee
17 changed files with 179 additions and 117 deletions

5
README.md Normal file
View file

@ -0,0 +1,5 @@
<img src="static/logo-colored.svg" width=128 height=128 alt="logo" />
# SDF-2D library
Documentation will be provided by the end of september 2020.

View file

@ -19,6 +19,10 @@
"url": "https://github.com/schmelczerandras/sdf-2d/issues", "url": "https://github.com/schmelczerandras/sdf-2d/issues",
"email": "andras@schmelczer.dev" "email": "andras@schmelczer.dev"
}, },
"repository": {
"type": "git",
"url": "git://github.com/schmelczerandras/sdf-2d.git"
},
"license": "ISC", "license": "ISC",
"scripts": { "scripts": {
"start": "shx rm -rf lib/* && webpack --mode development -w", "start": "shx rm -rf lib/* && webpack --mode development -w",
@ -30,10 +34,11 @@
"lib" "lib"
], ],
"sideEffects": false, "sideEffects": false,
"optimization": { "dependencies": {
"usedExports": true "gl-matrix": "^3.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/gl-matrix": "^2.4.5",
"@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1", "@typescript-eslint/parser": "^3.10.1",
"eslint": "^7.9.0", "eslint": "^7.9.0",
@ -41,7 +46,6 @@
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-unused-imports": "^0.1.3", "eslint-plugin-unused-imports": "^0.1.3",
"gl-matrix": "^3.3.0",
"prettier": "^2.1.2", "prettier": "^2.1.2",
"raw-loader": "^4.0.1", "raw-loader": "^4.0.1",
"shx": "^0.3.2", "shx": "^0.3.2",

View file

@ -5,7 +5,7 @@ export abstract class Drawable {
// This should be overriden by inherited classes // This should be overriden by inherited classes
public static readonly descriptor: DrawableDescriptor; public static readonly descriptor: DrawableDescriptor;
public abstract distance(target: vec2): number; public abstract minDistance(target: vec2): number;
protected abstract getObjectToSerialize(transform2d: mat2d, transform1d: number): any; protected abstract getObjectToSerialize(transform2d: mat2d, transform1d: number): any;

View file

@ -1,8 +1,8 @@
import { mat2d, vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor'; import { DrawableDescriptor } from '../drawable-descriptor';
import { LightDrawable } from './light-drawable';
export class CircleLight extends Drawable { export class CircleLight extends LightDrawable {
public static readonly descriptor: DrawableDescriptor = { public static readonly descriptor: DrawableDescriptor = {
uniformName: 'circleLights', uniformName: 'circleLights',
uniformCountMacroName: 'CIRCLE_LIGHT_COUNT', uniformCountMacroName: 'CIRCLE_LIGHT_COUNT',
@ -10,19 +10,7 @@ export class CircleLight extends Drawable {
empty: new CircleLight(vec2.fromValues(0, 0), vec3.fromValues(0, 0, 0), 0), empty: new CircleLight(vec2.fromValues(0, 0), vec3.fromValues(0, 0, 0), 0),
}; };
constructor(public center: vec2, public color: vec3, public intensity: number) { constructor(center: vec2, color: vec3, intensity: number) {
super(); super(center, color, intensity);
}
public distance(target: vec2): number {
return vec2.dist(this.center, target);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
color: this.color,
intensity: this.intensity,
};
} }
} }

View file

@ -1,39 +1,33 @@
import { mat2d, vec2, vec3 } from 'gl-matrix'; import { mat2d, vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor'; import { DrawableDescriptor } from '../drawable-descriptor';
import { LightDrawable } from './light-drawable';
export class Flashlight extends Drawable { export class Flashlight extends LightDrawable {
public static readonly descriptor: DrawableDescriptor = { public static readonly descriptor: DrawableDescriptor = {
uniformName: 'flashlights', uniformName: 'flashlights',
uniformCountMacroName: 'FLASHLIGHT_COUNT', uniformCountMacroName: 'FLASHLIGHT_COUNT',
shaderCombinationSteps: [0, 1, 2, 4], shaderCombinationSteps: [0, 1, 2, 4],
empty: new Flashlight( empty: new Flashlight(
vec2.fromValues(0, 0),
vec2.fromValues(0, 0), vec2.fromValues(0, 0),
vec3.fromValues(0, 0, 0), vec3.fromValues(0, 0, 0),
0 0,
vec2.fromValues(0, 0)
), ),
}; };
public constructor( public constructor(
public center: vec2, center: vec2,
public direction: vec2, color: vec3,
public color: vec3, intensity: number,
public intensity: number public direction: vec2
) { ) {
super(); super(center, color, intensity);
}
public distance(_: vec2): number {
return 0;
} }
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any { protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return { return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d), ...super.getObjectToSerialize(transform2d, transform1d),
direction: this.direction, direction: this.direction,
intensity: this.intensity,
color: this.color,
}; };
} }
} }

View file

@ -0,0 +1,26 @@
import { mat2d, vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../../main';
export class LightDrawable extends Drawable {
protected lightnessRatio = 1;
constructor(public center: vec2, public color: vec3, public intensity: number) {
super();
}
public setLightnessRatio(ratio: number) {
this.lightnessRatio = ratio;
}
public minDistance(target: vec2): number {
return vec2.dist(this.center, target);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
color: this.color,
intensity: this.intensity * this.lightnessRatio,
};
}
}

View file

@ -36,7 +36,7 @@ export class Circle extends Drawable {
super(); super();
} }
public distance(target: vec2): number { public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius; return vec2.dist(this.center, target) - this.radius;
} }

View file

@ -60,7 +60,7 @@ export class InvertedTunnel extends Drawable {
super(); super();
} }
public distance(target: vec2): number { public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from); const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);

View file

@ -65,7 +65,7 @@ export class Tunnel extends Drawable {
super(); super();
} }
public distance(target: vec2): number { public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from); const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);

View file

@ -1,33 +1,13 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Drawable } from '../../drawables/drawable'; import { Drawable } from '../../../drawables/drawable';
import { DrawableDescriptor } from '../../drawables/drawable-descriptor'; import { Insights } from '../insights';
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer'; import { RenderPass } from './render-pass';
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
import { Insights } from './insights';
import { RenderingPassName } from './rendering-pass-name';
export class RenderingPass { export class DistanceRenderPass extends RenderPass {
public tileMultiplier = 8; public tileMultiplier = 8;
public isWorldInverted = false; public isWorldInverted = false;
private drawables: Array<Drawable> = []; private drawables: Array<Drawable> = [];
private program: UniformArrayAutoScalingProgram;
constructor(
gl: WebGL2RenderingContext,
private frame: FrameBuffer,
private name: RenderingPassName
) {
this.program = new UniformArrayAutoScalingProgram(gl);
}
public async initialize(
shaderSources: [string, string],
descriptors: Array<DrawableDescriptor>,
substitutions: { [name: string]: any } = {}
): Promise<void> {
await this.program.initialize(shaderSources, descriptors, substitutions);
}
public addDrawable(drawable: Drawable) { public addDrawable(drawable: Drawable) {
this.drawables.push(drawable); this.drawables.push(drawable);
@ -72,7 +52,7 @@ export class RenderingPass {
); );
const drawablesNearTile = this.drawables.filter( const drawablesNearTile = this.drawables.filter(
(d) => d.distance(tileCenterWorldCoordinates) < 2 * worldR (d) => d.minDistance(tileCenterWorldCoordinates) < 2 * worldR
); );
drawnDrawablesCount += drawablesNearTile.length; drawnDrawablesCount += drawablesNearTile.length;
@ -89,18 +69,19 @@ export class RenderingPass {
} }
} }
Insights.setValue(['render pass', this.name, 'all drawables'], this.drawables.length);
Insights.setValue(['render pass', this.name, 'tile count'], this.tileMultiplier ** 2);
Insights.setValue( Insights.setValue(
['render pass', this.name, 'rendered drawables'], ['render pass', 'distance', 'all drawables'],
this.drawables.length
);
Insights.setValue(
['render pass', 'distance', 'tile count'],
this.tileMultiplier ** 2
);
Insights.setValue(
['render pass', 'distance', 'rendered drawables'],
drawnDrawablesCount / this.tileMultiplier ** 2 drawnDrawablesCount / this.tileMultiplier ** 2
); );
this.drawables = []; this.drawables = [];
} }
public destroy(): void {
this.frame.destroy();
this.program.destroy();
}
} }

View file

@ -0,0 +1,58 @@
import { vec2 } from 'gl-matrix';
import { LightDrawable } from '../../../drawables/lights/light-drawable';
import { clamp01 } from '../../../helper/clamp';
import { Insights } from '../insights';
import { RenderPass } from './render-pass';
export class LightsRenderPass extends RenderPass {
public lightPhaseOutLength = 400;
private drawables: Array<LightDrawable> = [];
public addDrawable(drawable: LightDrawable) {
this.drawables.push(drawable);
}
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
this.frame.bindAndClear(inputTexture);
const tileCenterWorldCoordinates = vec2.transformMat2d(
vec2.create(),
vec2.fromValues(0.5, 0.5),
commonUniforms.uvToWorld
);
const drawablesNearTile = this.drawables.filter((l) => {
const d = vec2.subtract(
vec2.create(),
[
Math.abs(l.center.x - tileCenterWorldCoordinates.x),
Math.abs(l.center.y - tileCenterWorldCoordinates.y),
],
vec2.scale(vec2.create(), commonUniforms.worldAreaInView, 0.5)
);
const distance =
vec2.length([Math.max(d.x, 0), Math.max(d.y, 0)]) +
Math.min(Math.max(d.x, d.y), 0.0);
l.setLightnessRatio(clamp01(1 - distance / this.lightPhaseOutLength));
return distance < this.lightPhaseOutLength;
});
drawablesNearTile.forEach((p) =>
p.serializeToUniforms(
commonUniforms,
commonUniforms.transformWorldToNDC,
commonUniforms.scaleWorldLengthToNDC
)
);
this.program.draw(commonUniforms);
Insights.setValue(
['render pass', 'lights', 'rendered drawables'],
drawablesNearTile.length
);
this.drawables = [];
}
}

View file

@ -0,0 +1,26 @@
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer';
import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program';
export abstract class RenderPass {
protected program: UniformArrayAutoScalingProgram;
constructor(gl: WebGL2RenderingContext, protected frame: FrameBuffer) {
this.program = new UniformArrayAutoScalingProgram(gl);
}
public async initialize(
shaderSources: [string, string],
descriptors: Array<DrawableDescriptor>,
substitutions: { [name: string]: any } = {}
): Promise<void> {
await this.program.initialize(shaderSources, descriptors, substitutions);
}
public abstract render(commonUniforms: any, inputTexture?: WebGLTexture): void;
public destroy(): void {
this.frame.destroy();
this.program.destroy();
}
}

View file

@ -1,4 +0,0 @@
export enum RenderingPassName {
distance = 'distance',
pixel = 'pixel',
}

View file

@ -1,6 +1,7 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../../drawables/drawable'; import { Drawable } from '../../drawables/drawable';
import { DrawableDescriptor } from '../../drawables/drawable-descriptor'; import { DrawableDescriptor } from '../../drawables/drawable-descriptor';
import { LightDrawable } from '../../drawables/lights/light-drawable';
import { msToString } from '../../helper/ms-to-string'; import { msToString } from '../../helper/ms-to-string';
import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer'; import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer';
import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer'; import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer';
@ -9,9 +10,9 @@ import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { ParallelCompiler } from '../graphics-library/parallel-compiler'; import { ParallelCompiler } from '../graphics-library/parallel-compiler';
import { FpsAutoscaler } from './fps-autoscaler'; import { FpsAutoscaler } from './fps-autoscaler';
import { Insights } from './insights'; import { Insights } from './insights';
import { DistanceRenderPass } from './render-pass/distance-render-pass';
import { LightsRenderPass } from './render-pass/lights-render-pass';
import { Renderer } from './renderer'; import { Renderer } from './renderer';
import { RenderingPass } from './rendering-pass';
import { RenderingPassName } from './rendering-pass-name';
import { RuntimeSettings } from './settings/runtime-settings'; import { RuntimeSettings } from './settings/runtime-settings';
import { StartupSettings } from './settings/startup-settings'; import { StartupSettings } from './settings/startup-settings';
import distanceFragmentShader from './shaders/distance-fs.glsl'; import distanceFragmentShader from './shaders/distance-fs.glsl';
@ -20,15 +21,14 @@ import lightsFragmentShader from './shaders/shading-fs.glsl';
import lightsVertexShader from './shaders/shading-vs.glsl'; import lightsVertexShader from './shaders/shading-vs.glsl';
import { UniformsProvider } from './uniforms-provider'; import { UniformsProvider } from './uniforms-provider';
type Passes = { [key in keyof typeof RenderingPassName]: RenderingPass };
export class WebGl2Renderer implements Renderer { export class WebGl2Renderer implements Renderer {
private gl: WebGL2RenderingContext; private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch; private stopwatch?: WebGlStopwatch;
private uniformsProvider: UniformsProvider; private uniformsProvider: UniformsProvider;
private distanceFieldFrameBuffer: IntermediateFrameBuffer; private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private distancePass: DistanceRenderPass;
private lightsPass: LightsRenderPass;
private lightingFrameBuffer: DefaultFrameBuffer; private lightingFrameBuffer: DefaultFrameBuffer;
private passes: Passes;
private autoscaler: FpsAutoscaler; private autoscaler: FpsAutoscaler;
private applyRuntimeSettings: { private applyRuntimeSettings: {
@ -39,12 +39,10 @@ export class WebGl2Renderer implements Renderer {
this.lightingFrameBuffer.enableHighDpiRendering = v; this.lightingFrameBuffer.enableHighDpiRendering = v;
}, },
tileMultiplier: (v) => { tileMultiplier: (v) => {
this.passes[RenderingPassName.distance].tileMultiplier = v; this.distancePass.tileMultiplier = v;
this.passes[RenderingPassName.pixel].tileMultiplier = 1;
}, },
isWorldInverted: (v) => { isWorldInverted: (v) => {
this.passes[RenderingPassName.distance].isWorldInverted = v; this.distancePass.isWorldInverted = v;
this.passes[RenderingPassName.pixel].isWorldInverted = v;
}, },
shadowLength: (v) => { shadowLength: (v) => {
this.uniformsProvider.shadowLength = v; this.uniformsProvider.shadowLength = v;
@ -75,9 +73,8 @@ export class WebGl2Renderer implements Renderer {
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl); this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl);
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl); this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl);
this.passes = this.createPasses(); this.distancePass = new DistanceRenderPass(this.gl, this.distanceFieldFrameBuffer);
this.lightsPass = new LightsRenderPass(this.gl, this.lightingFrameBuffer);
this.passes.pixel.tileMultiplier = 1;
this.uniformsProvider = new UniformsProvider(this.gl); this.uniformsProvider = new UniformsProvider(this.gl);
@ -88,21 +85,6 @@ export class WebGl2Renderer implements Renderer {
}); });
} }
private createPasses(): Passes {
return {
[RenderingPassName.distance]: new RenderingPass(
this.gl,
this.distanceFieldFrameBuffer,
RenderingPassName.distance
),
[RenderingPassName.pixel]: new RenderingPass(
this.gl,
this.lightingFrameBuffer,
RenderingPassName.pixel
),
};
}
public async initialize( public async initialize(
palette: Array<vec3>, palette: Array<vec3>,
settingsOverrides: Partial<StartupSettings> settingsOverrides: Partial<StartupSettings>
@ -112,14 +94,14 @@ export class WebGl2Renderer implements Renderer {
const promises: Array<Promise<void>> = []; const promises: Array<Promise<void>> = [];
promises.push( promises.push(
this.passes[RenderingPassName.distance].initialize( this.distancePass.initialize(
[distanceVertexShader, distanceFragmentShader], [distanceVertexShader, distanceFragmentShader],
this.descriptors.filter(WebGl2Renderer.hasSdf) this.descriptors.filter(WebGl2Renderer.hasSdf)
) )
); );
promises.push( promises.push(
this.passes[RenderingPassName.pixel].initialize( this.lightsPass.initialize(
[lightsVertexShader, lightsFragmentShader], [lightsVertexShader, lightsFragmentShader],
this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)), this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)),
{ {
@ -150,9 +132,9 @@ export class WebGl2Renderer implements Renderer {
public addDrawable(drawable: Drawable): void { public addDrawable(drawable: Drawable): void {
if (WebGl2Renderer.hasSdf((drawable.constructor as typeof Drawable).descriptor)) { if (WebGl2Renderer.hasSdf((drawable.constructor as typeof Drawable).descriptor)) {
this.passes[RenderingPassName.distance].addDrawable(drawable); this.distancePass.addDrawable(drawable);
} else { } else {
this.passes[RenderingPassName.pixel].addDrawable(drawable); this.lightsPass.addDrawable(drawable as LightDrawable);
} }
} }
@ -193,10 +175,8 @@ export class WebGl2Renderer implements Renderer {
shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()), shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
}; };
this.passes[RenderingPassName.distance].render( this.distancePass.render(this.uniformsProvider.getUniforms(common));
this.uniformsProvider.getUniforms(common) this.lightsPass.render(
);
this.passes[RenderingPassName.pixel].render(
this.uniformsProvider.getUniforms(common), this.uniformsProvider.getUniforms(common),
this.distanceFieldFrameBuffer.colorTexture this.distanceFieldFrameBuffer.colorTexture
); );
@ -219,7 +199,7 @@ export class WebGl2Renderer implements Renderer {
} }
public destroy(): void { public destroy(): void {
this.passes.distance.destroy(); this.distancePass.destroy();
this.passes.pixel.destroy(); this.lightsPass.destroy();
} }
} }

View file

@ -5,6 +5,7 @@ import { Renderer } from './graphics/rendering/renderer';
import { StartupSettings } from './graphics/rendering/settings/startup-settings'; import { StartupSettings } from './graphics/rendering/settings/startup-settings';
import { WebGl2Renderer } from './graphics/rendering/webgl2-renderer'; import { WebGl2Renderer } from './graphics/rendering/webgl2-renderer';
import { applyArrayPlugins } from './helper/array'; import { applyArrayPlugins } from './helper/array';
export { Drawable } from './drawables/drawable'; export { Drawable } from './drawables/drawable';
export { DrawableDescriptor } from './drawables/drawable-descriptor'; export { DrawableDescriptor } from './drawables/drawable-descriptor';
export { CircleLight } from './drawables/lights/circle-light'; export { CircleLight } from './drawables/lights/circle-light';
@ -13,7 +14,6 @@ export { Circle } from './drawables/shapes/circle';
export { InvertedTunnel } from './drawables/shapes/inverted-tunnel'; export { InvertedTunnel } from './drawables/shapes/inverted-tunnel';
export { Tunnel } from './drawables/shapes/tunnel'; export { Tunnel } from './drawables/shapes/tunnel';
export { Renderer } from './graphics/rendering/renderer'; export { Renderer } from './graphics/rendering/renderer';
export { RenderingPassName } from './graphics/rendering/rendering-pass-name';
declare global { declare global {
interface Array<T> { interface Array<T> {

View file

@ -4,7 +4,7 @@
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"target": "es6", "target": "ES2017",
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"experimentalDecorators": true, "experimentalDecorators": true,

View file

@ -14,7 +14,7 @@ module.exports = {
output: { output: {
path: PATHS.bundles, path: PATHS.bundles,
filename: '[name].js', filename: '[name].js',
library: 'SDF2D', library: 'sdf-2d',
libraryTarget: 'umd', libraryTarget: 'umd',
umdNamedDefine: true, umdNamedDefine: true,
}, },
@ -23,8 +23,12 @@ module.exports = {
aggregateTimeout: 600, aggregateTimeout: 600,
ignored: /node_modules/, ignored: /node_modules/,
}, },
externals: {
'gl-matrix': 'gl-matrix',
},
optimization: { optimization: {
minimize: true, minimize: true,
usedExports: true,
minimizer: [ minimizer: [
new TerserJSPlugin({ new TerserJSPlugin({
sourceMap: true, sourceMap: true,