Add palette system
This commit is contained in:
parent
f60ae06f59
commit
98d1fc9ca2
19 changed files with 196 additions and 113 deletions
|
|
@ -16,7 +16,7 @@ export class Circle extends Drawable {
|
||||||
myMinDistance = min(myMinDistance, dist);
|
myMinDistance = min(myMinDistance, dist);
|
||||||
}
|
}
|
||||||
minDistance = min(minDistance, myMinDistance);
|
minDistance = min(minDistance, myMinDistance);
|
||||||
color = mix(2.0, color, step(
|
color = mix(1.0 / {paletteSize}, color, step(
|
||||||
distanceNdcPixelSize + SURFACE_OFFSET,
|
distanceNdcPixelSize + SURFACE_OFFSET,
|
||||||
myMinDistance
|
myMinDistance
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class Tunnel extends Drawable {
|
||||||
myMinDistance = min(myMinDistance, currentDistance);
|
myMinDistance = min(myMinDistance, currentDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
color = mix(2.0, color, step(
|
color = mix(2.0 / {paletteSize}, color, step(
|
||||||
distanceNdcPixelSize + SURFACE_OFFSET,
|
distanceNdcPixelSize + SURFACE_OFFSET,
|
||||||
myMinDistance
|
myMinDistance
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,15 @@ export abstract class FrameBuffer {
|
||||||
// null means the default framebuffer
|
// null means the default framebuffer
|
||||||
protected frameBuffer: WebGLFramebuffer | null = null;
|
protected frameBuffer: WebGLFramebuffer | null = null;
|
||||||
|
|
||||||
constructor(protected gl: UniversalRenderingContext) {}
|
constructor(protected readonly gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public bindAndClear(colorInput?: WebGLTexture) {
|
public bindAndClear(inputTextures: Array<WebGLTexture>) {
|
||||||
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
|
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
|
||||||
|
|
||||||
if (colorInput) {
|
inputTextures.forEach((t, i) => {
|
||||||
this.gl.bindTexture(this.gl.TEXTURE_2D, colorInput);
|
this.gl.activeTexture(this.gl.TEXTURE0 + i);
|
||||||
}
|
this.gl.bindTexture(this.gl.TEXTURE_2D, t);
|
||||||
|
});
|
||||||
|
|
||||||
this.gl.viewport(0, 0, this.size.x, this.size.y);
|
this.gl.viewport(0, 0, this.size.x, this.size.y);
|
||||||
this.gl.clearColor(0, 0, 0, 0);
|
this.gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@ import { FrameBuffer } from './frame-buffer';
|
||||||
|
|
||||||
export class IntermediateFrameBuffer extends FrameBuffer {
|
export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
private frameTexture: WebGLTexture;
|
private frameTexture: WebGLTexture;
|
||||||
|
private floatEnabled = false;
|
||||||
private floatLinearEnabled = false;
|
private floatLinearEnabled = false;
|
||||||
|
|
||||||
constructor(gl: UniversalRenderingContext) {
|
constructor(gl: UniversalRenderingContext) {
|
||||||
super(gl);
|
super(gl);
|
||||||
|
|
||||||
if (gl.isWebGL2) {
|
|
||||||
enableExtension(gl, 'EXT_color_buffer_float');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
enableExtension(gl, 'EXT_color_buffer_float');
|
||||||
|
this.floatEnabled = true;
|
||||||
|
|
||||||
enableExtension(gl, 'OES_texture_float_linear');
|
enableExtension(gl, 'OES_texture_float_linear');
|
||||||
this.floatLinearEnabled = true;
|
this.floatLinearEnabled = true;
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -21,6 +21,8 @@ export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// can only return null on lost context
|
// can only return null on lost context
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
|
||||||
this.frameTexture = this.gl.createTexture()!;
|
this.frameTexture = this.gl.createTexture()!;
|
||||||
this.configureTexture();
|
this.configureTexture();
|
||||||
|
|
||||||
|
|
@ -43,17 +45,17 @@ export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
const hasChanged = super.setSize();
|
const hasChanged = super.setSize();
|
||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture);
|
this.bind();
|
||||||
|
|
||||||
this.gl.texImage2D(
|
this.gl.texImage2D(
|
||||||
this.gl.TEXTURE_2D,
|
this.gl.TEXTURE_2D,
|
||||||
0,
|
0,
|
||||||
this.gl.isWebGL2 ? this.gl.RG16F : this.gl.RGBA,
|
this.floatEnabled ? this.gl.RG16F : this.gl.RGB,
|
||||||
this.size.x,
|
this.size.x,
|
||||||
this.size.y,
|
this.size.y,
|
||||||
0,
|
0,
|
||||||
this.gl.isWebGL2 ? this.gl.RG : this.gl.RGBA,
|
this.floatEnabled ? this.gl.RG : this.gl.RGB,
|
||||||
this.gl.isWebGL2 ? this.gl.FLOAT : this.gl.UNSIGNED_BYTE,
|
this.floatEnabled ? this.gl.FLOAT : this.gl.UNSIGNED_BYTE,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -61,8 +63,14 @@ export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
return hasChanged;
|
return hasChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private configureTexture() {
|
private bind() {
|
||||||
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
||||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture);
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private configureTexture() {
|
||||||
|
this.bind();
|
||||||
|
|
||||||
this.gl.texParameteri(
|
this.gl.texParameteri(
|
||||||
this.gl.TEXTURE_2D,
|
this.gl.TEXTURE_2D,
|
||||||
this.gl.TEXTURE_MAG_FILTER,
|
this.gl.TEXTURE_MAG_FILTER,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ export const loadUniform = (
|
||||||
(gl: UniversalRenderingContext, value: any, location: WebGLUniformLocation) => void
|
(gl: UniversalRenderingContext, value: any, location: WebGLUniformLocation) => void
|
||||||
> = new Map();
|
> = new Map();
|
||||||
{
|
{
|
||||||
|
converters.set(WebGLRenderingContext.SAMPLER_2D, (gl, v, l) => {
|
||||||
|
gl.uniform1i(l, v);
|
||||||
|
});
|
||||||
|
|
||||||
converters.set(WebGLRenderingContext.FLOAT, (gl, v, l) => {
|
converters.set(WebGLRenderingContext.FLOAT, (gl, v, l) => {
|
||||||
if (v instanceof Array || v[0] instanceof Float32Array) {
|
if (v instanceof Array || v[0] instanceof Float32Array) {
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export class WebGlStopwatch {
|
||||||
private timerExtension: any;
|
private timerExtension: any;
|
||||||
private timerQuery?: WebGLQuery;
|
private timerQuery?: WebGLQuery;
|
||||||
|
|
||||||
constructor(private gl: UniversalRenderingContext) {
|
constructor(private readonly gl: UniversalRenderingContext) {
|
||||||
this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2');
|
this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
66
src/graphics/graphics-library/palette-texture.ts
Normal file
66
src/graphics/graphics-library/palette-texture.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { vec3, vec4 } from 'gl-matrix';
|
||||||
|
import { UniversalRenderingContext } from './universal-rendering-context';
|
||||||
|
|
||||||
|
export class PaletteTexture {
|
||||||
|
private texture: WebGLTexture;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly gl: UniversalRenderingContext,
|
||||||
|
private readonly paletteSize: number
|
||||||
|
) {
|
||||||
|
this.texture = gl.createTexture()!;
|
||||||
|
this.bind();
|
||||||
|
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get colorTexture(): WebGLTexture {
|
||||||
|
return this.texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bind() {
|
||||||
|
this.gl.activeTexture(this.gl.TEXTURE1);
|
||||||
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPalette(colors: Array<vec3 | vec4>) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = this.paletteSize;
|
||||||
|
canvas.height = 1;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
const imageData = ctx.createImageData(this.paletteSize, 1);
|
||||||
|
|
||||||
|
colors.forEach((c, i) => {
|
||||||
|
imageData.data[4 * i + 0] = c[0] * 255;
|
||||||
|
imageData.data[4 * i + 1] = c[1] * 255;
|
||||||
|
imageData.data[4 * i + 2] = c[2] * 255;
|
||||||
|
imageData.data[4 * i + 3] = c.length == 4 ? c[3] : 255;
|
||||||
|
});
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
this.setImage(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setImage(image: TexImageSource) {
|
||||||
|
this.bind();
|
||||||
|
|
||||||
|
this.gl.texImage2D(
|
||||||
|
this.gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
this.gl.RGBA,
|
||||||
|
this.gl.RGBA,
|
||||||
|
this.gl.UNSIGNED_BYTE,
|
||||||
|
image
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.gl.deleteTexture(this.texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,10 +75,16 @@ export abstract class ParallelCompiler {
|
||||||
program: WebGLProgram,
|
program: WebGLProgram,
|
||||||
substitutions: { [name: string]: string }
|
substitutions: { [name: string]: string }
|
||||||
): ShaderWithSource {
|
): ShaderWithSource {
|
||||||
const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => {
|
let processedSource = source;
|
||||||
const value = substitutions[name];
|
let replaceHappened: boolean;
|
||||||
return Number.isInteger(value) ? `${value}.0` : value;
|
do {
|
||||||
});
|
replaceHappened = false;
|
||||||
|
processedSource = processedSource.replace(/{(.+)}/gm, (_, name: string): string => {
|
||||||
|
replaceHappened = true;
|
||||||
|
const value = substitutions[name];
|
||||||
|
return Number.isInteger(value) ? `${value}.0` : value;
|
||||||
|
});
|
||||||
|
} while (replaceHappened);
|
||||||
|
|
||||||
// can only return null on lost context
|
// can only return null on lost context
|
||||||
const shader = ParallelCompiler.gl.createShader(type)!;
|
const shader = ParallelCompiler.gl.createShader(type)!;
|
||||||
|
|
@ -136,11 +142,9 @@ export abstract class ParallelCompiler {
|
||||||
try {
|
try {
|
||||||
ParallelCompiler.checkShader(shader);
|
ParallelCompiler.checkShader(shader);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
for (const match of e
|
for (const match of e.toString().matchAll(/ERROR: 0:(\d+): (.*)$/gm)) {
|
||||||
.toString()
|
const line = Number.parseInt(match[1]);
|
||||||
.matchAll(/ERROR: 0:(?<line>\d+): (?<error>.*)$/gm)) {
|
const error = match[2];
|
||||||
const line = Number.parseInt(match.groups.line);
|
|
||||||
const error = match.groups.error;
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: ${error}\nSource (line ${line}):\n${
|
`Error: ${error}\nSource (line ${line}):\n${
|
||||||
shader.source.split('\n')[line - 1]
|
shader.source.split('\n')[line - 1]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default abstract class Program implements IProgram {
|
||||||
type: GLenum;
|
type: GLenum;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
constructor(protected gl: UniversalRenderingContext) {}
|
constructor(protected readonly gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(
|
||||||
[vertexShaderSource, fragmentShaderSource]: [string, string],
|
[vertexShaderSource, fragmentShaderSource]: [string, string],
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export class UniformArrayAutoScalingProgram implements IProgram {
|
||||||
private drawingRectangleBottomLeft = vec2.fromValues(0, 0);
|
private drawingRectangleBottomLeft = vec2.fromValues(0, 0);
|
||||||
private drawingRectangleSize = vec2.fromValues(1, 1);
|
private drawingRectangleSize = vec2.fromValues(1, 1);
|
||||||
|
|
||||||
constructor(private gl: UniversalRenderingContext) {}
|
constructor(private readonly gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(
|
||||||
shaderSources: [string, string],
|
shaderSources: [string, string],
|
||||||
|
|
@ -86,10 +86,10 @@ export class UniformArrayAutoScalingProgram implements IProgram {
|
||||||
shaderSources: [string, string]
|
shaderSources: [string, string]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const processedSubstitutions = {
|
const processedSubstitutions = {
|
||||||
...substitutions,
|
|
||||||
macroDefinitions: this.getMacroDefinitions(combination, descriptors),
|
macroDefinitions: this.getMacroDefinitions(combination, descriptors),
|
||||||
declarations: this.getDeclarations(combination, descriptors),
|
declarations: this.getDeclarations(combination, descriptors),
|
||||||
functionCalls: this.getFunctionCalls(combination, descriptors),
|
functionCalls: this.getFunctionCalls(combination, descriptors),
|
||||||
|
...substitutions,
|
||||||
};
|
};
|
||||||
|
|
||||||
const program = new FragmentShaderOnlyProgram(this.gl);
|
const program = new FragmentShaderOnlyProgram(this.gl);
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ export class DistanceRenderPass extends RenderPass {
|
||||||
this.drawables.push(drawable);
|
this.drawables.push(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
|
public render(commonUniforms: any, ...inputTextures: Array<WebGLTexture>) {
|
||||||
this.frame.bindAndClear(inputTexture);
|
this.frame.bindAndClear(inputTextures);
|
||||||
|
|
||||||
const stepsInUV = 1 / this.tileMultiplier;
|
const stepsInUV = 1 / this.tileMultiplier;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export class LightsRenderPass extends RenderPass {
|
||||||
this.drawables.push(drawable);
|
this.drawables.push(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
|
public render(commonUniforms: any, ...inputTextures: Array<WebGLTexture>) {
|
||||||
this.frame.bindAndClear(inputTexture);
|
this.frame.bindAndClear(inputTextures);
|
||||||
|
|
||||||
const tileCenterWorldCoordinates = vec2.transformMat2d(
|
const tileCenterWorldCoordinates = vec2.transformMat2d(
|
||||||
vec2.create(),
|
vec2.create(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { vec2, vec3 } 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 { DrawableDescriptor } from '../../drawables/drawable-descriptor';
|
||||||
import { LightDrawable } from '../../drawables/lights/light-drawable';
|
import { LightDrawable } from '../../drawables/lights/light-drawable';
|
||||||
|
|
@ -6,6 +6,7 @@ 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';
|
||||||
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
|
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
|
||||||
|
import { PaletteTexture } from '../graphics-library/palette-texture';
|
||||||
import { ParallelCompiler } from '../graphics-library/parallel-compiler';
|
import { ParallelCompiler } from '../graphics-library/parallel-compiler';
|
||||||
import {
|
import {
|
||||||
getUniversalRenderingContext,
|
getUniversalRenderingContext,
|
||||||
|
|
@ -29,13 +30,14 @@ import lightsVertexShader from './shaders/shading-vs.glsl';
|
||||||
import { UniformsProvider } from './uniforms-provider';
|
import { UniformsProvider } from './uniforms-provider';
|
||||||
|
|
||||||
export class RendererImplementation implements Renderer {
|
export class RendererImplementation implements Renderer {
|
||||||
private gl: UniversalRenderingContext;
|
private readonly gl: UniversalRenderingContext;
|
||||||
private stopwatch?: WebGlStopwatch;
|
private stopwatch?: WebGlStopwatch;
|
||||||
private uniformsProvider: UniformsProvider;
|
private uniformsProvider: UniformsProvider;
|
||||||
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
|
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
|
||||||
private distancePass: DistanceRenderPass;
|
private distancePass: DistanceRenderPass;
|
||||||
private lightsPass: LightsRenderPass;
|
|
||||||
private lightingFrameBuffer: DefaultFrameBuffer;
|
private lightingFrameBuffer: DefaultFrameBuffer;
|
||||||
|
private lightsPass: LightsRenderPass;
|
||||||
|
private palette?: PaletteTexture;
|
||||||
private autoscaler: FpsAutoscaler;
|
private autoscaler: FpsAutoscaler;
|
||||||
|
|
||||||
private applyRuntimeSettings: {
|
private applyRuntimeSettings: {
|
||||||
|
|
@ -45,25 +47,17 @@ export class RendererImplementation implements Renderer {
|
||||||
this.distanceFieldFrameBuffer.enableHighDpiRendering = v;
|
this.distanceFieldFrameBuffer.enableHighDpiRendering = v;
|
||||||
this.lightingFrameBuffer.enableHighDpiRendering = v;
|
this.lightingFrameBuffer.enableHighDpiRendering = v;
|
||||||
},
|
},
|
||||||
tileMultiplier: (v) => {
|
tileMultiplier: (v) => (this.distancePass.tileMultiplier = v),
|
||||||
this.distancePass.tileMultiplier = v;
|
isWorldInverted: (v) => (this.distancePass.isWorldInverted = v),
|
||||||
},
|
shadowLength: (v) => (this.uniformsProvider.shadowLength = v),
|
||||||
isWorldInverted: (v) => {
|
ambientLight: (v) => (this.uniformsProvider.ambientLight = v),
|
||||||
this.distancePass.isWorldInverted = v;
|
lightCutoffDistance: (v) => (this.lightsPass.lightCutoffDistance = v),
|
||||||
},
|
colorPalette: (v) => this.palette!.setPalette(v),
|
||||||
shadowLength: (v) => {
|
|
||||||
this.uniformsProvider.shadowLength = v;
|
|
||||||
},
|
|
||||||
ambientLight: (v) => {
|
|
||||||
this.uniformsProvider.ambientLight = v;
|
|
||||||
},
|
|
||||||
lightCutoffDistance: (v) => {
|
|
||||||
this.lightsPass.lightCutoffDistance = v;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static defaultStartupSettings: StartupSettings = {
|
private static defaultStartupSettings: StartupSettings = {
|
||||||
shadowTraceCount: '16',
|
shadowTraceCount: 16,
|
||||||
|
paletteSize: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
setRuntimeSettings(overrides: Partial<RuntimeSettings>): void {
|
setRuntimeSettings(overrides: Partial<RuntimeSettings>): void {
|
||||||
|
|
@ -88,6 +82,8 @@ export class RendererImplementation implements Renderer {
|
||||||
|
|
||||||
this.uniformsProvider = new UniformsProvider(this.gl);
|
this.uniformsProvider = new UniformsProvider(this.gl);
|
||||||
|
|
||||||
|
this.queryPrecisions();
|
||||||
|
|
||||||
this.autoscaler = new FpsAutoscaler({
|
this.autoscaler = new FpsAutoscaler({
|
||||||
distanceRenderScale: (v) =>
|
distanceRenderScale: (v) =>
|
||||||
(this.distanceFieldFrameBuffer.renderScale = v as number),
|
(this.distanceFieldFrameBuffer.renderScale = v as number),
|
||||||
|
|
@ -95,15 +91,13 @@ export class RendererImplementation implements Renderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(settingsOverrides: Partial<StartupSettings>): Promise<void> {
|
||||||
palette: Array<vec3>,
|
|
||||||
settingsOverrides: Partial<StartupSettings>
|
|
||||||
): Promise<void> {
|
|
||||||
const settings = {
|
const settings = {
|
||||||
...RendererImplementation.defaultStartupSettings,
|
...RendererImplementation.defaultStartupSettings,
|
||||||
...settingsOverrides,
|
...settingsOverrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.palette = new PaletteTexture(this.gl, settings.paletteSize);
|
||||||
const promises: Array<Promise<void>> = [];
|
const promises: Array<Promise<void>> = [];
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
|
|
@ -111,7 +105,10 @@ export class RendererImplementation implements Renderer {
|
||||||
this.gl.isWebGL2
|
this.gl.isWebGL2
|
||||||
? [distanceVertexShader, distanceFragmentShader]
|
? [distanceVertexShader, distanceFragmentShader]
|
||||||
: [distanceVertexShader100, distanceFragmentShader100],
|
: [distanceVertexShader100, distanceFragmentShader100],
|
||||||
this.descriptors.filter(RendererImplementation.hasSdf)
|
this.descriptors.filter(RendererImplementation.hasSdf),
|
||||||
|
{
|
||||||
|
paletteSize: settings.paletteSize,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
promises.push(
|
promises.push(
|
||||||
|
|
@ -121,9 +118,7 @@ export class RendererImplementation implements Renderer {
|
||||||
: [lightsVertexShader100, lightsFragmentShader100],
|
: [lightsVertexShader100, lightsFragmentShader100],
|
||||||
this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)),
|
this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)),
|
||||||
{
|
{
|
||||||
palette: this.generatePaletteCode(palette),
|
shadowTraceCount: settings.shadowTraceCount.toString(),
|
||||||
colorCount: palette.length.toString(),
|
|
||||||
shadowTraceCount: settings.shadowTraceCount,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -147,6 +142,30 @@ export class RendererImplementation implements Renderer {
|
||||||
return Object.prototype.hasOwnProperty.call(descriptor, 'sdf');
|
return Object.prototype.hasOwnProperty.call(descriptor, 'sdf');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private queryPrecisions() {
|
||||||
|
const precisionToObject = (shader: GLenum, precision: GLenum) => {
|
||||||
|
const toExponent = (v: number): string => `2^${v}`;
|
||||||
|
const p = this.gl.getShaderPrecisionFormat(shader, precision)!;
|
||||||
|
return {
|
||||||
|
range: `[${toExponent(p.rangeMin)}, ${toExponent(p.rangeMax)}]`,
|
||||||
|
precision: toExponent(p.precision),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Insights.setValue('precisions', {
|
||||||
|
'vertex shader': {
|
||||||
|
'low float': precisionToObject(this.gl.VERTEX_SHADER, this.gl.LOW_FLOAT),
|
||||||
|
'medium float': precisionToObject(this.gl.VERTEX_SHADER, this.gl.MEDIUM_FLOAT),
|
||||||
|
'high float': precisionToObject(this.gl.VERTEX_SHADER, this.gl.HIGH_FLOAT),
|
||||||
|
},
|
||||||
|
'fragment shader': {
|
||||||
|
'low float': precisionToObject(this.gl.FRAGMENT_SHADER, this.gl.LOW_FLOAT),
|
||||||
|
'medium float': precisionToObject(this.gl.FRAGMENT_SHADER, this.gl.MEDIUM_FLOAT),
|
||||||
|
'high float': precisionToObject(this.gl.FRAGMENT_SHADER, this.gl.HIGH_FLOAT),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public addDrawable(drawable: Drawable): void {
|
public addDrawable(drawable: Drawable): void {
|
||||||
if (
|
if (
|
||||||
RendererImplementation.hasSdf((drawable.constructor as typeof Drawable).descriptor)
|
RendererImplementation.hasSdf((drawable.constructor as typeof Drawable).descriptor)
|
||||||
|
|
@ -161,18 +180,6 @@ export class RendererImplementation implements Renderer {
|
||||||
this.autoscaler.autoscale(deltaTime);
|
this.autoscaler.autoscale(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private generatePaletteCode(palette: Array<vec3>) {
|
|
||||||
const numberToGlslFloat = (n: number) => (Number.isInteger(n) ? `${n}.0` : `${n}`);
|
|
||||||
return palette
|
|
||||||
.map(
|
|
||||||
(c, i) =>
|
|
||||||
`${this.gl.isWebGL2 ? '' : `colors[${i}] = `}vec3(${numberToGlslFloat(
|
|
||||||
c[0]
|
|
||||||
)}, ${numberToGlslFloat(c[1])}, ${numberToGlslFloat(c[2])})`
|
|
||||||
)
|
|
||||||
.join(this.gl.isWebGL2 ? ',\n' : ';\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderDrawables() {
|
public renderDrawables() {
|
||||||
if (this.stopwatch) {
|
if (this.stopwatch) {
|
||||||
if (this.stopwatch.isReady) {
|
if (this.stopwatch.isReady) {
|
||||||
|
|
@ -190,6 +197,10 @@ export class RendererImplementation implements Renderer {
|
||||||
this.lightingFrameBuffer.setSize();
|
this.lightingFrameBuffer.setSize();
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
|
// texture units
|
||||||
|
distanceTexture: 0,
|
||||||
|
palette: 1,
|
||||||
|
// regular uniforms
|
||||||
distanceNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
|
distanceNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
|
||||||
shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
|
shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
|
||||||
};
|
};
|
||||||
|
|
@ -197,7 +208,8 @@ export class RendererImplementation implements Renderer {
|
||||||
this.distancePass.render(this.uniformsProvider.getUniforms(common));
|
this.distancePass.render(this.uniformsProvider.getUniforms(common));
|
||||||
this.lightsPass.render(
|
this.lightsPass.render(
|
||||||
this.uniformsProvider.getUniforms(common),
|
this.uniformsProvider.getUniforms(common),
|
||||||
this.distanceFieldFrameBuffer.colorTexture
|
this.distanceFieldFrameBuffer.colorTexture,
|
||||||
|
this.palette!.colorTexture
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.stopwatch?.isRunning) {
|
if (this.stopwatch?.isRunning) {
|
||||||
|
|
@ -220,5 +232,6 @@ export class RendererImplementation implements Renderer {
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this.distancePass.destroy();
|
this.distancePass.destroy();
|
||||||
this.lightsPass.destroy();
|
this.lightsPass.destroy();
|
||||||
|
this.palette!.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ export interface RuntimeSettings {
|
||||||
isWorldInverted: boolean;
|
isWorldInverted: boolean;
|
||||||
shadowLength: number;
|
shadowLength: number;
|
||||||
lightCutoffDistance: number;
|
lightCutoffDistance: number;
|
||||||
|
colorPalette: Array<vec3>;
|
||||||
ambientLight: vec3;
|
ambientLight: vec3;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export interface StartupSettings {
|
export interface StartupSettings {
|
||||||
shadowTraceCount: string;
|
shadowTraceCount: number;
|
||||||
|
paletteSize: number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,16 @@ uniform vec2 squareToAspectRatioTimes2;
|
||||||
uniform float shadingNdcPixelSize;
|
uniform float shadingNdcPixelSize;
|
||||||
uniform float shadowLength;
|
uniform float shadowLength;
|
||||||
uniform sampler2D distanceTexture;
|
uniform sampler2D distanceTexture;
|
||||||
|
uniform sampler2D palette;
|
||||||
|
|
||||||
varying vec2 position;
|
varying vec2 position;
|
||||||
varying vec2 uvCoordinates;
|
varying vec2 uvCoordinates;
|
||||||
uniform vec3 ambientLight;
|
|
||||||
|
|
||||||
vec3 colors[{colorCount}];
|
uniform vec3 ambientLight;
|
||||||
|
|
||||||
float getDistance(in vec2 target, out vec3 color) {
|
float getDistance(in vec2 target, out vec3 color) {
|
||||||
vec4 values = texture2D(distanceTexture, target);
|
vec4 values = texture2D(distanceTexture, target);
|
||||||
color = vec3(0.5);
|
color = texture2D(palette, vec2(values[1], 0.0)).rgb;
|
||||||
return values[0];
|
return values[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,13 +72,11 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 lighting = ambientLight;
|
vec3 lighting = ambientLight;
|
||||||
|
|
||||||
{palette};
|
|
||||||
|
|
||||||
vec3 colorAtPosition;
|
vec3 colorAtPosition;
|
||||||
float startingDistance = getDistance(uvCoordinates, colorAtPosition);
|
float startingDistance = getDistance(uvCoordinates, colorAtPosition);
|
||||||
|
|
||||||
if (startingDistance < 0.0) {
|
if (startingDistance <= 0.0) {
|
||||||
#ifdef CIRCLE_LIGHT_COUNT
|
#ifdef CIRCLE_LIGHT_COUNT
|
||||||
#if CIRCLE_LIGHT_COUNT > 0
|
#if CIRCLE_LIGHT_COUNT > 0
|
||||||
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
|
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,16 @@ uniform vec2 squareToAspectRatioTimes2;
|
||||||
uniform float shadingNdcPixelSize;
|
uniform float shadingNdcPixelSize;
|
||||||
uniform float shadowLength;
|
uniform float shadowLength;
|
||||||
uniform sampler2D distanceTexture;
|
uniform sampler2D distanceTexture;
|
||||||
|
uniform sampler2D palette;
|
||||||
|
|
||||||
in vec2 position;
|
in vec2 position;
|
||||||
in vec2 uvCoordinates;
|
in vec2 uvCoordinates;
|
||||||
|
|
||||||
uniform vec3 ambientLight;
|
uniform vec3 ambientLight;
|
||||||
|
|
||||||
vec3[{colorCount}] colors = vec3[](
|
|
||||||
{palette}
|
|
||||||
);
|
|
||||||
|
|
||||||
float getDistance(in vec2 target, out vec3 color) {
|
float getDistance(in vec2 target, out vec3 color) {
|
||||||
vec4 values = texture(distanceTexture, target);
|
vec4 values = texture(distanceTexture, target);
|
||||||
color = colors[int(values[1])];
|
color = texture(palette, vec2(values[1], 0.0)).rgb;
|
||||||
return values[0];
|
return values[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,12 @@ export class UniformsProvider {
|
||||||
|
|
||||||
private scaleWorldLengthToNDC = 1;
|
private scaleWorldLengthToNDC = 1;
|
||||||
private transformWorldToNDC = mat2d.create();
|
private transformWorldToNDC = mat2d.create();
|
||||||
|
|
||||||
private viewAreaBottomLeft = vec2.create();
|
private viewAreaBottomLeft = vec2.create();
|
||||||
private worldAreaInView = vec2.create();
|
private worldAreaInView = vec2.create();
|
||||||
private squareToAspectRatio = vec2.create();
|
private squareToAspectRatio = vec2.create();
|
||||||
private uvToWorld = mat2d.create();
|
private uvToWorld = mat2d.create();
|
||||||
|
|
||||||
public constructor(private gl: UniversalRenderingContext) {}
|
public constructor(private readonly gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public getUniforms(uniforms: any): any {
|
public getUniforms(uniforms: any): any {
|
||||||
return {
|
return {
|
||||||
|
|
@ -23,34 +22,12 @@ export class UniformsProvider {
|
||||||
uvToWorld: this.uvToWorld,
|
uvToWorld: this.uvToWorld,
|
||||||
worldAreaInView: this.worldAreaInView,
|
worldAreaInView: this.worldAreaInView,
|
||||||
squareToAspectRatio: this.squareToAspectRatio,
|
squareToAspectRatio: this.squareToAspectRatio,
|
||||||
|
squareToAspectRatioTimes2: vec2.scale(vec2.create(), this.squareToAspectRatio, 2),
|
||||||
scaleWorldLengthToNDC: this.scaleWorldLengthToNDC,
|
scaleWorldLengthToNDC: this.scaleWorldLengthToNDC,
|
||||||
transformWorldToNDC: this.transformWorldToNDC,
|
transformWorldToNDC: this.transformWorldToNDC,
|
||||||
squareToAspectRatioTimes2: vec2.scale(vec2.create(), this.squareToAspectRatio, 2),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getScreenToWorldTransform(screenSize: vec2) {
|
|
||||||
const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
|
|
||||||
mat2d.scale(
|
|
||||||
transform,
|
|
||||||
transform,
|
|
||||||
vec2.divide(vec2.create(), this.worldAreaInView, screenSize)
|
|
||||||
);
|
|
||||||
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
|
|
||||||
|
|
||||||
return transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
public uvToWorldCoordinate(screenUvPosition: vec2): vec2 {
|
|
||||||
const resolution = vec2.fromValues(this.gl.canvas.width, this.gl.canvas.height);
|
|
||||||
|
|
||||||
return vec2.transformMat2d(
|
|
||||||
vec2.create(),
|
|
||||||
vec2.multiply(vec2.create(), screenUvPosition, resolution),
|
|
||||||
this.getScreenToWorldTransform(resolution)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getViewArea(): vec2 {
|
public getViewArea(): vec2 {
|
||||||
return this.worldAreaInView;
|
return this.worldAreaInView;
|
||||||
}
|
}
|
||||||
|
|
@ -91,4 +68,19 @@ export class UniformsProvider {
|
||||||
this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
|
this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
|
||||||
mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView);
|
mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public screenToWorldPosition(screenPosition: vec2): vec2 {
|
||||||
|
const resolution = vec2.fromValues(this.gl.canvas.width, this.gl.canvas.height);
|
||||||
|
|
||||||
|
const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
|
||||||
|
|
||||||
|
mat2d.scale(
|
||||||
|
transform,
|
||||||
|
transform,
|
||||||
|
vec2.divide(vec2.create(), this.worldAreaInView, resolution)
|
||||||
|
);
|
||||||
|
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
|
||||||
|
|
||||||
|
return vec2.transformMat2d(vec2.create(), screenPosition, transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { vec3 } from 'gl-matrix';
|
|
||||||
import { DrawableDescriptor } from './drawables/drawable-descriptor';
|
import { DrawableDescriptor } from './drawables/drawable-descriptor';
|
||||||
import { Insights } from './graphics/rendering/insights';
|
import { Insights } from './graphics/rendering/insights';
|
||||||
import { Renderer } from './graphics/rendering/renderer';
|
import { Renderer } from './graphics/rendering/renderer';
|
||||||
|
|
@ -32,12 +31,11 @@ applyArrayPlugins();
|
||||||
export async function compile(
|
export async function compile(
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
descriptors: Array<DrawableDescriptor>,
|
descriptors: Array<DrawableDescriptor>,
|
||||||
palette: Array<vec3>,
|
|
||||||
settings: Partial<StartupSettings> = {}
|
settings: Partial<StartupSettings> = {}
|
||||||
): Promise<Renderer> {
|
): Promise<Renderer> {
|
||||||
return Insights.measureFunction('startup', async () => {
|
return Insights.measureFunction('startup', async () => {
|
||||||
const renderer = new RendererImplementation(canvas, descriptors);
|
const renderer = new RendererImplementation(canvas, descriptors);
|
||||||
await renderer.initialize(palette, settings);
|
await renderer.initialize(settings);
|
||||||
return renderer;
|
return renderer;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue