From 7bfad8711bbb702160be45b53183751d5ab8212f Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Sun, 4 Oct 2020 10:48:26 +0200 Subject: [PATCH] Add textures --- package.json | 2 +- src/drawables/shapes/inverted-tunnel.ts | 42 +++++++++--- .../frame-buffer/frame-buffer.ts | 8 +-- .../graphics-library/palette-texture.ts | 65 ------------------- .../program/fragment-shader-only-program.ts | 8 +-- .../graphics-library/program/program.ts | 4 +- .../texture/palette-texture.ts | 29 +++++++++ .../texture/texture-options.ts | 22 +++++++ .../graphics-library/texture/texture.ts | 62 ++++++++++++++++++ .../render-pass/distance-render-pass.ts | 3 +- .../render-pass/lights-render-pass.ts | 10 ++- .../rendering/render-pass/render-pass.ts | 13 ++-- .../renderer/context-aware-renderer.ts | 7 ++ .../rendering/renderer/noise-renderer.ts | 51 +++++++++++++++ .../renderer/renderer-implementation.ts | 43 +++++++++--- src/graphics/rendering/renderer/renderer.ts | 12 ++++ .../settings/default-runtime-settings.ts | 1 + .../rendering/settings/runtime-settings.ts | 13 ++++ .../rendering/shaders/distance-fs.glsl | 2 + .../rendering/shaders/random-fs-100.glsl | 32 +++++++++ src/graphics/rendering/shaders/random-fs.glsl | 34 ++++++++++ .../rendering/shaders/random-vs-100.glsl | 18 +++++ src/graphics/rendering/shaders/random-vs.glsl | 18 +++++ src/graphics/rendering/uniforms-provider.ts | 10 ++- src/main.ts | 2 + 25 files changed, 407 insertions(+), 104 deletions(-) delete mode 100644 src/graphics/graphics-library/palette-texture.ts create mode 100644 src/graphics/graphics-library/texture/palette-texture.ts create mode 100644 src/graphics/graphics-library/texture/texture-options.ts create mode 100644 src/graphics/graphics-library/texture/texture.ts create mode 100644 src/graphics/rendering/renderer/noise-renderer.ts create mode 100644 src/graphics/rendering/shaders/random-fs-100.glsl create mode 100644 src/graphics/rendering/shaders/random-fs.glsl create mode 100644 src/graphics/rendering/shaders/random-vs-100.glsl create mode 100644 src/graphics/rendering/shaders/random-vs.glsl diff --git a/package.json b/package.json index 6eb24cc..b7ac76a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sdf-2d", - "version": "0.3.6", + "version": "0.4.0", "description": "Graphics framework for efficiently rendering 2D signed distance fields.", "keywords": [ "webgl", diff --git a/src/drawables/shapes/inverted-tunnel.ts b/src/drawables/shapes/inverted-tunnel.ts index f09d0ae..18c7cea 100644 --- a/src/drawables/shapes/inverted-tunnel.ts +++ b/src/drawables/shapes/inverted-tunnel.ts @@ -6,6 +6,8 @@ import { DrawableDescriptor } from '../drawable-descriptor'; /** * @category Drawable + * + * Providing a noise texture is required for this drawable. */ export class InvertedTunnel extends Drawable { public static descriptor: DrawableDescriptor = { @@ -16,6 +18,24 @@ export class InvertedTunnel extends Drawable { uniform float fromRadii[INVERTED_TUNNEL_COUNT]; uniform float toRadii[INVERTED_TUNNEL_COUNT]; + uniform sampler2D noiseTexture; + + #ifdef WEBGL2_IS_AVAILABLE + float myTerrain(float h) { + return texture( + noiseTexture, + vec2(h, 0.5) + )[0] - 0.5; + } + #else + float myTerrain(float h) { + return texture2D( + noiseTexture, + vec2(h, 0.5) + )[0] - 0.5; + } + #endif + float invertedTunnelMinDistance(vec2 target, out float colorIndex) { colorIndex = 3.0; @@ -24,17 +44,16 @@ export class InvertedTunnel extends Drawable { vec2 targetFromDelta = target - froms[i]; - float h = clamp( - dot(targetFromDelta, toFromDeltas[i]) - / dot(toFromDeltas[i], toFromDeltas[i]), - 0.0, 1.0 - ); + float h = dot(targetFromDelta, toFromDeltas[i]) + / dot(toFromDeltas[i], toFromDeltas[i]); + + float clampedH = clamp(h, 0.0, 1.0); float currentDistance = -mix( - fromRadii[i], toRadii[i], h + fromRadii[i], toRadii[i], clampedH ) + distance( - targetFromDelta, toFromDeltas[i] * h - ); + targetFromDelta, toFromDeltas[i] * clampedH + ) - myTerrain(h) / 12.0; minDistance = min(minDistance, currentDistance); } @@ -53,7 +72,12 @@ export class InvertedTunnel extends Drawable { }, uniformCountMacroName: 'INVERTED_TUNNEL_COUNT', shaderCombinationSteps: [0, 1, 4, 16, 32], - empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0), + empty: new InvertedTunnel( + vec2.fromValues(10000, 10000), + vec2.fromValues(10000, 10000), + 0, + 0 + ), }; constructor( diff --git a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts index b567236..e00053c 100644 --- a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts @@ -1,4 +1,5 @@ import { vec2 } from 'gl-matrix'; +import { Texture } from '../texture/texture'; import { UniversalRenderingContext } from '../universal-rendering-context'; /** @internal */ @@ -13,13 +14,10 @@ export abstract class FrameBuffer { constructor(protected readonly gl: UniversalRenderingContext) {} - public bindAndClear(inputTextures: Array) { + public bindAndClear(inputTextures: Array) { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); - inputTextures.forEach((t, i) => { - this.gl.activeTexture(this.gl.TEXTURE0 + i); - this.gl.bindTexture(this.gl.TEXTURE_2D, t); - }); + inputTextures.forEach((t) => t.bind()); this.gl.viewport(0, 0, this.size.x, this.size.y); } diff --git a/src/graphics/graphics-library/palette-texture.ts b/src/graphics/graphics-library/palette-texture.ts deleted file mode 100644 index f4e1e66..0000000 --- a/src/graphics/graphics-library/palette-texture.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { vec3, vec4 } from 'gl-matrix'; -import { UniversalRenderingContext } from './universal-rendering-context'; - -/** @internal */ -export class PaletteTexture { - private texture: WebGLTexture; - - constructor( - private readonly gl: UniversalRenderingContext, - private readonly paletteSize: number - ) { - this.texture = gl.createTexture()!; - this.bind(); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - } - - public get colorTexture(): WebGLTexture { - return this.texture; - } - - private bind() { - this.gl.activeTexture(this.gl.TEXTURE1); - this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); - } - - public setPalette(colors: Array) { - const canvas = document.createElement('canvas'); - canvas.width = this.paletteSize; - canvas.height = 1; - - const ctx = canvas.getContext('2d')!; - const imageData = ctx.createImageData(this.paletteSize, 1); - - colors.forEach((c, i) => { - imageData.data[4 * i + 0] = c[0] * 255; - imageData.data[4 * i + 1] = c[1] * 255; - imageData.data[4 * i + 2] = c[2] * 255; - imageData.data[4 * i + 3] = c.length == 4 ? c[3] * 255 : 255; - }); - ctx.putImageData(imageData, 0, 0); - - this.setImage(canvas); - } - - public setImage(image: TexImageSource) { - this.bind(); - - this.gl.texImage2D( - this.gl.TEXTURE_2D, - 0, - this.gl.RGBA, - this.gl.RGBA, - this.gl.UNSIGNED_BYTE, - image - ); - } - - public destroy() { - this.gl.deleteTexture(this.texture); - } -} diff --git a/src/graphics/graphics-library/program/fragment-shader-only-program.ts b/src/graphics/graphics-library/program/fragment-shader-only-program.ts index 52e20a7..dd3705d 100644 --- a/src/graphics/graphics-library/program/fragment-shader-only-program.ts +++ b/src/graphics/graphics-library/program/fragment-shader-only-program.ts @@ -16,10 +16,10 @@ export class FragmentShaderOnlyProgram extends Program { public async initialize( sources: [string, string], - substitutions: { [name: string]: string }, - compiler: ParallelCompiler + compiler: ParallelCompiler, + substitutions: { [name: string]: string } = {} ): Promise { - await super.initialize(sources, substitutions, compiler); + await super.initialize(sources, compiler, substitutions); this.prepareScreenQuad('vertexPosition'); } @@ -32,7 +32,7 @@ export class FragmentShaderOnlyProgram extends Program { } } - public draw(uniforms: { [name: string]: any }) { + public draw(uniforms: { [name: string]: any } = {}) { super.draw(uniforms); this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); } diff --git a/src/graphics/graphics-library/program/program.ts b/src/graphics/graphics-library/program/program.ts index 0e20bc7..f2aa09a 100644 --- a/src/graphics/graphics-library/program/program.ts +++ b/src/graphics/graphics-library/program/program.ts @@ -20,8 +20,8 @@ export default abstract class Program implements IProgram { public async initialize( [vertexShaderSource, fragmentShaderSource]: [string, string], - substitutions: { [name: string]: string }, - compiler: ParallelCompiler + compiler: ParallelCompiler, + substitutions: { [name: string]: string } = {} ): Promise { substitutions = { ...substitutions }; diff --git a/src/graphics/graphics-library/texture/palette-texture.ts b/src/graphics/graphics-library/texture/palette-texture.ts new file mode 100644 index 0000000..4515730 --- /dev/null +++ b/src/graphics/graphics-library/texture/palette-texture.ts @@ -0,0 +1,29 @@ +import { vec3, vec4 } from 'gl-matrix'; +import { UniversalRenderingContext } from '../universal-rendering-context'; +import { Texture } from './texture'; + +/** @internal */ +export class PaletteTexture extends Texture { + constructor(gl: UniversalRenderingContext, private readonly paletteSize: number) { + super(gl, 1); + } + + public setPalette(colors: Array) { + const canvas = document.createElement('canvas'); + canvas.width = this.paletteSize; + canvas.height = 1; + + const ctx = canvas.getContext('2d')!; + const imageData = ctx.createImageData(this.paletteSize, 1); + + colors.forEach((c, i) => { + imageData.data[4 * i + 0] = c[0] * 255; + imageData.data[4 * i + 1] = c[1] * 255; + imageData.data[4 * i + 2] = c[2] * 255; + imageData.data[4 * i + 3] = c.length == 4 ? c[3] * 255 : 255; + }); + ctx.putImageData(imageData, 0, 0); + + this.setImage(canvas); + } +} diff --git a/src/graphics/graphics-library/texture/texture-options.ts b/src/graphics/graphics-library/texture/texture-options.ts new file mode 100644 index 0000000..46f8d1b --- /dev/null +++ b/src/graphics/graphics-library/texture/texture-options.ts @@ -0,0 +1,22 @@ +export enum WrapOptions { + CLAMP_TO_EDGE = 'CLAMP_TO_EDGE', + REPEAT = 'REPEAT', + MIRRORED_REPEAT = 'MIRRORED_REPEAT', +} + +export enum FilteringOptions { + LINEAR = 'LINEAR', + NEAREST = 'NEAREST', +} + +export interface TextureOptions { + wrapS: WrapOptions; + wrapT: WrapOptions; + minFilter: FilteringOptions; + maxFilter: FilteringOptions; +} + +export type TextureWithOptions = { + source: TexImageSource; + overrides: Partial; +}; diff --git a/src/graphics/graphics-library/texture/texture.ts b/src/graphics/graphics-library/texture/texture.ts new file mode 100644 index 0000000..ff9114e --- /dev/null +++ b/src/graphics/graphics-library/texture/texture.ts @@ -0,0 +1,62 @@ +import { UniversalRenderingContext } from '../universal-rendering-context'; +import { FilteringOptions, TextureOptions, WrapOptions } from './texture-options'; + +/** + * @internal + */ +export class Texture { + protected texture: WebGLTexture; + + constructor( + protected readonly gl: UniversalRenderingContext, + private textureUnitId: number, + textureOptionOverrides: Partial = {} + ) { + const defaultTextureOptions: TextureOptions = { + wrapS: WrapOptions.CLAMP_TO_EDGE, + wrapT: WrapOptions.CLAMP_TO_EDGE, + minFilter: FilteringOptions.NEAREST, + maxFilter: FilteringOptions.NEAREST, + }; + + const textureOptions = { + ...defaultTextureOptions, + ...textureOptionOverrides, + }; + + this.texture = gl.createTexture()!; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl[textureOptions.wrapS]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl[textureOptions.wrapT]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl[textureOptions.minFilter]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl[textureOptions.maxFilter]); + } + + public get colorTexture(): WebGLTexture { + return this.texture; + } + + public bind() { + this.gl.activeTexture(this.gl.TEXTURE0 + this.textureUnitId); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + } + + public setImage(image: TexImageSource) { + this.bind(); + + this.gl.texImage2D( + this.gl.TEXTURE_2D, + 0, + this.gl.RGBA, + this.gl.RGBA, + this.gl.UNSIGNED_BYTE, + image + ); + } + + public destroy() { + this.gl.deleteTexture(this.texture); + } +} diff --git a/src/graphics/rendering/render-pass/distance-render-pass.ts b/src/graphics/rendering/render-pass/distance-render-pass.ts index 75b85d1..d06f5b4 100644 --- a/src/graphics/rendering/render-pass/distance-render-pass.ts +++ b/src/graphics/rendering/render-pass/distance-render-pass.ts @@ -1,5 +1,6 @@ import { vec2 } from 'gl-matrix'; import { Drawable } from '../../../drawables/drawable'; +import { Texture } from '../../graphics-library/texture/texture'; import { Insights } from '../insights'; import { RenderPass } from './render-pass'; @@ -14,7 +15,7 @@ export class DistanceRenderPass extends RenderPass { this.drawables.push(drawable); } - public render(commonUniforms: any, ...inputTextures: Array) { + public render(commonUniforms: any, ...inputTextures: Array) { this.frame.bindAndClear(inputTextures); const stepsInUV = 1 / this.tileMultiplier; diff --git a/src/graphics/rendering/render-pass/lights-render-pass.ts b/src/graphics/rendering/render-pass/lights-render-pass.ts index d958bf2..fd7ac9e 100644 --- a/src/graphics/rendering/render-pass/lights-render-pass.ts +++ b/src/graphics/rendering/render-pass/lights-render-pass.ts @@ -1,6 +1,7 @@ import { vec2 } from 'gl-matrix'; import { LightDrawable } from '../../../drawables/lights/light-drawable'; import { clamp01 } from '../../../helper/clamp'; +import { Texture } from '../../graphics-library/texture/texture'; import { Insights } from '../insights'; import { RenderPass } from './render-pass'; @@ -13,7 +14,14 @@ export class LightsRenderPass extends RenderPass { this.drawables.push(drawable); } - public render(commonUniforms: any, ...inputTextures: Array) { + public render( + commonUniforms: any, + distanceTexture: WebGLTexture, + inputTextures: Array + ) { + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, distanceTexture); + this.frame.bindAndClear(inputTextures); const tileCenterWorldCoordinates = vec2.transformMat2d( diff --git a/src/graphics/rendering/render-pass/render-pass.ts b/src/graphics/rendering/render-pass/render-pass.ts index c2111ed..c798c72 100644 --- a/src/graphics/rendering/render-pass/render-pass.ts +++ b/src/graphics/rendering/render-pass/render-pass.ts @@ -8,21 +8,22 @@ import { UniversalRenderingContext } from '../../graphics-library/universal-rend export abstract class RenderPass { protected program: UniformArrayAutoScalingProgram; - constructor(gl: UniversalRenderingContext, protected frame: FrameBuffer) { + constructor( + protected readonly gl: UniversalRenderingContext, + protected frame: FrameBuffer + ) { this.program = new UniformArrayAutoScalingProgram(gl); } public async initialize( shaderSources: [string, string], descriptors: Array, - substitutions: { [name: string]: any } = {}, - compiler: ParallelCompiler + compiler: ParallelCompiler, + substitutions: { [name: string]: any } = {} ): Promise { - await this.program.initialize(shaderSources, descriptors, substitutions, compiler); + await this.program.initialize(shaderSources, descriptors, compiler, substitutions); } - public abstract render(commonUniforms: any, inputTexture?: WebGLTexture): void; - public destroy(): void { this.frame.destroy(); this.program.destroy(); diff --git a/src/graphics/rendering/renderer/context-aware-renderer.ts b/src/graphics/rendering/renderer/context-aware-renderer.ts index 3d1055c..80d17d5 100644 --- a/src/graphics/rendering/renderer/context-aware-renderer.ts +++ b/src/graphics/rendering/renderer/context-aware-renderer.ts @@ -136,6 +136,13 @@ export class ContextAwareRenderer implements Renderer { return this.handle(() => this.renderer.autoscaleQuality(deltaTime), undefined); } + public displayToWorldCoordinates(displayCoordinates: vec2): vec2 { + return this.handle( + () => this.renderer.displayToWorldCoordinates(displayCoordinates), + vec2.create() + ); + } + public renderDrawables(): void { return this.handle(() => this.renderer.renderDrawables(), undefined); } diff --git a/src/graphics/rendering/renderer/noise-renderer.ts b/src/graphics/rendering/renderer/noise-renderer.ts new file mode 100644 index 0000000..0449939 --- /dev/null +++ b/src/graphics/rendering/renderer/noise-renderer.ts @@ -0,0 +1,51 @@ +import { vec2 } from 'gl-matrix'; +import { ParallelCompiler } from '../../graphics-library/parallel-compiler'; +import { FragmentShaderOnlyProgram } from '../../graphics-library/program/fragment-shader-only-program'; +import { getUniversalRenderingContext } from '../../graphics-library/universal-rendering-context'; +import randomFragment100 from '../shaders/random-fs-100.glsl'; +import randomFragment from '../shaders/random-fs.glsl'; +import randomVertex100 from '../shaders/random-vs-100.glsl'; +import randomVertex from '../shaders/random-vs.glsl'; + +/** + * Create a renderer, draw a (1D) noise texture with it, + * then destroy the used resources, while returning the generated texture + * in the form of a canvas. + * + * @param textureSize The resolution of the end result. + * @param scale A starting value can be 60 + * @param amplitude A starting value can be 0.125 + * @param ignoreWebGL2 Ignore WebGL2, even when its available. + */ +export const renderNoise = async ( + textureSize: vec2, + scale: number, + amplitude: number, + ignoreWebGL2 = false +): Promise => { + const canvas = document.createElement('canvas'); + canvas.width = textureSize.x; + canvas.height = textureSize.y; + + const gl = getUniversalRenderingContext(canvas, ignoreWebGL2); + const program = new FragmentShaderOnlyProgram(gl); + const compiler = new ParallelCompiler(gl); + + const pogramPromise = program.initialize( + gl.isWebGL2 ? [randomVertex, randomFragment] : [randomVertex100, randomFragment100], + compiler + ); + + await compiler.compilePrograms(); + await pogramPromise; + + program.bind(); + program.draw({ + scale, + amplitude, + }); + + program.destroy(); + + return canvas; +}; diff --git a/src/graphics/rendering/renderer/renderer-implementation.ts b/src/graphics/rendering/renderer/renderer-implementation.ts index f361bed..62afa50 100644 --- a/src/graphics/rendering/renderer/renderer-implementation.ts +++ b/src/graphics/rendering/renderer/renderer-implementation.ts @@ -6,8 +6,10 @@ import { msToString } from '../../../helper/ms-to-string'; import { DefaultFrameBuffer } from '../../graphics-library/frame-buffer/default-frame-buffer'; import { IntermediateFrameBuffer } from '../../graphics-library/frame-buffer/intermediate-frame-buffer'; import { WebGlStopwatch } from '../../graphics-library/helper/stopwatch'; -import { PaletteTexture } from '../../graphics-library/palette-texture'; import { ParallelCompiler } from '../../graphics-library/parallel-compiler'; +import { PaletteTexture } from '../../graphics-library/texture/palette-texture'; +import { Texture } from '../../graphics-library/texture/texture'; +import { TextureWithOptions } from '../../graphics-library/texture/texture-options'; import { getUniversalRenderingContext, UniversalRenderingContext, @@ -43,6 +45,8 @@ export class RendererImplementation implements Renderer { private palette!: PaletteTexture; private autoscaler: FpsAutoscaler; + private textures: Array = []; + private applyRuntimeSettings: { [key in keyof RuntimeSettings]: (value: any) => void; } = { @@ -53,6 +57,26 @@ export class RendererImplementation implements Renderer { tileMultiplier: (v) => (this.distancePass.tileMultiplier = v), isWorldInverted: (v) => (this.distancePass.isWorldInverted = v), backgroundColor: (v) => (this.uniformsProvider.backgroundColor = v), + textures: (v: { [textureName: string]: TexImageSource | TextureWithOptions }) => { + this.textures.forEach((t) => t.destroy()); + this.textures = []; + + let id = 2; + for (const key in v) { + this.uniformsProvider.textures[key] = id; + let texture: Texture; + + if (Object.prototype.hasOwnProperty.call(v[key], 'source')) { + texture = new Texture(this.gl, id++, (v[key] as TextureWithOptions).overrides); + texture.setImage((v[key] as TextureWithOptions).source); + } else { + texture = new Texture(this.gl, id++); + texture.setImage(v[key] as TexImageSource); + } + + this.textures.push(texture); + } + }, ambientLight: (v) => (this.uniformsProvider.ambientLight = v), lightCutoffDistance: (v) => (this.lightsPass.lightCutoffDistance = v), colorPalette: (v) => this.palette.setPalette(v), @@ -108,7 +132,6 @@ export class RendererImplementation implements Renderer { this.setRuntimeSettings(defaultRuntimeSettings); const promises: Array> = []; - const compiler = new ParallelCompiler(this.gl); promises.push( @@ -117,10 +140,10 @@ export class RendererImplementation implements Renderer { ? [distanceVertexShader, distanceFragmentShader] : [distanceVertexShader100, distanceFragmentShader100], this.descriptors.filter(RendererImplementation.hasSdf), + compiler, { paletteSize: settings.paletteSize, - }, - compiler + } ) ); promises.push( @@ -129,10 +152,10 @@ export class RendererImplementation implements Renderer { ? [lightsVertexShader, lightsFragmentShader] : [lightsVertexShader100, lightsFragmentShader100], this.descriptors.filter((d) => !RendererImplementation.hasSdf(d)), + compiler, { shadowTraceCount: settings.shadowTraceCount.toString(), - }, - compiler + } ) ); @@ -219,13 +242,13 @@ export class RendererImplementation implements Renderer { shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()), }; - this.distancePass.render(this.uniformsProvider.getUniforms(common)); + this.distancePass.render(this.uniformsProvider.getUniforms(common), ...this.textures); this.gl.enable(this.gl.BLEND); this.lightsPass.render( this.uniformsProvider.getUniforms(common), this.distanceFieldFrameBuffer.colorTexture, - this.palette.colorTexture + [this.palette] ); this.gl.disable(this.gl.BLEND); @@ -234,6 +257,10 @@ export class RendererImplementation implements Renderer { } } + public displayToWorldCoordinates(displayCoordinates: vec2): vec2 { + return this.uniformsProvider.screenToWorldPosition(displayCoordinates); + } + public setViewArea(topLeft: vec2, size: vec2) { this.uniformsProvider.setViewArea(topLeft, size); } diff --git a/src/graphics/rendering/renderer/renderer.ts b/src/graphics/rendering/renderer/renderer.ts index 0b07dff..9c54685 100644 --- a/src/graphics/rendering/renderer/renderer.ts +++ b/src/graphics/rendering/renderer/renderer.ts @@ -29,6 +29,18 @@ export interface Renderer { */ setViewArea(topLeft: vec2, size: vec2): void; + /** + * Return the world coordinates from a pixel's position. + * + * The view area coordinates are also given in world coordinates. + * + * Useful for picking. + * + * @param displayCoordinates The origin is in the display's top left corner. + * Just as in mouse events' clientX and clientY. + */ + displayToWorldCoordinates(displayCoordinates: vec2): vec2; + /** * Patch the current runtime settings with new values. * @param overrides diff --git a/src/graphics/rendering/settings/default-runtime-settings.ts b/src/graphics/rendering/settings/default-runtime-settings.ts index 06bdb8e..e65f81f 100644 --- a/src/graphics/rendering/settings/default-runtime-settings.ts +++ b/src/graphics/rendering/settings/default-runtime-settings.ts @@ -12,4 +12,5 @@ export const defaultRuntimeSettings: RuntimeSettings = { backgroundColor: vec4.fromValues(1, 1, 1, 1), colorPalette: [], ambientLight: vec3.fromValues(0.25, 0.15, 0.25), + textures: {}, }; diff --git a/src/graphics/rendering/settings/runtime-settings.ts b/src/graphics/rendering/settings/runtime-settings.ts index 079c193..ca0b0d9 100644 --- a/src/graphics/rendering/settings/runtime-settings.ts +++ b/src/graphics/rendering/settings/runtime-settings.ts @@ -1,4 +1,5 @@ import { vec3, vec4 } from 'gl-matrix'; +import { TextureWithOptions } from '../../graphics-library/texture/texture-options'; /** * Interface for a configuration object containing the settings @@ -52,6 +53,18 @@ export interface RuntimeSettings { */ colorPalette: Array; + /** + * It is possible to use your own textures in your SDF definitions. + * + * The keys of the object should be the name used to reference them in the GLSL code, + * and the values should be the textures themselves or a TextureWithOptions specifying + * the texture's [[TextureOptions]]. + * It can be a canvas, img element, Image and so on. + */ + textures: { + [textureName: string]: TexImageSource | TextureWithOptions; + }; + /** * A light affecting every pixel (even the ones inside objects). */ diff --git a/src/graphics/rendering/shaders/distance-fs.glsl b/src/graphics/rendering/shaders/distance-fs.glsl index e16eef4..6b1e33a 100644 --- a/src/graphics/rendering/shaders/distance-fs.glsl +++ b/src/graphics/rendering/shaders/distance-fs.glsl @@ -6,6 +6,8 @@ uniform float maxMinDistance; uniform float distanceNdcPixelSize; in vec2 position; +#define WEBGL2_IS_AVAILABLE + {macroDefinitions} {declarations} diff --git a/src/graphics/rendering/shaders/random-fs-100.glsl b/src/graphics/rendering/shaders/random-fs-100.glsl new file mode 100644 index 0000000..00fa10e --- /dev/null +++ b/src/graphics/rendering/shaders/random-fs-100.glsl @@ -0,0 +1,32 @@ +#version 100 + +precision lowp float; + +varying vec2 uvCoordinates; + +uniform float scale; +uniform float amplitude; + +float noise(float x){ + return fract(sin(x) * 43758.5453123) / 100.0; +} + +float terrain(float x) { + float result = 0.0; + + float frequency = 0.01; + float amplitude = 1.0; + + const float pi = 3.141592654; + for (int i = 0; i < 8; i++) { + result += sin(2.0 * pi * x * frequency - 2.0 * pi * noise(float(i) * 200.0)) * amplitude; + frequency *= 1.5; + amplitude /= 1.2; + } + + return result; +} + +void main() { + gl_FragColor = vec4(vec3(terrain(uvCoordinates.x * scale) * amplitude + 0.5), 1.0); +} diff --git a/src/graphics/rendering/shaders/random-fs.glsl b/src/graphics/rendering/shaders/random-fs.glsl new file mode 100644 index 0000000..0fb3054 --- /dev/null +++ b/src/graphics/rendering/shaders/random-fs.glsl @@ -0,0 +1,34 @@ +#version 300 es + +precision lowp float; + +in vec2 uvCoordinates; + +uniform float scale; +uniform float amplitude; + +float noise(float x){ + return fract(sin(x) * 43758.5453123) / 100.0; +} + +float terrain(float x) { + float result = 0.0; + + float frequency = 0.01; + float amplitude = 1.0; + + const float pi = 3.141592654; + for (int i = 0; i < 8; i++) { + result += sin(2.0 * pi * x * frequency - 2.0 * pi * noise(float(i) * 200.0)) * amplitude; + frequency *= 1.5; + amplitude /= 1.2; + } + + return result; +} + +out vec4 fragmentColor; + +void main() { + fragmentColor = vec4(vec3(terrain(uvCoordinates.x * scale) * amplitude + 0.5), 1.0); +} diff --git a/src/graphics/rendering/shaders/random-vs-100.glsl b/src/graphics/rendering/shaders/random-vs-100.glsl new file mode 100644 index 0000000..02d895a --- /dev/null +++ b/src/graphics/rendering/shaders/random-vs-100.glsl @@ -0,0 +1,18 @@ +#version 100 + +precision lowp float; + +attribute vec4 vertexPosition; +varying vec2 uvCoordinates; + +void main() { + gl_Position = vec4(vertexPosition.xy, 0.0, 1.0); + + uvCoordinates = ( + vec3(vertexPosition.xy, 1.0) + * mat3( + 0.5, 0.0, 0.5, + 0.0, 0.5, 0.5, + 0.0, 0.0, 1.0 + )).xy; +} diff --git a/src/graphics/rendering/shaders/random-vs.glsl b/src/graphics/rendering/shaders/random-vs.glsl new file mode 100644 index 0000000..c7da0c8 --- /dev/null +++ b/src/graphics/rendering/shaders/random-vs.glsl @@ -0,0 +1,18 @@ +#version 300 es + +precision lowp float; + +in vec4 vertexPosition; +out vec2 uvCoordinates; + +void main() { + gl_Position = vec4(vertexPosition.xy, 0.0, 1.0); + + uvCoordinates = ( + vec3(vertexPosition.xy, 1.0) + * mat3( + 0.5, 0.0, 0.5, + 0.0, 0.5, 0.5, + 0.0, 0.0, 1.0 + )).xy; +} diff --git a/src/graphics/rendering/uniforms-provider.ts b/src/graphics/rendering/uniforms-provider.ts index 48cd264..2281910 100644 --- a/src/graphics/rendering/uniforms-provider.ts +++ b/src/graphics/rendering/uniforms-provider.ts @@ -5,6 +5,7 @@ import { UniversalRenderingContext } from '../graphics-library/universal-renderi export class UniformsProvider { public ambientLight!: vec3; public _backgroundColor!: vec4; + public textures: { [textureName: string]: number } = {}; private scaleWorldLengthToNDC = 1; private transformWorldToNDC = mat2d.create(); @@ -18,6 +19,7 @@ export class UniformsProvider { public getUniforms(uniforms: any): any { return { ...uniforms, + ...this.textures, ambientLight: this.ambientLight, backgroundColor: this._backgroundColor, uvToWorld: this.uvToWorld, @@ -79,7 +81,11 @@ export class UniformsProvider { } public screenToWorldPosition(screenPosition: vec2): vec2 { - const resolution = vec2.fromValues(this.gl.canvas.width, this.gl.canvas.height); + const { width, height } = (this.gl + .canvas as HTMLCanvasElement).getBoundingClientRect(); + + const position = vec2.fromValues(screenPosition.x, height - screenPosition.y); + const resolution = vec2.fromValues(width, height); const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft); @@ -90,6 +96,6 @@ export class UniformsProvider { ); mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5)); - return vec2.transformMat2d(vec2.create(), screenPosition, transform); + return vec2.transformMat2d(vec2.create(), position, transform); } } diff --git a/src/main.ts b/src/main.ts index 9d3b79a..318358e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -72,4 +72,6 @@ export * from './drawables/shapes/droplet'; export * from './drawables/shapes/inverted-tunnel'; export * from './drawables/shapes/meta-circle'; export * from './drawables/shapes/rotated-rectangle'; +export * from './graphics/graphics-library/texture/texture-options'; +export * from './graphics/rendering/renderer/noise-renderer'; export * from './graphics/rendering/renderer/renderer';