Add lighting shader

This commit is contained in:
schmelczerandras 2020-07-22 22:47:26 +02:00
parent e88b4589d2
commit 0cd383794c
11 changed files with 144 additions and 295 deletions

View file

@ -0,0 +1,8 @@
import { Command } from '../command';
import { vec2 } from 'gl-matrix';
export class CursorMoveCommand extends Command {
public constructor(public readonly position?: vec2) {
super();
}
}

View file

@ -6,15 +6,17 @@ import { Rectangle } from '../math/rectangle';
import { IntermediateFrameBuffer } from './graphics-library/intermediate-frame-buffer'; import { IntermediateFrameBuffer } from './graphics-library/intermediate-frame-buffer';
import { FrameBuffer } from './graphics-library/frame-buffer'; import { FrameBuffer } from './graphics-library/frame-buffer';
import { DefaultFrameBuffer } from './graphics-library/default-frame-buffer'; import { DefaultFrameBuffer } from './graphics-library/default-frame-buffer';
import { translate } from 'gl-matrix/src/gl-matrix/mat2d';
export class WebGl2Renderer implements Drawer { export class WebGl2Renderer implements Drawer {
private gl: WebGL2RenderingContext; private gl: WebGL2RenderingContext;
private stopwatch: WebGlStopwatch; private stopwatch: WebGlStopwatch;
private viewBox: Rectangle = new Rectangle(); private viewBox: Rectangle = new Rectangle();
private nextFrameUniforms: any; private uniforms: any;
private cursorPosition = vec2.create(); private cursorPosition = vec2.create();
private frameBuffers: Array<FrameBuffer> = []; private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private lightingFrameBuffer: DefaultFrameBuffer;
constructor( constructor(
private canvas: HTMLCanvasElement, private canvas: HTMLCanvasElement,
@ -26,20 +28,16 @@ export class WebGl2Renderer implements Drawer {
throw new Error('WebGl2 is not supported'); throw new Error('WebGl2 is not supported');
} }
this.frameBuffers.push( this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl, [
new IntermediateFrameBuffer(this.gl, [ new FragmentShaderOnlyProgram(this.gl, shaderSources[0]),
new FragmentShaderOnlyProgram(this.gl, shaderSources[0]), ]);
])
);
this.frameBuffers.push( this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl, [
new DefaultFrameBuffer(this.gl, [ new FragmentShaderOnlyProgram(this.gl, shaderSources[1]),
new FragmentShaderOnlyProgram(this.gl, shaderSources[1]), ]);
])
);
this.frameBuffers[0].renderScale = 1; this.distanceFieldFrameBuffer.renderScale = 0.2;
this.frameBuffers[1].renderScale = 1; this.lightingFrameBuffer.renderScale = 1;
try { try {
this.stopwatch = new WebGlStopwatch(this.gl); this.stopwatch = new WebGlStopwatch(this.gl);
@ -48,13 +46,54 @@ export class WebGl2Renderer implements Drawer {
startFrame(): void { startFrame(): void {
this.stopwatch?.start(); this.stopwatch?.start();
this.nextFrameUniforms = {}; this.uniforms = {};
this.frameBuffers.forEach((f) => f.setSize()); this.distanceFieldFrameBuffer.setSize();
this.lightingFrameBuffer.setSize();
} }
public finishFrame() { public finishFrame() {
const resolution = vec2.fromValues(this.canvas.width, this.canvas.height); 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 screenToWorld = this.getScreenToWorldTransform(resolution);
const worldToDistanceUV = mat2d.scale(
mat2d.create(),
distanceScreenToWorld,
this.distanceFieldFrameBuffer.getSize()
);
mat2d.invert(worldToDistanceUV, worldToDistanceUV);
const cursorPosition = vec2.transformMat2d(
vec2.create(),
vec2.multiply(vec2.create(), this.cursorPosition, resolution),
screenToWorld
);
this.giveUniforms({
distanceScreenToWorld,
lightingScreenToWorld,
worldToDistanceUV,
cursorPosition,
});
this.distanceFieldFrameBuffer.render(this.uniforms);
this.lightingFrameBuffer.render(
this.uniforms,
this.distanceFieldFrameBuffer.texture
);
this.stopwatch?.stop();
}
private getScreenToWorldTransform(screenSize: vec2) {
const transform = mat2d.fromTranslation( const transform = mat2d.fromTranslation(
mat2d.create(), mat2d.create(),
this.viewBox.topLeft this.viewBox.topLeft
@ -62,29 +101,11 @@ export class WebGl2Renderer implements Drawer {
mat2d.scale( mat2d.scale(
transform, transform,
transform, transform,
vec2.divide( vec2.divide(vec2.create(), this.viewBox.size, screenSize)
vec2.create(),
this.viewBox.size,
this.frameBuffers[0].getSize()
)
); );
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5)); mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
this.nextFrameUniforms.transform = transform;
const transformUV = mat2d.fromScaling( return transform;
mat2d.create(),
vec2.divide(vec2.create(), vec2.fromValues(1, 1), resolution)
);
mat2d.translate(transformUV, transformUV, vec2.fromValues(0.5, 0.5));
this.nextFrameUniforms.transformUV = transformUV;
this.frameBuffers[0].render(this.nextFrameUniforms);
this.frameBuffers[1].render(
this.nextFrameUniforms,
(this.frameBuffers[0] as IntermediateFrameBuffer).texture
);
this.stopwatch?.stop();
} }
public setCameraPosition(position: vec2) { public setCameraPosition(position: vec2) {
@ -96,7 +117,7 @@ export class WebGl2Renderer implements Drawer {
} }
public giveUniforms(uniforms: any): void { public giveUniforms(uniforms: any): void {
this.nextFrameUniforms = { ...this.nextFrameUniforms, ...uniforms }; this.uniforms = { ...this.uniforms, ...uniforms };
} }
public setInViewArea(size: number): vec2 { public setInViewArea(size: number): vec2 {

View file

@ -12,8 +12,8 @@ import { InfoText } from './objects/types/info-text';
import { timeIt } from './helper/timing'; import { timeIt } from './helper/timing';
import caveFragmentShader from '../shaders/cave-distance-fs.glsl'; import caveFragmentShader from '../shaders/cave-distance-fs.glsl';
import lightsShader from '../shaders/rainbow-shading-fs.glsl'; // import lightsShader from '../shaders/rainbow-shading-fs.glsl';
//import lightsShader from '../shaders/lights-shading-fs.glsl'; import lightsShader from '../shaders/lights-shading-fs.glsl';
import { Dungeon } from './objects/types/dungeon'; import { Dungeon } from './objects/types/dungeon';
import { BeforeDrawCommand } from './commands/types/before-draw'; import { BeforeDrawCommand } from './commands/types/before-draw';

View file

@ -5,6 +5,7 @@ import { SwipeCommand } from '../commands/types/swipe';
import { ZoomCommand } from '../commands/types/zoom'; import { ZoomCommand } from '../commands/types/zoom';
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { clamp01 } from '../helper/clamp'; import { clamp01 } from '../helper/clamp';
import { CursorMoveCommand } from '../commands/types/cursor-move-command';
export class MouseListener extends CommandGenerator { export class MouseListener extends CommandGenerator {
private previousPosition = vec2.create(); private previousPosition = vec2.create();
@ -25,8 +26,9 @@ export class MouseListener extends CommandGenerator {
}); });
target.addEventListener('mousemove', (event: MouseEvent) => { target.addEventListener('mousemove', (event: MouseEvent) => {
const position = this.positionFromEvent(event);
if (this.isMouseDown) { if (this.isMouseDown) {
const position = this.positionFromEvent(event);
this.sendCommand( this.sendCommand(
new SwipeCommand( new SwipeCommand(
vec2.subtract(vec2.create(), this.previousPosition, position) vec2.subtract(vec2.create(), this.previousPosition, position)
@ -34,6 +36,8 @@ export class MouseListener extends CommandGenerator {
); );
this.previousPosition = position; this.previousPosition = position;
} }
this.sendCommand(new CursorMoveCommand(position));
}); });
target.addEventListener('mouseup', (event: MouseEvent) => { target.addEventListener('mouseup', (event: MouseEvent) => {

View file

@ -4,6 +4,7 @@ import { ZoomCommand } from '../../commands/types/zoom';
import { BeforeDrawCommand } from '../../commands/types/before-draw'; import { BeforeDrawCommand } from '../../commands/types/before-draw';
import { PrimaryActionCommand } from '../../commands/types/primary-action'; import { PrimaryActionCommand } from '../../commands/types/primary-action';
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { CursorMoveCommand } from '../../commands/types/cursor-move-command';
export class Camera extends GameObject { export class Camera extends GameObject {
private inViewArea = 1920 * 1080; private inViewArea = 1920 * 1080;
@ -14,11 +15,11 @@ export class Camera extends GameObject {
this.addCommandExecutor(BeforeDrawCommand, this.draw.bind(this)); this.addCommandExecutor(BeforeDrawCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
this.addCommandExecutor( this.addCommandExecutor(
PrimaryActionCommand, CursorMoveCommand,
this.setCursorPosition.bind(this) this.setCursorPosition.bind(this)
); );
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
} }
private draw(c: BeforeDrawCommand) { private draw(c: BeforeDrawCommand) {
@ -35,7 +36,7 @@ export class Camera extends GameObject {
this.inViewArea *= c.factor; this.inViewArea *= c.factor;
} }
private setCursorPosition(c: PrimaryActionCommand) { private setCursorPosition(c: CursorMoveCommand) {
this.cursorPosition = c.position; this.cursorPosition = c.position;
} }
} }

View file

@ -20,10 +20,10 @@ export class Dungeon extends GameObject {
let previousRadius = 0; let previousRadius = 0;
let previousEnd = vec2.create(); let previousEnd = vec2.create();
for (let i = 0; i < 5000; i += 50) { for (let i = 0; i < 500000; i += 500) {
const height = previousEnd.y + (Math.random() - 0.5) * 200; const height = previousEnd.y + (Math.random() - 0.5) * 2000;
const currentEnd = vec2.fromValues(i, height); const currentEnd = vec2.fromValues(i, height);
const currentToRadius = Math.random() * 10 + 30; const currentToRadius = Math.random() * 10 + 300;
this.lines.push({ this.lines.push({
start: previousEnd, start: previousEnd,

View file

@ -36,11 +36,11 @@ float getDistance(in vec2 target) {
return -minDistance; return -minDistance;
} }
uniform mat3 transform; uniform mat3 distanceScreenToWorld;
out vec4 fragmentColor; out vec4 fragmentColor;
void main() { void main() {
vec2 position = (vec3(gl_FragCoord.xy, 1.0) * transform).xy; vec2 position = (vec3(gl_FragCoord.xy, 1.0) * distanceScreenToWorld).xy;
float distance = getDistance(position); float distance = getDistance(position);
fragmentColor = vec4(vec3(0.0), distance / 256.0 + 0.5); fragmentColor = vec4(vec3(0.0), distance / 256.0 + 0.5);
} }

View file

@ -1,75 +0,0 @@
#version 300 es
precision mediump float;
#define SMOOTHING 10.0
#define INFINITY 10000.0;
#define LINE_COUNT 100
float interpolate(float from, float to, float quotient) {
return from + (to - from) * smoothstep(0.0, 1.0, quotient);
}
vec2 rotate90deg(in vec2 vector) {
return vec2(-vector.y, vector.x);
}
uniform struct Line {
vec2 from;
vec2 to;
vec2 normal;
bool isLineEnd;
}[LINE_COUNT] lines;
float lineDistance(in vec2 position, in Line line, out float h) {
vec2 pa = position - line.from, ba = line.to - line.from;
h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
vec2 delta = pa - ba*h;
// sign can return 0, double sign prevents this
float side = sign(sign(dot(delta, line.normal)) - 0.5);
return length(delta) * side;
}
float getDistance(in vec2 target) {
float positiveMinDistance = INFINITY;
float negativeMaxDistance = INFINITY;
float leftJoinAcuteness = 0.0;
vec2 splitterLineNormalStart = vec2(-1.0, 0.0);
for (int i = 0; i < LINE_COUNT - 1; i++) {
vec2 splitterLineNormalEnd = rotate90deg(normalize(lines[i].normal + lines[i + 1].normal));
float h;
float distanceToCurrent = lineDistance(target, lines[i], h);
float rightJoinAcuteness = dot(lines[i + 1].to - lines[i].from, lines[i + 1].normal - lines[i].normal);
distanceToCurrent -= interpolate(
sign(leftJoinAcuteness) * SMOOTHING,
sign(rightJoinAcuteness) * SMOOTHING, h
);
leftJoinAcuteness = rightJoinAcuteness;
if (
!(
dot(target - lines[i].from, splitterLineNormalStart * -sign(dot(lines[i].to - lines[i].from, splitterLineNormalStart))) > 0.0
|| dot(target - lines[i].to, splitterLineNormalEnd * sign(dot(lines[i].from - lines[i].to, splitterLineNormalEnd))) <= 0.0
)
) {
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;
}
return positiveMinDistance < negativeMaxDistance ? positiveMinDistance : -negativeMaxDistance;
}
uniform mat3 transform;
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);
}

View file

@ -2,21 +2,13 @@
precision mediump float; precision mediump float;
#define INFINITY 1.0 / 0.0 #define INFINITY 10000.0
#define WORLD_SIZE 4 #define LIGHTS_SIZE 3
#define LIGHTS_SIZE 2
#define LIGHT_PENETRATION 0.95 #define LIGHT_PENETRATION 0.95
#define ANTIALIASING_RADIUS 1.0 #define ANTIALIASING_RADIUS 1.0
uniform vec2 resolution;
uniform vec2 mouse;
uniform sampler2D distanceTexture;
uniform mat3 transformUV;
out vec4 fragmentColor;
struct Light { struct Light {
vec2 center; vec2 center;
float radius; float radius;
@ -24,62 +16,38 @@ struct Light {
float intensity; float intensity;
}; };
struct Circle { uniform sampler2D distanceTexture;
vec2 center; uniform mat3 worldToDistanceUV;
float radius; uniform mat3 lightingScreenToWorld;
vec3 color; uniform vec2 cursorPosition;
};
Light lights[LIGHTS_SIZE]; Light lights[LIGHTS_SIZE];
Circle world[WORLD_SIZE];
vec3 red = vec3(5.0, 0.0, 2.0);
vec3 blue = vec3(0.0, 0.0, 3.0);
float circleDistance(in vec2 position, in Circle circle)
{
return length(position - circle.center) - circle.radius;
}
float circleDistance(in vec2 position, in Light circle) float circleDistance(in vec2 position, in Light circle)
{ {
return length(position - circle.center) - circle.radius; return length(position - circle.center) - circle.radius;
} }
float getDistance(in vec2 target) { float getDistance(in vec2 target, out vec3 color) {
float distance = INFINITY; vec2 targetUV = (vec3(target.xy, 1.0) * worldToDistanceUV).xy;
for (int i = 0; i < WORLD_SIZE; i++) { vec4 values = texture(distanceTexture, targetUV);
distance = min(distance, circleDistance(target, world[i])); color = values.rgb;
} return (values.a - 0.5) * 256.0;
return distance;
}
float getDistance(in vec2 target, out Circle nearest) {
float distance = INFINITY;
for (int i = 0; i < WORLD_SIZE; i++) {
float distanceToCurrent = circleDistance(target, world[i]);
if (distanceToCurrent < distance) {
distance = distanceToCurrent;
nearest = world[i];
}
}
return distance;
} }
void createWorld() { void createWorld() {
lights[0] = Light(mouse, 40.5, vec3(1.0), 25.0); 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[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);
world[0] = Circle(vec2(250.0, 100.0), 12.5, blue);
world[1] = Circle(vec2(150.0, 50.0), 32.5, red);
world[2] = Circle(vec2(300.0, 350.0), 52.5, blue);
} }
float escapeFromObject(inout vec2 position, in vec2 direction) { float escapeFromObject(inout vec2 position, in vec2 direction) {
float fractionOfLightPenetrating = 1.0; float fractionOfLightPenetrating = 1.0;
float rayLength = 0.0; float rayLength = 0.0;
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
float minDistance = getDistance(position); vec3 color;
float minDistance = getDistance(position, color);
if (minDistance >= 0.0) { if (minDistance >= 0.0) {
return fractionOfLightPenetrating; return fractionOfLightPenetrating;
} }
@ -94,10 +62,11 @@ float escapeFromObject(inout vec2 position, in vec2 direction) {
float getFractionOfLightArriving(in vec2 position, in vec2 direction, in float lightDistance, in float lightRadius) { float getFractionOfLightArriving(in vec2 position, in vec2 direction, in float lightDistance, in float lightRadius) {
float fractionOfLightArriving = 1.0; float fractionOfLightArriving = 1.0;
vec3 color;
float rayLength = 0.0; float rayLength = 0.0;
for (int j = 0; j < 64; j++) { for (int j = 0; j < 64; j++) {
float minDistance = getDistance(position + direction * rayLength); float minDistance = getDistance(position + direction * rayLength, color);
fractionOfLightArriving = min(fractionOfLightArriving, minDistance / rayLength); fractionOfLightArriving = min(fractionOfLightArriving, minDistance / rayLength);
rayLength += max(1.0, abs(minDistance)); rayLength += max(1.0, abs(minDistance));
@ -110,25 +79,27 @@ float getFractionOfLightArriving(in vec2 position, in vec2 direction, in float l
return 0.0; return 0.0;
} }
vec3 getPixelColor(in vec2 position, in bool startsInside, in vec3 colorBias) { vec3 getPixelColor(in vec2 targetLighting, in bool startsInside, in vec3 colorBias) {
vec3 result = vec3(0.0); vec3 result = vec3(0.0);
for (int i = 0; i < LIGHTS_SIZE; i++) { for (int i = 0; i < LIGHTS_SIZE; i++) {
Light light = lights[i]; Light light = lights[i];
float lightDistance = circleDistance(position, light); float lightDistance = circleDistance(targetLighting, light);
vec3 lightColor = normalize(light.color) * light.intensity / mix(1.0, lightDistance, clamp(lightDistance, 0.0, 1.0)); vec3 lightColor = normalize(light.color) * light.intensity
/ mix(1.0, lightDistance, clamp(lightDistance, 0.0, 1.0));
if (lightDistance < 0.0) { if (lightDistance < 0.0) {
return lightColor; return lightColor;
} }
vec2 lightDirection = normalize(light.center - position); vec2 lightDirection = normalize(light.center - targetLighting);
vec2 rayStart = position; vec2 rayStart = targetLighting;
float fractionOfLightPenetrating = 1.0; float fractionOfLightPenetrating = 1.0;
if (startsInside) { if (startsInside) {
fractionOfLightPenetrating = escapeFromObject(rayStart, lightDirection); fractionOfLightPenetrating = escapeFromObject(rayStart, lightDirection);
lightColor *= colorBias; lightColor *= colorBias;
} }
@ -139,7 +110,8 @@ vec3 getPixelColor(in vec2 position, in bool startsInside, in vec3 colorBias) {
return clamp(result, 0.0, 1.0); return clamp(result, 0.0, 1.0);
} }
vec3 getPixelColorAntialiased(in vec2 position) {
/*vec3 getPixelColorAntialiased(in vec2 position) {
Circle nearest; Circle nearest;
float minDistance = getDistance(position, nearest); float minDistance = getDistance(position, nearest);
if (0.0 < minDistance && minDistance < 1.0) { if (0.0 < minDistance && minDistance < 1.0) {
@ -148,13 +120,23 @@ vec3 getPixelColorAntialiased(in vec2 position) {
} }
return getPixelColor(position, minDistance < 0.0, minDistance < 0.0 ? nearest.color : vec3(1.0)); return getPixelColor(position, minDistance < 0.0, minDistance < 0.0 ? nearest.color : vec3(1.0));
} }*/
out vec4 fragmentColor;
void main() { void main() {
createWorld(); createWorld();
vec2 position = gl_FragCoord.xy + vec2(0.5); vec2 pixelWorldCoordinates = (vec3(gl_FragCoord.xy, 1.0) * lightingScreenToWorld).xy;
vec3 color = getPixelColorAntialiased(position);
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); fragmentColor = vec4(color, 1.0);
if (distance(cursorPosition, pixelWorldCoordinates) < 50.0) {
fragmentColor = vec4(vec3(1.0, 1.0, 0.0), 1.0);
}
} }

View file

@ -22,12 +22,28 @@ vec4 smoothRainbow(float x) {
uniform sampler2D distanceTexture; uniform sampler2D distanceTexture;
uniform mat3 transformUV; uniform mat3 worldToDistanceUV;
uniform mat3 lightingScreenToWorld;
out vec4 fragmentColor; out vec4 fragmentColor;
uniform vec2 cursorPosition;
float getDistance(in vec2 targetUV, out vec3 color) {
vec4 values = texture(distanceTexture, targetUV);
color = values.rgb;
return values.a;
}
void main() { void main() {
vec2 position = (vec3(gl_FragCoord.xy, 1.0) * transformUV).xy; vec2 targetUV = (vec3(gl_FragCoord.xy, 1.0) * worldToDistanceUV).xy;
vec4 previous = texture(distanceTexture, position); vec2 targetLighting = (vec3(gl_FragCoord.xy, 1.0) * lightingScreenToWorld).xy;
vec4 previous = texture(distanceTexture, targetUV);
//fragmentColor = smoothRainbow(previous.a); //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); 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) {
fragmentColor = vec4(1.0, 1.0, 0.0, 1.0);
}
} }

View file

@ -2,7 +2,6 @@
precision mediump float; precision mediump float;
const float smoothing = 10.0;
const float inf = 1000000.0; const float inf = 1000000.0;
const float pi = atan(1.0) * 4.0; const float pi = atan(1.0) * 4.0;
@ -11,16 +10,6 @@ float interpolate(float from, float to, float quotient) {
return from + (to - from) * clamp(steppedQ, 0.0, 1.0); return from + (to - from) * clamp(steppedQ, 0.0, 1.0);
} }
vec2 rotate90deg(in vec2 vector) {
return vec2(-vector.y, vector.x);
}
struct Line {
vec2 a;
vec2 b;
vec2 normal;
bool isLineEnd;
}[16] lines;
float noise(float x){ float noise(float x){
return fract(sin(x) * 43758.5453123); return fract(sin(x) * 43758.5453123);
@ -57,101 +46,4 @@ float noise(vec2 st) {
dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x), dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ), mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y); dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
} }
float lineDistance(in vec2 position, in Line line, out float h) {
vec2 pa = position - line.a, ba = line.b - line.a;
h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
vec2 delta = pa - ba*h;
// sign can return 0, double sign prevents this
float side = sign(sign(dot(delta, line.normal)) - 0.5);
return length(delta) * side; //+ terrain(length(ba * h));
}
Line endDummyLineFromLine(Line line) {
return Line(line.b, line.b + rotate90deg(line.normal), line.normal, false);
}
float getDistance(in vec2 target) {
float minDistance = inf;
float leftJoinAcuteness = 0.0;
vec2 splitterLineNormalStart = vec2(-1.0, 0.0);
bool skipDistanceToPrevious = true;
for (int i = 0; i < lines.length(); i++) {
Line current = lines[i];
Line next;
if (current.isLineEnd || i + 1 == lines.length()) {
next = endDummyLineFromLine(current);
} else {
next = lines[i + 1];
}
vec2 splitterLineNormalEnd = rotate90deg(normalize(current.normal + next.normal));
float h;
float distanceToCurrent = lineDistance(target, current, h);
float rightJoinAcuteness = dot(next.b - current.a, next.normal - current.normal);
distanceToCurrent -= interpolate(
sign(leftJoinAcuteness) * smoothing,
sign(rightJoinAcuteness) * smoothing, h
);
leftJoinAcuteness = rightJoinAcuteness;
if (
!(
dot(target - current.a, splitterLineNormalStart * -sign(dot(current.b - current.a, splitterLineNormalStart))) > 0.0
|| dot(target - current.b, splitterLineNormalEnd * sign(dot(current.a - current.b, splitterLineNormalEnd))) <= 0.0
) && abs(distanceToCurrent) < abs(minDistance)
) {
minDistance = distanceToCurrent;
}
splitterLineNormalStart = splitterLineNormalEnd;
}
return minDistance;
}
void createWorld() {
lines[0] = Line(vec2(0.0, 300.0), vec2(550.0, 140.0), vec2(1.0), false);
lines[1] = Line(vec2(550.0, 140.0), vec2(750.0, 130.0), vec2(1.0), false);
lines[2] = Line(vec2(750.0, 130.0), vec2(650.0, 230.0), vec2(1.0), false);
lines[3] = Line(vec2(650.0, 230.0), vec2(850.0, 230.0), vec2(1.0), false);
lines[4] = Line(vec2(850.0, 230.0), vec2(800.0, 150.0), vec2(1.0), false);
lines[5] = Line(vec2(800.0, 150.0), vec2(1000.0, 120.0), vec2(1.0), false);
lines[6] = Line(vec2(1000.0, 120.0), vec2(1150, 120.0), vec2(1.0), false);
lines[7] = Line(vec2(1150, 120.0), vec2(10200, 350.0), vec2(1.0), true);
lines[8] = Line(vec2(0.0, 600.0), vec2(550.0, 440.0), vec2(-1.0), false);
lines[9] = Line(vec2(550.0, 440.0), vec2(750.0, 430.0), vec2(-1.0), false);
lines[10] = Line(vec2(750.0, 430.0), vec2(650.0, 530.0), vec2(-1.0), false);
lines[11] = Line(vec2(650.0, 530.0), vec2(850.0, 530.0), vec2(-1.0), false);
lines[12] = Line(vec2(850.0, 530.0), vec2(820.0, 450.0), vec2(-1.0), false);
lines[13] = Line(vec2(820.0, 450.0), vec2(1000.0, 420.0), vec2(-1.0), false);
lines[14] = Line(vec2(1000.0, 420.0), vec2(1150, 420.0), vec2(-1.0), false);
lines[15] = Line(vec2(1150, 420.0), vec2(10200, 650.0), vec2(-1.0), true);
for (int i = 0; i < lines.length(); i++) {
vec2 tangent = lines[i].b - lines[i].a;
lines[i].normal = normalize(
vec2(-lines[i].normal.x * tangent.y, lines[i].normal.x * tangent.x)
);
}
}
uniform vec2 cameraPosition;
uniform vec2 viewBoxSize;
uniform vec2 resolution;
out vec4 fragmentColor;
void main() {
createWorld();
vec2 pixelPosition = gl_FragCoord.xy + vec2(0.5);
vec2 position = pixelPosition / resolution * viewBoxSize + cameraPosition;
fragmentColor = vec4(vec3(1.0) * clamp(0.0, 1.0, getDistance(position)), 1.0);
}