From 1d4980ae286665591590cc47a669a0b5aa938b8f Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Tue, 22 Sep 2020 16:41:59 +0200 Subject: [PATCH] Add WebGL compatibility --- src/drawables/drawable-descriptor.ts | 2 +- src/drawables/drawable.ts | 14 +- src/drawables/lights/circle-light.ts | 8 +- src/drawables/lights/flashlight.ts | 7 +- src/drawables/shapes/circle.ts | 15 +- src/drawables/shapes/inverted-tunnel.ts | 70 +++++---- src/drawables/shapes/tunnel.ts | 68 ++++---- .../frame-buffer/default-frame-buffer.ts | 3 +- .../frame-buffer/frame-buffer.ts | 3 +- .../frame-buffer/intermediate-frame-buffer.ts | 13 +- .../helper/enable-extension.ts | 5 +- .../helper/get-webgl2-context.ts | 9 -- .../graphics-library/helper/load-uniform.ts | 32 ++-- .../graphics-library/helper/stopwatch.ts | 3 +- .../graphics-library/parallel-compiler.ts | 58 +++++-- .../program/fragment-shader-only-program.ts | 31 +++- .../graphics-library/program/program.ts | 5 +- .../uniform-array-autoscaling-program.ts | 14 +- .../universal-rendering-context.ts | 34 ++++ .../rendering/render-pass/render-pass.ts | 3 +- ...renderer.ts => renderer-implementation.ts} | 48 ++++-- .../rendering/shaders/distance-fs-100.glsl | 23 +++ .../rendering/shaders/distance-vs-100.glsl | 15 ++ .../rendering/shaders/shading-fs-100.glsl | 145 ++++++++++++++++++ .../rendering/shaders/shading-fs.glsl | 68 ++++---- .../rendering/shaders/shading-vs-100.glsl | 55 +++++++ .../rendering/shaders/shading-vs.glsl | 21 +-- src/graphics/rendering/uniforms-provider.ts | 3 +- src/main.ts | 4 +- 29 files changed, 567 insertions(+), 212 deletions(-) delete mode 100644 src/graphics/graphics-library/helper/get-webgl2-context.ts create mode 100644 src/graphics/graphics-library/universal-rendering-context.ts rename src/graphics/rendering/{webgl2-renderer.ts => renderer-implementation.ts} (80%) create mode 100644 src/graphics/rendering/shaders/distance-fs-100.glsl create mode 100644 src/graphics/rendering/shaders/distance-vs-100.glsl create mode 100644 src/graphics/rendering/shaders/shading-fs-100.glsl create mode 100644 src/graphics/rendering/shaders/shading-vs-100.glsl diff --git a/src/drawables/drawable-descriptor.ts b/src/drawables/drawable-descriptor.ts index f8e468f..72a8098 100644 --- a/src/drawables/drawable-descriptor.ts +++ b/src/drawables/drawable-descriptor.ts @@ -1,7 +1,7 @@ import { Drawable } from './drawable'; export interface DrawableDescriptor { - uniformName: string; + propertyUniformMapping: { [property: string]: string }; uniformCountMacroName: string; sdf?: { shader: string; diff --git a/src/drawables/drawable.ts b/src/drawables/drawable.ts index f7265fb..626e20f 100644 --- a/src/drawables/drawable.ts +++ b/src/drawables/drawable.ts @@ -6,7 +6,6 @@ export abstract class Drawable { public static readonly descriptor: DrawableDescriptor; public abstract minDistance(target: vec2): number; - protected abstract getObjectToSerialize(transform2d: mat2d, transform1d: number): any; public serializeToUniforms( @@ -14,12 +13,15 @@ export abstract class Drawable { transform2d: mat2d, transform1d: number ): void { - const { uniformName } = (this.constructor as typeof Drawable).descriptor; + const { propertyUniformMapping } = (this.constructor as typeof Drawable).descriptor; - if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) { - uniforms[uniformName] = []; - } + const serialized = this.getObjectToSerialize(transform2d, transform1d); + 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]); + }); } } diff --git a/src/drawables/lights/circle-light.ts b/src/drawables/lights/circle-light.ts index 1b37853..f916099 100644 --- a/src/drawables/lights/circle-light.ts +++ b/src/drawables/lights/circle-light.ts @@ -4,9 +4,13 @@ import { LightDrawable } from './light-drawable'; export class CircleLight extends LightDrawable { public static readonly descriptor: DrawableDescriptor = { - uniformName: 'circleLights', + propertyUniformMapping: { + center: 'circleLightCenters', + color: 'circleLightColors', + intensity: 'circleLightIntensities', + }, 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), }; diff --git a/src/drawables/lights/flashlight.ts b/src/drawables/lights/flashlight.ts index 9d60c36..6574e3c 100644 --- a/src/drawables/lights/flashlight.ts +++ b/src/drawables/lights/flashlight.ts @@ -4,7 +4,12 @@ import { LightDrawable } from './light-drawable'; export class Flashlight extends LightDrawable { public static readonly descriptor: DrawableDescriptor = { - uniformName: 'flashlights', + propertyUniformMapping: { + center: 'flashlightCenters', + color: 'flashlightColors', + intensity: 'flashlightIntensities', + direction: 'flashlightDirections', + }, uniformCountMacroName: 'FLASHLIGHT_COUNT', shaderCombinationSteps: [0, 1, 2, 4], empty: new Flashlight( diff --git a/src/drawables/shapes/circle.ts b/src/drawables/shapes/circle.ts index 051db2d..ea352bf 100644 --- a/src/drawables/shapes/circle.ts +++ b/src/drawables/shapes/circle.ts @@ -6,15 +6,13 @@ export class Circle extends Drawable { public static descriptor: DrawableDescriptor = { sdf: { shader: ` - uniform struct { - vec2 center; - float radius; - }[CIRCLE_COUNT] circles; + uniform vec2 circleCenters[CIRCLE_COUNT]; + uniform vec2 circleRadii[CIRCLE_COUNT]; void circleMinDistance(inout float minDistance, inout float color) { float myMinDistance = maxMinDistance; 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); } minDistance = min(minDistance, myMinDistance); @@ -26,9 +24,12 @@ export class Circle extends Drawable { `, distanceFunctionName: 'circleMinDistance', }, - uniformName: 'circles', + propertyUniformMapping: { + center: 'circleCenters', + radius: 'circleRadii', + }, 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), }; diff --git a/src/drawables/shapes/inverted-tunnel.ts b/src/drawables/shapes/inverted-tunnel.ts index 3a02434..8d3ccad 100644 --- a/src/drawables/shapes/inverted-tunnel.ts +++ b/src/drawables/shapes/inverted-tunnel.ts @@ -8,44 +8,46 @@ export class InvertedTunnel extends Drawable { public static descriptor: DrawableDescriptor = { sdf: { shader: ` - uniform struct InvertedTunnel { - vec2 from; - vec2 toFromDelta; - float fromRadius; - float toRadius; - }[INVERTED_TUNNEL_COUNT] invertedTunnels; + uniform vec2 froms[INVERTED_TUNNEL_COUNT]; + uniform vec2 toFromDeltas[INVERTED_TUNNEL_COUNT]; + uniform float fromRadii[INVERTED_TUNNEL_COUNT]; + uniform float toRadii[INVERTED_TUNNEL_COUNT]; - void invertedTunnelMinDistance(inout float minDistance, inout float color) { - float myMinDistance = -minDistance; - for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) { - InvertedTunnel tunnel = invertedTunnels[i]; - vec2 targetFromDelta = position - tunnel.from; - - float h = clamp( - dot(targetFromDelta, tunnel.toFromDelta) - / dot(tunnel.toFromDelta, tunnel.toFromDelta), - 0.0, 1.0 - ); - - float currentDistance = -mix( - tunnel.fromRadius, tunnel.toRadius, h - ) + distance( - targetFromDelta, tunnel.toFromDelta * h - ); - - myMinDistance = min(myMinDistance, currentDistance); - } - - color = mix(0.0, color, step( - distanceNdcPixelSize + SURFACE_OFFSET, - -myMinDistance - )); - minDistance = -myMinDistance; + void invertedTunnelMinDistance(inout float minDistance, inout float color) { + float myMinDistance = -minDistance; + for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) { + vec2 targetFromDelta = position - froms[i]; + + float h = clamp( + dot(targetFromDelta, toFromDeltas[i]) + / dot(toFromDeltas[i], toFromDeltas[i]), + 0.0, 1.0 + ); + + float currentDistance = -mix( + fromRadii[i], toRadii[i], h + ) + distance( + targetFromDelta, toFromDeltas[i] * h + ); + + myMinDistance = min(myMinDistance, currentDistance); } - `, + + color = mix(0.0, color, step( + distanceNdcPixelSize + SURFACE_OFFSET, + -myMinDistance + )); + minDistance = -myMinDistance; + } + `, distanceFunctionName: 'invertedTunnelMinDistance', }, - uniformName: 'invertedTunnels', + propertyUniformMapping: { + from: 'froms', + toFromDelta: 'toFromDeltas', + fromRadius: 'fromRadii', + toRadius: 'toRadii', + }, uniformCountMacroName: 'INVERTED_TUNNEL_COUNT', shaderCombinationSteps: [0, 1, 4, 16, 32], empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0), diff --git a/src/drawables/shapes/tunnel.ts b/src/drawables/shapes/tunnel.ts index 3fe837a..4f77758 100644 --- a/src/drawables/shapes/tunnel.ts +++ b/src/drawables/shapes/tunnel.ts @@ -8,44 +8,46 @@ export class Tunnel extends Drawable { public static descriptor: DrawableDescriptor = { sdf: { shader: ` - uniform struct Tunnel { - vec2 from; - vec2 toFromDelta; - float fromRadius; - float toRadius; - }[TUNNEL_COUNT] tunnels; + uniform vec2 froms[TUNNEL_COUNT]; + uniform vec2 toFromDeltas[TUNNEL_COUNT]; + uniform float fromRadii[TUNNEL_COUNT]; + uniform float toRadii[TUNNEL_COUNT]; - void tunnelMinDistance(inout float minDistance, inout float color) { - float myMinDistance = minDistance; - for (int i = 0; i < TUNNEL_COUNT; i++) { - Tunnel tunnel = tunnels[i]; - vec2 targetFromDelta = position - tunnel.from; - - float h = clamp( - dot(targetFromDelta, tunnel.toFromDelta) - / dot(tunnel.toFromDelta, tunnel.toFromDelta), - 0.0, 1.0 - ); - - float currentDistance = -mix( - tunnel.fromRadius, tunnel.toRadius, h - ) + distance( - targetFromDelta, tunnel.toFromDelta * h - ); + void tunnelMinDistance(inout float minDistance, inout float color) { + float myMinDistance = minDistance; + for (int i = 0; i < TUNNEL_COUNT; i++) { + vec2 targetFromDelta = position - froms[i]; + + float h = clamp( + dot(targetFromDelta, toFromDeltas[i]) + / dot(toFromDeltas[i], toFromDeltas[i]), + 0.0, 1.0 + ); + + float currentDistance = -mix( + fromRadii[i], toRadii[i], h + ) + distance( + targetFromDelta, toFromDeltas[i] * h + ); - myMinDistance = min(myMinDistance, currentDistance); - } - - color = mix(2.0, color, step( - distanceNdcPixelSize + SURFACE_OFFSET, - myMinDistance - )); - minDistance = min(minDistance, myMinDistance); + myMinDistance = min(myMinDistance, currentDistance); } - `, + + color = mix(2.0, color, step( + distanceNdcPixelSize + SURFACE_OFFSET, + myMinDistance + )); + minDistance = min(minDistance, myMinDistance); + } + `, distanceFunctionName: 'tunnelMinDistance', }, - uniformName: 'tunnels', + propertyUniformMapping: { + from: 'froms', + toFromDelta: 'toFromDeltas', + fromRadius: 'fromRadii', + toRadius: 'toRadii', + }, uniformCountMacroName: 'TUNNEL_COUNT', shaderCombinationSteps: [0, 1, 4, 16, 32], empty: new Tunnel( diff --git a/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts index 2f25a50..668f1f5 100644 --- a/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts @@ -1,7 +1,8 @@ +import { UniversalRenderingContext } from '../universal-rendering-context'; import { FrameBuffer } from './frame-buffer'; export class DefaultFrameBuffer extends FrameBuffer { - constructor(gl: WebGL2RenderingContext) { + constructor(gl: UniversalRenderingContext) { super(gl); this.frameBuffer = null; this.setSize(); diff --git a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts index f5af835..540cf88 100644 --- a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts @@ -1,4 +1,5 @@ import { vec2 } from 'gl-matrix'; +import { UniversalRenderingContext } from '../universal-rendering-context'; export abstract class FrameBuffer { public renderScale = 1; @@ -9,7 +10,7 @@ export abstract class FrameBuffer { // null means the default framebuffer protected frameBuffer: WebGLFramebuffer | null = null; - constructor(protected gl: WebGL2RenderingContext) {} + constructor(protected gl: UniversalRenderingContext) {} public bindAndClear(colorInput?: WebGLTexture) { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); diff --git a/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts index 399a584..40e2d41 100644 --- a/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts @@ -1,14 +1,17 @@ import { enableExtension } from '../helper/enable-extension'; +import { UniversalRenderingContext } from '../universal-rendering-context'; import { FrameBuffer } from './frame-buffer'; export class IntermediateFrameBuffer extends FrameBuffer { private frameTexture: WebGLTexture; private floatLinearEnabled = false; - constructor(gl: WebGL2RenderingContext) { + constructor(gl: UniversalRenderingContext) { super(gl); - enableExtension(gl, 'EXT_color_buffer_float'); + if (gl.isWebGL2) { + enableExtension(gl, 'EXT_color_buffer_float'); + } try { enableExtension(gl, 'OES_texture_float_linear'); @@ -45,12 +48,12 @@ export class IntermediateFrameBuffer extends FrameBuffer { this.gl.texImage2D( this.gl.TEXTURE_2D, 0, - this.gl.RG16F, + this.gl.isWebGL2 ? this.gl.RG16F : this.gl.RGBA, this.size.x, this.size.y, 0, - this.gl.RG, - this.gl.FLOAT, + this.gl.isWebGL2 ? this.gl.RG : this.gl.RGBA, + this.gl.isWebGL2 ? this.gl.FLOAT : this.gl.UNSIGNED_BYTE, null ); } diff --git a/src/graphics/graphics-library/helper/enable-extension.ts b/src/graphics/graphics-library/helper/enable-extension.ts index 96a622f..5e1fd4f 100644 --- a/src/graphics/graphics-library/helper/enable-extension.ts +++ b/src/graphics/graphics-library/helper/enable-extension.ts @@ -1,4 +1,5 @@ import { Insights } from '../../rendering/insights'; +import { UniversalRenderingContext } from '../universal-rendering-context'; const extensions: Map = new Map(); @@ -11,7 +12,7 @@ const logExtensions = () => { }; export const tryEnableExtension = ( - gl: WebGL2RenderingContext, + gl: UniversalRenderingContext, name: string ): any | null => { if (extensions.has(name)) { @@ -30,7 +31,7 @@ export const tryEnableExtension = ( return extension; }; -export const enableExtension = (gl: WebGL2RenderingContext, name: string): any => { +export const enableExtension = (gl: UniversalRenderingContext, name: string): any => { const extension = tryEnableExtension(gl, name); if (extension === null) { diff --git a/src/graphics/graphics-library/helper/get-webgl2-context.ts b/src/graphics/graphics-library/helper/get-webgl2-context.ts deleted file mode 100644 index 30abe93..0000000 --- a/src/graphics/graphics-library/helper/get-webgl2-context.ts +++ /dev/null @@ -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; -}; diff --git a/src/graphics/graphics-library/helper/load-uniform.ts b/src/graphics/graphics-library/helper/load-uniform.ts index 2b00015..f6622e1 100644 --- a/src/graphics/graphics-library/helper/load-uniform.ts +++ b/src/graphics/graphics-library/helper/load-uniform.ts @@ -1,20 +1,21 @@ import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix'; +import { UniversalRenderingContext } from '../universal-rendering-context'; const loaderMat3 = mat3.create(); export const loadUniform = ( - gl: WebGL2RenderingContext, + gl: UniversalRenderingContext, value: any, type: GLenum, location: WebGLUniformLocation ): any => { const converters: Map< GLenum, - (gl: WebGL2RenderingContext, value: any, location: WebGLUniformLocation) => void + (gl: UniversalRenderingContext, value: any, location: WebGLUniformLocation) => void > = new Map(); { - converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => { - if (v instanceof Array) { + converters.set(WebGLRenderingContext.FLOAT, (gl, v, l) => { + if (v instanceof Array || v[0] instanceof Float32Array) { if (v.length == 0) { return; } @@ -25,19 +26,18 @@ export const loadUniform = ( } }); - converters.set(WebGL2RenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array, l) => { + converters.set(WebGLRenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array, l) => { if (v.length == 0) { return; } - if (v[0] instanceof Array) { + if (v[0] instanceof Array || v[0] instanceof Float32Array) { const result = new Float32Array(v.length * 2); for (let i = 0; i < v.length; i++) { result[2 * i] = (v[i] as Array).x; result[2 * i + 1] = (v[i] as Array).y; } - gl.uniform2fv(l, result); } else { gl.uniform2fv(l, v as vec2); @@ -45,13 +45,13 @@ export const loadUniform = ( }); converters.set( - WebGL2RenderingContext.FLOAT_VEC3, + WebGLRenderingContext.FLOAT_VEC3, (gl, v: ReadonlyVec3 | Array, l) => { if (v.length == 0) { return; } - if (v[0] instanceof Array) { + if (v[0] instanceof Array || v[0] instanceof Float32Array) { const result = new Float32Array(v.length * 3); for (let i = 0; i < v.length; i++) { @@ -67,11 +67,19 @@ export const loadUniform = ( } ); - converters.set(WebGL2RenderingContext.FLOAT_MAT3, (gl, v, l) => { - gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v)); + converters.set(WebGLRenderingContext.FLOAT_MAT3, (gl, v, l) => { + if (gl.isWebGL2) { + 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)) { throw new Error(`Unimplemented webgl type: ${type}`); diff --git a/src/graphics/graphics-library/helper/stopwatch.ts b/src/graphics/graphics-library/helper/stopwatch.ts index 2537ae8..55a15de 100644 --- a/src/graphics/graphics-library/helper/stopwatch.ts +++ b/src/graphics/graphics-library/helper/stopwatch.ts @@ -1,3 +1,4 @@ +import { UniversalRenderingContext } from '../universal-rendering-context'; import { enableExtension } from './enable-extension'; enum StopwatchState { @@ -13,7 +14,7 @@ export class WebGlStopwatch { private timerExtension: any; private timerQuery?: WebGLQuery; - constructor(private gl: WebGL2RenderingContext) { + constructor(private gl: UniversalRenderingContext) { this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2'); } diff --git a/src/graphics/graphics-library/parallel-compiler.ts b/src/graphics/graphics-library/parallel-compiler.ts index 4ad5eea..8449594 100644 --- a/src/graphics/graphics-library/parallel-compiler.ts +++ b/src/graphics/graphics-library/parallel-compiler.ts @@ -1,18 +1,23 @@ import { wait } from '../../helper/wait'; import { Insights } from '../rendering/insights'; import { tryEnableExtension } from './helper/enable-extension'; +import { UniversalRenderingContext } from './universal-rendering-context'; type CompilingProgram = { program: WebGLProgram; resolvePromise: ((program: WebGLProgram) => void) | null; + vertexShader: ShaderWithSource; + fragmentShader: ShaderWithSource; }; +type ShaderWithSource = WebGLShader & { source: string }; + export abstract class ParallelCompiler { private static extension?: any; - private static gl: WebGL2RenderingContext; + private static gl: UniversalRenderingContext; private static programs: Array = []; - public static initialize(gl: WebGL2RenderingContext) { + public static initialize(gl: UniversalRenderingContext) { ParallelCompiler.gl = gl; ParallelCompiler.extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile'); } @@ -28,14 +33,14 @@ export abstract class ParallelCompiler { // can only return null on lost context const program = ParallelCompiler.gl.createProgram()!; - ParallelCompiler.compileShader( + const vertexShader = ParallelCompiler.compileShader( vertexShaderSource, ParallelCompiler.gl.VERTEX_SHADER, program, substitutions ); - ParallelCompiler.compileShader( + const fragmentShader = ParallelCompiler.compileShader( fragmentShaderSource, ParallelCompiler.gl.FRAGMENT_SHADER, program, @@ -45,6 +50,8 @@ export abstract class ParallelCompiler { ParallelCompiler.programs.push({ program, resolvePromise, + vertexShader, + fragmentShader, }); return promise; @@ -67,7 +74,7 @@ export abstract class ParallelCompiler { type: GLenum, program: WebGLProgram, substitutions: { [name: string]: string } - ) { + ): ShaderWithSource { const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => { const value = substitutions[name]; return Number.isInteger(value) ? `${value}.0` : value; @@ -79,7 +86,11 @@ export abstract class ParallelCompiler { this.gl.shaderSource(shader, processedSource); this.gl.compileShader(shader); this.gl.attachShader(program, shader); - this.gl.deleteShader(shader); + + const result = shader as ShaderWithSource; + result.source = processedSource; + + return result; } private static resolveFinishedPrograms() { @@ -93,7 +104,7 @@ export abstract class ParallelCompiler { ParallelCompiler.extension.COMPLETION_STATUS_KHR ) ) { - ParallelCompiler.checkProgram(p.program); + ParallelCompiler.checkProgram(p); done.push(p); 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( - program, + program.program, ParallelCompiler.gl.LINK_STATUS ); if (!success && !ParallelCompiler.gl.isContextLost()) { - ParallelCompiler.gl.getAttachedShaders(program)?.forEach((s) => { - ParallelCompiler.checkShader(s); - }); + ParallelCompiler.prettyPrintErrorsIfThereAreAny(program.vertexShader); + 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:(?\d+): (?.*)$/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'); } } diff --git a/src/graphics/graphics-library/program/fragment-shader-only-program.ts b/src/graphics/graphics-library/program/fragment-shader-only-program.ts index 8ac38fa..3647083 100644 --- a/src/graphics/graphics-library/program/fragment-shader-only-program.ts +++ b/src/graphics/graphics-library/program/fragment-shader-only-program.ts @@ -1,10 +1,13 @@ +import { enableExtension } from '../helper/enable-extension'; +import { UniversalRenderingContext } from '../universal-rendering-context'; import Program from './program'; export class FragmentShaderOnlyProgram extends Program { private vao?: WebGLVertexArrayObject; - - constructor(gl: WebGL2RenderingContext) { + private vertexArrayExtension: any; + constructor(gl: UniversalRenderingContext) { super(gl); + this.vertexArrayExtension = enableExtension(this.gl, 'OES_vertex_array_object'); } public async initialize( @@ -17,7 +20,11 @@ export class FragmentShaderOnlyProgram extends Program { public bind() { super.bind(); - this.gl.bindVertexArray(this.vao!); + if (this.gl.isWebGL2) { + this.gl.bindVertexArray(this.vao!); + } else { + this.vertexArrayExtension.createVertexArrayOES(); + } } public draw(uniforms: { [name: string]: any }) { @@ -26,7 +33,12 @@ export class FragmentShaderOnlyProgram extends Program { } public destroy(): void { - this.gl.deleteVertexArray(this.vao!); + if (this.gl.isWebGL2) { + this.gl.deleteVertexArray(this.vao!); + } else { + this.vertexArrayExtension.deleteVertexArrayOES(this.vao!); + } + super.destroy(); } @@ -45,10 +57,15 @@ export class FragmentShaderOnlyProgram extends Program { this.gl.STATIC_DRAW ); - // can only return null on lost context - this.vao = this.gl.createVertexArray()!; + if (this.gl.isWebGL2) { + // can only return null on lost context + 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.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0); } diff --git a/src/graphics/graphics-library/program/program.ts b/src/graphics/graphics-library/program/program.ts index b6c6108..b8b3205 100644 --- a/src/graphics/graphics-library/program/program.ts +++ b/src/graphics/graphics-library/program/program.ts @@ -1,6 +1,7 @@ import { mat2d, vec2 } from 'gl-matrix'; import { loadUniform } from '../helper/load-uniform'; import { ParallelCompiler } from '../parallel-compiler'; +import { UniversalRenderingContext } from '../universal-rendering-context'; import { IProgram } from './i-program'; export default abstract class Program implements IProgram { @@ -14,7 +15,7 @@ export default abstract class Program implements IProgram { type: GLenum; }> = []; - constructor(protected gl: WebGL2RenderingContext) {} + constructor(protected gl: UniversalRenderingContext) {} public async initialize( [vertexShaderSource, fragmentShaderSource]: [string, string], @@ -67,7 +68,7 @@ export default abstract class Program implements IProgram { private queryUniforms() { const uniformCount = this.gl.getProgramParameter( this.program!, - WebGL2RenderingContext.ACTIVE_UNIFORMS + WebGLRenderingContext.ACTIVE_UNIFORMS ); for (let i = 0; i < uniformCount; i++) { diff --git a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts index 9568022..d51544a 100644 --- a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts +++ b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts @@ -2,6 +2,7 @@ import { mat2d, vec2 } from 'gl-matrix'; import { DrawableDescriptor } from '../../../drawables/drawable-descriptor'; import { getCombinations } from '../../../helper/get-combinations'; import { last } from '../../../helper/last'; +import { UniversalRenderingContext } from '../universal-rendering-context'; import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; import { IProgram } from './i-program'; @@ -16,7 +17,7 @@ export class UniformArrayAutoScalingProgram implements IProgram { private drawingRectangleBottomLeft = vec2.fromValues(0, 0); private drawingRectangleSize = vec2.fromValues(1, 1); - constructor(private gl: WebGL2RenderingContext) {} + constructor(private gl: UniversalRenderingContext) {} public async initialize( shaderSources: [string, string], @@ -44,9 +45,14 @@ export class UniformArrayAutoScalingProgram implements IProgram { } public draw(uniforms: { [name: string]: any }): void { - const values = this.descriptors!.map((d) => - uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0 - ); + const values = this.descriptors!.map((d) => { + 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])); diff --git a/src/graphics/graphics-library/universal-rendering-context.ts b/src/graphics/graphics-library/universal-rendering-context.ts new file mode 100644 index 0000000..3b299eb --- /dev/null +++ b/src/graphics/graphics-library/universal-rendering-context.ts @@ -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; +}; diff --git a/src/graphics/rendering/render-pass/render-pass.ts b/src/graphics/rendering/render-pass/render-pass.ts index c252e9b..d45988b 100644 --- a/src/graphics/rendering/render-pass/render-pass.ts +++ b/src/graphics/rendering/render-pass/render-pass.ts @@ -1,11 +1,12 @@ import { DrawableDescriptor } from '../../../drawables/drawable-descriptor'; import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer'; import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program'; +import { UniversalRenderingContext } from '../../graphics-library/universal-rendering-context'; export abstract class RenderPass { protected program: UniformArrayAutoScalingProgram; - constructor(gl: WebGL2RenderingContext, protected frame: FrameBuffer) { + constructor(gl: UniversalRenderingContext, protected frame: FrameBuffer) { this.program = new UniformArrayAutoScalingProgram(gl); } diff --git a/src/graphics/rendering/webgl2-renderer.ts b/src/graphics/rendering/renderer-implementation.ts similarity index 80% rename from src/graphics/rendering/webgl2-renderer.ts rename to src/graphics/rendering/renderer-implementation.ts index 0ada946..e9989b5 100644 --- a/src/graphics/rendering/webgl2-renderer.ts +++ b/src/graphics/rendering/renderer-implementation.ts @@ -5,9 +5,12 @@ import { LightDrawable } from '../../drawables/lights/light-drawable'; import { msToString } from '../../helper/ms-to-string'; import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer'; import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer'; -import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context'; import { WebGlStopwatch } from '../graphics-library/helper/stopwatch'; import { ParallelCompiler } from '../graphics-library/parallel-compiler'; +import { + getUniversalRenderingContext, + UniversalRenderingContext, +} from '../graphics-library/universal-rendering-context'; import { FpsAutoscaler } from './fps-autoscaler'; import { Insights } from './insights'; 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 { RuntimeSettings } from './settings/runtime-settings'; import { StartupSettings } from './settings/startup-settings'; +import distanceFragmentShader100 from './shaders/distance-fs-100.glsl'; import distanceFragmentShader from './shaders/distance-fs.glsl'; +import distanceVertexShader100 from './shaders/distance-vs-100.glsl'; import distanceVertexShader from './shaders/distance-vs.glsl'; +import lightsFragmentShader100 from './shaders/shading-fs-100.glsl'; import lightsFragmentShader from './shaders/shading-fs.glsl'; +import lightsVertexShader100 from './shaders/shading-vs-100.glsl'; import lightsVertexShader from './shaders/shading-vs.glsl'; import { UniformsProvider } from './uniforms-provider'; -export class WebGl2Renderer implements Renderer { - private gl: WebGL2RenderingContext; +export class RendererImplementation implements Renderer { + private gl: UniversalRenderingContext; private stopwatch?: WebGlStopwatch; private uniformsProvider: UniformsProvider; private distanceFieldFrameBuffer: IntermediateFrameBuffer; @@ -69,7 +76,7 @@ export class WebGl2Renderer implements Renderer { private canvas: HTMLCanvasElement, private descriptors: Array ) { - this.gl = getWebGl2Context(canvas); + this.gl = getUniversalRenderingContext(canvas); ParallelCompiler.initialize(this.gl); @@ -92,23 +99,30 @@ export class WebGl2Renderer implements Renderer { palette: Array, settingsOverrides: Partial ): Promise { - const settings = { ...WebGl2Renderer.defaultStartupSettings, ...settingsOverrides }; + const settings = { + ...RendererImplementation.defaultStartupSettings, + ...settingsOverrides, + }; const promises: Array> = []; promises.push( this.distancePass.initialize( - [distanceVertexShader, distanceFragmentShader], - this.descriptors.filter(WebGl2Renderer.hasSdf) + this.gl.isWebGL2 + ? [distanceVertexShader, distanceFragmentShader] + : [distanceVertexShader100, distanceFragmentShader100], + this.descriptors.filter(RendererImplementation.hasSdf) ) ); - promises.push( this.lightsPass.initialize( - [lightsVertexShader, lightsFragmentShader], - this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)), + this.gl.isWebGL2 + ? [lightsVertexShader, lightsFragmentShader] + : [lightsVertexShader100, lightsFragmentShader100], + this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)), { palette: this.generatePaletteCode(palette), + colorCount: palette.length.toString(), shadowTraceCount: settings.shadowTraceCount, } ) @@ -134,7 +148,9 @@ export class WebGl2Renderer implements Renderer { } 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); } else { 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}`); return palette .map( - (c) => - `vec3(${numberToGlslFloat(c[0])}, ${numberToGlslFloat( - c[1] - )}, ${numberToGlslFloat(c[2])})` + (c, i) => + `${this.gl.isWebGL2 ? '' : `colors[${i}] = `}vec3(${numberToGlslFloat( + c[0] + )}, ${numberToGlslFloat(c[1])}, ${numberToGlslFloat(c[2])})` ) - .join(',\n'); + .join(this.gl.isWebGL2 ? ',\n' : ';\n'); } public renderDrawables() { diff --git a/src/graphics/rendering/shaders/distance-fs-100.glsl b/src/graphics/rendering/shaders/distance-fs-100.glsl new file mode 100644 index 0000000..e1ed46e --- /dev/null +++ b/src/graphics/rendering/shaders/distance-fs-100.glsl @@ -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); +} diff --git a/src/graphics/rendering/shaders/distance-vs-100.glsl b/src/graphics/rendering/shaders/distance-vs-100.glsl new file mode 100644 index 0000000..4f5c453 --- /dev/null +++ b/src/graphics/rendering/shaders/distance-vs-100.glsl @@ -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; +} diff --git a/src/graphics/rendering/shaders/shading-fs-100.glsl b/src/graphics/rendering/shaders/shading-fs-100.glsl new file mode 100644 index 0000000..2aca79e --- /dev/null +++ b/src/graphics/rendering/shaders/shading-fs-100.glsl @@ -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); +} diff --git a/src/graphics/rendering/shaders/shading-fs.glsl b/src/graphics/rendering/shaders/shading-fs.glsl index fda718c..c519ead 100644 --- a/src/graphics/rendering/shaders/shading-fs.glsl +++ b/src/graphics/rendering/shaders/shading-fs.glsl @@ -18,7 +18,7 @@ in vec2 uvCoordinates; uniform vec3 ambientLight; -vec3[3] colors = vec3[]( +vec3[colorCount] colors = vec3[]( {palette} ); @@ -50,25 +50,23 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 #ifdef CIRCLE_LIGHT_COUNT #if CIRCLE_LIGHT_COUNT > 0 - uniform struct CircleLight { - vec2 center; - float intensity; - vec3 color; - }[CIRCLE_LIGHT_COUNT] circleLights; + uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT]; + uniform float circleLightIntensities[CIRCLE_LIGHT_COUNT]; + uniform vec3 circleLightColors[CIRCLE_LIGHT_COUNT]; in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; - vec3 colorInPosition(CircleLight light, out float lightCenterDistance) { - lightCenterDistance = distance(light.center, position); - return light.color / pow( - lightCenterDistance / light.intensity + 1.0, 2.0 + vec3 colorInPosition(int lightIndex, out float lightCenterDistance) { + lightCenterDistance = distance(circleLightCenters[lightIndex], position); + return circleLightColors[lightIndex] / pow( + lightCenterDistance / circleLightIntensities[lightIndex] + 1.0, 2.0 ); } - vec3 colorInPositionInside(CircleLight light) { - float lightCenterDistance = distance(light.center, position); - return light.color / pow( - lightCenterDistance / (light.intensity * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 + vec3 colorInPositionInside(int lightIndex) { + float lightCenterDistance = distance(circleLightCenters[lightIndex], position); + return circleLightColors[lightIndex] / pow( + lightCenterDistance / (circleLightIntensities[lightIndex] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 ); } #endif @@ -76,31 +74,31 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 #ifdef FLASHLIGHT_COUNT #if FLASHLIGHT_COUNT > 0 - uniform struct Flashlight { - vec2 center; - vec2 direction; - float intensity; - vec3 color; - }[FLASHLIGHT_COUNT] flashlights; + uniform vec2 flashlightCenters[FLASHLIGHT_COUNT]; + uniform float flashlightIntensities[FLASHLIGHT_COUNT]; + uniform vec3 flashlightColors[FLASHLIGHT_COUNT]; + uniform vec2 flashlightDirections[FLASHLIGHT_COUNT]; - in vec2[FLASHLIGHT_COUNT] flashlightDirections; + in vec2[FLASHLIGHT_COUNT] flashlightActualDirections; float intensityInDirection(vec2 lightDirection, vec2 targetDirection) { 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) { - lightCenterDistance = distance(light.center, position); - return intensityInDirection(light.direction, positionDirection) * light.color / pow( - lightCenterDistance / light.intensity + 1.0, 2.0 - ); + vec3 colorInPosition(int lightIndex, vec2 positionDirection, out float lightCenterDistance) { + lightCenterDistance = distance(flashlightCenters[lightIndex], position); + return intensityInDirection(flashlightDirections[lightIndex], positionDirection) * + flashlightColors[lightIndex] / pow( + lightCenterDistance / flashlightIntensities[lightIndex] + 1.0, 2.0 + ); } - vec3 colorInPositionInside(Flashlight light, vec2 positionDirection) { - float lightCenterDistance = distance(light.center, position); - return intensityInDirection(light.direction, positionDirection) * light.color / pow( - lightCenterDistance / (light.intensity * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 - ); + vec3 colorInPositionInside(int lightIndex, vec2 positionDirection) { + float lightCenterDistance = distance(flashlightCenters[lightIndex], position); + return intensityInDirection(flashlightDirections[lightIndex], positionDirection) * + 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 #if CIRCLE_LIGHT_COUNT > 0 for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - lighting += colorInPositionInside(circleLights[i]); + lighting += colorInPositionInside(i); } #endif #endif @@ -124,7 +122,7 @@ void main() { #ifdef FLASHLIGHT_COUNT #if FLASHLIGHT_COUNT > 0 for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - lighting += colorInPositionInside(flashlights[i], normalize(flashlightDirections[i])); + lighting += colorInPositionInside(i, normalize(flashlightActualDirections[i])); } #endif #endif @@ -137,7 +135,7 @@ void main() { vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(circleLights[i], lightCenterDistance); + vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance); lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); } @@ -151,7 +149,7 @@ void main() { vec2 direction = originalDirection / squareToAspectRatioTimes2; float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(flashlights[i], originalDirection, lightCenterDistance); + vec3 lightColorAtPosition = colorInPosition(i, originalDirection, lightCenterDistance); if (length(lightColorAtPosition) < 0.01) { continue; diff --git a/src/graphics/rendering/shaders/shading-vs-100.glsl b/src/graphics/rendering/shaders/shading-vs-100.glsl new file mode 100644 index 0000000..c785eca --- /dev/null +++ b/src/graphics/rendering/shaders/shading-vs-100.glsl @@ -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 +} diff --git a/src/graphics/rendering/shaders/shading-vs.glsl b/src/graphics/rendering/shaders/shading-vs.glsl index 860fe47..6e23548 100644 --- a/src/graphics/rendering/shaders/shading-vs.glsl +++ b/src/graphics/rendering/shaders/shading-vs.glsl @@ -14,26 +14,15 @@ uniform vec2 squareToAspectRatio; #ifdef CIRCLE_LIGHT_COUNT #if CIRCLE_LIGHT_COUNT > 0 - uniform struct CircleLight { - vec2 center; - float intensity; - vec3 color; - }[CIRCLE_LIGHT_COUNT] circleLights; - + uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT]; out vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; #endif #endif #ifdef FLASHLIGHT_COUNT #if FLASHLIGHT_COUNT > 0 - uniform struct Flashlight { - vec2 center; - vec2 direction; - float intensity; - vec3 color; - }[FLASHLIGHT_COUNT] flashlights; - - out vec2[FLASHLIGHT_COUNT] flashlightDirections; + uniform vec2 flashlightCenters[FLASHLIGHT_COUNT]; + out vec2[FLASHLIGHT_COUNT] flashlightActualDirections; #endif #endif @@ -51,7 +40,7 @@ void main() { #ifdef CIRCLE_LIGHT_COUNT #if CIRCLE_LIGHT_COUNT > 0 for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - circleLightDirections[i] = circleLights[i].center - position; + circleLightDirections[i] = circleLightCenters[i] - position; } #endif #endif @@ -59,7 +48,7 @@ void main() { #ifdef FLASHLIGHT_COUNT #if FLASHLIGHT_COUNT > 0 for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - flashlightDirections[i] = flashlights[i].center - position; + flashlightActualDirections[i] = flashlightCenters[i] - position; } #endif #endif diff --git a/src/graphics/rendering/uniforms-provider.ts b/src/graphics/rendering/uniforms-provider.ts index 20556d1..6ec7c88 100644 --- a/src/graphics/rendering/uniforms-provider.ts +++ b/src/graphics/rendering/uniforms-provider.ts @@ -1,4 +1,5 @@ import { mat2d, vec2, vec3 } from 'gl-matrix'; +import { UniversalRenderingContext } from '../graphics-library/universal-rendering-context'; export class UniformsProvider { public ambientLight = vec3.fromValues(0.25, 0.15, 0.25); @@ -12,7 +13,7 @@ export class UniformsProvider { private squareToAspectRatio = vec2.create(); private uvToWorld = mat2d.create(); - public constructor(private gl: WebGL2RenderingContext) {} + public constructor(private gl: UniversalRenderingContext) {} public getUniforms(uniforms: any): any { return { diff --git a/src/main.ts b/src/main.ts index 0154567..c55acfc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,8 +2,8 @@ import { vec3 } from 'gl-matrix'; import { DrawableDescriptor } from './drawables/drawable-descriptor'; import { Insights } from './graphics/rendering/insights'; import { Renderer } from './graphics/rendering/renderer'; +import { RendererImplementation } from './graphics/rendering/renderer-implementation'; import { StartupSettings } from './graphics/rendering/settings/startup-settings'; -import { WebGl2Renderer } from './graphics/rendering/webgl2-renderer'; import { applyArrayPlugins } from './helper/array'; export { Drawable } from './drawables/drawable'; @@ -36,7 +36,7 @@ export async function compile( settings: Partial = {} ): Promise { return Insights.measureFunction('startup', async () => { - const renderer = new WebGl2Renderer(canvas, descriptors); + const renderer = new RendererImplementation(canvas, descriptors); await renderer.initialize(palette, settings); return renderer; });