Render in NDC

This commit is contained in:
schmelczerandras 2020-08-18 22:36:30 +02:00
parent 40a660b7cb
commit 05682dd2d5
16 changed files with 106 additions and 82 deletions

View file

@ -9,6 +9,7 @@ export class DrawableBlob extends Blob implements IDrawable {
uniformName: 'blobs',
countMacroName: 'blobCount',
shaderCombinationSteps: settings.shaderCombinations.blobSteps,
empty: new DrawableBlob(vec2.fromValues(0, 0)),
};
public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void {

View file

@ -9,6 +9,7 @@ export class DrawableTunnel extends TunnelShape implements IDrawable {
uniformName: 'lines',
countMacroName: 'lineCount',
shaderCombinationSteps: settings.shaderCombinations.lineSteps,
empty: new DrawableTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0),
};
public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void {
@ -19,7 +20,7 @@ export class DrawableTunnel extends TunnelShape implements IDrawable {
uniforms[uniformName].push({
from: vec2.transformMat2d(vec2.create(), this.from, transform),
toFromDelta: vec2.transformMat2d(vec2.create(), this.toFromDelta, transform),
toFromDelta: vec2.scale(vec2.create(), this.toFromDelta, scale),
fromRadius: this.fromRadius * scale,
toRadius: this.toRadius * scale,
});

View file

@ -1,5 +1,8 @@
import { IDrawable } from './i-drawable';
export interface IDrawableDescriptor {
uniformName: string;
countMacroName: string;
shaderCombinationSteps: Array<number>;
readonly empty: IDrawable;
}

View file

@ -8,6 +8,7 @@ export class CircleLight implements ILight {
uniformName: 'circleLights',
countMacroName: 'circleLightCount',
shaderCombinationSteps: settings.shaderCombinations.circleLightSteps,
empty: new CircleLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0),
};
constructor(
@ -17,7 +18,7 @@ export class CircleLight implements ILight {
public lightness: number
) {}
public distance(target: vec2): number {
public distance(_: vec2): number {
return 0;
}

View file

@ -8,6 +8,7 @@ export class PointLight implements ILight {
uniformName: 'pointLights',
countMacroName: 'pointLightCount',
shaderCombinationSteps: settings.shaderCombinations.pointLightSteps,
empty: new PointLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0),
};
public constructor(

View file

@ -1,4 +1,4 @@
import { vec2 } from 'gl-matrix';
import { mat2d, vec2 } from 'gl-matrix';
import { getCombinations } from '../../../helper/get-combinations';
import { last } from '../../../helper/last';
import { IDrawableDescriptor } from '../../drawables/i-drawable-descriptor';
@ -12,33 +12,40 @@ export class UniformArrayAutoScalingProgram implements IProgram {
}> = [];
private current: FragmentShaderOnlyProgram;
private drawingRectangleTopLeft = vec2.fromValues(0, 0);
private drawingRectangleSize = vec2.fromValues(1, 1);
constructor(
private gl: WebGL2RenderingContext,
shaderSources: [string, string],
private options: Array<IDrawableDescriptor>
private descriptors: Array<IDrawableDescriptor>
) {
const names = options.map((o) => o.countMacroName);
const names = descriptors.map((o) => o.countMacroName);
for (const combination of getCombinations(
options.map((o) => o.shaderCombinationSteps)
descriptors.map((o) => o.shaderCombinationSteps)
)) {
this.createProgram(names, combination, shaderSources);
}
}
public bindAndSetUniforms(uniforms: { [name: string]: any }): void {
const values = this.options.map((o) =>
uniforms[o.uniformName] ? uniforms[o.uniformName].length : 0
const values = this.descriptors.map((d) =>
uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0
);
const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i]));
this.current = closest ? closest.program : last(this.programs).program;
if (closest) {
this.descriptors.map((d, i) => {
const difference = closest.values[i] - values[i];
for (let i = 0; i < difference; i++) {
d.empty.serializeToUniforms(uniforms, 0, mat2d.create());
}
});
}
this.current.setDrawingRectangle(
this.drawingRectangleTopLeft,
this.drawingRectangleSize

View file

@ -28,7 +28,12 @@ export class RenderingPass {
this.drawables.push(drawable);
}
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
public render(
commonUniforms: any,
scale: number,
transform: mat2d,
inputTexture?: WebGLTexture
) {
this.frame.bindAndClear(inputTexture);
const q = 1 / settings.tileMultiplier;
const tileUvSize = vec2.fromValues(q, q);
@ -51,13 +56,10 @@ export class RenderingPass {
let sumLineCount = 0;
const scale = 1;
const transform = mat2d.create();
for (let x = 0; x < 1; x += q) {
for (let y = 0; y < 1; y += q) {
const uniforms = { ...commonUniforms };
uniforms.maxMinDistance = 2 * worldR;
uniforms.maxMinDistance = 2 * worldR * scale;
const uvBottomLeft = vec2.fromValues(x, y);
this.program.setDrawingRectangle(uvBottomLeft, tileUvSize);
@ -69,7 +71,7 @@ export class RenderingPass {
);
const primitivesNearTile = this.drawables.filter(
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
(d) => d.distance(tileCenterWorldCoordinates) < 2 * worldR
);
sumLineCount += primitivesNearTile.length;

View file

@ -6,7 +6,6 @@ import { IDrawable } from '../drawables/i-drawable';
import { CircleLight } from '../drawables/lights/circle-light';
import { ILight } from '../drawables/lights/i-light';
import { PointLight } from '../drawables/lights/point-light';
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
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';
@ -21,32 +20,28 @@ import { RenderingPass } from './rendering-pass';
export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch;
private upscaleTransform = mat2d.create();
private linearDownscale = 1;
private viewBoxBottomLeft = vec2.create();
private viewBoxSize = vec2.create();
private viewAreaScale: vec2;
private cursorPosition = vec2.create();
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private lightingFrameBuffer: DefaultFrameBuffer;
private distancePass: RenderingPass;
private lightingPass: RenderingPass;
private autoscaler: FpsAutoscaler;
private matrices: {
distanceScreenToWorld?: mat2d;
worldToDistanceUV?: mat2d;
cursorPosition?: mat2d;
ndcToUv?: mat2d;
uvToWorld?: mat2d;
} = { ndcToUv: mat2d.fromValues(0.5, 0, 0, 0.5, 0.5, 0.5) };
} = {};
constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) {
this.gl = getWebGl2Context(canvas);
@ -92,6 +87,7 @@ export class WebGl2Renderer implements IRenderer {
this.autoscaler.autoscale(deltaTime);
this.stopwatch?.start();
this.distanceFieldFrameBuffer.setSize();
this.lightingFrameBuffer.setSize();
}
@ -99,16 +95,24 @@ export class WebGl2Renderer implements IRenderer {
public finishFrame() {
this.calculateMatrices();
this.distancePass.render(this.uniforms);
this.lightingPass.render(this.uniforms, this.distanceFieldFrameBuffer.colorTexture);
this.distancePass.render(this.uniforms, this.linearDownscale, this.upscaleTransform);
this.lightingPass.render(
this.uniforms,
this.linearDownscale,
this.upscaleTransform,
this.distanceFieldFrameBuffer.colorTexture
);
this.stopwatch?.stop();
}
private get uniforms(): any {
const cursorPosition = this.screenUvToWorldCoordinate(this.cursorPosition);
return { ...this.matrices, cursorPosition, viewBoxSize: this.viewBoxSize };
return {
...this.matrices,
cursorPosition,
viewAreaScale: this.viewAreaScale,
};
}
private calculateMatrices() {
@ -157,12 +161,31 @@ export class WebGl2Renderer implements IRenderer {
}
public setViewArea(viewArea: BoundingBoxBase) {
const targetRange = 2 ** 7;
this.viewBoxSize = viewArea.size;
this.viewBoxBottomLeft = vec2.add(
vec2.create(),
viewArea.topLeft,
vec2.fromValues(0, -viewArea.size.y)
);
this.linearDownscale = targetRange / Math.max(this.viewBoxSize.x, this.viewBoxSize.y);
this.viewAreaScale = vec2.fromValues(
(this.viewBoxSize.x * this.linearDownscale) / 2,
(this.viewBoxSize.y * this.linearDownscale) / 2
);
mat2d.fromScaling(
this.upscaleTransform,
vec2.fromValues(this.linearDownscale, this.linearDownscale)
);
const translate = vec2.scale(vec2.create(), this.viewBoxBottomLeft, -1);
vec2.subtract(translate, translate, vec2.scale(vec2.create(), this.viewBoxSize, 0.5));
mat2d.translate(this.upscaleTransform, this.upscaleTransform, translate);
}
public setCursorPosition(position: vec2): void {

View file

@ -22,10 +22,9 @@ export const settings = {
},
tileMultiplier: 8,
shaderMacros: {
edgeSmoothing: 10,
headRadius: 5,
torsoRadius: 8,
footRadius: 2,
headRadius: 0.05,
torsoRadius: 0.08,
footRadius: 0.02,
},
shaderCombinations: {
lineSteps: [0, 1, 2, 4, 8, 16, 128],

View file

@ -69,18 +69,18 @@ uniform float maxMinDistance;
}
#endif
in vec2 worldCoordinates;
in vec2 position;
out vec2 fragmentColor;
void main() {
float minDistance = -maxMinDistance;
#if LINE_COUNT > 0
lineMinDistance(worldCoordinates, minDistance);
lineMinDistance(position, minDistance);
#endif
#if BLOB_COUNT > 0
blobMinDistance(worldCoordinates, minDistance);
//blobMinDistance(position, minDistance);
#endif
fragmentColor = vec2(minDistance, 0.0);

View file

@ -3,15 +3,13 @@
precision mediump float;
#define INFINITY 1000.0
#define LIGHT_DROP 500.0
#define LIGHT_DROP 50.0
#define AMBIENT_LIGHT vec3(0.15)
#define CIRCLE_LIGHT_COUNT {circleLightCount}
#define POINT_LIGHT_COUNT {pointLightCount}
#define EDGE_SMOOTHING {edgeSmoothing}
uniform sampler2D distanceTexture;
uniform vec2 viewBoxSize;
vec3[4] colors = vec3[4](
vec3(0.5),
@ -50,8 +48,10 @@ float getDistance(in vec2 target) {
in vec2[POINT_LIGHT_COUNT] pointLightDirections;
#endif
in vec2 worldCoordinates;
in vec2 position;
in vec2 uvCoordinates;
uniform vec2 viewAreaScale;
out vec4 fragmentColor;
void main() {
@ -63,13 +63,13 @@ void main() {
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
float lightCenterDistance = distance(
circleLights[i].center,
worldCoordinates
position
);
float lightDistance = lightCenterDistance - circleLights[i].radius;
/*if (lightDistance < 0.0) {
if (lightDistance < 0.0) {
lighting = vec3(1.0, 1.0, 0.0);
}*/
}
vec3 lightColorAtPosition = circleLights[i].value / pow(
lightDistance / LIGHT_DROP + 1.0, 2.0
@ -77,8 +77,7 @@ void main() {
float q = INFINITY;
float rayLength = startingDistance;
vec2 direction = normalize(circleLightDirections[i]) / viewBoxSize;
vec2 direction = normalize(circleLightDirections[i]) / viewAreaScale / 2.0;
for (int j = 0; j < 48; j++) {
if (rayLength >= lightDistance) {
lighting += lightColorAtPosition * clamp(
@ -124,6 +123,7 @@ void main() {
}*/
#endif
fragmentColor = vec4(vec3(startingDistance / 100.0), 1.0);
fragmentColor = vec4(
colorAtPosition * lighting * step(0.0, startingDistance),
1.0

View file

@ -3,14 +3,13 @@
precision mediump float;
uniform mat3 modelTransform;
uniform mat3 uvToWorld;
uniform mat3 ndcToUv;
uniform vec2 viewAreaScale;
in vec4 vertexPosition;
out vec2 worldCoordinates;
out vec2 position;
void main() {
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform;
worldCoordinates = (vertexPosition2D * ndcToUv * uvToWorld).xy;
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
position = vertexPosition2D.xy * viewAreaScale;
}

View file

@ -6,15 +6,13 @@ precision mediump float;
#define POINT_LIGHT_COUNT {pointLightCount}
uniform mat3 modelTransform;
uniform mat3 cameraTransform;
uniform mat3 ndcToUv;
uniform mat3 uvToWorld;
in vec4 vertexPosition;
out vec2 worldCoordinates;
out vec2 position;
out vec2 uvCoordinates;
uniform vec2 viewAreaScale;
#if CIRCLE_LIGHT_COUNT > 0
uniform struct CircleLight {
vec2 center;
@ -37,21 +35,24 @@ out vec2 uvCoordinates;
void main() {
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform;
vec3 uvCoordinates1 = vertexPosition2D * ndcToUv;
worldCoordinates = (uvCoordinates1 * uvToWorld).xy;
uvCoordinates = (uvCoordinates1).xy;
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
position = vertexPosition2D.xy * viewAreaScale;
uvCoordinates = (vertexPosition2D * mat3(
0.5, 0.0, 0.5,
0.0, 0.5, 0.5,
0.0, 0.0, 1.0
)).xy;
#if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
circleLightDirections[i] = circleLights[i].center - worldCoordinates;
circleLightDirections[i] = circleLights[i].center - position;
}
#endif
#if POINT_LIGHT_COUNT > 0
for (int i = 0; i < POINT_LIGHT_COUNT; i++) {
pointLightDirections[i] = pointLights[i].center - worldCoordinates;
pointLightDirections[i] = pointLights[i].center - position;
}
#endif
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
}

View file

@ -3,8 +3,8 @@ import { CommandGenerator } from '../commands/command-generator';
import { clamp01 } from '../helper/clamp';
import { CursorMoveCommand } from './commands/cursor-move-command';
import { PrimaryActionCommand } from './commands/primary-action';
import { SwipeCommand } from './commands/swipe';
import { SecondaryActionCommand } from './commands/secondary-action';
import { SwipeCommand } from './commands/swipe';
import { ZoomCommand } from './commands/zoom';
export class MouseListener extends CommandGenerator {
@ -39,11 +39,11 @@ export class MouseListener extends CommandGenerator {
this.sendCommand(new CursorMoveCommand(position));
});
target.addEventListener('mouseup', (event: MouseEvent) => {
target.addEventListener('mouseup', (_: MouseEvent) => {
this.isMouseDown = false;
});
target.addEventListener('mouseleave', (event: MouseEvent) => {
target.addEventListener('mouseleave', (_: MouseEvent) => {
this.isMouseDown = false;
});

View file

@ -15,11 +15,8 @@ import { Lamp } from './lamp';
export class Character extends GameObject {
private keysDown: Set<string> = new Set();
private light = new Lamp(vec2.create(), 40, vec3.fromValues(0.67, 0.0, 0.33), 2);
private shape = new DrawableBlob(vec2.create());
private static speed = 1.5;
constructor(private game: IGame) {

View file

@ -1,33 +1,22 @@
import { vec2 } from 'gl-matrix';
import { IShape } from '../i-shape';
import { BoundingBox } from '../bounding-box';
import { Circle } from './circle';
import { settings } from '../../drawing/settings';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
import { Circle } from './circle';
export class Blob implements IShape {
private static readonly boundingCircleRadius = 19;
private static readonly headOffset = vec2.fromValues(0, 15);
private static readonly torsoOffset = vec2.fromValues(0, 0);
private static readonly leftFootOffset = vec2.fromValues(-5, -10);
private static readonly rightFootOffset = vec2.fromValues(5, -10);
public readonly isInverted = false;
protected boundingCircle = new Circle(vec2.create(), Blob.boundingCircleRadius);
protected head = new Circle(vec2.create(), settings.shaderMacros.headRadius);
protected torso = new Circle(vec2.create(), settings.shaderMacros.torsoRadius);
protected leftFoot = new Circle(vec2.create(), settings.shaderMacros.footRadius);
protected rightFoot = new Circle(vec2.create(), settings.shaderMacros.footRadius);
public constructor(center: vec2, public readonly gameObject: GameObject = null) {
this.position = center;
}