mrt, vertex-shader loading

This commit is contained in:
Gyuri Horák 2023-02-21 16:42:50 +01:00
parent b0d7efe523
commit e79127a474
Signed by: dyuri
GPG Key ID: 4993F07B3EAE8D38
4 changed files with 212 additions and 72 deletions

10
TODO.md Normal file
View File

@ -0,0 +1,10 @@
- textures
- image
- canvas
- video
- webcam
- audio
- mic?
- fix mouse, add button, drag
- device orientation

View File

@ -23,18 +23,7 @@
</head> </head>
<body> <body>
<repa-shader fs-input="fsinput" alpha mouse snippets="noise.glsl" width=512 height=512> <repa-shader fs-input="fsinput" alpha mouse snippets="noise.glsl" width=512 height=512>
<script type="x-shader/x-fragment"> <script type="x-shader/x-fragmen[DELETE]t">
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 col = .5 + .5 * cos(uv.xyx + time + vec3(0, 2, 4));
float dist = distance(uv, mouse);
float circle = smoothstep(.15, .2, dist);
vec4 acolor = vec4(col * (1. - circle), (1. - circle)) + circle * texture(backbuffer, uv - vec2(.01, .01)) * .9;
outColor = vec4(acolor);
}
</script>
<script type="x-shader/x-fragmen">
// https://twigl.app/?ol=true&ss=-NOAlYulOVLklxMdxBDx // https://twigl.app/?ol=true&ss=-NOAlYulOVLklxMdxBDx
void main() { void main() {
vec2 n,N,q,p=FC.xy/r.y; vec2 n,N,q,p=FC.xy/r.y;

69
demo/mrt.html Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>&lt;repa-shader&gt; demo</title>
<script type="module" src="../src/repa-shader.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: repeating-linear-gradient(45deg, #333, #333 10px, #666 10px, #666 20px);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
#fsinput {
width: 90vw;
height: 50vh;
}
</style>
</head>
<body>
<repa-shader fs-input="fsinput" mouse snippets="noise.glsl" width=512 height=512 render-target-count=2>
<script type="x-shader/x-fragment">
#define R rotate2D
vec2 c=FC.xy;
vec2 uv=FC.xy/r.xy;
vec2 q;
vec2 p=(c+c-r)/r.y;
vec2 n;
vec4 color;
float S=6.;
float a;
float d=dot(p,p);
float e=200.;
p/=.7-d;
p+=t/PI;
for(float i = 0.; i < e; i++) {
color+=(texture(b1,(c/r-.5)*i/e+.5))/e;
p*=R(5.);
n*=R(5.);
a+=dot(sin(q=p*S+i-abs(n)*R(t*.2))/S,r/r);
n+=cos(q),S*=1.1;
}
// a = max(s,.9-a*.2-d);
a = max(0.,.9-a*.2-d);
o1=pow(a+a*vec4(8,4,1,0)/e,color+40.);
float center = smoothstep(.45, .55, uv.x);
o0=center*color + o1;
</script>
</repa-shader>
<div>
<textarea id="fsinput"></textarea>
</div>
<button id="update">Update</button>
<script>
const updateButton = document.getElementById('update');
updateButton.addEventListener('click', () => {
const fsinput = document.getElementById('fsinput');
const shader = document.querySelector('repa-shader');
shader.render(fsinput.value);
});
</script>
</body>
</html>

View File

@ -15,16 +15,12 @@ const CHUNKS = {
#define m mouse #define m mouse
#define t time #define t time
#define f frame #define f frame
#define b backbuffer
#define o outColor
precision highp float; precision highp float;
uniform vec2 resolution; uniform vec2 resolution;
uniform vec2 mouse; uniform vec3 mouse;
uniform float time; uniform float time;
uniform float frame; uniform float frame;
uniform sampler2D backbuffer; `,
out vec4 outColor;
`, // TODO MRT
geekestStart: ` geekestStart: `
void main() { void main() {
`, `,
@ -34,22 +30,24 @@ void main() {
}; };
const DEMO_FS = ` const DEMO_FS = `
precision highp float;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
out vec4 outColor;
void main() { void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy; vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 col = .5 + .5 * cos(uv.xyx + time + vec3(0, 2, 4)); vec3 col = .5 + .5 * cos(uv.xyx + time + vec3(0, 2, 4));
float dist = distance(uv, mouse); float dist = distance(uv, mouse.xy);
float circle = smoothstep(.1, .2, dist) * .5 + .5; float circle = smoothstep(.1, .2, dist) * .5 + .5;
vec4 acolor = vec4(col * circle, circle); vec4 acolor = vec4(col * circle, circle);
outColor = vec4(acolor); outColor = vec4(acolor);
} }
`; `;
const DEFAULT_VS = `#version 300 es
in vec3 position;
void main(){
gl_Position=vec4(position, 1.);
}
`;
class RepaShader extends HTMLElement { class RepaShader extends HTMLElement {
constructor() { constructor() {
super(); super();
@ -94,12 +92,16 @@ class RepaShader extends HTMLElement {
this._gl.disable(this._gl.BLEND); this._gl.disable(this._gl.BLEND);
this._gl.clearColor(0,0,0,1); this._gl.clearColor(0,0,0,1);
this.render(this.getFragmentShaderSource()); this.render();
} }
render(source, time) { disconnectedCallback() {
// TODO stop animation
}
async render(source, time) {
if (!source) { if (!source) {
return; source = await this.getFragmentShaderSource();
} }
this._fsSource = source; this._fsSource = source;
this.reset(time); this.reset(time);
@ -161,10 +163,11 @@ class RepaShader extends HTMLElement {
this._gl.viewport(0, 0, width, height); this._gl.viewport(0, 0, width, height);
} }
_onMouseMove(e) { _onMouseEvent(e) {
const x = Math.min(Math.max(e.offsetX, 0), this._target.width); const x = Math.min(Math.max(e.offsetX, 0), this._target.width);
const y = Math.min(Math.max(e.offsetY, 0), this._target.height); const y = Math.min(Math.max(e.offsetY, 0), this._target.height);
this._mousePosition = [x / this._target.width, 1 - y / this._target.height]; const btn = e.buttons;
this._mousePosition = [x / this._target.width, 1 - y / this._target.height, btn];
} }
_resetBuffer(buff) { _resetBuffer(buff) {
@ -186,11 +189,15 @@ class RepaShader extends HTMLElement {
buff.renderbuffer = null; buff.renderbuffer = null;
} }
// texture // textures
if (buff.texture && this._gl.isTexture(buff.texture)) { if (buff.textures?.length) {
this._gl.bindTexture(this._gl.TEXTURE_2D, null); buff.textures.forEach(t => {
this._gl.deleteTexture(buff.texture); if (this._gl.isTexture(t)) {
buff.texture = null; this._gl.bindTexture(this._gl.TEXTURE_2D, null);
this._gl.deleteTexture(t);
}
});
buff.textures = null;
} }
} }
@ -207,15 +214,21 @@ class RepaShader extends HTMLElement {
this._gl.renderbufferStorage(this._gl.RENDERBUFFER, this._gl.DEPTH_COMPONENT16, w, h); this._gl.renderbufferStorage(this._gl.RENDERBUFFER, this._gl.DEPTH_COMPONENT16, w, h);
this._gl.framebufferRenderbuffer(this._gl.FRAMEBUFFER, this._gl.DEPTH_ATTACHMENT, this._gl.RENDERBUFFER, buff.renderbuffer); this._gl.framebufferRenderbuffer(this._gl.FRAMEBUFFER, this._gl.DEPTH_ATTACHMENT, this._gl.RENDERBUFFER, buff.renderbuffer);
// texture // textures
buff.texture = this._gl.createTexture(); buff.textures = [];
this._gl.bindTexture(this._gl.TEXTURE_2D, buff.texture); buff.buffers = []; // TODO ???
this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, w, h, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, null); for (let i = 0; i < this.mrt; i++) {
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR); let texture = this._gl.createTexture();
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR); this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE); this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, w, h, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, null);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE); this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
this._gl.framebufferTexture2D(this._gl.FRAMEBUFFER, this._gl.COLOR_ATTACHMENT0, this._gl.TEXTURE_2D, buff.texture, 0); this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
this._gl.framebufferTexture2D(this._gl.FRAMEBUFFER, this._gl.COLOR_ATTACHMENT0 + i, this._gl.TEXTURE_2D, texture, 0);
buff.textures.push(texture);
buff.buffers.push(this._gl.COLOR_ATTACHMENT0 + i);
}
// unbind // unbind
this._gl.bindTexture(this._gl.TEXTURE_2D, null); this._gl.bindTexture(this._gl.TEXTURE_2D, null);
@ -233,17 +246,19 @@ class RepaShader extends HTMLElement {
async reset(time) { async reset(time) {
this._resizeTarget(); this._resizeTarget();
this._resetBuffers(); this._resetBuffers();
this.backbuffer = this._createBuffer(this._target.width, this._target.height);
this.frontbuffer = this._createBuffer(this._target.width, this._target.height); this.frontbuffer = this._createBuffer(this._target.width, this._target.height);
this.backbuffer = this._createBuffer(this._target.width, this._target.height);
if (this.hasAttribute('mouse')) { if (this.hasAttribute('mouse')) {
this._target.addEventListener('pointermove', this._onMouseMove.bind(this)); this._target.addEventListener('mousemove', this._onMouseEvent.bind(this));
this._target.addEventListener('mousedown', this._onMouseEvent.bind(this));
this._target.addEventListener('mouseup', this._onMouseEvent.bind(this));
} }
this.mode = this.getAttribute('mode') || this.mode; this.mode = this.getAttribute('mode') || this.mode;
const program = this._gl.createProgram(); const program = this._gl.createProgram();
const vs = this._createShader(program, this.VS, true); const vs = this._createShader(program, await this.getVS(), true);
if (!vs) { if (!vs) {
return; return;
} }
@ -265,7 +280,7 @@ class RepaShader extends HTMLElement {
return; return;
} }
// TODO sound? mrt? textures? // TODO sound? textures?
if (this._program) { if (this._program) {
this._gl.deleteProgram(this._program); this._gl.deleteProgram(this._program);
@ -277,10 +292,14 @@ class RepaShader extends HTMLElement {
this._uniLocation.mouse = this._gl.getUniformLocation(this.program, 'mouse'); this._uniLocation.mouse = this._gl.getUniformLocation(this.program, 'mouse');
this._uniLocation.time = this._gl.getUniformLocation(this.program, 'time'); this._uniLocation.time = this._gl.getUniformLocation(this.program, 'time');
this._uniLocation.frame = this._gl.getUniformLocation(this.program, 'frame'); this._uniLocation.frame = this._gl.getUniformLocation(this.program, 'frame');
this._uniLocation.backbuffer = this._gl.getUniformLocation(this.program, 'backbuffer');
// backbuffers
for (let i = 0; i < this.mrt; i++) {
this._uniLocation[`backbuffer${i}`] = this._gl.getUniformLocation(this.program, `backbuffer${i}`);
}
this._attLocation = this._gl.getAttribLocation(this.program, 'position'); this._attLocation = this._gl.getAttribLocation(this.program, 'position');
this._mousePosition= [0, 0]; this._mousePosition= [0, 0, 0];
this._startTime = Date.now(); this._startTime = Date.now();
this._frame = 0; this._frame = 0;
@ -288,10 +307,6 @@ class RepaShader extends HTMLElement {
} }
draw(time) { draw(time) {
if (this.running) {
requestAnimationFrame(this.draw.bind(this));
}
if (time) { if (time) {
this._nowTime = time; this._nowTime = time;
} else { } else {
@ -305,15 +320,19 @@ class RepaShader extends HTMLElement {
this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this.frontbuffer.framebuffer); this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this.frontbuffer.framebuffer);
// backbuffer // backbuffer
this._gl.activeTexture(this._gl.TEXTURE0); this._gl.drawBuffers(this.frontbuffer.buffers);
this._gl.bindTexture(this._gl.TEXTURE_2D, this.backbuffer.texture);
this._gl.uniform1i(this._uniLocation.backbuffer, 0); for (let i = 0; i < this.mrt; i++) {
this._gl.activeTexture(this._gl.TEXTURE0 + i);
this._gl.bindTexture(this._gl.TEXTURE_2D, this.backbuffer.textures[i]);
this._gl.uniform1i(this._uniLocation[`backbuffer${i}`], i);
}
this._gl.enableVertexAttribArray(this._attLocation); this._gl.enableVertexAttribArray(this._attLocation);
this._gl.vertexAttribPointer(this._attLocation, 3, this._gl.FLOAT, false, 0, 0); this._gl.vertexAttribPointer(this._attLocation, 3, this._gl.FLOAT, false, 0, 0);
this._gl.clear(this._gl.COLOR_BUFFER_BIT); this._gl.clear(this._gl.COLOR_BUFFER_BIT);
this._gl.uniform2fv(this._uniLocation.resolution, [this._target.width, this._target.height]); this._gl.uniform2fv(this._uniLocation.resolution, [this._target.width, this._target.height]);
this._gl.uniform2fv(this._uniLocation.mouse, this._mousePosition); this._gl.uniform3fv(this._uniLocation.mouse, this._mousePosition);
this._gl.uniform1f(this._uniLocation.time, this._nowTime * .001); this._gl.uniform1f(this._uniLocation.time, this._nowTime * .001);
this._gl.uniform1f(this._uniLocation.frame, this._frame); this._gl.uniform1f(this._uniLocation.frame, this._frame);
@ -323,7 +342,7 @@ class RepaShader extends HTMLElement {
this._gl.useProgram(this._postProgram); this._gl.useProgram(this._postProgram);
this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null); this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null);
this._gl.activeTexture(this._gl.TEXTURE0); this._gl.activeTexture(this._gl.TEXTURE0);
this._gl.bindTexture(this._gl.TEXTURE_2D, this.frontbuffer.texture); this._gl.bindTexture(this._gl.TEXTURE_2D, this.frontbuffer.textures[0]);
this._gl.enableVertexAttribArray(this._postAttLocation); this._gl.enableVertexAttribArray(this._postAttLocation);
this._gl.vertexAttribPointer(this._postAttLocation, 3, this._gl.FLOAT, false, 0, 0); this._gl.vertexAttribPointer(this._postAttLocation, 3, this._gl.FLOAT, false, 0, 0);
this._gl.clear(this._gl.COLOR_BUFFER_BIT); this._gl.clear(this._gl.COLOR_BUFFER_BIT);
@ -336,6 +355,10 @@ class RepaShader extends HTMLElement {
[this.frontbuffer, this.backbuffer] = [this.backbuffer, this.frontbuffer]; [this.frontbuffer, this.backbuffer] = [this.backbuffer, this.frontbuffer];
// TODO draw callback // TODO draw callback
if (this.running) {
requestAnimationFrame(this.draw.bind(this));
}
} }
run() { run() {
@ -377,11 +400,11 @@ class RepaShader extends HTMLElement {
} }
get width() { get width() {
return this.getAttribute('width'); return parseInt(this.getAttribute('width'), 10);
} }
get height() { get height() {
return this.getAttribute('height'); return parseInt(this.getAttribute('height'), 10);
} }
_createTarget() { _createTarget() {
@ -412,12 +435,8 @@ class RepaShader extends HTMLElement {
return this._target; return this._target;
} }
get VS() { get mrt() {
return `#version 300 es return +this.getAttribute('render-target-count') || 1;
in vec3 position;
void main(){
gl_Position=vec4(position, 1.);
}`;
} }
get postVS() { get postVS() {
@ -441,13 +460,59 @@ void main() {
}`; }`;
} }
_getRenderTargets() {
const targets = [];
if (this.mrt > 1) {
for (let i = 0; i < this.mrt; i++) {
const target = `
#define b${i} backbuffer${i}
#define o${i} outColor${i}
uniform sampler2D backbuffer${i};
layout (location = ${i}) out vec4 outColor${i};
`;
targets.push(target);
}
} else {
targets.push(`
#define b backbuffer0
#define o outColor0
#define backbuffer backbuffer0
#define outColor outColor0
uniform sampler2D backbuffer0;
layout (location = 0) out vec4 outColor0;
`);
}
return targets.join('');
}
async getVS() {
let source = '';
const vsEl = this.querySelector('script[type="x-shader/x-vertex"]');
if (vsEl) {
const vsSrc = vsEl.getAttribute('src');
if (vsSrc) {
source = await fetch(vsSrc).then(res => res.text());
} else {
source = vsEl.textContent.trim();
}
}
if (!source) {
source = DEFAULT_VS;
}
return source;
}
async getFS() { async getFS() {
// auto guessing mode // auto guessing mode
// - starts with #version -> raw // - starts with #version -> raw
// - contains `precision` -> twigl classic300es // - contains `precision` -> twigl classic300es
// - no `precision`, but has `main()` -> twigl geeker300es // - no `precision`, but has `main()` -> twigl geeker300es
// - no `precision`, no `main()` -> twigl geekest300es // - no `precision`, no `main()` -> twigl geekest300es
// TODO: mrt
let mode = this.mode; let mode = this.mode;
if (!mode) { if (!mode) {
const hasVersion = this._fsSource.startsWith('#version'); const hasVersion = this._fsSource.startsWith('#version');
@ -476,12 +541,14 @@ void main() {
break; break;
case 'geeker': case 'geeker':
snippets = await this._getSnippets(); snippets = await this._getSnippets();
start = CHUNKS.es300 + CHUNKS.geeker + snippets; start = CHUNKS.es300 + CHUNKS.geeker + this._getRenderTargets() + snippets;
break; break;
case 'geekest': case 'geekest':
snippets = await this._getSnippets(); snippets = await this._getSnippets();
const noise = await this.getSnippet('noise.glsl'); if (!snippets) {
start = CHUNKS.es300 + CHUNKS.geeker + noise + snippets + CHUNKS.geekestStart; snippets = await this.getSnippet('noise.glsl');
}
start = CHUNKS.es300 + CHUNKS.geeker + this._getRenderTargets() + snippets + CHUNKS.geekestStart;
end = CHUNKS.geekestEnd; end = CHUNKS.geekestEnd;
break; break;
} }
@ -489,7 +556,7 @@ void main() {
return `${start}\n${this._fsSource}\n${end}`; return `${start}\n${this._fsSource}\n${end}`;
} }
getFragmentShaderSource() { async getFragmentShaderSource() {
let source = ''; let source = '';
// text area editor // text area editor
@ -506,7 +573,12 @@ void main() {
if (!source) { if (!source) {
const fsEl = this.querySelector('script[type="x-shader/x-fragment"]'); const fsEl = this.querySelector('script[type="x-shader/x-fragment"]');
if (fsEl) { if (fsEl) {
source = fsEl.textContent; const fsSrc = fsEl.getAttribute('src');
if (fsSrc) {
source = await fetch(fsSrc).then(res => res.text());
} else {
source = fsEl.textContent.trim();
}
} }
} }