Add palette system

This commit is contained in:
schmelczerandras 2020-09-23 16:27:02 +02:00
parent f60ae06f59
commit 98d1fc9ca2
19 changed files with 196 additions and 113 deletions

View file

@ -13,8 +13,8 @@ export class DistanceRenderPass extends RenderPass {
this.drawables.push(drawable);
}
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
this.frame.bindAndClear(inputTexture);
public render(commonUniforms: any, ...inputTextures: Array<WebGLTexture>) {
this.frame.bindAndClear(inputTextures);
const stepsInUV = 1 / this.tileMultiplier;

View file

@ -12,8 +12,8 @@ export class LightsRenderPass extends RenderPass {
this.drawables.push(drawable);
}
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
this.frame.bindAndClear(inputTexture);
public render(commonUniforms: any, ...inputTextures: Array<WebGLTexture>) {
this.frame.bindAndClear(inputTextures);
const tileCenterWorldCoordinates = vec2.transformMat2d(
vec2.create(),

View file

@ -1,4 +1,4 @@
import { vec2, vec3 } from 'gl-matrix';
import { vec2 } from 'gl-matrix';
import { Drawable } from '../../drawables/drawable';
import { DrawableDescriptor } from '../../drawables/drawable-descriptor';
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 { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer';
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { PaletteTexture } from '../graphics-library/palette-texture';
import { ParallelCompiler } from '../graphics-library/parallel-compiler';
import {
getUniversalRenderingContext,
@ -29,13 +30,14 @@ import lightsVertexShader from './shaders/shading-vs.glsl';
import { UniformsProvider } from './uniforms-provider';
export class RendererImplementation implements Renderer {
private gl: UniversalRenderingContext;
private readonly gl: UniversalRenderingContext;
private stopwatch?: WebGlStopwatch;
private uniformsProvider: UniformsProvider;
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private distancePass: DistanceRenderPass;
private lightsPass: LightsRenderPass;
private lightingFrameBuffer: DefaultFrameBuffer;
private lightsPass: LightsRenderPass;
private palette?: PaletteTexture;
private autoscaler: FpsAutoscaler;
private applyRuntimeSettings: {
@ -45,25 +47,17 @@ export class RendererImplementation implements Renderer {
this.distanceFieldFrameBuffer.enableHighDpiRendering = v;
this.lightingFrameBuffer.enableHighDpiRendering = v;
},
tileMultiplier: (v) => {
this.distancePass.tileMultiplier = v;
},
isWorldInverted: (v) => {
this.distancePass.isWorldInverted = v;
},
shadowLength: (v) => {
this.uniformsProvider.shadowLength = v;
},
ambientLight: (v) => {
this.uniformsProvider.ambientLight = v;
},
lightCutoffDistance: (v) => {
this.lightsPass.lightCutoffDistance = v;
},
tileMultiplier: (v) => (this.distancePass.tileMultiplier = v),
isWorldInverted: (v) => (this.distancePass.isWorldInverted = v),
shadowLength: (v) => (this.uniformsProvider.shadowLength = v),
ambientLight: (v) => (this.uniformsProvider.ambientLight = v),
lightCutoffDistance: (v) => (this.lightsPass.lightCutoffDistance = v),
colorPalette: (v) => this.palette!.setPalette(v),
};
private static defaultStartupSettings: StartupSettings = {
shadowTraceCount: '16',
shadowTraceCount: 16,
paletteSize: 4,
};
setRuntimeSettings(overrides: Partial<RuntimeSettings>): void {
@ -88,6 +82,8 @@ export class RendererImplementation implements Renderer {
this.uniformsProvider = new UniformsProvider(this.gl);
this.queryPrecisions();
this.autoscaler = new FpsAutoscaler({
distanceRenderScale: (v) =>
(this.distanceFieldFrameBuffer.renderScale = v as number),
@ -95,15 +91,13 @@ export class RendererImplementation implements Renderer {
});
}
public async initialize(
palette: Array<vec3>,
settingsOverrides: Partial<StartupSettings>
): Promise<void> {
public async initialize(settingsOverrides: Partial<StartupSettings>): Promise<void> {
const settings = {
...RendererImplementation.defaultStartupSettings,
...settingsOverrides,
};
this.palette = new PaletteTexture(this.gl, settings.paletteSize);
const promises: Array<Promise<void>> = [];
promises.push(
@ -111,7 +105,10 @@ export class RendererImplementation implements Renderer {
this.gl.isWebGL2
? [distanceVertexShader, distanceFragmentShader]
: [distanceVertexShader100, distanceFragmentShader100],
this.descriptors.filter(RendererImplementation.hasSdf)
this.descriptors.filter(RendererImplementation.hasSdf),
{
paletteSize: settings.paletteSize,
}
)
);
promises.push(
@ -121,9 +118,7 @@ export class RendererImplementation implements Renderer {
: [lightsVertexShader100, lightsFragmentShader100],
this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)),
{
palette: this.generatePaletteCode(palette),
colorCount: palette.length.toString(),
shadowTraceCount: settings.shadowTraceCount,
shadowTraceCount: settings.shadowTraceCount.toString(),
}
)
);
@ -147,6 +142,30 @@ export class RendererImplementation implements Renderer {
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 {
if (
RendererImplementation.hasSdf((drawable.constructor as typeof Drawable).descriptor)
@ -161,18 +180,6 @@ export class RendererImplementation implements Renderer {
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() {
if (this.stopwatch) {
if (this.stopwatch.isReady) {
@ -190,6 +197,10 @@ export class RendererImplementation implements Renderer {
this.lightingFrameBuffer.setSize();
const common = {
// texture units
distanceTexture: 0,
palette: 1,
// regular uniforms
distanceNdcPixelSize: 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.lightsPass.render(
this.uniformsProvider.getUniforms(common),
this.distanceFieldFrameBuffer.colorTexture
this.distanceFieldFrameBuffer.colorTexture,
this.palette!.colorTexture
);
if (this.stopwatch?.isRunning) {
@ -220,5 +232,6 @@ export class RendererImplementation implements Renderer {
public destroy(): void {
this.distancePass.destroy();
this.lightsPass.destroy();
this.palette!.destroy();
}
}

View file

@ -6,5 +6,6 @@ export interface RuntimeSettings {
isWorldInverted: boolean;
shadowLength: number;
lightCutoffDistance: number;
colorPalette: Array<vec3>;
ambientLight: vec3;
}

View file

@ -1,3 +1,4 @@
export interface StartupSettings {
shadowTraceCount: string;
shadowTraceCount: number;
paletteSize: number;
}

View file

@ -12,16 +12,16 @@ uniform vec2 squareToAspectRatioTimes2;
uniform float shadingNdcPixelSize;
uniform float shadowLength;
uniform sampler2D distanceTexture;
uniform sampler2D palette;
varying vec2 position;
varying vec2 uvCoordinates;
uniform vec3 ambientLight;
vec3 colors[{colorCount}];
uniform vec3 ambientLight;
float getDistance(in vec2 target, out vec3 color) {
vec4 values = texture2D(distanceTexture, target);
color = vec3(0.5);
color = texture2D(palette, vec2(values[1], 0.0)).rgb;
return values[0];
}
@ -72,13 +72,11 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
void main() {
vec3 lighting = ambientLight;
{palette};
vec3 colorAtPosition;
float startingDistance = getDistance(uvCoordinates, colorAtPosition);
if (startingDistance < 0.0) {
if (startingDistance <= 0.0) {
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {

View file

@ -12,19 +12,16 @@ uniform vec2 squareToAspectRatioTimes2;
uniform float shadingNdcPixelSize;
uniform float shadowLength;
uniform sampler2D distanceTexture;
uniform sampler2D palette;
in vec2 position;
in vec2 uvCoordinates;
uniform vec3 ambientLight;
vec3[{colorCount}] colors = vec3[](
{palette}
);
float getDistance(in vec2 target, out vec3 color) {
vec4 values = texture(distanceTexture, target);
color = colors[int(values[1])];
color = texture(palette, vec2(values[1], 0.0)).rgb;
return values[0];
}

View file

@ -7,13 +7,12 @@ export class UniformsProvider {
private scaleWorldLengthToNDC = 1;
private transformWorldToNDC = mat2d.create();
private viewAreaBottomLeft = vec2.create();
private worldAreaInView = vec2.create();
private squareToAspectRatio = vec2.create();
private uvToWorld = mat2d.create();
public constructor(private gl: UniversalRenderingContext) {}
public constructor(private readonly gl: UniversalRenderingContext) {}
public getUniforms(uniforms: any): any {
return {
@ -23,34 +22,12 @@ export class UniformsProvider {
uvToWorld: this.uvToWorld,
worldAreaInView: this.worldAreaInView,
squareToAspectRatio: this.squareToAspectRatio,
squareToAspectRatioTimes2: vec2.scale(vec2.create(), this.squareToAspectRatio, 2),
scaleWorldLengthToNDC: this.scaleWorldLengthToNDC,
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 {
return this.worldAreaInView;
}
@ -91,4 +68,19 @@ export class UniformsProvider {
this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
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);
}
}