diff --git a/frontend/src/scripts/drawing/drawables/drawable-blob.ts b/frontend/src/scripts/drawing/drawables/drawable-blob.ts index 7c1b5b0..27ac4b4 100644 --- a/frontend/src/scripts/drawing/drawables/drawable-blob.ts +++ b/frontend/src/scripts/drawing/drawables/drawable-blob.ts @@ -19,7 +19,6 @@ export class DrawableBlob extends Blob implements IDrawable { } uniforms[uniformName].push({ headCenter: vec2.transformMat2d(vec2.create(), this.head.center, transform), - torsoCenter: vec2.transformMat2d(vec2.create(), this.torso.center, transform), leftFootCenter: vec2.transformMat2d(vec2.create(), this.leftFoot.center, transform), rightFootCenter: vec2.transformMat2d( vec2.create(), @@ -27,9 +26,8 @@ export class DrawableBlob extends Blob implements IDrawable { transform ), headRadius: this.headRadius * scale, - torsoRadius: this.torsoRadius * scale, footRadius: this.footRadius * scale, - k: this.k * scale, + k: this.k / scale, }); } } diff --git a/frontend/src/scripts/drawing/drawables/lights/circle-light.ts b/frontend/src/scripts/drawing/drawables/lights/circle-light.ts index 0f1ecf2..8bf7001 100644 --- a/frontend/src/scripts/drawing/drawables/lights/circle-light.ts +++ b/frontend/src/scripts/drawing/drawables/lights/circle-light.ts @@ -13,7 +13,7 @@ export class CircleLight implements ILight { constructor( public center: vec2, - public radius: number, + public lightDrop: number, public color: vec3, public lightness: number ) {} @@ -31,7 +31,7 @@ export class CircleLight implements ILight { uniforms[uniformName].push({ center: vec2.transformMat2d(vec2.create(), this.center, transform), - radius: this.radius * scale, + lightDrop: this.lightDrop, value: this.value, }); } diff --git a/frontend/src/scripts/drawing/drawables/lights/point-light.ts b/frontend/src/scripts/drawing/drawables/lights/flashlight.ts similarity index 59% rename from frontend/src/scripts/drawing/drawables/lights/point-light.ts rename to frontend/src/scripts/drawing/drawables/lights/flashlight.ts index d5cce8e..180b464 100644 --- a/frontend/src/scripts/drawing/drawables/lights/point-light.ts +++ b/frontend/src/scripts/drawing/drawables/lights/flashlight.ts @@ -3,27 +3,34 @@ import { settings } from '../../settings'; import { IDrawableDescriptor } from '../i-drawable-descriptor'; import { ILight } from './i-light'; -export class PointLight implements ILight { +export class Flashlight implements ILight { public static descriptor: IDrawableDescriptor = { - uniformName: 'pointLights', - countMacroName: 'pointLightCount', - shaderCombinationSteps: settings.shaderCombinations.pointLightSteps, - empty: new PointLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0), + uniformName: 'flashlights', + countMacroName: 'flashlightCount', + shaderCombinationSteps: settings.shaderCombinations.flashlightSteps, + empty: new Flashlight( + vec2.fromValues(0, 0), + vec2.fromValues(0, 0), + 0, + vec3.fromValues(0, 0, 0), + 0 + ), }; public constructor( public center: vec2, - public radius: number, + public direction: vec2, + public lightDrop: number, public color: vec3, public lightness: number ) {} - public distance(target: vec2): number { - return vec2.distance(this.center, target) - this.radius; + public distance(_: vec2): number { + return 0; } public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void { - const listName = PointLight.descriptor.uniformName; + const listName = Flashlight.descriptor.uniformName; if (!Object.prototype.hasOwnProperty.call(uniforms, listName)) { uniforms[listName] = []; @@ -31,7 +38,8 @@ export class PointLight implements ILight { uniforms[listName].push({ center: vec2.transformMat2d(vec2.create(), this.center, transform), - radius: this.radius * scale, + direction: this.direction, + lightDrop: this.lightDrop, value: this.value, }); } diff --git a/frontend/src/scripts/drawing/rendering/uniforms-provider.ts b/frontend/src/scripts/drawing/rendering/uniforms-provider.ts index a452af3..e34b5e4 100644 --- a/frontend/src/scripts/drawing/rendering/uniforms-provider.ts +++ b/frontend/src/scripts/drawing/rendering/uniforms-provider.ts @@ -15,11 +15,11 @@ export class UniformsProvider { public constructor(private gl: WebGL2RenderingContext) {} - public get uniforms(): any { + public getUniforms(uniforms: any): any { const cursorPosition = this.uvToWorldCoordinate(this.cursorPosition); return { + ...uniforms, cursorPosition, - pixelSize: 0.05, uvToWorld: this.uvToWorld, worldAreaInView: this.worldAreaInView, squareToAspectRatio: this.squareToAspectRatio, diff --git a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts index c845cb2..7085b84 100644 --- a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts +++ b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts @@ -4,8 +4,8 @@ import { DrawableBlob } from '../drawables/drawable-blob'; import { DrawableTunnel } from '../drawables/drawable-tunnel'; import { IDrawable } from '../drawables/i-drawable'; import { CircleLight } from '../drawables/lights/circle-light'; +import { Flashlight } from '../drawables/lights/flashlight'; import { ILight } from '../drawables/lights/i-light'; -import { PointLight } from '../drawables/lights/point-light'; 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'; @@ -47,7 +47,7 @@ export class WebGl2Renderer implements IRenderer { this.lightingPass = new RenderingPass( this.gl, [lightsVertexShader, lightsFragmentShader], - [CircleLight.descriptor, PointLight.descriptor], + [CircleLight.descriptor, Flashlight.descriptor], this.lightingFrameBuffer ); @@ -95,9 +95,13 @@ export class WebGl2Renderer implements IRenderer { } public finishFrame() { - this.distancePass.render(this.uniformsProvider.uniforms); + const common = { + distanceUvPixelSize: 2 / this.distanceFieldFrameBuffer.getSize().y, + }; + + this.distancePass.render(this.uniformsProvider.getUniforms(common)); this.lightingPass.render( - this.uniformsProvider.uniforms, + this.uniformsProvider.getUniforms(common), this.distanceFieldFrameBuffer.colorTexture ); diff --git a/frontend/src/scripts/drawing/settings.ts b/frontend/src/scripts/drawing/settings.ts index 9cc44dd..a52c185 100644 --- a/frontend/src/scripts/drawing/settings.ts +++ b/frontend/src/scripts/drawing/settings.ts @@ -59,6 +59,6 @@ export const settings = { lineSteps: [0, 1, 2, 4, 8, 16, 128], blobSteps: [0, 1, 2, 8], circleLightSteps: [0, 1], - pointLightSteps: [0, 1, 2, 3], + flashlightSteps: [0, 1], }, }; diff --git a/frontend/src/scripts/drawing/shaders/distance-fs.glsl b/frontend/src/scripts/drawing/shaders/distance-fs.glsl index 4b6a95c..09d4269 100644 --- a/frontend/src/scripts/drawing/shaders/distance-fs.glsl +++ b/frontend/src/scripts/drawing/shaders/distance-fs.glsl @@ -6,7 +6,7 @@ precision lowp float; #define BLOB_COUNT {blobCount} uniform float maxMinDistance; -uniform float pixelSize; +uniform float distanceUvPixelSize; in vec2 position; @@ -38,7 +38,7 @@ in vec2 position; myMinDistance = min(myMinDistance, lineDistance); } - color = mix(0.0, color, step(pixelSize, -myMinDistance)); + color = mix(0.0, color, step(distanceUvPixelSize, -myMinDistance)); minDistance = -myMinDistance; } @@ -47,11 +47,9 @@ in vec2 position; #if BLOB_COUNT > 0 uniform struct { vec2 headCenter; - vec2 torsoCenter; vec2 leftFootCenter; vec2 rightFootCenter; float headRadius; - float torsoRadius; float footRadius; float k; }[BLOB_COUNT] blobs; @@ -63,12 +61,11 @@ in vec2 position; void blobMinDistance(inout float minDistance, inout float color) { for (int i = 0; i < BLOB_COUNT; i++) { float res = exp2(-blobs[i].k * circleMinDistance(blobs[i].headCenter, blobs[i].headRadius)); - res += exp2(-blobs[i].k * circleMinDistance(blobs[i].torsoCenter, blobs[i].torsoRadius)); res += exp2(-blobs[i].k * circleMinDistance(blobs[i].leftFootCenter, blobs[i].footRadius)); res += exp2(-blobs[i].k * circleMinDistance(blobs[i].rightFootCenter, blobs[i].footRadius)); res = -log2(res) / blobs[i].k; - color = mix(1.0, color, step(pixelSize, res)); + color = mix(1.0, color, step(distanceUvPixelSize, res)); minDistance = min(minDistance, res); } @@ -85,7 +82,7 @@ void main() { lineMinDistance(minDistance, color); #endif - #if BLOB_COUNT > 0 + #if BLOB_COUNT > 10 blobMinDistance(minDistance, color); #endif diff --git a/frontend/src/scripts/drawing/shaders/shading-fs.glsl b/frontend/src/scripts/drawing/shaders/shading-fs.glsl index 0a9e7c5..e51ae7b 100644 --- a/frontend/src/scripts/drawing/shaders/shading-fs.glsl +++ b/frontend/src/scripts/drawing/shaders/shading-fs.glsl @@ -3,20 +3,22 @@ precision lowp float; #define INFINITY 1000.0 -#define LIGHT_DROP 4.0 -#define AMBIENT_LIGHT vec3(0.3) +#define LIGHT_DROP_INSIDE_RATIO 0.3 +#define AMBIENT_LIGHT vec3(0.25, 0.15, 0.25) +#define SHADOW_HARDNESS 150.0 #define CIRCLE_LIGHT_COUNT {circleLightCount} -#define POINT_LIGHT_COUNT {pointLightCount} +#define FLASHLIGHT_COUNT {flashlightCount} uniform bool softShadowsEnabled; - -#define AIR_COLOR vec3(0.5) - +uniform vec2 squareToAspectRatioTimes2; uniform sampler2D distanceTexture; +in vec2 position; +in vec2 uvCoordinates; + vec3[3] colors = vec3[]( - vec3(0.1, 0.05, 0.1), + vec3(0.4, 0.35, 0.6), // cave color vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0) ); @@ -24,7 +26,6 @@ vec3[3] colors = vec3[]( float getDistance(in vec2 target, out vec3 color) { vec4 values = texture(distanceTexture, target); color = colors[int(values[1])]; - return values[0]; } @@ -32,116 +33,143 @@ float getDistance(in vec2 target) { return texture(distanceTexture, target)[0]; } +float softShadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { + float rayLength = startingDistance; + float q = 1.0 / SHADOW_HARDNESS; + + for (int j = 0; j < 96; j++) { + float minDistance = getDistance(uvCoordinates + direction * rayLength); + + q = min(q, minDistance / rayLength); + rayLength += minDistance / 2.0; + + if (rayLength >= lightCenterDistance) { + return q * SHADOW_HARDNESS; + } + } + + return 0.0; +} + +float hardShadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { + float rayLength = startingDistance; + + for (int j = 0; j < 32; j++) { + rayLength += getDistance(uvCoordinates + direction * rayLength); + } + + return step(lightCenterDistance, rayLength); +} + +float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { + return softShadowsEnabled ? + softShadowTransparency(startingDistance, lightCenterDistance, direction) : + hardShadowTransparency(startingDistance, lightCenterDistance, direction); +} + + #if CIRCLE_LIGHT_COUNT > 0 - uniform struct { + uniform struct CircleLight { vec2 center; - float radius; + float lightDrop; vec3 value; }[CIRCLE_LIGHT_COUNT] circleLights; in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; -#endif -#if POINT_LIGHT_COUNT > 0 - uniform struct { - vec2 center; - float radius; - vec3 value; - }[POINT_LIGHT_COUNT] pointLights; - - in vec2[POINT_LIGHT_COUNT] pointLightDirections; -#endif - -in vec2 position; -in vec2 uvCoordinates; -uniform vec2 squareToAspectRatioTimes2; - -out vec4 fragmentColor; - -void main() { - vec3 colorAtPosition; - float startingDistance = getDistance(uvCoordinates, colorAtPosition); - if (startingDistance < 0.0) { - fragmentColor = vec4(colorAtPosition, 1.0); - return; + vec3 colorInPosition(CircleLight light, out float lightCenterDistance) { + lightCenterDistance = distance(light.center, position); + return light.value / pow( + lightCenterDistance / light.lightDrop + 1.0, 2.0 + ); } - colorAtPosition = AIR_COLOR; + vec3 colorInPositionInside(CircleLight light) { + float lightCenterDistance = distance(light.center, position); + return light.value / pow( + lightCenterDistance / (light.lightDrop * LIGHT_DROP_INSIDE_RATIO) + 1.0, 2.0 + ); + } +#endif +#if FLASHLIGHT_COUNT > 0 + uniform struct Flashlight { + vec2 center; + vec2 direction; + float lightDrop; + vec3 value; + }[FLASHLIGHT_COUNT] flashlights; + + in vec2[FLASHLIGHT_COUNT] flashlightDirections; + + 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.value / pow( + lightCenterDistance / light.lightDrop + 1.0, 2.0 + ); + } + + vec3 colorInPositionInside(Flashlight light, vec2 positionDirection) { + float lightCenterDistance = distance(light.center, position); + return intensityInDirection(light.direction, positionDirection) * light.value / pow( + lightCenterDistance / (light.lightDrop * LIGHT_DROP_INSIDE_RATIO) + 1.0, 2.0 + ); + } +#endif + +out vec4 fragmentColor; +void main() { vec3 lighting = AMBIENT_LIGHT; - - #if CIRCLE_LIGHT_COUNT > 0 + + vec3 colorAtPosition; + float startingDistance = getDistance(uvCoordinates, colorAtPosition); + + if (startingDistance < 0.0) { + #if CIRCLE_LIGHT_COUNT > 0 for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - float lightCenterDistance = distance(circleLights[i].center, position); - float lightDistance = lightCenterDistance - circleLights[i].radius; + lighting += colorInPositionInside(circleLights[i]); + } + #endif - vec3 lightColorAtPosition = circleLights[i].value / pow( - lightDistance / LIGHT_DROP + 1.0, 2.0 - ); + #if FLASHLIGHT_COUNT > 0 + for (int i = 0; i < FLASHLIGHT_COUNT; i++) { + lighting += colorInPositionInside(flashlights[i], normalize(flashlightDirections[i])); + } + #endif + } else { + colorAtPosition = vec3(1.0); - vec2 targetDirection = vec2(-1.0, 0.0); - vec2 originalDirection = normalize(circleLightDirections[i]); + #if CIRCLE_LIGHT_COUNT > 0 + for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { + vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; + + float lightCenterDistance; + vec3 lightColorAtPosition = colorInPosition(circleLights[i], lightCenterDistance); + + lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); + } + #endif + + #if FLASHLIGHT_COUNT > 0 + for (int i = 0; i < FLASHLIGHT_COUNT; i++) { + vec2 originalDirection = normalize(flashlightDirections[i]); vec2 direction = originalDirection / squareToAspectRatioTimes2; - lightColorAtPosition *= pow(max(0.0, dot(targetDirection, originalDirection)), 10.0); - - float rayLength = startingDistance; - - if (softShadowsEnabled) { - float q = INFINITY; - for (int j = 0; j < 96; j++) { - if (rayLength >= lightDistance) { - lighting += lightColorAtPosition * clamp( - (q * 2.0) / circleLights[i].radius * lightCenterDistance, 0.0, 1.0 - ) * step(0.0, startingDistance); - break; - } - - float minDistance = getDistance(uvCoordinates + direction * rayLength); - q = min(q, minDistance / rayLength); - - rayLength += minDistance / 2.0; - } - } else { - for (int j = 0; j < 32; j++) { - rayLength += getDistance(uvCoordinates + direction * rayLength); - } - if (rayLength >= lightCenterDistance) { - lighting += lightColorAtPosition * step(0.0, startingDistance); - } + float lightCenterDistance; + vec3 lightColorAtPosition = colorInPosition(flashlights[i], originalDirection, lightCenterDistance); + + if (length(lightColorAtPosition) < 0.01) { + continue; } + + lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); } - #endif - - #if POINT_LIGHT_COUNT > 0 - /*for (int i = 0; i < POINT_LIGHT_COUNT; i++) { - float lightDistance = distance(pointLights[i].center, worldCoordinates); - - vec3 lightColorAtPosition = mix( - pointLights[i].value, - vec3(0.0), - sqrt(clamp(lightDistance / pointLights[i].radius, 0.0, 1.0)) - ); - - - float q = INFINITY; - float rayLength = startingDistance; - float exponentialDecayDistance = startingDistance; - vec2 direction = normalize(pointLightDirections[i]) / viewBoxSize; - - for (int j = 0; j < 48; j++) { - if (rayLength > lightDistance) { - lighting += lightColorAtPosition * step(0.0, q); - break; - } - - float minDistance = getDistance(uvCoordinates + direction * rayLength); - exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0; - q = min(q, exponentialDecayDistance); - rayLength += minDistance; - } - }*/ - #endif + #endif + } fragmentColor = vec4(colorAtPosition * lighting, 1.0); } diff --git a/frontend/src/scripts/drawing/shaders/shading-vs.glsl b/frontend/src/scripts/drawing/shaders/shading-vs.glsl index 407503a..5d13acf 100644 --- a/frontend/src/scripts/drawing/shaders/shading-vs.glsl +++ b/frontend/src/scripts/drawing/shaders/shading-vs.glsl @@ -3,7 +3,7 @@ precision lowp float; #define CIRCLE_LIGHT_COUNT {circleLightCount} -#define POINT_LIGHT_COUNT {pointLightCount} +#define FLASHLIGHT_COUNT {flashlightCount} uniform mat3 modelTransform; in vec4 vertexPosition; @@ -14,23 +14,24 @@ out vec2 uvCoordinates; uniform vec2 squareToAspectRatio; #if CIRCLE_LIGHT_COUNT > 0 - uniform struct { + uniform struct CircleLight { vec2 center; - float radius; + float lightDrop; vec3 value; }[CIRCLE_LIGHT_COUNT] circleLights; out vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; #endif -#if POINT_LIGHT_COUNT > 0 - uniform struct { +#if FLASHLIGHT_COUNT > 0 + uniform struct Flashlight { vec2 center; - float radius; + vec2 direction; + float lightDrop; vec3 value; - }[POINT_LIGHT_COUNT] pointLights; + }[FLASHLIGHT_COUNT] flashlights; - out vec2[POINT_LIGHT_COUNT] pointLightDirections; + out vec2[FLASHLIGHT_COUNT] flashlightDirections; #endif void main() { @@ -50,9 +51,9 @@ void main() { } #endif - #if POINT_LIGHT_COUNT > 0 - for (int i = 0; i < POINT_LIGHT_COUNT; i++) { - pointLightDirections[i] = pointLights[i].center - position; + #if FLASHLIGHT_COUNT > 0 + for (int i = 0; i < FLASHLIGHT_COUNT; i++) { + flashlightDirections[i] = flashlights[i].center - position; } #endif } diff --git a/frontend/src/scripts/objects/types/character.ts b/frontend/src/scripts/objects/types/character.ts index b6e34f7..d11411f 100644 --- a/frontend/src/scripts/objects/types/character.ts +++ b/frontend/src/scripts/objects/types/character.ts @@ -1,7 +1,7 @@ import { vec2, vec3 } from 'gl-matrix'; import { RenderCommand } from '../../drawing/commands/render'; import { DrawableBlob } from '../../drawing/drawables/drawable-blob'; -import { CircleLight } from '../../drawing/drawables/lights/circle-light'; +import { Flashlight } from '../../drawing/drawables/lights/flashlight'; import { IGame } from '../../i-game'; import { KeyDownCommand } from '../../input/commands/key-down'; import { KeyUpCommand } from '../../input/commands/key-up'; @@ -14,14 +14,21 @@ import { GameObject } from '../game-object'; export class Character extends GameObject { private keysDown: Set = new Set(); - private light: CircleLight; + private light: Flashlight; private shape = new DrawableBlob(vec2.create()); - private static speed = 4.5; + private static speed = 1.5; constructor(private game: IGame) { super(); - this.light = new CircleLight(vec2.create(), 30, vec3.fromValues(0.67, 0.0, 0.33), 2); + this.light = new Flashlight( + vec2.create(), + vec2.fromValues(-1, 0), + 0.7, + vec3.fromValues(1, 0.6, 0.45), + 1.5 + ); + //this.light = new CircleLight(vec2.create(), 0.2, vec3.fromValues(1, 0.6, 0.45), 1.5); this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); diff --git a/frontend/src/scripts/shapes/types/blob.ts b/frontend/src/scripts/shapes/types/blob.ts index c625071..5f01c42 100644 --- a/frontend/src/scripts/shapes/types/blob.ts +++ b/frontend/src/scripts/shapes/types/blob.ts @@ -8,23 +8,17 @@ export class Blob implements IShape { public readonly boundingCircleRadius = 100; protected readonly headRadius = 40; - protected readonly torsoRadius = 60; - protected readonly footRadius = 30; + protected readonly footRadius = 15; protected readonly k = 1000000; - private readonly headOffset = vec2.fromValues( - 0, - this.headRadius + this.torsoRadius / 2 - ); - private readonly torsoOffset = vec2.fromValues(0, 0); - private readonly leftFootOffset = vec2.fromValues(-20, -60); - private readonly rightFootOffset = vec2.fromValues(20, -60); + private readonly headOffset = vec2.fromValues(0, -15); + private readonly leftFootOffset = vec2.fromValues(-12, -60); + private readonly rightFootOffset = vec2.fromValues(12, -60); public readonly isInverted = false; protected boundingCircle = new Circle(vec2.create(), this.boundingCircleRadius); protected head = new Circle(vec2.create(), this.headRadius); - protected torso = new Circle(vec2.create(), this.torsoRadius); protected leftFoot = new Circle(vec2.create(), this.footRadius); protected rightFoot = new Circle(vec2.create(), this.footRadius); public constructor(center: vec2, public readonly gameObject: GameObject = null) { @@ -34,7 +28,6 @@ export class Blob implements IShape { public set position(value: vec2) { vec2.copy(this.boundingCircle.center, value); vec2.add(this.head.center, value, this.headOffset); - vec2.add(this.torso.center, value, this.torsoOffset); vec2.add(this.leftFoot.center, value, this.leftFootOffset); vec2.add(this.rightFoot.center, value, this.rightFootOffset); }