Some improvements

This commit is contained in:
schmelczerandras 2020-09-12 12:18:11 +02:00
parent d078d67d58
commit 4f75ce7065
12 changed files with 160 additions and 161 deletions

View file

@ -8,13 +8,12 @@ export class CircleLight implements ILight {
uniformName: 'circleLights',
countMacroName: 'circleLightCount',
shaderCombinationSteps: settings.shaderCombinations.circleLightSteps,
empty: new CircleLight(vec2.fromValues(0, 0), 0, 0, vec3.fromValues(0, 0, 0), 0),
empty: new CircleLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0),
};
constructor(
public center: vec2,
public radius: number,
public traceRadius: number,
public color: vec3,
public lightness: number
) {}
@ -33,7 +32,6 @@ export class CircleLight implements ILight {
uniforms[uniformName].push({
center: vec2.transformMat2d(vec2.create(), this.center, transform),
radius: this.radius * scale,
traceRadius: this.traceRadius * scale,
value: this.value,
});
}

View file

@ -1,4 +1,4 @@
import { mat2d, vec2 } from 'gl-matrix';
import { vec2 } from 'gl-matrix';
import { IDrawable } from '../drawables/i-drawable';
import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor';
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer';
@ -30,12 +30,7 @@ export class RenderingPass {
this.drawables.push(drawable);
}
public render(
commonUniforms: any,
scale: number,
transform: mat2d,
inputTexture?: WebGLTexture
) {
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
this.frame.bindAndClear(inputTexture);
const stepsInUV = 1 / settings.tileMultiplier;
@ -49,7 +44,7 @@ export class RenderingPass {
for (let x = -1; x < 1; x += stepsInNDC) {
for (let y = -1; y < 1; y += stepsInNDC) {
const uniforms = { ...commonUniforms };
uniforms.maxMinDistance = 2 * worldR * scale;
uniforms.maxMinDistance = 2 * worldR * uniforms.scaleWorldLengthToNDC;
const ndcBottomLeft = vec2.fromValues(x, y);
@ -73,7 +68,11 @@ export class RenderingPass {
);
primitivesNearTile.forEach((p) =>
p.serializeToUniforms(uniforms, scale, transform)
p.serializeToUniforms(
uniforms,
uniforms.scaleWorldLengthToNDC,
uniforms.transformWorldToNDC
)
);
this.program.bindAndSetUniforms(uniforms);

View file

@ -0,0 +1,95 @@
import { mat2d, vec2 } from 'gl-matrix';
import { BoundingBoxBase } from '../../shapes/bounding-box-base';
export class UniformsProvider {
private scaleWorldLengthToNDC = 1;
private transformWorldToNDC = mat2d.create();
private viewAreaBottomLeft = vec2.create();
private worldAreaInView = vec2.create();
private squareToAspectRatio: vec2;
private uvToWorld: mat2d;
private cursorPosition = vec2.create();
public softShadowsEnabled: boolean;
public constructor(private gl: WebGL2RenderingContext) {}
public get uniforms(): any {
const cursorPosition = this.uvToWorldCoordinate(this.cursorPosition);
return {
cursorPosition,
pixelSize: 0.05,
uvToWorld: this.uvToWorld,
worldAreaInView: this.worldAreaInView,
squareToAspectRatio: this.squareToAspectRatio,
scaleWorldLengthToNDC: this.scaleWorldLengthToNDC,
transformWorldToNDC: this.transformWorldToNDC,
squareToAspectRatioTimes2: vec2.scale(vec2.create(), this.squareToAspectRatio, 2),
softShadowsEnabled: this.softShadowsEnabled,
};
}
private getScreenToWorldTransform(screenSize: vec2) {
const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
mat2d.scale(
transform,
transform,
vec2.divide(vec2.create(), this.worldAreaInView, screenSize)
);
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
return transform;
}
public uvToWorldCoordinate(screenUvPosition: vec2): vec2 {
const resolution = vec2.fromValues(this.gl.canvas.width, this.gl.canvas.height);
return vec2.transformMat2d(
vec2.create(),
vec2.multiply(vec2.create(), screenUvPosition, resolution),
this.getScreenToWorldTransform(resolution)
);
}
public setViewArea(viewArea: BoundingBoxBase) {
this.worldAreaInView = viewArea.size;
// world coordinates
this.viewAreaBottomLeft = vec2.add(
vec2.create(),
viewArea.topLeft,
vec2.fromValues(0, -viewArea.size.y)
);
const scaleLongerEdgeTo1 =
1 / Math.max(this.worldAreaInView.x, this.worldAreaInView.y);
this.squareToAspectRatio = vec2.fromValues(
this.worldAreaInView.x * scaleLongerEdgeTo1,
this.worldAreaInView.y * scaleLongerEdgeTo1
);
this.scaleWorldLengthToNDC = scaleLongerEdgeTo1 * 2;
mat2d.fromScaling(
this.transformWorldToNDC,
vec2.fromValues(this.scaleWorldLengthToNDC, this.scaleWorldLengthToNDC)
);
mat2d.translate(
this.transformWorldToNDC,
this.transformWorldToNDC,
vec2.fromValues(
-this.viewAreaBottomLeft.x - 0.5 * this.worldAreaInView.x,
-this.viewAreaBottomLeft.y - 0.5 * this.worldAreaInView.y
)
);
this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView);
}
public setCursorPosition(position: vec2): void {
this.cursorPosition = position;
}
}

View file

@ -1,4 +1,4 @@
import { mat2d, vec2 } from 'gl-matrix';
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from '../../shapes/bounding-box-base';
import { DrawableBlob } from '../drawables/drawable-blob';
import { DrawableTunnel } from '../drawables/drawable-tunnel';
@ -11,26 +11,18 @@ import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/interm
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { IRenderer } from '../i-renderer';
import caveFragmentShader from '../shaders/cave-distance-fs.glsl';
import lightsFragmentShader from '../shaders/lights-shading-fs.glsl';
import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl';
import distanceFragmentShader from '../shaders/distance-fs.glsl';
import distanceVertexShader from '../shaders/distance-vs.glsl';
import lightsFragmentShader from '../shaders/shading-fs.glsl';
import lightsVertexShader from '../shaders/shading-vs.glsl';
import { FpsAutoscaler } from './fps-autoscaler';
import { RenderingPass } from './rendering-pass';
import { UniformsProvider } from './uniforms-provider';
export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch;
private scaleWorldToNDC = mat2d.create();
private scaleWorldAreaInViewToNDC = 1;
private viewAreaBottomLeft = vec2.create();
private worldAreaInView = vec2.create();
private squareToAspectRatio: vec2;
private uvToWorld: mat2d;
private cursorPosition = vec2.create();
private uniformsProvider: UniformsProvider;
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private lightingFrameBuffer: DefaultFrameBuffer;
private distancePass: RenderingPass;
@ -39,8 +31,6 @@ export class WebGl2Renderer implements IRenderer {
private initializePromise: Promise<[void, void]> = null;
private softShadowsEnabled: boolean;
constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) {
this.gl = getWebGl2Context(canvas);
@ -49,7 +39,7 @@ export class WebGl2Renderer implements IRenderer {
this.distancePass = new RenderingPass(
this.gl,
[caveVertexShader, caveFragmentShader],
[distanceVertexShader, distanceFragmentShader],
[DrawableTunnel.descriptor, DrawableBlob.descriptor],
this.distanceFieldFrameBuffer
);
@ -66,11 +56,14 @@ export class WebGl2Renderer implements IRenderer {
this.lightingPass.initialize(),
]);
this.uniformsProvider = new UniformsProvider(this.gl);
this.autoscaler = new FpsAutoscaler({
distanceRenderScale: (v) =>
(this.distanceFieldFrameBuffer.renderScale = v as number),
finalRenderScale: (v) => (this.lightingFrameBuffer.renderScale = v as number),
softShadowsEnabled: (v) => (this.softShadowsEnabled = v as boolean),
softShadowsEnabled: (v) =>
(this.uniformsProvider.softShadowsEnabled = v as boolean),
});
try {
@ -102,105 +95,27 @@ export class WebGl2Renderer implements IRenderer {
}
public finishFrame() {
this.calculateMatrices();
this.distancePass.render(
this.uniforms,
this.scaleWorldAreaInViewToNDC,
this.scaleWorldToNDC
);
this.distancePass.render(this.uniformsProvider.uniforms);
this.lightingPass.render(
this.uniforms,
this.scaleWorldAreaInViewToNDC,
this.scaleWorldToNDC,
this.uniformsProvider.uniforms,
this.distanceFieldFrameBuffer.colorTexture
);
this.stopwatch?.stop();
}
private get uniforms(): any {
const cursorPosition = this.uvToWorldCoordinate(this.cursorPosition);
return {
cursorPosition,
pixelSize:
(4.5 * this.scaleWorldAreaInViewToNDC) /
this.distanceFieldFrameBuffer.renderScale,
uvToWorld: this.uvToWorld,
worldAreaInView: this.worldAreaInView,
squareToAspectRatio: this.squareToAspectRatio,
softShadowsEnabled: this.softShadowsEnabled,
};
public setViewArea(viewArea: BoundingBoxBase) {
this.uniformsProvider.setViewArea(viewArea);
}
private calculateMatrices() {
this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView);
}
private getScreenToWorldTransform(screenSize: vec2) {
const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft);
mat2d.scale(
transform,
transform,
vec2.divide(vec2.create(), this.worldAreaInView, screenSize)
);
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
return transform;
}
public uvToWorldCoordinate(screenUvPosition: vec2): vec2 {
const resolution = vec2.fromValues(this.canvas.width, this.canvas.height);
return vec2.transformMat2d(
vec2.create(),
vec2.multiply(vec2.create(), screenUvPosition, resolution),
this.getScreenToWorldTransform(resolution)
);
public setCursorPosition(position: vec2) {
this.uniformsProvider.setCursorPosition(position);
}
public get canvasSize(): vec2 {
return vec2.fromValues(this.canvas.clientWidth, this.canvas.clientHeight);
}
public setViewArea(viewArea: BoundingBoxBase) {
this.worldAreaInView = viewArea.size;
// world coordinates
this.viewAreaBottomLeft = vec2.add(
vec2.create(),
viewArea.topLeft,
vec2.fromValues(0, -viewArea.size.y)
);
const scaleLongerEdgeTo1 =
1 / Math.max(this.worldAreaInView.x, this.worldAreaInView.y);
this.squareToAspectRatio = vec2.fromValues(
this.worldAreaInView.x * scaleLongerEdgeTo1,
this.worldAreaInView.y * scaleLongerEdgeTo1
);
this.scaleWorldAreaInViewToNDC = scaleLongerEdgeTo1 * 2;
mat2d.fromScaling(
this.scaleWorldToNDC,
vec2.fromValues(this.scaleWorldAreaInViewToNDC, this.scaleWorldAreaInViewToNDC)
);
const translate = vec2.scale(vec2.create(), this.viewAreaBottomLeft, -1);
vec2.subtract(
translate,
translate,
vec2.scale(vec2.create(), this.worldAreaInView, 0.5)
);
mat2d.translate(this.scaleWorldToNDC, this.scaleWorldToNDC, translate);
}
public setCursorPosition(position: vec2): void {
this.cursorPosition = position;
}
public drawInfoText(text: string) {
if (this.overlay.innerText != text) {
this.overlay.innerText = text;

View file

@ -17,18 +17,18 @@ export const settings = {
softShadowsEnabled: false,
},
{
distanceRenderScale: 0.2,
distanceRenderScale: 0.3,
finalRenderScale: 1.0,
softShadowsEnabled: false,
},
/* {
distanceRenderScale: 0.3,
{
distanceRenderScale: 0.5,
finalRenderScale: 1.0,
softShadowsEnabled: true,
softShadowsEnabled: false,
},
{
distanceRenderScale: 1.0,
finalRenderScale: 1.25,
distanceRenderScale: 0.5,
finalRenderScale: 1.0,
softShadowsEnabled: true,
},
{
@ -45,7 +45,7 @@ export const settings = {
distanceRenderScale: 2,
finalRenderScale: 2,
softShadowsEnabled: true,
},*/
},
],
startingTargetIndex: 2,
scalingOptions: {

View file

@ -87,9 +87,9 @@ void main() {
#if BLOB_COUNT > 0
blobMinDistance(minDistance, color);
//fragmentColor = vec2(-1.0, 2.0);
//return;
#endif
fragmentColor = vec2(minDistance, color);
// minDistance / 2.0: NDC to UV scale
fragmentColor = vec2(minDistance / 2.0, color);
}

View file

@ -3,26 +3,25 @@
precision lowp float;
#define INFINITY 1000.0
#define LIGHT_DROP 0.25
#define SOFT_SHADOWS_QUALITY 2.0
#define AMBIENT_LIGHT vec3(0.75)
#define LIGHT_DROP 4.0
#define AMBIENT_LIGHT vec3(0.3)
#define CIRCLE_LIGHT_COUNT {circleLightCount}
#define POINT_LIGHT_COUNT {pointLightCount}
uniform bool softShadowsEnabled;
#define AIR_COLOR vec3(0.4)
#define AIR_COLOR vec3(0.5)
uniform sampler2D distanceTexture;
vec3[3] colors = vec3[](
vec3(0.1, 0.05, 0.15),
vec3(0.1, 0.05, 0.1),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
float getDistance(in vec2 target, out vec3 color, out float nearness) {
float getDistance(in vec2 target, out vec3 color) {
vec4 values = texture(distanceTexture, target);
color = colors[int(values[1])];
@ -37,7 +36,6 @@ float getDistance(in vec2 target) {
uniform struct {
vec2 center;
float radius;
float traceRadius;
vec3 value;
}[CIRCLE_LIGHT_COUNT] circleLights;
@ -56,14 +54,13 @@ float getDistance(in vec2 target) {
in vec2 position;
in vec2 uvCoordinates;
uniform vec2 squareToAspectRatio;
uniform vec2 squareToAspectRatioTimes2;
out vec4 fragmentColor;
void main() {
vec3 colorAtPosition;
float nearness;
float startingDistance = getDistance(uvCoordinates, colorAtPosition, nearness);
float startingDistance = getDistance(uvCoordinates, colorAtPosition);
if (startingDistance < 0.0) {
fragmentColor = vec4(colorAtPosition, 1.0);
return;
@ -77,18 +74,23 @@ void main() {
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
float lightCenterDistance = distance(circleLights[i].center, position);
float lightDistance = lightCenterDistance - circleLights[i].radius;
float traceDistance = lightCenterDistance - circleLights[i].traceRadius;
vec3 lightColorAtPosition = circleLights[i].value / pow(
lightDistance / LIGHT_DROP + 1.0, 2.0
);
vec2 targetDirection = vec2(-1.0, 0.0);
vec2 originalDirection = normalize(circleLightDirections[i]);
vec2 direction = originalDirection / squareToAspectRatioTimes2;
lightColorAtPosition *= pow(max(0.0, dot(targetDirection, originalDirection)), 10.0);
float rayLength = startingDistance;
if (softShadowsEnabled) {
float q = INFINITY;
float rayLength = startingDistance / SOFT_SHADOWS_QUALITY;
vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatio / 2.05;
for (int j = 0; j < 48 * int(ceil(SOFT_SHADOWS_QUALITY)); j++) {
if (rayLength >= traceDistance) {
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);
@ -96,18 +98,15 @@ void main() {
}
float minDistance = getDistance(uvCoordinates + direction * rayLength);
q = min(q, minDistance / rayLength);
rayLength += minDistance / SOFT_SHADOWS_QUALITY;
rayLength += minDistance / 2.0;
}
} else {
float rayLength = startingDistance;
vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatio / 2.05;
for (int j = 0; j < 24; j++) {
float currentDistance = getDistance(uvCoordinates + direction * rayLength);
rayLength += currentDistance;
for (int j = 0; j < 32; j++) {
rayLength += getDistance(uvCoordinates + direction * rayLength);
}
if (rayLength >= traceDistance) {
if (rayLength >= lightCenterDistance) {
lighting += lightColorAtPosition * step(0.0, startingDistance);
}
}

View file

@ -17,7 +17,6 @@ uniform vec2 squareToAspectRatio;
uniform struct {
vec2 center;
float radius;
float traceRadius;
vec3 value;
}[CIRCLE_LIGHT_COUNT] circleLights;

View file

@ -21,13 +21,7 @@ export class Character extends GameObject {
constructor(private game: IGame) {
super();
this.light = new CircleLight(
vec2.create(),
40,
this.shape.boundingCircleRadius * 2,
vec3.fromValues(0.67, 0.0, 0.33),
2
);
this.light = new CircleLight(vec2.create(), 30, vec3.fromValues(0.67, 0.0, 0.33), 2);
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this));
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
@ -115,7 +109,7 @@ export class Character extends GameObject {
private setPosition(value: vec2) {
this.shape.position = value;
this.light.center = value;
this.light.center = vec2.add(vec2.create(), value, vec2.fromValues(150, 0));
}
public stepHandler(c: StepCommand) {

View file

@ -10,7 +10,7 @@ export class Lamp extends GameObject {
constructor(center: vec2, radius: number, color: vec3, lightness: number) {
super();
this.light = new CircleLight(center, radius, radius, color, lightness);
this.light = new CircleLight(center, radius, color, lightness);
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));

View file

@ -11,7 +11,7 @@ body {
#overlay {
font-family: Helvetica, Arial, sans-serif;
font-size: 1em;
font-size: 0.75em;
margin: 0.75em 1em;
user-select: none;
pointer-events: none;
@ -25,7 +25,7 @@ body {
white-space: pre;
@media (max-width: 800px) {
font-size: 0.75em;
font-size: 0.65em;
}
}