diff --git a/README.md b/README.md index 77cf9a1..9bfd1c9 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,6 @@ A good-looking 2D adventure. - docker engine dashboard? - local dev env - prettier script +- monitoring +- traefik +- digital ocean docker contianer registry diff --git a/frontend/src/scripts/drawing/graphics-library/default-frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/default-frame-buffer.ts new file mode 100644 index 0000000..91792f7 --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/default-frame-buffer.ts @@ -0,0 +1,26 @@ +import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; +import { FrameBuffer } from './frame-buffer'; + +export class DefaultFrameBuffer extends FrameBuffer { + constructor( + gl: WebGL2RenderingContext, + programs: Array + ) { + super(gl, programs); + this.frameBuffer = null; + + this.setSize(); + } + + public setSize() { + super.setSize(); + + if ( + this.gl.canvas.width !== this.size.x || + this.gl.canvas.height !== this.size.y + ) { + this.gl.canvas.width = this.size.x; + this.gl.canvas.height = this.size.y; + } + } +} diff --git a/frontend/src/scripts/drawing/graphics-library/fragment-shader-only-program.ts b/frontend/src/scripts/drawing/graphics-library/fragment-shader-only-program.ts index 9def980..1d941c8 100644 --- a/frontend/src/scripts/drawing/graphics-library/fragment-shader-only-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/fragment-shader-only-program.ts @@ -59,6 +59,19 @@ export class FragmentShaderOnlyProgram { type: glUniform.type, }); } + + console.log(this.uniforms); + + this.uniforms.map((u1) => { + const isSingle = + this.uniforms.filter((u2) => u2.name.includes(u1.name[0])).length == 1; + if (u1.name.includes('0') && isSingle) { + u1.name = u1.name.slice(0, -1); + } + return u1; + }); + + console.log(this.uniforms); } private createProgram(fragmentShaderSource: string) { diff --git a/frontend/src/scripts/drawing/graphics-library/frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/frame-buffer.ts new file mode 100644 index 0000000..f1a8aad --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/frame-buffer.ts @@ -0,0 +1,48 @@ +import { Vec2 } from '../../math/vec2'; +import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; + +export abstract class FrameBuffer { + public renderScale = 1; + public enableHighDpiRendering = false; + + protected size: Vec2; + protected frameBuffer: WebGLFramebuffer; + + constructor( + protected gl: WebGL2RenderingContext, + protected programs: Array + ) {} + + public render(uniforms: any, input?: WebGLTexture) { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); + + if (input !== null) { + this.gl.bindTexture(this.gl.TEXTURE_2D, input); + } + + this.gl.viewport(0, 0, this.size.x, this.size.y); + this.gl.clearColor(0, 0, 0, 0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + this.programs.forEach((p) => { + p.bind(); + p.setUniforms(uniforms); + p.draw(); + }); + } + + public setSize() { + const realToCssPixels = window.devicePixelRatio * this.renderScale; + const canvasWidth = (this.gl.canvas as HTMLCanvasElement).clientWidth; + const canvasHeight = (this.gl.canvas as HTMLCanvasElement).clientHeight; + + const displayWidth = Math.floor(canvasWidth * realToCssPixels); + const displayHeight = Math.floor(canvasHeight * realToCssPixels); + + this.size = new Vec2(displayWidth, displayHeight); + } + + public getSize(): Vec2 { + return this.size; + } +} diff --git a/frontend/src/scripts/drawing/graphics-library/intermediate-frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/intermediate-frame-buffer.ts new file mode 100644 index 0000000..2fbf504 --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/intermediate-frame-buffer.ts @@ -0,0 +1,75 @@ +import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; +import { FrameBuffer } from './frame-buffer'; + +export class IntermediateFrameBuffer extends FrameBuffer { + private frameTexture: WebGLTexture; + + constructor( + gl: WebGL2RenderingContext, + programs: Array + ) { + super(gl, programs); + + this.frameTexture = this.gl.createTexture(); + this.configureTexture(); + + this.frameBuffer = this.gl.createFramebuffer(); + this.configureFrameBuffer(); + + this.setSize(); + } + + public get texture(): WebGLTexture { + return this.frameTexture; + } + + public setSize() { + super.setSize(); + + this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture); + + this.gl.texImage2D( + this.gl.TEXTURE_2D, + 0, + this.gl.RGBA, + this.size.x, + this.size.y, + 0, + this.gl.RGBA, + this.gl.UNSIGNED_BYTE, + null + ); + } + + private configureTexture() { + this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture); + + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_MIN_FILTER, + this.gl.LINEAR + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_S, + this.gl.CLAMP_TO_EDGE + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_T, + this.gl.CLAMP_TO_EDGE + ); + } + + private configureFrameBuffer() { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); + const attachmentPoint = this.gl.COLOR_ATTACHMENT0; + this.gl.framebufferTexture2D( + this.gl.FRAMEBUFFER, + attachmentPoint, + this.gl.TEXTURE_2D, + this.frameTexture, + 0 + ); + } +} diff --git a/frontend/src/scripts/drawing/graphics-library/load-uniform.ts b/frontend/src/scripts/drawing/graphics-library/load-uniform.ts index 6b0699e..d9bf6a9 100644 --- a/frontend/src/scripts/drawing/graphics-library/load-uniform.ts +++ b/frontend/src/scripts/drawing/graphics-library/load-uniform.ts @@ -16,12 +16,24 @@ export const loadUniform = ( ) => void > = new Map(); { - converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => - gl.uniform1f(l, v) - ); + converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => { + gl.uniform1fv(l, new Float32Array(v)); + }); - converters.set(WebGL2RenderingContext.FLOAT_VEC2, (gl, v: Vec2, l) => - gl.uniform2fv(l, new Float32Array(v.list)) + converters.set( + WebGL2RenderingContext.FLOAT_VEC2, + (gl, v: Vec2 | Array, l) => { + if (v instanceof Array) { + const result = new Float32Array(v.length * 2); + for (let i = 0; i < v.length; i++) { + result[2 * i] = v[i].x; + result[2 * i + 1] = v[i].y; + } + gl.uniform2fv(l, new Float32Array(result)); + } else { + gl.uniform2fv(l, new Float32Array(v.list)); + } + } ); converters.set(WebGL2RenderingContext.FLOAT_MAT3, (gl, v: Mat3, l) => diff --git a/frontend/src/scripts/drawing/webgl2-renderer.ts b/frontend/src/scripts/drawing/webgl2-renderer.ts index 87a3cba..7099bde 100644 --- a/frontend/src/scripts/drawing/webgl2-renderer.ts +++ b/frontend/src/scripts/drawing/webgl2-renderer.ts @@ -4,18 +4,17 @@ import { Mat3 } from '../math/mat3'; import { FragmentShaderOnlyProgram } from './graphics-library/fragment-shader-only-program'; import { WebGlStopwatch } from './graphics-library/stopwatch'; import { Rectangle } from '../math/rectangle'; +import { IntermediateFrameBuffer } from './graphics-library/intermediate-frame-buffer'; +import { FrameBuffer } from './graphics-library/frame-buffer'; +import { DefaultFrameBuffer } from './graphics-library/default-frame-buffer'; export class WebGl2Renderer implements Drawer { private gl: WebGL2RenderingContext; - private program: FragmentShaderOnlyProgram; private stopwatch: WebGlStopwatch; - public enableHighDpiRendering = false; - public renderScale = 0.5; - private viewBox: Rectangle = new Rectangle(); - private nextFrameUniforms: any; + private frameBuffers: Array = []; constructor( private canvas: HTMLCanvasElement, @@ -27,53 +26,52 @@ export class WebGl2Renderer implements Drawer { throw new Error('WebGl2 is not supported'); } - this.program = new FragmentShaderOnlyProgram(this.gl, shaderSources[0]); + this.frameBuffers.push( + new IntermediateFrameBuffer(this.gl, [ + new FragmentShaderOnlyProgram(this.gl, shaderSources[0]), + ]) + ); + + this.frameBuffers.push( + new DefaultFrameBuffer(this.gl, [ + new FragmentShaderOnlyProgram(this.gl, shaderSources[1]), + ]) + ); + + this.frameBuffers[0].renderScale = 0.2; + this.frameBuffers[1].renderScale = 1; try { this.stopwatch = new WebGlStopwatch(this.gl); } catch {} } - private handleResize() { - const realToCssPixels = window.devicePixelRatio * this.renderScale; - - const displayWidth = Math.floor(this.canvas.clientWidth * realToCssPixels); - const displayHeight = Math.floor( - this.canvas.clientHeight * realToCssPixels - ); - - if ( - this.canvas.width !== displayWidth || - this.canvas.height !== displayHeight - ) { - this.canvas.width = displayWidth; - this.canvas.height = displayHeight; - } - - this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); - } - - public startFrame() { + startFrame(): void { this.stopwatch?.start(); - - this.handleResize(); - - this.gl.clearColor(0, 0, 0, 0); - this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); - - this.program.bind(); this.nextFrameUniforms = {}; + this.frameBuffers.forEach((f) => f.setSize()); } public finishFrame() { const resolution = new Vec2(this.canvas.width, this.canvas.height); this.nextFrameUniforms.transform = Mat3.translateMatrix(new Vec2(0.5, 0.5)) - .times(Mat3.scaleMatrix(this.viewBox.size.divide(resolution))) + .times( + Mat3.scaleMatrix( + this.viewBox.size.divide(this.frameBuffers[0].getSize()) + ) + ) .times(Mat3.translateMatrix(this.viewBox.topLeft)); - this.program.setUniforms(this.nextFrameUniforms); - this.program.draw(); + this.nextFrameUniforms.transformUV = Mat3.translateMatrix( + new Vec2(0.5, 0.5) + ).times(Mat3.scaleMatrix(new Vec2(1).divide(resolution))); + + this.frameBuffers[0].render(this.nextFrameUniforms); + this.frameBuffers[1].render( + this.nextFrameUniforms, + (this.frameBuffers[0] as IntermediateFrameBuffer).texture + ); this.stopwatch?.stop(); } diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 1c4b14b..f96beee 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -11,7 +11,9 @@ import { Character } from './objects/types/character'; import { InfoText } from './objects/types/info-text'; import { timeIt } from './helper/timing'; -import caveFragmentShader from '../shaders/cave-fs.glsl'; +import caveFragmentShader from '../shaders/cave-distance-fs.glsl'; +import lightsShader from '../shaders/rainbow-shading-fs.glsl'; +//import lightsShader from '../shaders/lights-shading-fs.glsl'; import { Dungeon } from './objects/types/dungeon'; import { BeforeDrawCommand } from './commands/types/before-draw'; @@ -34,7 +36,10 @@ export class Game { [this.objects] ); - this.renderer = new WebGl2Renderer(canvas, overlay, [caveFragmentShader]); + this.renderer = new WebGl2Renderer(canvas, overlay, [ + caveFragmentShader, + lightsShader, + ]); this.initializeScene(); requestAnimationFrame(this.gameLoop.bind(this)); diff --git a/frontend/src/scripts/objects/types/dungeon.ts b/frontend/src/scripts/objects/types/dungeon.ts index b1883dd..0cf32f7 100644 --- a/frontend/src/scripts/objects/types/dungeon.ts +++ b/frontend/src/scripts/objects/types/dungeon.ts @@ -4,10 +4,9 @@ import { Vec2 } from '../../math/vec2'; import { last } from '../../helper/last'; export interface Line { - from: Vec2; - to: Vec2; - normal: Vec2; - isLineEnd: boolean; + start: Vec2; + end: Vec2; + radius: number; } export class Dungeon extends GameObject { @@ -19,62 +18,35 @@ export class Dungeon extends GameObject { this.addCommandExecutor(DrawCommand, this.draw.bind(this)); let previousHeight = 0; - let previousPoint = { - from: new Vec2(), - to: new Vec2(-10000, 0), - }; + let previousEnd = new Vec2(); for (let i = 0; i < 5000; i += 50) { const height = previousHeight + (Math.random() - 0.5) * 200; previousHeight = height; - const current = { - from: previousPoint.to, - to: new Vec2(i, height), - normal: new Vec2(1.0), - isLineEnd: false, - }; - this.lines.push(current); - previousPoint = current; + const currentEnd = new Vec2(i, height); + + this.lines.push({ + start: previousEnd, + end: currentEnd, + radius: Math.random() * 15 + 15, + }); + + previousEnd = currentEnd; } - - last(this.lines).to = last(this.lines).to.add(new Vec2(10000, 0)); - last(this.lines).isLineEnd = true; - - const delta = new Vec2(200, 400); - this.lines = [ - ...this.lines, - ...this.lines.map(({ from, to, normal, isLineEnd }) => ({ - normal: normal.scale(-1), - from: from.add(delta), - to: to.add(delta), - isLineEnd, - })), - ]; - - this.calculateNormals(); - } - - private calculateNormals() { - this.lines.forEach((l) => { - const tangent = l.to.subtract(l.from); - l.normal = new Vec2( - -l.normal.x * tangent.y, - l.normal.x * tangent.x - ).normalized; - }); } private draw(c: DrawCommand) { - const linesToBeDrawn: Array = []; + const lines: Array = []; + const radii: Array = []; for (let line of this.lines) { - if (c.drawer.isOnScreen(line.from) || c.drawer.isOnScreen(line.to)) { - linesToBeDrawn.push(line); - } else if (line.isLineEnd && last(linesToBeDrawn) != null) { - last(linesToBeDrawn).isLineEnd = true; + if (c.drawer.isOnScreen(line.start) || c.drawer.isOnScreen(line.end)) { + lines.push(line.start); + lines.push(line.end); + radii.push(line.radius); } } - c.drawer.giveUniforms({ lines: linesToBeDrawn }); + c.drawer.giveUniforms({ lines, radii }); } } diff --git a/frontend/src/shaders/cave-distance-fs.glsl b/frontend/src/shaders/cave-distance-fs.glsl new file mode 100644 index 0000000..ef94e29 --- /dev/null +++ b/frontend/src/shaders/cave-distance-fs.glsl @@ -0,0 +1,39 @@ +#version 300 es + +precision mediump float; + + +#define INFINITY 10000.0 +#define LINE_COUNT 50 + + +uniform vec2[LINE_COUNT * 2] lines; +uniform float[LINE_COUNT] radii; + +float lineDistance(in vec2 target, in vec2 start, in vec2 end, in float radius) { + vec2 pa = target - start, ba = end - start; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return distance(pa, ba * h) - radius; +} + +float getDistance(in vec2 target) { + float minDistance = INFINITY; + + for (int i = 0; i < LINE_COUNT; i++) { + vec2 start = lines[2 * i]; + vec2 end = lines[2 * i + 1]; + float r = radii[i]; + minDistance = min(minDistance, lineDistance(target, start, end, r)); + } + + return -minDistance; +} + +uniform mat3 transform; +out vec4 fragmentColor; + +void main() { + vec2 position = (vec3(gl_FragCoord.xy, 1.0) * transform).xy; + float distance = getDistance(position); + fragmentColor = vec4(vec3(0.0), distance / 256.0 + 0.5); +} diff --git a/frontend/src/shaders/cave-fs.glsl b/frontend/src/shaders/cave-fs copy.glsl similarity index 90% rename from frontend/src/shaders/cave-fs.glsl rename to frontend/src/shaders/cave-fs copy.glsl index 528478d..891985c 100644 --- a/frontend/src/shaders/cave-fs.glsl +++ b/frontend/src/shaders/cave-fs copy.glsl @@ -33,7 +33,7 @@ float lineDistance(in vec2 position, in Line line, out float h) { float getDistance(in vec2 target) { float positiveMinDistance = INFINITY; - float negativeMaxDistance = -INFINITY; + float negativeMaxDistance = INFINITY; float leftJoinAcuteness = 0.0; vec2 splitterLineNormalStart = vec2(-1.0, 0.0); @@ -55,9 +55,9 @@ float getDistance(in vec2 target) { || dot(target - lines[i].to, splitterLineNormalEnd * sign(dot(lines[i].from - lines[i].to, splitterLineNormalEnd))) <= 0.0 ) ) { - float distanceToCurrentSign = sign(distanceToCurrent); - positiveMinDistance = min(positiveMinDistance, 1.0 / (0.5 + distanceToCurrentSign / 2.0) * distanceToCurrent); - negativeMaxDistance = max(negativeMaxDistance, -1.0 / (0.5 - distanceToCurrentSign / 2.0) * distanceToCurrent); + float distanceToCurrentSign = sign(distanceToCurrent) / 2.0; + positiveMinDistance = min(positiveMinDistance, 1.0 / (0.5 + distanceToCurrentSign) * abs(distanceToCurrent)); + negativeMaxDistance = min(negativeMaxDistance, 1.0 / (0.5 - distanceToCurrentSign) * abs(distanceToCurrent)); } splitterLineNormalStart = splitterLineNormalEnd; } @@ -71,4 +71,5 @@ out vec4 fragmentColor; void main() { vec2 position = (vec3(gl_FragCoord.xy, 1.0) * transform).xy; fragmentColor = vec4(vec3(1.0) * clamp(0.0, 1.0, getDistance(position)), 1.0); + } diff --git a/frontend/src/shaders/ikeda-00-1593370449049.frag b/frontend/src/shaders/lights-shading-fs.glsl similarity index 95% rename from frontend/src/shaders/ikeda-00-1593370449049.frag rename to frontend/src/shaders/lights-shading-fs.glsl index 52ad335..3ca4754 100644 --- a/frontend/src/shaders/ikeda-00-1593370449049.frag +++ b/frontend/src/shaders/lights-shading-fs.glsl @@ -1,8 +1,6 @@ #version 300 es -#ifdef GL_ES precision mediump float; -#endif #define INFINITY 1.0 / 0.0 @@ -13,8 +11,11 @@ precision mediump float; #define ANTIALIASING_RADIUS 1.0 uniform vec2 resolution; -// uniform vec2 u_mouse; -uniform float time; +uniform vec2 mouse; +uniform sampler2D distanceTexture; +uniform mat3 transformUV; + +out vec4 fragmentColor; struct Light { vec2 center; @@ -66,7 +67,7 @@ float getDistance(in vec2 target, out Circle nearest) { } void createWorld() { - lights[0] = Light(u_mouse, 40.5, vec3(1.0), 25.0); + lights[0] = Light(mouse, 40.5, vec3(1.0), 25.0); lights[1] = Light(vec2(100.0, 350.0), 52.5,vec3(2.0, 1.0, 0.25), 20.5); world[0] = Circle(vec2(250.0, 100.0), 12.5, blue); @@ -155,5 +156,5 @@ void main() { vec2 position = gl_FragCoord.xy + vec2(0.5); vec3 color = getPixelColorAntialiased(position); - gl_FragColor = vec4(color, 1.0); + fragmentColor = vec4(color, 1.0); } diff --git a/frontend/src/shaders/rainbow.frag b/frontend/src/shaders/rainbow-shading-fs.glsl similarity index 59% rename from frontend/src/shaders/rainbow.frag rename to frontend/src/shaders/rainbow-shading-fs.glsl index c4915c4..3bfd8ad 100644 --- a/frontend/src/shaders/rainbow.frag +++ b/frontend/src/shaders/rainbow-shading-fs.glsl @@ -1,3 +1,8 @@ +#version 300 es + +precision mediump float; + + vec3 rainbow(float level) { float r = float(level <= 2.0) + float(level > 4.0) * 0.5; float g = max(1.0 - abs(level - 2.0) * 0.5, 0.0); @@ -15,4 +20,13 @@ vec4 smoothRainbow(float x) { return vec4(mix(a, b, fract(x*6.0)), 1.0); } -fragmentColor = smoothRainbow(getDistance(position) / 30.0 + 0.5); \ No newline at end of file + +uniform sampler2D distanceTexture; +uniform mat3 transformUV; +out vec4 fragmentColor; + +void main() { + vec2 position = (vec3(gl_FragCoord.xy, 1.0) * transformUV).xy; + vec4 previous = texture(distanceTexture, position); + fragmentColor = smoothRainbow(previous.a); +}