Skip to content

Commit be078ac

Browse files
authored
Merge pull request #208 from ChemistAion/pr#webgl2
WebGL2 explicit mode with vertex shader support
2 parents 5d00fdb + 9c31810 commit be078ac

29 files changed

+987
-62
lines changed

demos/vertex/pass1.glsl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// WebGL2 / GLSL ES 3.00
2+
// Pass 1: fragment pass with a custom vertex shader (#iVertex).
3+
4+
#iVertex "file://pass1_iVertex.glsl"
5+
6+
in vec2 vUV;
7+
8+
void mainImage(out vec4 fragColor, in vec2 fragCoord)
9+
{
10+
vec2 uv = vUV;
11+
12+
// Simple gradient + rings.
13+
float rings = 0.5 + 0.5 * cos(40.0 * length(uv - 0.5) - iTime * 2.0);
14+
vec3 col = vec3(uv.x, uv.y, 0.0);
15+
col = mix(col, vec3(1.0, 0.2, 0.1), rings * 0.35);
16+
17+
fragColor = vec4(col, 1.0);
18+
}

demos/vertex/pass1_iVertex.glsl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// WebGL2 / GLSL ES 3.00
2+
// Pass 1 vertex shader for the iVertex demo.
3+
// Use from a fragment pass via: #iVertex "file://pass1_iVertex.glsl"
4+
5+
out vec2 vUV;
6+
7+
void main() {
8+
const float d = 0.6;
9+
10+
vec2 ndc;
11+
int id = gl_VertexID % 3;
12+
if (id == 0) {
13+
ndc = vec2(-1.0, -1.0);
14+
}
15+
else if (id == 1) {
16+
ndc = vec2(1.0, -1.0 + d);
17+
}
18+
else {
19+
ndc = vec2(-1.0 + d, 1.0);
20+
}
21+
22+
vUV = ndc * 0.5 + 0.5;
23+
gl_Position = vec4(ndc, 0.0, 1.0);
24+
}

demos/vertex/pass2.glsl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// WebGL2 / GLSL ES 3.00
2+
// Pass 2: fragment pass with a custom vertex shader (#iVertex).
3+
4+
#iVertex "file://pass2_iVertex.glsl"
5+
6+
in vec2 vUV;
7+
8+
void mainImage(out vec4 fragColor, in vec2 fragCoord)
9+
{
10+
vec2 uv = vUV;
11+
12+
// Stripes + checker modulation.
13+
float stripes = 0.5 + 0.5 * sin((uv.x + uv.y * 0.5) * 20.0 + iTime * 1.5);
14+
float checker = mod(floor(uv.x * 10.0) + floor(uv.y * 10.0), 2.0);
15+
16+
vec3 a = vec3(0.1, 0.25, 0.95);
17+
vec3 b = vec3(1.0, 0.9, 0.2);
18+
vec3 col = mix(a, b, stripes);
19+
col *= mix(0.75, 1.0, checker);
20+
21+
fragColor = vec4(col, 1.0);
22+
}

demos/vertex/pass2_iVertex.glsl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// WebGL2 / GLSL ES 3.00
2+
// Pass 2 vertex shader for the iVertex demo.
3+
// Use from a fragment pass via: #iVertex "file://pass2_iVertex.glsl"
4+
5+
out vec2 vUV;
6+
7+
void main()
8+
{
9+
const float d = 0.6;
10+
11+
vec2 ndc;
12+
int id = gl_VertexID % 3;
13+
if (id == 0) {
14+
ndc = vec2(-1.0, 1.0 - d);
15+
}
16+
else if (id == 1) {
17+
ndc = vec2(1.0 - d, -1.0);
18+
}
19+
else {
20+
ndc = vec2(1.0, 1.0);
21+
}
22+
23+
vUV = clamp(ndc * 0.5 + 0.5, 0.0, 1.0);
24+
gl_Position = vec4(ndc, 0.0, 1.0);
25+
}

