Organize imports and work on integrating lighting

This commit is contained in:
schmelczerandras 2020-07-24 23:00:26 +02:00
parent 0cd383794c
commit affb1b4f4f
16 changed files with 170 additions and 155 deletions

View file

@ -1,5 +1,8 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.tabSize": 2,
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}

View file

@ -1,4 +1,3 @@
import passthroughVertexShader from '../../../shaders/passthrough-vs.glsl';
import { createShader } from './create-shader';
import { loadUniform } from './load-uniform';
@ -15,9 +14,10 @@ export class FragmentShaderOnlyProgram {
constructor(
private gl: WebGL2RenderingContext,
passthroughVertexShaderSource: string,
fragmentShaderSource: string
) {
this.createProgram(fragmentShaderSource);
this.createProgram(passthroughVertexShaderSource, fragmentShaderSource);
this.prepareScreenQuad('a_position');
this.queryUniforms();
}
@ -74,13 +74,16 @@ export class FragmentShaderOnlyProgram {
console.log(this.uniforms);
}
private createProgram(fragmentShaderSource: string) {
private createProgram(
passthroughVertexShaderSource: string,
fragmentShaderSource: string
) {
this.program = this.gl.createProgram();
const vertexShader = createShader(
this.gl,
this.gl.VERTEX_SHADER,
passthroughVertexShader
passthroughVertexShaderSource
);
this.gl.attachShader(this.program, vertexShader);
this.shaders.push(vertexShader);

View file

@ -18,18 +18,27 @@ export const loadUniform = (
> = new Map();
{
converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => {
if (v.length == 0) {
return;
}
gl.uniform1fv(l, new Float32Array(v));
});
converters.set(
WebGL2RenderingContext.FLOAT_VEC2,
(gl, v: vec2 | Array<vec2>, l) => {
if (v.length == 0) {
return;
}
if (v[0] instanceof Array) {
const result = new Float32Array(v.length * 2);
for (let i = 0; i < v.length; i++) {
result[2 * i] = (v[i] as Array<number>).x;
result[2 * i + 1] = (v[i] as Array<number>).y;
}
gl.uniform2fv(l, new Float32Array(result));
} else {
gl.uniform2fv(l, v as vec2);

View file

@ -1,16 +1,14 @@
import { Drawer } from './drawer';
import { mat2d, vec2, mat2, mat3 } from 'gl-matrix';
import { FragmentShaderOnlyProgram } from './graphics-library/fragment-shader-only-program';
import { WebGlStopwatch } from './graphics-library/stopwatch';
import { mat2d, vec2 } from 'gl-matrix';
import { Rectangle } from '../math/rectangle';
import { IntermediateFrameBuffer } from './graphics-library/intermediate-frame-buffer';
import { FrameBuffer } from './graphics-library/frame-buffer';
import { Drawer } from './drawer';
import { DefaultFrameBuffer } from './graphics-library/default-frame-buffer';
import { translate } from 'gl-matrix/src/gl-matrix/mat2d';
import { FragmentShaderOnlyProgram } from './graphics-library/fragment-shader-only-program';
import { IntermediateFrameBuffer } from './graphics-library/intermediate-frame-buffer';
import { WebGlStopwatch } from './graphics-library/stopwatch';
export class WebGl2Renderer implements Drawer {
private gl: WebGL2RenderingContext;
private stopwatch: WebGlStopwatch;
private stopwatch?: WebGlStopwatch;
private viewBox: Rectangle = new Rectangle();
private uniforms: any;
@ -21,7 +19,7 @@ export class WebGl2Renderer implements Drawer {
constructor(
private canvas: HTMLCanvasElement,
private overlay: HTMLElement,
shaderSources: Array<string>
shaderSources: Array<[string, string]>
) {
this.gl = this.canvas.getContext('webgl2');
if (!this.gl) {
@ -29,14 +27,14 @@ export class WebGl2Renderer implements Drawer {
}
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl, [
new FragmentShaderOnlyProgram(this.gl, shaderSources[0]),
new FragmentShaderOnlyProgram(this.gl, ...shaderSources[0]),
]);
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl, [
new FragmentShaderOnlyProgram(this.gl, shaderSources[1]),
new FragmentShaderOnlyProgram(this.gl, ...shaderSources[1]),
]);
this.distanceFieldFrameBuffer.renderScale = 0.2;
this.distanceFieldFrameBuffer.renderScale = 1;
this.lightingFrameBuffer.renderScale = 1;
try {
@ -44,7 +42,7 @@ export class WebGl2Renderer implements Drawer {
} catch {}
}
startFrame(): void {
public startFrame(): void {
this.stopwatch?.start();
this.uniforms = {};
this.distanceFieldFrameBuffer.setSize();
@ -52,15 +50,31 @@ export class WebGl2Renderer implements Drawer {
}
public finishFrame() {
this.calculateOwnUniforms();
this.distanceFieldFrameBuffer.render(this.uniforms);
this.lightingFrameBuffer.render(
this.uniforms,
this.distanceFieldFrameBuffer.texture
);
this.stopwatch?.stop();
}
private calculateOwnUniforms() {
const resolution = vec2.fromValues(this.canvas.width, this.canvas.height);
const distanceScreenToWorld = this.getScreenToWorldTransform(
this.distanceFieldFrameBuffer.getSize()
);
const lightingScreenToWorld = this.getScreenToWorldTransform(
this.lightingFrameBuffer.getSize()
const ndcToWorld = mat2d.fromTranslation(
mat2d.create(),
this.viewBox.topLeft
);
mat2d.scale(ndcToWorld, ndcToWorld, this.viewBox.size);
mat2d.scale(ndcToWorld, ndcToWorld, vec2.fromValues(0.5, 0.5));
mat2d.translate(ndcToWorld, ndcToWorld, vec2.fromValues(1, 1));
const screenToWorld = this.getScreenToWorldTransform(resolution);
@ -79,18 +93,10 @@ export class WebGl2Renderer implements Drawer {
this.giveUniforms({
distanceScreenToWorld,
lightingScreenToWorld,
worldToDistanceUV,
cursorPosition,
ndcToWorld,
});
this.distanceFieldFrameBuffer.render(this.uniforms);
this.lightingFrameBuffer.render(
this.uniforms,
this.distanceFieldFrameBuffer.texture
);
this.stopwatch?.stop();
}
private getScreenToWorldTransform(screenSize: vec2) {

View file

@ -1,21 +1,21 @@
import caveFragmentShader from '../shaders/cave-distance-fs.glsl';
import lightsShader from '../shaders/lights-shading-fs.glsl';
import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl';
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
import { CommandBroadcaster } from './commands/command-broadcaster';
import { BeforeDrawCommand } from './commands/types/before-draw';
import { DrawCommand } from './commands/types/draw';
import { StepCommand } from './commands/types/step';
import { WebGl2Renderer } from './drawing/webgl2-renderer';
import { timeIt } from './helper/timing';
import { KeyboardListener } from './input/keyboard-listener';
import { MouseListener } from './input/mouse-listener';
import { TouchListener } from './input/touch-listener';
import { CommandBroadcaster } from './commands/command-broadcaster';
import { ObjectContainer } from './objects/object-container';
import { DrawCommand } from './commands/types/draw';
import { StepCommand } from './commands/types/step';
import { Character } from './objects/types/character';
import { InfoText } from './objects/types/info-text';
import { timeIt } from './helper/timing';
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';
import { InfoText } from './objects/types/info-text';
export class Game {
private previousTime: DOMHighResTimeStamp = 0;
@ -37,8 +37,8 @@ export class Game {
);
this.renderer = new WebGl2Renderer(canvas, overlay, [
caveFragmentShader,
lightsShader,
[caveVertexShader, caveFragmentShader],
[lightsVertexShader, lightsShader],
]);
this.initializeScene();

View file

@ -1,8 +1,8 @@
import { Typed } from '../transport/serializable';
import { IdentityManager } from '../identity/identity-manager';
import { vec2 } from 'gl-matrix';
import { Command } from '../commands/command';
import { CommandReceiver } from '../commands/command-receiver';
import { vec2 } from 'gl-matrix';
import { IdentityManager } from '../identity/identity-manager';
import { Typed } from '../transport/serializable';
export abstract class GameObject extends Typed implements CommandReceiver {
public readonly id = IdentityManager.generateId();

View file

@ -1,7 +1,7 @@
import { GameObject } from './game-object';
import { Id } from '../identity/identity';
import { Command } from '../commands/command';
import { CommandReceiver } from '../commands/command-receiver';
import { Id } from '../identity/identity';
import { GameObject } from './game-object';
export class ObjectContainer implements CommandReceiver {
private objects: Map<Id, GameObject> = new Map();

View file

@ -1,12 +1,12 @@
import { vec2 } from 'gl-matrix';
import { KeyDownCommand } from '../../commands/types/key-down';
import { KeyUpCommand } from '../../commands/types/key-up';
import { MoveToCommand } from '../../commands/types/move-to';
import { StepCommand } from '../../commands/types/step';
import { SwipeCommand } from '../../commands/types/swipe';
import { GameObject } from '../game-object';
import { ObjectContainer } from '../object-container';
import { Camera } from './camera';
import { MoveToCommand } from '../../commands/types/move-to';
import { StepCommand } from '../../commands/types/step';
import { KeyDownCommand } from '../../commands/types/key-down';
import { KeyUpCommand } from '../../commands/types/key-up';
import { SwipeCommand } from '../../commands/types/swipe';
import { vec2 } from 'gl-matrix';
export class Character extends GameObject {
private keysDown: Set<string> = new Set();

View file

@ -1,6 +1,6 @@
import { GameObject } from '../game-object';
import { DrawCommand } from '../../commands/types/draw';
import { vec2 } from 'gl-matrix';
import { DrawCommand } from '../../commands/types/draw';
import { GameObject } from '../game-object';
export interface Line {
start: vec2;
@ -17,13 +17,13 @@ export class Dungeon extends GameObject {
this.addCommandExecutor(DrawCommand, this.draw.bind(this));
let previousRadius = 0;
let previousRadius = 350;
let previousEnd = vec2.create();
for (let i = 0; i < 500000; i += 500) {
const height = previousEnd.y + (Math.random() - 0.5) * 2000;
const currentEnd = vec2.fromValues(i, height);
const currentToRadius = Math.random() * 10 + 300;
const currentToRadius = Math.random() * 300 + 150;
this.lines.push({
start: previousEnd,

View file

@ -36,11 +36,18 @@ float getDistance(in vec2 target) {
return -minDistance;
}
uniform mat3 distanceScreenToWorld;
vec3 branchlessTernary(float condition, vec3 ifPositive, vec3 ifNegative) {
float isPositive = (sign(condition) + 1.0) * 0.5;
return ifPositive * abs(isPositive) + ifNegative * (1.0 - isPositive);
}
in vec2 worldCoordinates;
out vec4 fragmentColor;
void main() {
vec2 position = (vec3(gl_FragCoord.xy, 1.0) * distanceScreenToWorld).xy;
float distance = getDistance(position);
fragmentColor = vec4(vec3(0.0), distance / 256.0 + 0.5);
float distance = getDistance(worldCoordinates);
fragmentColor = vec4(
branchlessTernary(distance, vec3(1.0), vec3(0.0, 1.0, 0.5)),
distance / 128.0 + 0.5
);
}

View file

@ -3,114 +3,96 @@
precision mediump float;
#define INFINITY 10000.0
#define LIGHTS_SIZE 3
#define LIGHT_PENETRATION 0.95
#define LIGHT_COUNT 3
#define LIGHT_PENETRATION 1000.0
#define ANTIALIASING_RADIUS 1.0
#define AMBIENT_LIGHT vec3(0.075)
struct Light {
vec2 center;
float radius;
vec3 color;
float intensity;
vec3 value;
};
uniform sampler2D distanceTexture;
uniform mat3 worldToDistanceUV;
uniform mat3 lightingScreenToWorld;
uniform vec2 cursorPosition;
Light lights[LIGHTS_SIZE];
float circleDistance(in vec2 position, in Light circle)
{
return length(position - circle.center) - circle.radius;
}
Light lights[LIGHT_COUNT];
float getDistance(in vec2 target, out vec3 color) {
// should avoid this matrix multiplication
vec2 targetUV = (vec3(target.xy, 1.0) * worldToDistanceUV).xy;
vec4 values = texture(distanceTexture, targetUV);
color = values.rgb;
return (values.a - 0.5) * 256.0;
return (values.a - 0.5) * 128.0;
}
float getDistance(in vec2 target) {
// should avoid this matrix multiplication
vec2 targetUV = (vec3(target.xy, 1.0) * worldToDistanceUV).xy;
vec4 values = texture(distanceTexture, targetUV);
return (values.a - 0.5) * 128.0;
}
void createWorld() {
lights[0] = Light(vec2(600, 700), 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);
lights[2] = Light(cursorPosition, 52.5,vec3(0.63, 0.07, 0.19), 200.5);
lights[0] = Light(vec2(600, 700), 40.5, normalize(vec3(1.0)) * 2.0);
lights[1] = Light(vec2(100.0, 350.0), 52.5, normalize(vec3(2.0, 1.0, 0.25)) * 0.5);
lights[2] = Light(cursorPosition, 52.5, normalize(vec3(0.63, 0.25, 0.5)) * 1.0);
}
float escapeFromObject(inout vec2 position, in vec2 direction) {
float fractionOfLightPenetrating = 1.0;
float getFractionOfLightArriving(
in vec2 target,
in vec2 direction,
in float lightDistance,
in float lightRadius
) {
float q = INFINITY;
float rayLength = 0.0;
for (int i = 0; i < 64; i++) {
vec3 color;
float minDistance = getDistance(position, color);
if (minDistance >= 0.0) {
return fractionOfLightPenetrating;
}
fractionOfLightPenetrating *= pow(LIGHT_PENETRATION, -minDistance);
rayLength += max(1.0, -minDistance);
position += direction * rayLength;
}
return 0.0;
}
float getFractionOfLightArriving(in vec2 position, in vec2 direction, in float lightDistance, in float lightRadius) {
float fractionOfLightArriving = 1.0;
vec3 color;
float rayLength = 0.0;
for (int j = 0; j < 64; j++) {
float minDistance = getDistance(position + direction * rayLength, color);
fractionOfLightArriving = min(fractionOfLightArriving, minDistance / rayLength);
rayLength += max(1.0, abs(minDistance));
if (rayLength > lightDistance) {
fractionOfLightArriving = (fractionOfLightArriving * lightDistance + lightRadius) / (2.0 * lightRadius);
return smoothstep(0.0, 1.0, fractionOfLightArriving);
}
float minDistance = getDistance(target + direction * rayLength);
q = min(q, minDistance / rayLength);
rayLength = min(lightDistance, rayLength + max(1.0, minDistance));
}
return 0.0;
return smoothstep(0.0, 1.0, q * (lightDistance + lightRadius) / lightRadius);
}
vec3 getPixelColor(in vec2 targetLighting, in bool startsInside, in vec3 colorBias) {
float square(in float a) {
return a*a;
}
vec3 getPixelColor(in vec2 worldCoordinates) {
vec3 result = vec3(0.0);
vec3 colorAtPosition;
float startingDistance = getDistance(worldCoordinates, colorAtPosition);
float fractionOfLightPenetrating = smoothstep(0.0, 1.0,
1.0 - (min(0.0, startingDistance) / LIGHT_PENETRATION)
);
for (int i = 0; i < LIGHTS_SIZE; i++) {
for (int i = 0; i < LIGHT_COUNT; i++) {
Light light = lights[i];
float lightDistance = circleDistance(targetLighting, light);
vec3 lightColor = normalize(light.color) * light.intensity
/ mix(1.0, lightDistance, clamp(lightDistance, 0.0, 1.0));
float lightDistance = distance(worldCoordinates, light.center) - light.radius;
vec3 lightColorAtPosition = light.value / square(max(0.0, lightDistance / 200.0) + 1.0);
vec2 lightDirection = normalize(light.center - worldCoordinates);
if (lightDistance < 0.0) {
return lightColor;
}
vec2 lightDirection = normalize(light.center - targetLighting);
vec2 rayStart = targetLighting;
float fractionOfLightPenetrating = 1.0;
if (startsInside) {
fractionOfLightPenetrating = escapeFromObject(rayStart, lightDirection);
lightColor *= colorBias;
}
float fractionOfLightArriving = getFractionOfLightArriving(
worldCoordinates, lightDirection, max(0.0, lightDistance), light.radius
);
float fractionOfLightArriving = getFractionOfLightArriving(rayStart, lightDirection, lightDistance, light.radius);
result += lightColor * fractionOfLightArriving * fractionOfLightPenetrating;
result += colorAtPosition * lightColorAtPosition * fractionOfLightArriving * fractionOfLightPenetrating;
}
// Add ambient light
result += colorAtPosition * AMBIENT_LIGHT;
return clamp(result, 0.0, 1.0);
}
/*vec3 getPixelColorAntialiased(in vec2 position) {
Circle nearest;
float minDistance = getDistance(position, nearest);
@ -122,21 +104,11 @@ vec3 getPixelColor(in vec2 targetLighting, in bool startsInside, in vec3 colorBi
return getPixelColor(position, minDistance < 0.0, minDistance < 0.0 ? nearest.color : vec3(1.0));
}*/
in vec2 worldCoordinates;
out vec4 fragmentColor;
void main() {
createWorld();
vec2 pixelWorldCoordinates = (vec3(gl_FragCoord.xy, 1.0) * lightingScreenToWorld).xy;
vec3 color;
float minDistance = getDistance(pixelWorldCoordinates, color);
color = getPixelColor(pixelWorldCoordinates, minDistance < 0.0, minDistance < 0.0 ? color : vec3(1.0));
fragmentColor = vec4(color, 1.0);
if (distance(cursorPosition, pixelWorldCoordinates) < 50.0) {
fragmentColor = vec4(vec3(1.0, 1.0, 0.0), 1.0);
}
// log2 for compenstaion?
fragmentColor = vec4(getPixelColor(worldCoordinates), 1.0);
}

View file

@ -0,0 +1,10 @@
#version 300 es
uniform mat3 ndcToWorld;
in vec4 a_position;
out vec2 worldCoordinates;
void main() {
worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy;
gl_Position = a_position;
}

View file

@ -0,0 +1,10 @@
#version 300 es
uniform mat3 ndcToWorld;
in vec4 a_position;
out vec2 worldCoordinates;
void main() {
worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy;
gl_Position = a_position;
}

View file

@ -1,7 +0,0 @@
#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}

View file

@ -34,16 +34,17 @@ float getDistance(in vec2 targetUV, out vec3 color) {
return values.a;
}
in vec2 worldCoordinates;
void main() {
vec2 targetUV = (vec3(gl_FragCoord.xy, 1.0) * worldToDistanceUV).xy;
vec2 targetLighting = (vec3(gl_FragCoord.xy, 1.0) * lightingScreenToWorld).xy;
vec2 targetUV = (vec3(worldCoordinates.xy, 1.0) * worldToDistanceUV).xy;
vec4 previous = texture(distanceTexture, targetUV);
//fragmentColor = smoothRainbow(previous.a);
fragmentColor = previous.a > 0.5 ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0);
if (distance(targetLighting, cursorPosition) < 0.01) {
if (distance(worldCoordinates, cursorPosition) < 10.0) {
fragmentColor = vec4(1.0, 1.0, 0.0, 1.0);
}
}