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

View file

@ -5,7 +5,7 @@ export abstract class Drawable {
// This should be overriden by inherited classes
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;

View file

@ -1,8 +1,8 @@
import { mat2d, vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { vec2, vec3 } from 'gl-matrix';
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 = {
uniformName: 'circleLights',
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),
};
constructor(public center: vec2, public color: vec3, public intensity: number) {
super();
}
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,
};
constructor(center: vec2, color: vec3, intensity: number) {
super(center, color, intensity);
}
}

View file

@ -1,39 +1,33 @@
import { mat2d, vec2, vec3 } from 'gl-matrix';
import { Drawable } from '../drawable';
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 = {
uniformName: 'flashlights',
uniformCountMacroName: 'FLASHLIGHT_COUNT',
shaderCombinationSteps: [0, 1, 2, 4],
empty: new Flashlight(
vec2.fromValues(0, 0),
vec2.fromValues(0, 0),
vec3.fromValues(0, 0, 0),
0
0,
vec2.fromValues(0, 0)
),
};
public constructor(
public center: vec2,
public direction: vec2,
public color: vec3,
public intensity: number
center: vec2,
color: vec3,
intensity: number,
public direction: vec2
) {
super();
}
public distance(_: vec2): number {
return 0;
super(center, color, intensity);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
...super.getObjectToSerialize(transform2d, transform1d),
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();
}
public distance(target: vec2): number {
public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius;
}

View file

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

View file

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

View file

@ -1,33 +1,13 @@
import { vec2 } from 'gl-matrix';
import { Drawable } from '../../drawables/drawable';
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';
import { Insights } from './insights';
import { RenderingPassName } from './rendering-pass-name';
import { Drawable } from '../../../drawables/drawable';
import { Insights } from '../insights';
import { RenderPass } from './render-pass';
export class RenderingPass {
export class DistanceRenderPass extends RenderPass {
public tileMultiplier = 8;
public isWorldInverted = false;
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) {
this.drawables.push(drawable);
@ -72,7 +52,7 @@ export class RenderingPass {
);
const drawablesNearTile = this.drawables.filter(
(d) => d.distance(tileCenterWorldCoordinates) < 2 * worldR
(d) => d.minDistance(tileCenterWorldCoordinates) < 2 * worldR
);
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(
['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
);
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 { Drawable } from '../../drawables/drawable';
import { DrawableDescriptor } from '../../drawables/drawable-descriptor';
import { LightDrawable } from '../../drawables/lights/light-drawable';
import { msToString } from '../../helper/ms-to-string';
import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-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 { FpsAutoscaler } from './fps-autoscaler';
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 { RenderingPass } from './rendering-pass';
import { RenderingPassName } from './rendering-pass-name';
import { RuntimeSettings } from './settings/runtime-settings';
import { StartupSettings } from './settings/startup-settings';
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 { UniformsProvider } from './uniforms-provider';
type Passes = { [key in keyof typeof RenderingPassName]: RenderingPass };
export class WebGl2Renderer implements Renderer {
private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch;
private uniformsProvider: UniformsProvider;
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private distancePass: DistanceRenderPass;
private lightsPass: LightsRenderPass;
private lightingFrameBuffer: DefaultFrameBuffer;
private passes: Passes;
private autoscaler: FpsAutoscaler;
private applyRuntimeSettings: {
@ -39,12 +39,10 @@ export class WebGl2Renderer implements Renderer {
this.lightingFrameBuffer.enableHighDpiRendering = v;
},
tileMultiplier: (v) => {
this.passes[RenderingPassName.distance].tileMultiplier = v;
this.passes[RenderingPassName.pixel].tileMultiplier = 1;
this.distancePass.tileMultiplier = v;
},
isWorldInverted: (v) => {
this.passes[RenderingPassName.distance].isWorldInverted = v;
this.passes[RenderingPassName.pixel].isWorldInverted = v;
this.distancePass.isWorldInverted = v;
},
shadowLength: (v) => {
this.uniformsProvider.shadowLength = v;
@ -75,9 +73,8 @@ export class WebGl2Renderer implements Renderer {
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl);
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl);
this.passes = this.createPasses();
this.passes.pixel.tileMultiplier = 1;
this.distancePass = new DistanceRenderPass(this.gl, this.distanceFieldFrameBuffer);
this.lightsPass = new LightsRenderPass(this.gl, this.lightingFrameBuffer);
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(
palette: Array<vec3>,
settingsOverrides: Partial<StartupSettings>
@ -112,14 +94,14 @@ export class WebGl2Renderer implements Renderer {
const promises: Array<Promise<void>> = [];
promises.push(
this.passes[RenderingPassName.distance].initialize(
this.distancePass.initialize(
[distanceVertexShader, distanceFragmentShader],
this.descriptors.filter(WebGl2Renderer.hasSdf)
)
);
promises.push(
this.passes[RenderingPassName.pixel].initialize(
this.lightsPass.initialize(
[lightsVertexShader, lightsFragmentShader],
this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)),
{
@ -150,9 +132,9 @@ export class WebGl2Renderer implements Renderer {
public addDrawable(drawable: Drawable): void {
if (WebGl2Renderer.hasSdf((drawable.constructor as typeof Drawable).descriptor)) {
this.passes[RenderingPassName.distance].addDrawable(drawable);
this.distancePass.addDrawable(drawable);
} 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()),
};
this.passes[RenderingPassName.distance].render(
this.uniformsProvider.getUniforms(common)
);
this.passes[RenderingPassName.pixel].render(
this.distancePass.render(this.uniformsProvider.getUniforms(common));
this.lightsPass.render(
this.uniformsProvider.getUniforms(common),
this.distanceFieldFrameBuffer.colorTexture
);
@ -219,7 +199,7 @@ export class WebGl2Renderer implements Renderer {
}
public destroy(): void {
this.passes.distance.destroy();
this.passes.pixel.destroy();
this.distancePass.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 { WebGl2Renderer } from './graphics/rendering/webgl2-renderer';
import { applyArrayPlugins } from './helper/array';
export { Drawable } from './drawables/drawable';
export { DrawableDescriptor } from './drawables/drawable-descriptor';
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 { Tunnel } from './drawables/shapes/tunnel';
export { Renderer } from './graphics/rendering/renderer';
export { RenderingPassName } from './graphics/rendering/rendering-pass-name';
declare global {
interface Array<T> {

View file

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

View file

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