demos/webgl2_features.glsl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// WebGL2 feature demo (GLSL ES 3.00)
2+
//
3+
// WebGL2 corresponds to OpenGL ES 3.00.
4+
// WebGL2 does NOT support OpenGL ES 3.10 / 3.20 features.
5+
//
6+
// Demo flow:
7+
// 1) Build UV + aspect-corrected coords; compute polar r/a.
8+
// 2) Generate uint hash from fragCoord + time for stable jitter.
9+
// 3) Warp UVs with a swirl + tiny jitter.
10+
// 4) WebGL2-only pack/unpack roundtrip on warped UVs:
11+
// packHalf2x16 -> unpackHalf2x16 -> packUnorm2x16 -> unpackUnorm2x16
12+
// Result feeds hue, so it directly affects final color.
13+
// 5) HSV->RGB gradient + vignette => final color.
14+
15+
uint hashU32(uint x) {
16+
x ^= x >> 16;
17+
x *= 0x7FEB352Du;
18+
x ^= x >> 15;
19+
x *= 0x846CA68Bu;
20+
x ^= x >> 16;
21+
return x;
22+
}
23+
24+
uint hash2(uvec2 p) {
25+
return hashU32(p.x ^ (p.y * 0x9E3779B9u));
26+
}
27+
28+
vec3 hsv2rgb(vec3 c) {
29+
vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0/3.0, 1.0/3.0)) * 6.0 - 3.0);
30+
vec3 rgb = clamp(p - 1.0, 0.0, 1.0);
31+
return c.z * mix(vec3(1.0), rgb, c.y);
32+
}
33+
34+
void main() {
35+
vec2 fragCoord = gl_FragCoord.xy;
36+
vec2 uv = fragCoord / iResolution.xy;
37+
vec2 p = uv * 2.0 - 1.0;
38+
p.x *= iResolution.x / iResolution.y;
39+
40+
float t = iTime * 0.25;
41+
float r = length(p);
42+
float a = atan(p.y, p.x);
43+
44+
// WebGL2 uint ops for subtle jitter.
45+
uint seed = hash2(uvec2(fragCoord) + uvec2(uint(iTime * 60.0), 123u));
46+
float jitter = float(seed & 1023u) / 1023.0;
47+
48+
// Warp the UVs a bit for a smooth, swirling gradient.
49+
vec2 warp = vec2(cos(a * 3.0 + t), sin(a * 2.0 - t)) * (0.05 - 0.04 * r);
50+
vec2 warped = uv + warp + (jitter - 0.5) * 0.003;
51+
52+
// WebGL2 pack/unpack: half-float roundtrip.
53+
uint packedHalf = packHalf2x16(warped);
54+
vec2 unpackedHalf = unpackHalf2x16(packedHalf);
55+
56+
// WebGL2 pack/unpack: normalized roundtrip.
57+
uint packedNorm = packUnorm2x16(unpackedHalf);
58+
vec2 unpackedNorm = unpackUnorm2x16(packedNorm);
59+
60+
float hue = fract(a / 6.2831853 + t + unpackedNorm.x * 0.15);
61+
float sat = 0.7;
62+
float val = 0.9 - 0.45 * smoothstep(0.15, 1.0, r);
63+
vec3 col = hsv2rgb(vec3(hue, sat, val));
64+
65+
// A soft vignette for contrast.
66+
col *= 0.25 + 0.75 * smoothstep(1.2, 0.2, r);
67+
68+
gl_FragColor = vec4(col, 1.0);
69+
}

demos/webgl2_iVertexDemo.glsl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// WebGL2 / GLSL ES 3.00 demo
2+
//
3+
// Uses #iVertex in two offscreen passes (iChannel1/iChannel2).
4+
// iChannel0 is a simple WebGL2-only background shader.
5+
//
6+
// Note: Set `shader-toy.glslVersion` to "WebGL2".
7+
8+
#iChannel0 "file://webgl2_features.glsl"
9+
#iChannel1 "file://vertex/pass1.glsl"
10+
#iChannel2 "file://vertex/pass2.glsl"
11+
12+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
13+
vec2 uv = fragCoord.xy / iResolution.xy;
14+
vec3 bg = texture2D(iChannel0, uv).rgb;
15+
vec3 p1 = texture2D(iChannel1, uv).rgb;
16+
vec3 p2 = texture2D(iChannel2, uv).rgb;
17+
vec3 col = bg + p1 + p2;
18+
fragColor = vec4(col, 1.0);
19+
}

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@
3737
],
3838
"description": "Force the rendering into a specific aspect ratio. Set either to zero or negative to ignore."
3939
},
40+
"shader-toy.webglVersion": {
41+
"type": "string",
42+
"default": "Default",
43+
"enum": [
44+
"Default",
45+
"WebGL2"
46+
],
47+
"description": "Select the WebGL mode. Default prefers WebGL2 but falls back to WebGL1. WebGL2 uses GLSL 300 ES and enables #iVertex."
48+
},
4049
"shader-toy.showCompileErrorsAsDiagnostics": {
4150
"type": "boolean",
4251
"default": true,

resources/webview/gl_context.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@
44
const root = global.ShaderToy = global.ShaderToy || {};
55
root.gl = root.gl || {};
66

7-
// Intentionally empty for now.
8-
// Future home for WebGL1/WebGL2 context selection + capability probing.
7+
root.gl.getContext = function (canvas, glslUseVersion3) {
8+
let gl = null;
9+
if (glslUseVersion3) {
10+
gl = canvas.getContext('webgl2');
11+
}
12+
else {
13+
gl = canvas.getContext('webgl2');
14+
if (gl == null) {
15+
gl = canvas.getContext('webgl', { antialias: true, preserveDrawingBuffer: true });
16+
}
17+
}
18+
19+
const isWebGL2 = gl != null && (typeof global.WebGL2RenderingContext !== 'undefined') && (gl instanceof global.WebGL2RenderingContext);
20+
if (gl == null) {
21+
const msg = glslUseVersion3
22+
? 'WebGL2 is required for shader-toy.webglVersion = "WebGL2", but it is not available in this WebView.'
23+
: 'WebGL is not available in this WebView (cannot create webgl2/webgl context).';
24+
try {
25+
const messageElement = global.document && global.document.getElementById
26+
? global.document.getElementById('message')
27+
: undefined;
28+
if (messageElement) {
29+
messageElement.innerText = msg;
30+
}
31+
} catch {
32+
// ignore
33+
}
34+
throw new Error(msg);
35+
}
36+
37+
return { gl, isWebGL2 };
38+
};
939
})(typeof window !== 'undefined' ? window : globalThis);

