Add WebGL compatibility

This commit is contained in:
schmelczerandras 2020-09-22 16:41:59 +02:00
parent 3725f56c78
commit 1d4980ae28
29 changed files with 567 additions and 212 deletions

View file

@ -1,7 +1,7 @@
import { Drawable } from './drawable'; import { Drawable } from './drawable';
export interface DrawableDescriptor { export interface DrawableDescriptor {
uniformName: string; propertyUniformMapping: { [property: string]: string };
uniformCountMacroName: string; uniformCountMacroName: string;
sdf?: { sdf?: {
shader: string; shader: string;

View file

@ -6,7 +6,6 @@ export abstract class Drawable {
public static readonly descriptor: DrawableDescriptor; public static readonly descriptor: DrawableDescriptor;
public abstract minDistance(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;
public serializeToUniforms( public serializeToUniforms(
@ -14,12 +13,15 @@ export abstract class Drawable {
transform2d: mat2d, transform2d: mat2d,
transform1d: number transform1d: number
): void { ): void {
const { uniformName } = (this.constructor as typeof Drawable).descriptor; const { propertyUniformMapping } = (this.constructor as typeof Drawable).descriptor;
if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) { const serialized = this.getObjectToSerialize(transform2d, transform1d);
uniforms[uniformName] = []; Object.entries(propertyUniformMapping).forEach(([k, v]) => {
if (!Object.prototype.hasOwnProperty.call(uniforms, v)) {
uniforms[v] = [];
} }
uniforms[uniformName].push(this.getObjectToSerialize(transform2d, transform1d)); uniforms[v].push(serialized[k]);
});
} }
} }

View file

@ -4,9 +4,13 @@ import { LightDrawable } from './light-drawable';
export class CircleLight extends LightDrawable { export class CircleLight extends LightDrawable {
public static readonly descriptor: DrawableDescriptor = { public static readonly descriptor: DrawableDescriptor = {
uniformName: 'circleLights', propertyUniformMapping: {
center: 'circleLightCenters',
color: 'circleLightColors',
intensity: 'circleLightIntensities',
},
uniformCountMacroName: 'CIRCLE_LIGHT_COUNT', uniformCountMacroName: 'CIRCLE_LIGHT_COUNT',
shaderCombinationSteps: [0, 1, 2, 4, 8], shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
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),
}; };

View file

@ -4,7 +4,12 @@ import { LightDrawable } from './light-drawable';
export class Flashlight extends LightDrawable { export class Flashlight extends LightDrawable {
public static readonly descriptor: DrawableDescriptor = { public static readonly descriptor: DrawableDescriptor = {
uniformName: 'flashlights', propertyUniformMapping: {
center: 'flashlightCenters',
color: 'flashlightColors',
intensity: 'flashlightIntensities',
direction: 'flashlightDirections',
},
uniformCountMacroName: 'FLASHLIGHT_COUNT', uniformCountMacroName: 'FLASHLIGHT_COUNT',
shaderCombinationSteps: [0, 1, 2, 4], shaderCombinationSteps: [0, 1, 2, 4],
empty: new Flashlight( empty: new Flashlight(

View file

@ -6,15 +6,13 @@ export class Circle extends Drawable {
public static descriptor: DrawableDescriptor = { public static descriptor: DrawableDescriptor = {
sdf: { sdf: {
shader: ` shader: `
uniform struct { uniform vec2 circleCenters[CIRCLE_COUNT];
vec2 center; uniform vec2 circleRadii[CIRCLE_COUNT];
float radius;
}[CIRCLE_COUNT] circles;
void circleMinDistance(inout float minDistance, inout float color) { void circleMinDistance(inout float minDistance, inout float color) {
float myMinDistance = maxMinDistance; float myMinDistance = maxMinDistance;
for (int i = 0; i < CIRCLE_COUNT; i++) { for (int i = 0; i < CIRCLE_COUNT; i++) {
float dist = distance(circles[i].center, position) - circles[i].radius; float dist = distance(circleCenters[i], position) - circleRadii[i];
myMinDistance = min(myMinDistance, dist); myMinDistance = min(myMinDistance, dist);
} }
minDistance = min(minDistance, myMinDistance); minDistance = min(minDistance, myMinDistance);
@ -26,9 +24,12 @@ export class Circle extends Drawable {
`, `,
distanceFunctionName: 'circleMinDistance', distanceFunctionName: 'circleMinDistance',
}, },
uniformName: 'circles', propertyUniformMapping: {
center: 'circleCenters',
radius: 'circleRadii',
},
uniformCountMacroName: 'CIRCLE_COUNT', uniformCountMacroName: 'CIRCLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 16, 32], shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new Circle(vec2.fromValues(0, 0), 0), empty: new Circle(vec2.fromValues(0, 0), 0),
}; };

View file

@ -8,29 +8,26 @@ export class InvertedTunnel extends Drawable {
public static descriptor: DrawableDescriptor = { public static descriptor: DrawableDescriptor = {
sdf: { sdf: {
shader: ` shader: `
uniform struct InvertedTunnel { uniform vec2 froms[INVERTED_TUNNEL_COUNT];
vec2 from; uniform vec2 toFromDeltas[INVERTED_TUNNEL_COUNT];
vec2 toFromDelta; uniform float fromRadii[INVERTED_TUNNEL_COUNT];
float fromRadius; uniform float toRadii[INVERTED_TUNNEL_COUNT];
float toRadius;
}[INVERTED_TUNNEL_COUNT] invertedTunnels;
void invertedTunnelMinDistance(inout float minDistance, inout float color) { void invertedTunnelMinDistance(inout float minDistance, inout float color) {
float myMinDistance = -minDistance; float myMinDistance = -minDistance;
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) { for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
InvertedTunnel tunnel = invertedTunnels[i]; vec2 targetFromDelta = position - froms[i];
vec2 targetFromDelta = position - tunnel.from;
float h = clamp( float h = clamp(
dot(targetFromDelta, tunnel.toFromDelta) dot(targetFromDelta, toFromDeltas[i])
/ dot(tunnel.toFromDelta, tunnel.toFromDelta), / dot(toFromDeltas[i], toFromDeltas[i]),
0.0, 1.0 0.0, 1.0
); );
float currentDistance = -mix( float currentDistance = -mix(
tunnel.fromRadius, tunnel.toRadius, h fromRadii[i], toRadii[i], h
) + distance( ) + distance(
targetFromDelta, tunnel.toFromDelta * h targetFromDelta, toFromDeltas[i] * h
); );
myMinDistance = min(myMinDistance, currentDistance); myMinDistance = min(myMinDistance, currentDistance);
@ -45,7 +42,12 @@ export class InvertedTunnel extends Drawable {
`, `,
distanceFunctionName: 'invertedTunnelMinDistance', distanceFunctionName: 'invertedTunnelMinDistance',
}, },
uniformName: 'invertedTunnels', propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT', uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32], shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0), empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0),

View file

@ -8,29 +8,26 @@ export class Tunnel extends Drawable {
public static descriptor: DrawableDescriptor = { public static descriptor: DrawableDescriptor = {
sdf: { sdf: {
shader: ` shader: `
uniform struct Tunnel { uniform vec2 froms[TUNNEL_COUNT];
vec2 from; uniform vec2 toFromDeltas[TUNNEL_COUNT];
vec2 toFromDelta; uniform float fromRadii[TUNNEL_COUNT];
float fromRadius; uniform float toRadii[TUNNEL_COUNT];
float toRadius;
}[TUNNEL_COUNT] tunnels;
void tunnelMinDistance(inout float minDistance, inout float color) { void tunnelMinDistance(inout float minDistance, inout float color) {
float myMinDistance = minDistance; float myMinDistance = minDistance;
for (int i = 0; i < TUNNEL_COUNT; i++) { for (int i = 0; i < TUNNEL_COUNT; i++) {
Tunnel tunnel = tunnels[i]; vec2 targetFromDelta = position - froms[i];
vec2 targetFromDelta = position - tunnel.from;
float h = clamp( float h = clamp(
dot(targetFromDelta, tunnel.toFromDelta) dot(targetFromDelta, toFromDeltas[i])
/ dot(tunnel.toFromDelta, tunnel.toFromDelta), / dot(toFromDeltas[i], toFromDeltas[i]),
0.0, 1.0 0.0, 1.0
); );
float currentDistance = -mix( float currentDistance = -mix(
tunnel.fromRadius, tunnel.toRadius, h fromRadii[i], toRadii[i], h
) + distance( ) + distance(
targetFromDelta, tunnel.toFromDelta * h targetFromDelta, toFromDeltas[i] * h
); );
myMinDistance = min(myMinDistance, currentDistance); myMinDistance = min(myMinDistance, currentDistance);
@ -45,7 +42,12 @@ export class Tunnel extends Drawable {
`, `,
distanceFunctionName: 'tunnelMinDistance', distanceFunctionName: 'tunnelMinDistance',
}, },
uniformName: 'tunnels', propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'TUNNEL_COUNT', uniformCountMacroName: 'TUNNEL_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32], shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new Tunnel( empty: new Tunnel(

View file

@ -1,7 +1,8 @@
import { UniversalRenderingContext } from '../universal-rendering-context';
import { FrameBuffer } from './frame-buffer'; import { FrameBuffer } from './frame-buffer';
export class DefaultFrameBuffer extends FrameBuffer { export class DefaultFrameBuffer extends FrameBuffer {
constructor(gl: WebGL2RenderingContext) { constructor(gl: UniversalRenderingContext) {
super(gl); super(gl);
this.frameBuffer = null; this.frameBuffer = null;
this.setSize(); this.setSize();

View file

@ -1,4 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { UniversalRenderingContext } from '../universal-rendering-context';
export abstract class FrameBuffer { export abstract class FrameBuffer {
public renderScale = 1; public renderScale = 1;
@ -9,7 +10,7 @@ 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: WebGL2RenderingContext) {} constructor(protected gl: UniversalRenderingContext) {}
public bindAndClear(colorInput?: WebGLTexture) { public bindAndClear(colorInput?: WebGLTexture) {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);

View file

@ -1,14 +1,17 @@
import { enableExtension } from '../helper/enable-extension'; import { enableExtension } from '../helper/enable-extension';
import { UniversalRenderingContext } from '../universal-rendering-context';
import { FrameBuffer } from './frame-buffer'; import { FrameBuffer } from './frame-buffer';
export class IntermediateFrameBuffer extends FrameBuffer { export class IntermediateFrameBuffer extends FrameBuffer {
private frameTexture: WebGLTexture; private frameTexture: WebGLTexture;
private floatLinearEnabled = false; private floatLinearEnabled = false;
constructor(gl: WebGL2RenderingContext) { constructor(gl: UniversalRenderingContext) {
super(gl); super(gl);
if (gl.isWebGL2) {
enableExtension(gl, 'EXT_color_buffer_float'); enableExtension(gl, 'EXT_color_buffer_float');
}
try { try {
enableExtension(gl, 'OES_texture_float_linear'); enableExtension(gl, 'OES_texture_float_linear');
@ -45,12 +48,12 @@ export class IntermediateFrameBuffer extends FrameBuffer {
this.gl.texImage2D( this.gl.texImage2D(
this.gl.TEXTURE_2D, this.gl.TEXTURE_2D,
0, 0,
this.gl.RG16F, this.gl.isWebGL2 ? this.gl.RG16F : this.gl.RGBA,
this.size.x, this.size.x,
this.size.y, this.size.y,
0, 0,
this.gl.RG, this.gl.isWebGL2 ? this.gl.RG : this.gl.RGBA,
this.gl.FLOAT, this.gl.isWebGL2 ? this.gl.FLOAT : this.gl.UNSIGNED_BYTE,
null null
); );
} }

View file

@ -1,4 +1,5 @@
import { Insights } from '../../rendering/insights'; import { Insights } from '../../rendering/insights';
import { UniversalRenderingContext } from '../universal-rendering-context';
const extensions: Map<string, any> = new Map(); const extensions: Map<string, any> = new Map();
@ -11,7 +12,7 @@ const logExtensions = () => {
}; };
export const tryEnableExtension = ( export const tryEnableExtension = (
gl: WebGL2RenderingContext, gl: UniversalRenderingContext,
name: string name: string
): any | null => { ): any | null => {
if (extensions.has(name)) { if (extensions.has(name)) {
@ -30,7 +31,7 @@ export const tryEnableExtension = (
return extension; return extension;
}; };
export const enableExtension = (gl: WebGL2RenderingContext, name: string): any => { export const enableExtension = (gl: UniversalRenderingContext, name: string): any => {
const extension = tryEnableExtension(gl, name); const extension = tryEnableExtension(gl, name);
if (extension === null) { if (extension === null) {

View file

@ -1,9 +0,0 @@
export const getWebGl2Context = (canvas: HTMLCanvasElement): WebGL2RenderingContext => {
const gl = canvas.getContext('webgl2');
if (!gl) {
throw new Error('WebGl2 is not supported');
}
return gl;
};

View file

@ -1,20 +1,21 @@
import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix'; import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
import { UniversalRenderingContext } from '../universal-rendering-context';
const loaderMat3 = mat3.create(); const loaderMat3 = mat3.create();
export const loadUniform = ( export const loadUniform = (
gl: WebGL2RenderingContext, gl: UniversalRenderingContext,
value: any, value: any,
type: GLenum, type: GLenum,
location: WebGLUniformLocation location: WebGLUniformLocation
): any => { ): any => {
const converters: Map< const converters: Map<
GLenum, GLenum,
(gl: WebGL2RenderingContext, value: any, location: WebGLUniformLocation) => void (gl: UniversalRenderingContext, value: any, location: WebGLUniformLocation) => void
> = new Map(); > = new Map();
{ {
converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => { converters.set(WebGLRenderingContext.FLOAT, (gl, v, l) => {
if (v instanceof Array) { if (v instanceof Array || v[0] instanceof Float32Array) {
if (v.length == 0) { if (v.length == 0) {
return; return;
} }
@ -25,19 +26,18 @@ export const loadUniform = (
} }
}); });
converters.set(WebGL2RenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array<vec2>, l) => { converters.set(WebGLRenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array<vec2>, l) => {
if (v.length == 0) { if (v.length == 0) {
return; return;
} }
if (v[0] instanceof Array) { if (v[0] instanceof Array || v[0] instanceof Float32Array) {
const result = new Float32Array(v.length * 2); const result = new Float32Array(v.length * 2);
for (let i = 0; i < v.length; i++) { for (let i = 0; i < v.length; i++) {
result[2 * i] = (v[i] as Array<number>).x; result[2 * i] = (v[i] as Array<number>).x;
result[2 * i + 1] = (v[i] as Array<number>).y; result[2 * i + 1] = (v[i] as Array<number>).y;
} }
gl.uniform2fv(l, result); gl.uniform2fv(l, result);
} else { } else {
gl.uniform2fv(l, v as vec2); gl.uniform2fv(l, v as vec2);
@ -45,13 +45,13 @@ export const loadUniform = (
}); });
converters.set( converters.set(
WebGL2RenderingContext.FLOAT_VEC3, WebGLRenderingContext.FLOAT_VEC3,
(gl, v: ReadonlyVec3 | Array<vec3>, l) => { (gl, v: ReadonlyVec3 | Array<vec3>, l) => {
if (v.length == 0) { if (v.length == 0) {
return; return;
} }
if (v[0] instanceof Array) { if (v[0] instanceof Array || v[0] instanceof Float32Array) {
const result = new Float32Array(v.length * 3); const result = new Float32Array(v.length * 3);
for (let i = 0; i < v.length; i++) { for (let i = 0; i < v.length; i++) {
@ -67,11 +67,19 @@ export const loadUniform = (
} }
); );
converters.set(WebGL2RenderingContext.FLOAT_MAT3, (gl, v, l) => { converters.set(WebGLRenderingContext.FLOAT_MAT3, (gl, v, l) => {
if (gl.isWebGL2) {
gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v)); gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v));
} else {
gl.uniformMatrix3fv(
l,
false,
mat3.transpose(mat3.create(), mat3.fromMat2d(loaderMat3, v))
);
}
}); });
converters.set(WebGL2RenderingContext.BOOL, (gl, v, l) => gl.uniform1i(l, v)); converters.set(WebGLRenderingContext.BOOL, (gl, v, l) => gl.uniform1i(l, v));
if (!converters.has(type)) { if (!converters.has(type)) {
throw new Error(`Unimplemented webgl type: ${type}`); throw new Error(`Unimplemented webgl type: ${type}`);

View file

@ -1,3 +1,4 @@
import { UniversalRenderingContext } from '../universal-rendering-context';
import { enableExtension } from './enable-extension'; import { enableExtension } from './enable-extension';
enum StopwatchState { enum StopwatchState {
@ -13,7 +14,7 @@ export class WebGlStopwatch {
private timerExtension: any; private timerExtension: any;
private timerQuery?: WebGLQuery; private timerQuery?: WebGLQuery;
constructor(private gl: WebGL2RenderingContext) { constructor(private gl: UniversalRenderingContext) {
this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2'); this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2');
} }

View file

@ -1,18 +1,23 @@
import { wait } from '../../helper/wait'; import { wait } from '../../helper/wait';
import { Insights } from '../rendering/insights'; import { Insights } from '../rendering/insights';
import { tryEnableExtension } from './helper/enable-extension'; import { tryEnableExtension } from './helper/enable-extension';
import { UniversalRenderingContext } from './universal-rendering-context';
type CompilingProgram = { type CompilingProgram = {
program: WebGLProgram; program: WebGLProgram;
resolvePromise: ((program: WebGLProgram) => void) | null; resolvePromise: ((program: WebGLProgram) => void) | null;
vertexShader: ShaderWithSource;
fragmentShader: ShaderWithSource;
}; };
type ShaderWithSource = WebGLShader & { source: string };
export abstract class ParallelCompiler { export abstract class ParallelCompiler {
private static extension?: any; private static extension?: any;
private static gl: WebGL2RenderingContext; private static gl: UniversalRenderingContext;
private static programs: Array<CompilingProgram> = []; private static programs: Array<CompilingProgram> = [];
public static initialize(gl: WebGL2RenderingContext) { public static initialize(gl: UniversalRenderingContext) {
ParallelCompiler.gl = gl; ParallelCompiler.gl = gl;
ParallelCompiler.extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile'); ParallelCompiler.extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile');
} }
@ -28,14 +33,14 @@ export abstract class ParallelCompiler {
// can only return null on lost context // can only return null on lost context
const program = ParallelCompiler.gl.createProgram()!; const program = ParallelCompiler.gl.createProgram()!;
ParallelCompiler.compileShader( const vertexShader = ParallelCompiler.compileShader(
vertexShaderSource, vertexShaderSource,
ParallelCompiler.gl.VERTEX_SHADER, ParallelCompiler.gl.VERTEX_SHADER,
program, program,
substitutions substitutions
); );
ParallelCompiler.compileShader( const fragmentShader = ParallelCompiler.compileShader(
fragmentShaderSource, fragmentShaderSource,
ParallelCompiler.gl.FRAGMENT_SHADER, ParallelCompiler.gl.FRAGMENT_SHADER,
program, program,
@ -45,6 +50,8 @@ export abstract class ParallelCompiler {
ParallelCompiler.programs.push({ ParallelCompiler.programs.push({
program, program,
resolvePromise, resolvePromise,
vertexShader,
fragmentShader,
}); });
return promise; return promise;
@ -67,7 +74,7 @@ export abstract class ParallelCompiler {
type: GLenum, type: GLenum,
program: WebGLProgram, program: WebGLProgram,
substitutions: { [name: string]: string } substitutions: { [name: string]: string }
) { ): ShaderWithSource {
const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => { const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => {
const value = substitutions[name]; const value = substitutions[name];
return Number.isInteger(value) ? `${value}.0` : value; return Number.isInteger(value) ? `${value}.0` : value;
@ -79,7 +86,11 @@ export abstract class ParallelCompiler {
this.gl.shaderSource(shader, processedSource); this.gl.shaderSource(shader, processedSource);
this.gl.compileShader(shader); this.gl.compileShader(shader);
this.gl.attachShader(program, shader); this.gl.attachShader(program, shader);
this.gl.deleteShader(shader);
const result = shader as ShaderWithSource;
result.source = processedSource;
return result;
} }
private static resolveFinishedPrograms() { private static resolveFinishedPrograms() {
@ -93,7 +104,7 @@ export abstract class ParallelCompiler {
ParallelCompiler.extension.COMPLETION_STATUS_KHR ParallelCompiler.extension.COMPLETION_STATUS_KHR
) )
) { ) {
ParallelCompiler.checkProgram(p.program); ParallelCompiler.checkProgram(p);
done.push(p); done.push(p);
p.resolvePromise!(p.program); p.resolvePromise!(p.program);
} }
@ -104,18 +115,39 @@ export abstract class ParallelCompiler {
); );
} }
private static checkProgram(program: WebGLProgram) { private static checkProgram(program: CompilingProgram) {
const success = ParallelCompiler.gl.getProgramParameter( const success = ParallelCompiler.gl.getProgramParameter(
program, program.program,
ParallelCompiler.gl.LINK_STATUS ParallelCompiler.gl.LINK_STATUS
); );
if (!success && !ParallelCompiler.gl.isContextLost()) { if (!success && !ParallelCompiler.gl.isContextLost()) {
ParallelCompiler.gl.getAttachedShaders(program)?.forEach((s) => { ParallelCompiler.prettyPrintErrorsIfThereAreAny(program.vertexShader);
ParallelCompiler.checkShader(s); ParallelCompiler.prettyPrintErrorsIfThereAreAny(program.fragmentShader);
});
throw new Error(ParallelCompiler.gl.getProgramInfoLog(program)!); throw new Error(ParallelCompiler.gl.getProgramInfoLog(program.program)!);
}
this.gl.deleteShader(program.vertexShader);
this.gl.deleteShader(program.fragmentShader);
}
private static prettyPrintErrorsIfThereAreAny(shader: ShaderWithSource) {
try {
ParallelCompiler.checkShader(shader);
} catch (e) {
for (const match of e
.toString()
.matchAll(/ERROR: 0:(?<line>\d+): (?<error>.*)$/gm)) {
const line = Number.parseInt(match.groups.line);
const error = match.groups.error;
console.error(
`Error: ${error}\nSource (line ${line}):\n${
shader.source.split('\n')[line - 1]
}`
);
}
throw new Error('Error while compiling shader');
} }
} }

View file

@ -1,10 +1,13 @@
import { enableExtension } from '../helper/enable-extension';
import { UniversalRenderingContext } from '../universal-rendering-context';
import Program from './program'; import Program from './program';
export class FragmentShaderOnlyProgram extends Program { export class FragmentShaderOnlyProgram extends Program {
private vao?: WebGLVertexArrayObject; private vao?: WebGLVertexArrayObject;
private vertexArrayExtension: any;
constructor(gl: WebGL2RenderingContext) { constructor(gl: UniversalRenderingContext) {
super(gl); super(gl);
this.vertexArrayExtension = enableExtension(this.gl, 'OES_vertex_array_object');
} }
public async initialize( public async initialize(
@ -17,7 +20,11 @@ export class FragmentShaderOnlyProgram extends Program {
public bind() { public bind() {
super.bind(); super.bind();
if (this.gl.isWebGL2) {
this.gl.bindVertexArray(this.vao!); this.gl.bindVertexArray(this.vao!);
} else {
this.vertexArrayExtension.createVertexArrayOES();
}
} }
public draw(uniforms: { [name: string]: any }) { public draw(uniforms: { [name: string]: any }) {
@ -26,7 +33,12 @@ export class FragmentShaderOnlyProgram extends Program {
} }
public destroy(): void { public destroy(): void {
if (this.gl.isWebGL2) {
this.gl.deleteVertexArray(this.vao!); this.gl.deleteVertexArray(this.vao!);
} else {
this.vertexArrayExtension.deleteVertexArrayOES(this.vao!);
}
super.destroy(); super.destroy();
} }
@ -45,10 +57,15 @@ export class FragmentShaderOnlyProgram extends Program {
this.gl.STATIC_DRAW this.gl.STATIC_DRAW
); );
if (this.gl.isWebGL2) {
// can only return null on lost context // can only return null on lost context
this.vao = this.gl.createVertexArray()!; this.vao = this.gl.createVertexArray()!;
this.gl.bindVertexArray(this.vao!);
} else {
this.vao = this.vertexArrayExtension.createVertexArrayOES();
this.vertexArrayExtension.bindVertexArrayOES(this.vao!);
}
this.gl.bindVertexArray(this.vao);
this.gl.enableVertexAttribArray(positionAttributeLocation); this.gl.enableVertexAttribArray(positionAttributeLocation);
this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0); this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
} }

View file

@ -1,6 +1,7 @@
import { mat2d, vec2 } from 'gl-matrix'; import { mat2d, vec2 } from 'gl-matrix';
import { loadUniform } from '../helper/load-uniform'; import { loadUniform } from '../helper/load-uniform';
import { ParallelCompiler } from '../parallel-compiler'; import { ParallelCompiler } from '../parallel-compiler';
import { UniversalRenderingContext } from '../universal-rendering-context';
import { IProgram } from './i-program'; import { IProgram } from './i-program';
export default abstract class Program implements IProgram { export default abstract class Program implements IProgram {
@ -14,7 +15,7 @@ export default abstract class Program implements IProgram {
type: GLenum; type: GLenum;
}> = []; }> = [];
constructor(protected gl: WebGL2RenderingContext) {} constructor(protected gl: UniversalRenderingContext) {}
public async initialize( public async initialize(
[vertexShaderSource, fragmentShaderSource]: [string, string], [vertexShaderSource, fragmentShaderSource]: [string, string],
@ -67,7 +68,7 @@ export default abstract class Program implements IProgram {
private queryUniforms() { private queryUniforms() {
const uniformCount = this.gl.getProgramParameter( const uniformCount = this.gl.getProgramParameter(
this.program!, this.program!,
WebGL2RenderingContext.ACTIVE_UNIFORMS WebGLRenderingContext.ACTIVE_UNIFORMS
); );
for (let i = 0; i < uniformCount; i++) { for (let i = 0; i < uniformCount; i++) {

View file

@ -2,6 +2,7 @@ import { mat2d, vec2 } from 'gl-matrix';
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor'; import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
import { getCombinations } from '../../../helper/get-combinations'; import { getCombinations } from '../../../helper/get-combinations';
import { last } from '../../../helper/last'; import { last } from '../../../helper/last';
import { UniversalRenderingContext } from '../universal-rendering-context';
import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; import { FragmentShaderOnlyProgram } from './fragment-shader-only-program';
import { IProgram } from './i-program'; import { IProgram } from './i-program';
@ -16,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: WebGL2RenderingContext) {} constructor(private gl: UniversalRenderingContext) {}
public async initialize( public async initialize(
shaderSources: [string, string], shaderSources: [string, string],
@ -44,9 +45,14 @@ export class UniformArrayAutoScalingProgram implements IProgram {
} }
public draw(uniforms: { [name: string]: any }): void { public draw(uniforms: { [name: string]: any }): void {
const values = this.descriptors!.map((d) => const values = this.descriptors!.map((d) => {
uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0 const uniformNames = last(Object.entries(d.propertyUniformMapping));
); if (!uniformNames) {
return 0;
}
const uniformName = uniformNames[1];
return uniforms[uniformName] ? uniforms[uniformName].length : 0;
});
const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i])); const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i]));

View file

@ -0,0 +1,34 @@
import { Insights } from '../rendering/insights';
export type UniversalRenderingContext = WebGL2RenderingContext &
WebGLRenderingContext & { isWebGL2: boolean };
export const getUniversalRenderingContext = (
canvas: HTMLCanvasElement,
ignoreWebGL2 = false
): UniversalRenderingContext => {
let context: WebGL2RenderingContext | WebGLRenderingContext | null = ignoreWebGL2
? null
: canvas.getContext('webgl2');
let webgl2Support = true;
if (!context) {
context = canvas.getContext('webgl');
webgl2Support = false;
}
if (!context) {
context = canvas.getContext('experimental-webgl') as WebGLRenderingContext;
}
if (!context) {
throw new Error('Neither WebGL or WebGL2 is supported');
}
Insights.setValue('using WebGL2', webgl2Support);
const result = context as UniversalRenderingContext;
result.isWebGL2 = webgl2Support;
return result;
};

View file

@ -1,11 +1,12 @@
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor'; import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer'; import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer';
import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program'; import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program';
import { UniversalRenderingContext } from '../../graphics-library/universal-rendering-context';
export abstract class RenderPass { export abstract class RenderPass {
protected program: UniformArrayAutoScalingProgram; protected program: UniformArrayAutoScalingProgram;
constructor(gl: WebGL2RenderingContext, protected frame: FrameBuffer) { constructor(gl: UniversalRenderingContext, protected frame: FrameBuffer) {
this.program = new UniformArrayAutoScalingProgram(gl); this.program = new UniformArrayAutoScalingProgram(gl);
} }

View file

@ -5,9 +5,12 @@ 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';
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch'; import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { ParallelCompiler } from '../graphics-library/parallel-compiler'; import { ParallelCompiler } from '../graphics-library/parallel-compiler';
import {
getUniversalRenderingContext,
UniversalRenderingContext,
} from '../graphics-library/universal-rendering-context';
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 { DistanceRenderPass } from './render-pass/distance-render-pass';
@ -15,14 +18,18 @@ import { LightsRenderPass } from './render-pass/lights-render-pass';
import { Renderer } from './renderer'; import { Renderer } from './renderer';
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 distanceFragmentShader100 from './shaders/distance-fs-100.glsl';
import distanceFragmentShader from './shaders/distance-fs.glsl'; import distanceFragmentShader from './shaders/distance-fs.glsl';
import distanceVertexShader100 from './shaders/distance-vs-100.glsl';
import distanceVertexShader from './shaders/distance-vs.glsl'; import distanceVertexShader from './shaders/distance-vs.glsl';
import lightsFragmentShader100 from './shaders/shading-fs-100.glsl';
import lightsFragmentShader from './shaders/shading-fs.glsl'; import lightsFragmentShader from './shaders/shading-fs.glsl';
import lightsVertexShader100 from './shaders/shading-vs-100.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';
export class WebGl2Renderer implements Renderer { export class RendererImplementation implements Renderer {
private gl: WebGL2RenderingContext; private gl: UniversalRenderingContext;
private stopwatch?: WebGlStopwatch; private stopwatch?: WebGlStopwatch;
private uniformsProvider: UniformsProvider; private uniformsProvider: UniformsProvider;
private distanceFieldFrameBuffer: IntermediateFrameBuffer; private distanceFieldFrameBuffer: IntermediateFrameBuffer;
@ -69,7 +76,7 @@ export class WebGl2Renderer implements Renderer {
private canvas: HTMLCanvasElement, private canvas: HTMLCanvasElement,
private descriptors: Array<DrawableDescriptor> private descriptors: Array<DrawableDescriptor>
) { ) {
this.gl = getWebGl2Context(canvas); this.gl = getUniversalRenderingContext(canvas);
ParallelCompiler.initialize(this.gl); ParallelCompiler.initialize(this.gl);
@ -92,23 +99,30 @@ export class WebGl2Renderer implements Renderer {
palette: Array<vec3>, palette: Array<vec3>,
settingsOverrides: Partial<StartupSettings> settingsOverrides: Partial<StartupSettings>
): Promise<void> { ): Promise<void> {
const settings = { ...WebGl2Renderer.defaultStartupSettings, ...settingsOverrides }; const settings = {
...RendererImplementation.defaultStartupSettings,
...settingsOverrides,
};
const promises: Array<Promise<void>> = []; const promises: Array<Promise<void>> = [];
promises.push( promises.push(
this.distancePass.initialize( this.distancePass.initialize(
[distanceVertexShader, distanceFragmentShader], this.gl.isWebGL2
this.descriptors.filter(WebGl2Renderer.hasSdf) ? [distanceVertexShader, distanceFragmentShader]
: [distanceVertexShader100, distanceFragmentShader100],
this.descriptors.filter(RendererImplementation.hasSdf)
) )
); );
promises.push( promises.push(
this.lightsPass.initialize( this.lightsPass.initialize(
[lightsVertexShader, lightsFragmentShader], this.gl.isWebGL2
this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)), ? [lightsVertexShader, lightsFragmentShader]
: [lightsVertexShader100, lightsFragmentShader100],
this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)),
{ {
palette: this.generatePaletteCode(palette), palette: this.generatePaletteCode(palette),
colorCount: palette.length.toString(),
shadowTraceCount: settings.shadowTraceCount, shadowTraceCount: settings.shadowTraceCount,
} }
) )
@ -134,7 +148,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 (
RendererImplementation.hasSdf((drawable.constructor as typeof Drawable).descriptor)
) {
this.distancePass.addDrawable(drawable); this.distancePass.addDrawable(drawable);
} else { } else {
this.lightsPass.addDrawable(drawable as LightDrawable); this.lightsPass.addDrawable(drawable as LightDrawable);
@ -149,12 +165,12 @@ export class WebGl2Renderer implements Renderer {
const numberToGlslFloat = (n: number) => (Number.isInteger(n) ? `${n}.0` : `${n}`); const numberToGlslFloat = (n: number) => (Number.isInteger(n) ? `${n}.0` : `${n}`);
return palette return palette
.map( .map(
(c) => (c, i) =>
`vec3(${numberToGlslFloat(c[0])}, ${numberToGlslFloat( `${this.gl.isWebGL2 ? '' : `colors[${i}] = `}vec3(${numberToGlslFloat(
c[1] c[0]
)}, ${numberToGlslFloat(c[2])})` )}, ${numberToGlslFloat(c[1])}, ${numberToGlslFloat(c[2])})`
) )
.join(',\n'); .join(this.gl.isWebGL2 ? ',\n' : ';\n');
} }
public renderDrawables() { public renderDrawables() {

View file

@ -0,0 +1,23 @@
#version 100
precision lowp float;
#define SURFACE_OFFSET 0.001
uniform float maxMinDistance;
uniform float distanceNdcPixelSize;
varying vec2 position;
{macroDefinitions}
{declarations}
void main() {
float minDistance = maxMinDistance;
float color = 0.0;
{functionCalls}
// minDistance / 2.0: NDC to UV scale
gl_FragColor = vec4(minDistance / 2.0, color, 0.0, 0.0);
}

View file

@ -0,0 +1,15 @@
#version 100
precision lowp float;
uniform mat3 modelTransform;
uniform vec2 squareToAspectRatio;
attribute vec4 vertexPosition;
varying vec2 position;
void main() {
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform;
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
position = vertexPosition2D.xy * squareToAspectRatio;
}

View file

@ -0,0 +1,145 @@
#version 100
precision lowp float;
#define INFINITY 1000.0
#define INTENSITY_INSIDE_RATIO 0.5
#define SHADOW_TRACE_COUNT {shadowTraceCount}
{macroDefinitions}
uniform vec2 squareToAspectRatioTimes2;
uniform float shadingNdcPixelSize;
uniform float shadowLength;
uniform sampler2D distanceTexture;
varying vec2 position;
varying vec2 uvCoordinates;
uniform vec3 ambientLight;
vec3 colors[{colorCount}];
float getDistance(in vec2 target, out vec3 color) {
vec4 values = texture2D(distanceTexture, target);
color = vec3(0.5);
return values[0];
}
float getDistance(in vec2 target) {
return texture2D(distanceTexture, target)[0];
}
float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) {
float rayLength = startingDistance;
for (int j = 0; j < SHADOW_TRACE_COUNT; j++) {
rayLength += getDistance(uvCoordinates + direction * rayLength);
}
return min(
1.0,
(
step(lightCenterDistance, rayLength) +
rayLength / (shadowLength * shadingNdcPixelSize)
) * 2.0
);
}
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT];
uniform float circleLightIntensities[CIRCLE_LIGHT_COUNT];
uniform vec3 circleLightColors[CIRCLE_LIGHT_COUNT];
varying vec2 circleLightDirections[CIRCLE_LIGHT_COUNT];
#endif
#endif
#ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0
uniform vec2 flashlightCenters[FLASHLIGHT_COUNT];
uniform float flashlightIntensities[FLASHLIGHT_COUNT];
uniform vec3 flashlightColors[FLASHLIGHT_COUNT];
uniform vec2 flashlightDirections[FLASHLIGHT_COUNT];
varying vec2 flashlightActualDirections[FLASHLIGHT_COUNT];
float intensityInDirection(vec2 lightDirection, vec2 targetDirection) {
return smoothstep(0.0, 1.0, 10.0 * max(0.0, dot(targetDirection, lightDirection) - 0.9));
}
#endif
#endif
void main() {
vec3 lighting = ambientLight;
{palette};
vec3 colorAtPosition;
float startingDistance = getDistance(uvCoordinates, colorAtPosition);
if (startingDistance < 0.0) {
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
float lightCenterDistance = distance(circleLightCenters[i], position);
lighting += circleLightColors[i] / pow(
lightCenterDistance / (circleLightIntensities[i] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0
);
}
#endif
#endif
#ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0
for (int i = 0; i < FLASHLIGHT_COUNT; i++) {
float lightCenterDistance = distance(flashlightCenters[i], position);
lighting += intensityInDirection(
flashlightDirections[i],
normalize(flashlightActualDirections[i])
) * flashlightColors[i] / pow(
lightCenterDistance / (flashlightIntensities[i] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0
);
}
#endif
#endif
} else {
colorAtPosition = vec3(1.0);
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2;
float lightCenterDistance = distance(circleLightCenters[i], position);
lighting += circleLightColors[i] / pow(
lightCenterDistance / circleLightIntensities[i] + 1.0, 2.0
) * shadowTransparency(startingDistance, lightCenterDistance, direction);
}
#endif
#endif
#ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0
for (int i = 0; i < FLASHLIGHT_COUNT; i++) {
vec2 originalDirection = normalize(flashlightDirections[i]);
vec2 direction = originalDirection / squareToAspectRatioTimes2;
float lightCenterDistance = distance(flashlightCenters[i], position);
vec3 lightColorAtPosition = intensityInDirection(flashlightDirections[i], positionDirection) *
flashlightColors[i] / pow(
lightCenterDistance / flashlightIntensities[i] + 1.0, 2.0
);
if (length(lightColorAtPosition) < 0.01) {
continue;
}
lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction);
}
#endif
#endif
}
gl_FragColor = vec4(colorAtPosition * lighting, 1.0);
}

View file

@ -18,7 +18,7 @@ in vec2 uvCoordinates;
uniform vec3 ambientLight; uniform vec3 ambientLight;
vec3[3] colors = vec3[]( vec3[colorCount] colors = vec3[](
{palette} {palette}
); );
@ -50,25 +50,23 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
#ifdef CIRCLE_LIGHT_COUNT #ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0 #if CIRCLE_LIGHT_COUNT > 0
uniform struct CircleLight { uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT];
vec2 center; uniform float circleLightIntensities[CIRCLE_LIGHT_COUNT];
float intensity; uniform vec3 circleLightColors[CIRCLE_LIGHT_COUNT];
vec3 color;
}[CIRCLE_LIGHT_COUNT] circleLights;
in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections;
vec3 colorInPosition(CircleLight light, out float lightCenterDistance) { vec3 colorInPosition(int lightIndex, out float lightCenterDistance) {
lightCenterDistance = distance(light.center, position); lightCenterDistance = distance(circleLightCenters[lightIndex], position);
return light.color / pow( return circleLightColors[lightIndex] / pow(
lightCenterDistance / light.intensity + 1.0, 2.0 lightCenterDistance / circleLightIntensities[lightIndex] + 1.0, 2.0
); );
} }
vec3 colorInPositionInside(CircleLight light) { vec3 colorInPositionInside(int lightIndex) {
float lightCenterDistance = distance(light.center, position); float lightCenterDistance = distance(circleLightCenters[lightIndex], position);
return light.color / pow( return circleLightColors[lightIndex] / pow(
lightCenterDistance / (light.intensity * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 lightCenterDistance / (circleLightIntensities[lightIndex] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0
); );
} }
#endif #endif
@ -76,30 +74,30 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
#ifdef FLASHLIGHT_COUNT #ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0 #if FLASHLIGHT_COUNT > 0
uniform struct Flashlight { uniform vec2 flashlightCenters[FLASHLIGHT_COUNT];
vec2 center; uniform float flashlightIntensities[FLASHLIGHT_COUNT];
vec2 direction; uniform vec3 flashlightColors[FLASHLIGHT_COUNT];
float intensity; uniform vec2 flashlightDirections[FLASHLIGHT_COUNT];
vec3 color;
}[FLASHLIGHT_COUNT] flashlights;
in vec2[FLASHLIGHT_COUNT] flashlightDirections; in vec2[FLASHLIGHT_COUNT] flashlightActualDirections;
float intensityInDirection(vec2 lightDirection, vec2 targetDirection) { float intensityInDirection(vec2 lightDirection, vec2 targetDirection) {
return smoothstep(0.0, 1.0, 10.0 * max(0.0, dot(targetDirection, lightDirection) - 0.9)); return smoothstep(0.0, 1.0, 10.0 * max(0.0, dot(targetDirection, lightDirection) - 0.9));
} }
vec3 colorInPosition(Flashlight light, vec2 positionDirection, out float lightCenterDistance) { vec3 colorInPosition(int lightIndex, vec2 positionDirection, out float lightCenterDistance) {
lightCenterDistance = distance(light.center, position); lightCenterDistance = distance(flashlightCenters[lightIndex], position);
return intensityInDirection(light.direction, positionDirection) * light.color / pow( return intensityInDirection(flashlightDirections[lightIndex], positionDirection) *
lightCenterDistance / light.intensity + 1.0, 2.0 flashlightColors[lightIndex] / pow(
lightCenterDistance / flashlightIntensities[lightIndex] + 1.0, 2.0
); );
} }
vec3 colorInPositionInside(Flashlight light, vec2 positionDirection) { vec3 colorInPositionInside(int lightIndex, vec2 positionDirection) {
float lightCenterDistance = distance(light.center, position); float lightCenterDistance = distance(flashlightCenters[lightIndex], position);
return intensityInDirection(light.direction, positionDirection) * light.color / pow( return intensityInDirection(flashlightDirections[lightIndex], positionDirection) *
lightCenterDistance / (light.intensity * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 flashlightColors[lightIndex] / pow(
lightCenterDistance / (flashlightIntensities[lightIndex] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0
); );
} }
#endif #endif
@ -116,7 +114,7 @@ void main() {
#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++) {
lighting += colorInPositionInside(circleLights[i]); lighting += colorInPositionInside(i);
} }
#endif #endif
#endif #endif
@ -124,7 +122,7 @@ void main() {
#ifdef FLASHLIGHT_COUNT #ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0 #if FLASHLIGHT_COUNT > 0
for (int i = 0; i < FLASHLIGHT_COUNT; i++) { for (int i = 0; i < FLASHLIGHT_COUNT; i++) {
lighting += colorInPositionInside(flashlights[i], normalize(flashlightDirections[i])); lighting += colorInPositionInside(i, normalize(flashlightActualDirections[i]));
} }
#endif #endif
#endif #endif
@ -137,7 +135,7 @@ void main() {
vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2;
float lightCenterDistance; float lightCenterDistance;
vec3 lightColorAtPosition = colorInPosition(circleLights[i], lightCenterDistance); vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance);
lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction);
} }
@ -151,7 +149,7 @@ void main() {
vec2 direction = originalDirection / squareToAspectRatioTimes2; vec2 direction = originalDirection / squareToAspectRatioTimes2;
float lightCenterDistance; float lightCenterDistance;
vec3 lightColorAtPosition = colorInPosition(flashlights[i], originalDirection, lightCenterDistance); vec3 lightColorAtPosition = colorInPosition(i, originalDirection, lightCenterDistance);
if (length(lightColorAtPosition) < 0.01) { if (length(lightColorAtPosition) < 0.01) {
continue; continue;

View file

@ -0,0 +1,55 @@
#version 100
precision lowp float;
{macroDefinitions}
uniform mat3 modelTransform;
attribute vec4 vertexPosition;
varying vec2 position;
varying vec2 uvCoordinates;
uniform vec2 squareToAspectRatio;
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT];
varying vec2 circleLightDirections[CIRCLE_LIGHT_COUNT];
#endif
#endif
#ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0
uniform vec2 flashlightCenters[FLASHLIGHT_COUNT];
varying vec2 flashlightActualDirections[FLASHLIGHT_COUNT];
#endif
#endif
void main() {
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform;
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
position = vertexPosition2D.xy * squareToAspectRatio;
uvCoordinates = (vertexPosition2D * mat3(
0.5, 0.0, 0.5,
0.0, 0.5, 0.5,
0.0, 0.0, 1.0
)).xy;
#ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
circleLightDirections[i] = circleLightCenters[i] - position;
}
#endif
#endif
#ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0
for (int i = 0; i < FLASHLIGHT_COUNT; i++) {
flashlightActualDirections[i] = flashlightCenters[i] - position;
}
#endif
#endif
}

View file

@ -14,26 +14,15 @@ uniform vec2 squareToAspectRatio;
#ifdef CIRCLE_LIGHT_COUNT #ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0 #if CIRCLE_LIGHT_COUNT > 0
uniform struct CircleLight { uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT];
vec2 center;
float intensity;
vec3 color;
}[CIRCLE_LIGHT_COUNT] circleLights;
out vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; out vec2[CIRCLE_LIGHT_COUNT] circleLightDirections;
#endif #endif
#endif #endif
#ifdef FLASHLIGHT_COUNT #ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0 #if FLASHLIGHT_COUNT > 0
uniform struct Flashlight { uniform vec2 flashlightCenters[FLASHLIGHT_COUNT];
vec2 center; out vec2[FLASHLIGHT_COUNT] flashlightActualDirections;
vec2 direction;
float intensity;
vec3 color;
}[FLASHLIGHT_COUNT] flashlights;
out vec2[FLASHLIGHT_COUNT] flashlightDirections;
#endif #endif
#endif #endif
@ -51,7 +40,7 @@ void main() {
#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++) {
circleLightDirections[i] = circleLights[i].center - position; circleLightDirections[i] = circleLightCenters[i] - position;
} }
#endif #endif
#endif #endif
@ -59,7 +48,7 @@ void main() {
#ifdef FLASHLIGHT_COUNT #ifdef FLASHLIGHT_COUNT
#if FLASHLIGHT_COUNT > 0 #if FLASHLIGHT_COUNT > 0
for (int i = 0; i < FLASHLIGHT_COUNT; i++) { for (int i = 0; i < FLASHLIGHT_COUNT; i++) {
flashlightDirections[i] = flashlights[i].center - position; flashlightActualDirections[i] = flashlightCenters[i] - position;
} }
#endif #endif
#endif #endif

View file

@ -1,4 +1,5 @@
import { mat2d, vec2, vec3 } from 'gl-matrix'; import { mat2d, vec2, vec3 } from 'gl-matrix';
import { UniversalRenderingContext } from '../graphics-library/universal-rendering-context';
export class UniformsProvider { export class UniformsProvider {
public ambientLight = vec3.fromValues(0.25, 0.15, 0.25); public ambientLight = vec3.fromValues(0.25, 0.15, 0.25);
@ -12,7 +13,7 @@ export class UniformsProvider {
private squareToAspectRatio = vec2.create(); private squareToAspectRatio = vec2.create();
private uvToWorld = mat2d.create(); private uvToWorld = mat2d.create();
public constructor(private gl: WebGL2RenderingContext) {} public constructor(private gl: UniversalRenderingContext) {}
public getUniforms(uniforms: any): any { public getUniforms(uniforms: any): any {
return { return {

View file

@ -2,8 +2,8 @@ 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';
import { RendererImplementation } from './graphics/rendering/renderer-implementation';
import { StartupSettings } from './graphics/rendering/settings/startup-settings'; import { StartupSettings } from './graphics/rendering/settings/startup-settings';
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';
@ -36,7 +36,7 @@ export async function compile(
settings: Partial<StartupSettings> = {} settings: Partial<StartupSettings> = {}
): Promise<Renderer> { ): Promise<Renderer> {
return Insights.measureFunction('startup', async () => { return Insights.measureFunction('startup', async () => {
const renderer = new WebGl2Renderer(canvas, descriptors); const renderer = new RendererImplementation(canvas, descriptors);
await renderer.initialize(palette, settings); await renderer.initialize(palette, settings);
return renderer; return renderer;
}); });