@liquid-glass-forge/webgl 0.1.0-rc.1 → 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/atlas.js +2 -1
- package/dist/dom.d.ts +4 -2
- package/dist/effects.d.ts +2 -1
- package/dist/effects.js +2 -2
- package/dist/shaders.d.ts +1 -1
- package/dist/shaders.js +10 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @liquid-glass-forge/webgl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
WebGL2 glass refraction and a shared atlas for compositing optical surfaces.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://namipsg.github.io/liquid-glass-forge/#playground)
|
|
6
|
+
|
|
7
|
+
[Live showcase](https://namipsg.github.io/liquid-glass-forge/#playground) · [Examples](https://namipsg.github.io/liquid-glass-forge/examples.html) · [Documentation](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/PHASE-6.md) · [Support](https://namipsg.github.io/liquid-glass-forge/support.html) · [Install by framework](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/QUICKSTART.md)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @liquid-glass-forge/webgl@0.1.0-rc.2
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Keep the Forge packages at matching versions. This is an experimental preview.
|
|
16
|
+
The [support guide](https://namipsg.github.io/liquid-glass-forge/support.html) describes browser and device qualification.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Experimental preview package, `0.1.0-rc.2`. Shared persistent WebGL2 atlas,
|
|
6
21
|
versioned optics, GPU blur and synchronous Canvas2D tile presentation.
|
|
7
22
|
|
|
8
23
|
Use `WebGLAtlas` from `@liquid-glass-forge/webgl` for owned Canvas2D targets.
|
package/dist/atlas.js
CHANGED
|
@@ -257,7 +257,7 @@ export class WebGLAtlas {
|
|
|
257
257
|
throw new WebGLError('ATTRIBUTES_UNSUPPORTED', 'Required context attributes not granted');
|
|
258
258
|
this.#maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
259
259
|
this.#maxViewport = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
|
|
260
|
-
this.#optics = this.#program(OPTICS, ['Source', 'Encoding', 'Size', 'Origin', 'Rect', 'Radius', 'Bevel', 'Strength', 'Prepared', 'QuantizeUp', 'SourceLinear', 'Calibration', 'Dispersion', 'Tint', 'Light', 'Rim', 'Specular']);
|
|
260
|
+
this.#optics = this.#program(OPTICS, ['Source', 'Encoding', 'Size', 'Origin', 'Rect', 'Radius', 'Bevel', 'Strength', 'Prepared', 'QuantizeUp', 'SourceLinear', 'Calibration', 'Dispersion', 'Tint', 'Light', 'Rim', 'Specular', 'LensProfile']);
|
|
261
261
|
this.#blur = this.#program(BLUR, ['Source', 'Encoding', 'Axis', 'Radius', 'Prepared', 'SourceLinear']);
|
|
262
262
|
this.#source = this.#texture();
|
|
263
263
|
this.#encoding = this.#texture();
|
|
@@ -541,6 +541,7 @@ export class WebGLAtlas {
|
|
|
541
541
|
gl.uniform1i(u.SourceLinear, this.#sourceLinear ? 1 : 0);
|
|
542
542
|
gl.uniform1i(u.Prepared, e.blur ? 1 : 0);
|
|
543
543
|
gl.uniform1i(u.QuantizeUp, this.#quantizeUp ? 1 : 0);
|
|
544
|
+
gl.uniform1i(u.LensProfile, e.lens ? 1 : 0);
|
|
544
545
|
gl.uniform1f(u.Dispersion, e.dispersion);
|
|
545
546
|
gl.uniform4f(u.Tint, ...e.tint);
|
|
546
547
|
gl.uniform2f(u.Light, ...e.light);
|
package/dist/dom.d.ts
CHANGED
|
@@ -111,13 +111,15 @@ export declare class OpticalRenderer implements GlassRenderer {
|
|
|
111
111
|
effectiveHeight: number | undefined;
|
|
112
112
|
qualityReduced: boolean;
|
|
113
113
|
reasons: readonly string[];
|
|
114
|
-
quality: Required<{
|
|
114
|
+
quality: Required<Omit<{
|
|
115
115
|
readonly maxPixels?: number;
|
|
116
116
|
readonly maxWidth?: number;
|
|
117
117
|
readonly maxHeight?: number;
|
|
118
118
|
readonly maxUpdatesPerSecond?: number;
|
|
119
119
|
readonly maxSourcePixels?: number;
|
|
120
|
-
|
|
120
|
+
readonly cadence?: 'balanced' | 'interactive';
|
|
121
|
+
}, "cadence">>;
|
|
122
|
+
cadencePolicy?: "interactive";
|
|
121
123
|
timings: (import("@liquid-glass-forge/cpu").CpuTimings & {
|
|
122
124
|
readonly captureMs: number;
|
|
123
125
|
readonly rateWaitMs: number;
|
package/dist/effects.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface WebGLEffects {
|
|
2
|
-
readonly model: 'rounded-material-v1';
|
|
2
|
+
readonly model: 'rounded-material-v1' | 'refractive-lens-v2';
|
|
3
3
|
readonly blurRadius?: number;
|
|
4
4
|
readonly dispersion?: number;
|
|
5
5
|
readonly tint?: readonly [number, number, number, number];
|
|
@@ -8,6 +8,7 @@ export interface WebGLEffects {
|
|
|
8
8
|
readonly light?: readonly [number, number];
|
|
9
9
|
}
|
|
10
10
|
export declare function effects(input?: WebGLEffects): Readonly<{
|
|
11
|
+
lens: boolean;
|
|
11
12
|
blur: number;
|
|
12
13
|
dispersion: number;
|
|
13
14
|
rim: number;
|
package/dist/effects.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { decodeSrgb, ContractError } from '@liquid-glass-forge/core';
|
|
2
2
|
export function effects(input) {
|
|
3
3
|
if (input !== undefined && (!input || typeof input !== 'object' || Array.isArray(input) ||
|
|
4
|
-
Reflect.ownKeys(input).some(k => typeof k !== 'string' || !['model', 'blurRadius', 'dispersion', 'tint', 'rim', 'specular', 'light'].includes(k)) ||
|
|
4
|
+
Reflect.ownKeys(input).some(k => typeof k !== 'string' || !['model', 'blurRadius', 'dispersion', 'tint', 'rim', 'specular', 'light'].includes(k)) || !['rounded-material-v1', 'refractive-lens-v2'].includes(input.model)))
|
|
5
5
|
throw new ContractError('Invalid WebGL material');
|
|
6
6
|
const blur = input?.blurRadius === undefined ? 0 : input.blurRadius, dispersion = input?.dispersion === undefined ? 0 : input.dispersion, rim = input?.rim === undefined ? 0 : input.rim, specular = input?.specular === undefined ? 0 : input.specular;
|
|
7
7
|
if (!Number.isInteger(blur) || blur < 0 || blur > 16 || ![dispersion, rim, specular].every(v => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1))
|
|
@@ -13,5 +13,5 @@ export function effects(input) {
|
|
|
13
13
|
if (!scale)
|
|
14
14
|
throw new ContractError('Light must be nonzero');
|
|
15
15
|
const x = light[0] / scale, y = light[1] / scale, length = Math.hypot(x, y);
|
|
16
|
-
return Object.freeze({ blur, dispersion, rim, specular, tint: [decodeSrgb(tint[0]), decodeSrgb(tint[1]), decodeSrgb(tint[2]), tint[3]], light: [x / length, y / length] });
|
|
16
|
+
return Object.freeze({ lens: input?.model === 'refractive-lens-v2', blur, dispersion, rim, specular, tint: [decodeSrgb(tint[0]), decodeSrgb(tint[1]), decodeSrgb(tint[2]), tint[3]], light: [x / length, y / length] });
|
|
17
17
|
}
|
package/dist/shaders.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const VERTEX = "#version 300 es\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));gl_Position=vec4(p*2.0-1.0,0,1);}";
|
|
2
|
-
export declare const OPTICS = "#version 300 es\nprecision highp float;\nprecision highp int;\nuniform highp sampler2D uSource;\nuniform highp sampler2D uEncoding;\nuniform vec2 uSize,uOrigin,uLight;\nuniform vec4 uRect,uTint;\nuniform float uRadius,uBevel,uStrength,uDispersion,uRim,uSpecular;\nuniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration;\nout vec4 result;\nfloat encodeByte(float v){int i=min(int(v*4096.0),4095);vec3 e=texelFetch(uEncoding,ivec2(i&63,i>>6),0).rgb;return e.r+(v>=e.b?1.0:(v<e.g?-1.0:0.0));}\nfloat decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}\nvec4 texel(ivec2 p){vec4 c=texelFetch(uSource,p,0);return uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}\nvec4 sampleAt(vec2 p){ivec2 s=textureSize(uSource,0);p=clamp(p,vec2(0.0),vec2(float(s.x-1),float(s.y-1)));ivec2 lo=ivec2(floor(p)),hi=min(lo+1,s-1);vec2 t=fract(p);\nif(t.x==0.0&&t.y==0.0)return texel(lo);\nreturn (texel(lo)*(1.0-t.x)+texel(ivec2(hi.x,lo.y))*t.x)*(1.0-t.y)+(texel(ivec2(lo.x,hi.y))*(1.0-t.x)+texel(hi)*t.x)*t.y;}\nvoid main(){\nif(uCalibration){vec4 c=texelFetch(uSource,ivec2(int(gl_FragCoord.x),0),0);float n=floor(c.r*65535.0+.5);result=vec4(floor(n/256.0)/255.0,mod(n,256.0)/255.0,0,1);return;}\nvec2 local=gl_FragCoord.xy-uOrigin,center=vec2(local.x,uSize.y-local.y),p=center-uSize*.5;\nvec2 q=abs(p)-(uSize*.5-uRadius),a=max(q,0.0);float len=length(a),d=len+min(max(q.x,q.y),0.0)-uRadius;\nfloat mask=clamp(.5-d,0.0,1.0);if(mask==0.0){result=vec4(0.0);return;}\nfloat w=clamp(1.0+d/
|
|
2
|
+
export declare const OPTICS = "#version 300 es\nprecision highp float;\nprecision highp int;\nuniform highp sampler2D uSource;\nuniform highp sampler2D uEncoding;\nuniform vec2 uSize,uOrigin,uLight;\nuniform vec4 uRect,uTint;\nuniform float uRadius,uBevel,uStrength,uDispersion,uRim,uSpecular;\nuniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration,uLensProfile;\nout vec4 result;\nfloat encodeByte(float v){int i=min(int(v*4096.0),4095);vec3 e=texelFetch(uEncoding,ivec2(i&63,i>>6),0).rgb;return e.r+(v>=e.b?1.0:(v<e.g?-1.0:0.0));}\nfloat decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}\nvec4 texel(ivec2 p){vec4 c=texelFetch(uSource,p,0);return uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}\nvec4 sampleAt(vec2 p){ivec2 s=textureSize(uSource,0);p=clamp(p,vec2(0.0),vec2(float(s.x-1),float(s.y-1)));ivec2 lo=ivec2(floor(p)),hi=min(lo+1,s-1);vec2 t=fract(p);\nif(t.x==0.0&&t.y==0.0)return texel(lo);\nreturn (texel(lo)*(1.0-t.x)+texel(ivec2(hi.x,lo.y))*t.x)*(1.0-t.y)+(texel(ivec2(lo.x,hi.y))*(1.0-t.x)+texel(hi)*t.x)*t.y;}\nvoid main(){\nif(uCalibration){vec4 c=texelFetch(uSource,ivec2(int(gl_FragCoord.x),0),0);float n=floor(c.r*65535.0+.5);result=vec4(floor(n/256.0)/255.0,mod(n,256.0)/255.0,0,1);return;}\nvec2 local=gl_FragCoord.xy-uOrigin,center=vec2(local.x,uSize.y-local.y),p=center-uSize*.5;\nvec2 q=abs(p)-(uSize*.5-uRadius),a=max(q,0.0);float len=length(a),d=len+min(max(q.x,q.y),0.0)-uRadius;\nfloat mask=clamp(.5-d,0.0,1.0);if(mask==0.0){result=vec4(0.0);return;}\n// Keep v2's bevel outside the medial axis at small corner radii. V1 is unchanged.\nfloat bevel=uLensProfile?min(uBevel,max(uRadius,.5)):uBevel;\nfloat w=clamp(1.0+d/bevel,0.0,1.0);w*=w;\nvec2 n=w>0.0?(len>0.0?a/len*sign(p):(q.x>q.y?vec2(sign(p.x),0.0):vec2(0.0,sign(p.y)))):vec2(0.0);\nfloat displacement=w;\nif(uLensProfile&&w>0.0){float u=sqrt(w),nz=sqrt(max(0.0,1.0-u*u)),eta=2.0/3.0;\nfloat bend=sqrt(1.0-eta*eta*u*u)-eta*nz;displacement=-u*bend/(eta+bend*nz);}\nvec2 scale=uRect.zw/uSize,base=uRect.xy+center*scale-.5,delta=n*uStrength*displacement*scale;\n// An exact source texel with all optional passes disabled is byte identity.\n// Avoid decode/interpolate/encode work only where bilinear weights are exactly zero.\nif(!uPrepared&&!uSourceLinear&&uDispersion==0.0&&uTint.a==0.0&&uRim==0.0&&uSpecular==0.0){\n vec2 at=clamp(base+delta,vec2(0.0),vec2(float(textureSize(uSource,0).x-1),float(textureSize(uSource,0).y-1)));\n if(all(equal(fract(at),vec2(0.0)))){vec4 raw=texelFetch(uSource,ivec2(at),0);float alpha=floor(raw.a*mask*255.0+.5);ivec3 bytes=ivec3(floor(raw.rgb*255.0+.5));ivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;result=alpha==0.0?vec4(0.0):vec4(vec3(premult)/255.0,alpha/255.0);return;}\n}\nvec4 c=sampleAt(base+delta);float alpha=floor(c.a*mask*255.0+.5);\nif(alpha==0.0){result=vec4(0.0);return;}\nvec3 rgb=c.a>0.0?c.rgb/c.a:vec3(0.0);\nif(uDispersion>0.0){vec4 r=sampleAt(base+delta*(1.0+uDispersion)),b=sampleAt(base+delta*(1.0-uDispersion));rgb.r=r.a>0.0?r.r/r.a:0.0;rgb.b=b.a>0.0?b.b/b.a:0.0;}\nif(uTint.a>0.0)rgb=mix(rgb,uTint.rgb,uTint.a);\nif(w>0.0&&(uRim>0.0||uSpecular>0.0)){float l=max(dot(n,uLight),0.0);l*=l;l*=l;l*=l;float h=clamp(w*(uRim+uSpecular*l),0.0,1.0);rgb=clamp(rgb+(1.0-rgb)*h,0.0,1.0);}\nivec3 bytes=ivec3(encodeByte(rgb.r),encodeByte(rgb.g),encodeByte(rgb.b));\nivec3 premult=(bytes*int(alpha)+(uQuantizeUp?254:127))/255;\nresult=vec4(vec3(premult)/255.0,alpha/255.0);\n}";
|
|
3
3
|
export declare const BLUR = "#version 300 es\nprecision highp float;\nprecision highp int;\nuniform highp sampler2D uSource;\nuniform highp sampler2D uEncoding;\nuniform ivec2 uAxis;\nuniform int uRadius;\nuniform bool uPrepared,uSourceLinear;\nout vec4 result;\nfloat decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}\nvoid main(){ivec2 p=ivec2(gl_FragCoord.xy),size=textureSize(uSource,0);vec4 sum=vec4(0.0);\nfor(int k=-16;k<=16;k++){if(abs(k)<=uRadius){vec4 c=texelFetch(uSource,clamp(p+uAxis*k,ivec2(0),size-1),0);sum+=uPrepared?c:(uSourceLinear?vec4(c.rgb*c.a,c.a):vec4(vec3(decodeByte(c.r),decodeByte(c.g),decodeByte(c.b))*c.a,c.a));}}\nresult=sum/float(2*uRadius+1);\n}";
|
|
4
4
|
export declare function encodingTextureData(): Float32Array;
|
package/dist/shaders.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const VERTEX = `#version 300 es
|
|
2
2
|
void main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));gl_Position=vec4(p*2.0-1.0,0,1);}`;
|
|
3
|
-
//
|
|
3
|
+
// The v1 path retains CONTRACTS.md and PHASE-5.md; the opt-in v2 path
|
|
4
|
+
// traces an air-to-glass ray through an elliptical bevel. The scalar oracle
|
|
4
5
|
// remains independent; production never obtains pixels from a GPU framebuffer.
|
|
5
6
|
// Explicit float constructors avoid ANGLE/D3D11 generating an ambiguous HLSL
|
|
6
7
|
// vec2_ctor_int overload for mixed integer and floating-point arguments.
|
|
@@ -12,7 +13,7 @@ uniform highp sampler2D uEncoding;
|
|
|
12
13
|
uniform vec2 uSize,uOrigin,uLight;
|
|
13
14
|
uniform vec4 uRect,uTint;
|
|
14
15
|
uniform float uRadius,uBevel,uStrength,uDispersion,uRim,uSpecular;
|
|
15
|
-
uniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration;
|
|
16
|
+
uniform bool uPrepared,uQuantizeUp,uSourceLinear,uCalibration,uLensProfile;
|
|
16
17
|
out vec4 result;
|
|
17
18
|
float encodeByte(float v){int i=min(int(v*4096.0),4095);vec3 e=texelFetch(uEncoding,ivec2(i&63,i>>6),0).rgb;return e.r+(v>=e.b?1.0:(v<e.g?-1.0:0.0));}
|
|
18
19
|
float decodeByte(float v){return v<=.04045?v/12.92:pow((v+.055)/1.055,2.4);}
|
|
@@ -25,9 +26,14 @@ if(uCalibration){vec4 c=texelFetch(uSource,ivec2(int(gl_FragCoord.x),0),0);float
|
|
|
25
26
|
vec2 local=gl_FragCoord.xy-uOrigin,center=vec2(local.x,uSize.y-local.y),p=center-uSize*.5;
|
|
26
27
|
vec2 q=abs(p)-(uSize*.5-uRadius),a=max(q,0.0);float len=length(a),d=len+min(max(q.x,q.y),0.0)-uRadius;
|
|
27
28
|
float mask=clamp(.5-d,0.0,1.0);if(mask==0.0){result=vec4(0.0);return;}
|
|
28
|
-
|
|
29
|
+
// Keep v2's bevel outside the medial axis at small corner radii. V1 is unchanged.
|
|
30
|
+
float bevel=uLensProfile?min(uBevel,max(uRadius,.5)):uBevel;
|
|
31
|
+
float w=clamp(1.0+d/bevel,0.0,1.0);w*=w;
|
|
29
32
|
vec2 n=w>0.0?(len>0.0?a/len*sign(p):(q.x>q.y?vec2(sign(p.x),0.0):vec2(0.0,sign(p.y)))):vec2(0.0);
|
|
30
|
-
|
|
33
|
+
float displacement=w;
|
|
34
|
+
if(uLensProfile&&w>0.0){float u=sqrt(w),nz=sqrt(max(0.0,1.0-u*u)),eta=2.0/3.0;
|
|
35
|
+
float bend=sqrt(1.0-eta*eta*u*u)-eta*nz;displacement=-u*bend/(eta+bend*nz);}
|
|
36
|
+
vec2 scale=uRect.zw/uSize,base=uRect.xy+center*scale-.5,delta=n*uStrength*displacement*scale;
|
|
31
37
|
// An exact source texel with all optional passes disabled is byte identity.
|
|
32
38
|
// Avoid decode/interpolate/encode work only where bilinear weights are exactly zero.
|
|
33
39
|
if(!uPrepared&&!uSourceLinear&&uDispersion==0.0&&uTint.a==0.0&&uRim==0.0&&uSpecular==0.0){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liquid-glass-forge/webgl",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Experimental shared-atlas WebGL2 optics and optional vanilla integration",
|
|
6
6
|
"type": "module",
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@liquid-glass-forge/core": "0.1.0-rc.
|
|
32
|
-
"@liquid-glass-forge/cpu": "0.1.0-rc.
|
|
33
|
-
"@liquid-glass-forge/dom": "0.1.0-rc.
|
|
31
|
+
"@liquid-glass-forge/core": "0.1.0-rc.2",
|
|
32
|
+
"@liquid-glass-forge/cpu": "0.1.0-rc.2",
|
|
33
|
+
"@liquid-glass-forge/dom": "0.1.0-rc.2"
|
|
34
34
|
},
|
|
35
|
+
"homepage": "https://namipsg.github.io/liquid-glass-forge/#playground",
|
|
35
36
|
"repository": {
|
|
36
37
|
"type": "git",
|
|
37
38
|
"url": "git+https://github.com/namipsg/liquid-glass-forge.git",
|