resources/webview/shader_compile.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@
1919
return (source || '').replace(new RegExp(`#line\\s+(\\d+)\\s+${SELF_SOURCE_ID}`, 'g'), '#line $1 0');
2020
};
2121

22+
root.shaderCompile.prepareFragmentShader = function (source, glslUseVersion3) {
23+
if (glslUseVersion3) {
24+
return (
25+
'#ifdef gl_FragColor\n' +
26+
'#define GLSL_FRAGCOLOR gl_FragColor\n' +
27+
'#else\n' +
28+
'layout(location = 0) out highp vec4 layoutFragColor;\n' +
29+
'#define gl_FragColor layoutFragColor\n' +
30+
'#define GLSL_FRAGCOLOR layoutFragColor\n' +
31+
'#endif\n' +
32+
'#define texture2D texture\n' +
33+
'#define textureCube texture\n' +
34+
'\n' + (source || '')
35+
);
36+
}
37+
return (
38+
'#define GLSL_FRAGCOLOR gl_FragColor\n' +
39+
'\n' + (source || '')
40+
);
41+
};
42+
43+
root.shaderCompile.prepareVertexShader = function (source, glslUseVersion3) {
44+
if (!glslUseVersion3) {
45+
throw new Error('Custom vertex shaders (#iVertex) require shader-toy.webglVersion = "WebGL2".');
46+
}
47+
48+
return (
49+
'#define texture2D texture\n' +
50+
'#define textureCube texture\n' +
51+
'\n' + (source || '')
52+
);
53+
};
54+
55+
const getCompileHeader = (glslUseVersion3) => {
56+
// Keep header line count stable (2 lines) for easier line-offset mapping.
57+
return glslUseVersion3
58+
? '#version 300 es\nprecision highp float;\n'
59+
: '// shader-toy\nprecision highp float;\n';
60+
};
61+
2262
root.shaderCompile.compileFragShader = function (gl, fsSource) {
2363
const fs = gl.createShader(gl.FRAGMENT_SHADER);
2464
const normalized = root.shaderCompile.normalizeLineDirectives(fsSource);
@@ -31,4 +71,34 @@
3171
}
3272
return true;
3373
};
74+
75+
root.shaderCompile.compileIncludeFragment = function (gl, fsSource, glslUseVersion3) {
76+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
77+
const normalized = root.shaderCompile.normalizeLineDirectives(fsSource);
78+
const header = getCompileHeader(glslUseVersion3);
79+
const wrapped = `${header}${normalized || ''}\nvoid main() {}\n`;
80+
gl.shaderSource(fs, wrapped);
81+
gl.compileShader(fs);
82+
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
83+
const fragmentLog = gl.getShaderInfoLog(fs);
84+
console.error('THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.COMPILE_STATUS', null, null, null, null, fragmentLog);
85+
return false;
86+
}
87+
return true;
88+
};
89+
90+
root.shaderCompile.compileVertexShader = function (gl, vsSource, glslUseVersion3) {
91+
const vs = gl.createShader(gl.VERTEX_SHADER);
92+
const normalized = root.shaderCompile.normalizeLineDirectives(vsSource);
93+
const header = getCompileHeader(glslUseVersion3);
94+
const wrapped = `${header}${normalized || ''}`;
95+
gl.shaderSource(vs, wrapped);
96+
gl.compileShader(vs);
97+
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
98+
const vertexLog = gl.getShaderInfoLog(vs);
99+
console.error('THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.COMPILE_STATUS', null, null, null, null, vertexLog);
100+
return false;
101+
}
102+
return true;
103+
};
34104
})(typeof window !== 'undefined' ? window : globalThis);

0 commit comments

Comments
 (0)