@lightningjs/renderer
Version:
Lightning 3 Renderer
193 lines (174 loc) • 6.16 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getNormalizedRgbaComponents } from '../../../lib/utils.js';
import type { WebGlCoreCtxTexture } from '../WebGlCoreCtxTexture.js';
import type { WebGlCoreRenderer } from '../WebGlCoreRenderer.js';
import { WebGlCoreShader } from '../WebGlCoreShader.js';
import type { ShaderProgramSources } from '../internal/ShaderUtils.js';
const IDENTITY_MATRIX_3x3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
declare module '../../../CoreShaderManager.js' {
interface ShaderMap {
SdfShader: typeof SdfShader;
}
}
/**
* Properties of the {@link SdfShader}
*/
export interface SdfShaderProps {
transform?: Float32Array;
scrollY?: number;
/**
* Color in RGBA format
*
* @remarks
* Color channels must NOT be premultiplied by alpha for best blending results.
*/
color?: number;
size?: number;
distanceRange?: number;
debug?: boolean;
}
/**
* SdfShader supports multi-channel and single-channel signed distance field textures.
*
* @remarks
* This Shader is used by the {@link SdfTextRenderer}. Do not use thie Shader
* directly. Instead create a Text Node and assign a SDF font family to it.
*
* @internalRemarks
* The only thing this shader does to support multi-channel SDFs is to
* add a median function to the fragment shader. If this one function call
* ends up being a performance bottleneck we can always look at ways to
* remove it.
*/
export class SdfShader extends WebGlCoreShader {
constructor(renderer: WebGlCoreRenderer) {
super({
renderer,
});
}
static z$__type__Props: SdfShaderProps;
override bindTextures(textures: WebGlCoreCtxTexture[]) {
const { glw } = this;
glw.activeTexture(0);
glw.bindTexture(textures[0]!.ctxTexture);
}
protected override bindProps(props: SdfShaderProps): void {
const resolvedProps = SdfShader.resolveDefaults(props);
for (const key in resolvedProps) {
if (key === 'transform') {
this.glw.uniformMatrix3fv(
this.getUniformLocation('u_transform'),
resolvedProps[key],
);
} else if (key === 'scrollY') {
this.glw.uniform1f(
this.getUniformLocation('u_scrollY'),
resolvedProps[key],
);
} else if (key === 'color') {
const components = getNormalizedRgbaComponents(resolvedProps.color);
this.glw.uniform4fv(this.getUniformLocation('u_color'), components);
} else if (key === 'size') {
this.glw.uniform1f(
this.getUniformLocation('u_size'),
resolvedProps[key],
);
} else if (key === 'distanceRange') {
this.glw.uniform1f(
this.getUniformLocation('u_distanceRange'),
resolvedProps[key],
);
} else if (key === 'debug') {
this.glw.uniform1i(
this.getUniformLocation('u_debug'),
resolvedProps[key] ? 1 : 0,
);
}
}
}
static override resolveDefaults(
props: SdfShaderProps = {},
): Required<SdfShaderProps> {
return {
transform: props.transform ?? IDENTITY_MATRIX_3x3,
scrollY: props.scrollY ?? 0,
color: props.color ?? 0xffffffff,
size: props.size ?? 16,
distanceRange: props.distanceRange ?? 1.0,
debug: props.debug ?? false,
};
}
static override shaderSources: ShaderProgramSources = {
vertex: `
precision highp float;
precision mediump float;
// an attribute is an input (in) to a vertex shader.
// It will receive data from a buffer
attribute vec2 a_position;
attribute vec2 a_textureCoordinate;
uniform vec2 u_resolution;
uniform mat3 u_transform;
uniform float u_scrollY;
uniform float u_pixelRatio;
uniform float u_size;
varying vec2 v_texcoord;
void main() {
vec2 scrolledPosition = a_position * u_size - vec2(0, u_scrollY);
vec2 transformedPosition = (u_transform * vec3(scrolledPosition, 1)).xy;
// Calculate screen space with pixel ratio
vec2 screenSpace = (transformedPosition * u_pixelRatio / u_resolution * 2.0 - 1.0) * vec2(1, -1);
gl_Position = vec4(screenSpace, 0.0, 1.0);
v_texcoord = a_textureCoordinate;
}
`,
fragment: `
precision highp float;
precision mediump float;
uniform vec4 u_color;
uniform sampler2D u_texture;
uniform float u_distanceRange;
uniform float u_pixelRatio;
uniform int u_debug;
varying vec2 v_texcoord;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main() {
vec3 sample = texture2D(u_texture, v_texcoord).rgb;
if (u_debug == 1) {
gl_FragColor = vec4(sample.r, sample.g, sample.b, 1.0);
return;
}
float scaledDistRange = u_distanceRange * u_pixelRatio;
float sigDist = scaledDistRange * (median(sample.r, sample.g, sample.b) - 0.5);
float opacity = clamp(sigDist + 0.5, 0.0, 1.0) * u_color.a;
// Build the final color.
// IMPORTANT: We must premultiply the color by the alpha value before returning it.
gl_FragColor = vec4(u_color.r * opacity, u_color.g * opacity, u_color.b * opacity, opacity);
}
`,
};
}