我一直在尝试将着色器从ShaderToy移植到常规WebGL,然后碰壁。我设法使多遍着色器起作用,但没有使自己作为输入通道的着色器。我在这里做了一个测试示例:https://www.shadertoy.com/view/WsfSzj。着色器有两遍,均以缓冲区A为输入。据我了解,当缓冲区以自身作为输入时,它将使用上一时间步长的自身输出。

因此,我尝试使用此结构制作两个rendertargets:

class RenderTarget {
   tex: Texture
   fbo: FrameBuffer
}


使用渲染循环:

gl.useProgram(prog1)
gl.bindFramebuffer(gl.TEXTURE_2D, rt1.fbo)
gl.bindTexture(gl.TEXTURE_2D, rt2.texture);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

gl.useProgram(prog2)
gl.bindFramebuffer(gl.TEXTURE_2D, null)
gl.bindTexture(gl.TEXTURE_2D, rt1.texture);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

swap(rt1, rt2)


但是,这仅渲染底部条,而不渲染粒子,我不知道为什么。我在这里有一个缩小的示例:https://jsfiddle.net/f7jv8s6y/7/



const fsSource = document.getElementById("shader-fs").text;

class RenderTarget {
  constructor(gl, width, height) {
    this.texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.texture);
    this.level = 0;
    const internalFormat = gl.RGBA;
    const border = 0;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    const data = new Uint8Array(width * height * 4);

    gl.texImage2D(gl.TEXTURE_2D, this.level, internalFormat, width, height, border, format, type, data);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    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);
    this.fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
  }
}

function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    const info = String(gl.getShaderInfoLog(shader));
    console.log(info, source);
    gl.deleteShader(shader);
    return info;
  }
  return shader;
}

function initProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  return shaderProgram;
}

function initQuad(gl) {
  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  const positions = [-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  return positionBuffer;
}


const vs = `#version 300 es
in vec2 aVertexPosition;
out vec2 uv;
void main() {
  uv = aVertexPosition;
  gl_Position =  vec4(aVertexPosition, 0., 1.0);
}`;


const fs = `#version 300 es
precision highp float;
uniform sampler2D iChannel0;
out vec4 fColor;
void main() {
vec2 p = gl_FragCoord.xy / vec2(420., 320.);
  fColor =  texture(iChannel0, p);
}`;

function init() {
  // Set up canvas and webgl2 context
  const width = 420;
  const height = 320;
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const gl = canvas.getContext("webgl2");
  document.body.appendChild(canvas);

  // Compile shaders and set up two render targets
  const prog1 = initProgram(gl, vs, fsSource);
  const prog2 = initProgram(gl, vs, fs);
  const rt1 = new RenderTarget(gl, width, height);
  const rt2 = new RenderTarget(gl, width, height);
  const rts = [rt1, rt2];

  // Bind vertex quad
  gl.useProgram(prog1);
  const quadPos = gl.getAttribLocation(prog1, 'aVertexPosition');
  const quad = initQuad(gl);
  gl.bindBuffer(gl.ARRAY_BUFFER, quad);
  gl.vertexAttribPointer(quadPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(quadPos);
  gl.viewport(0, 0, width, height);
  // Set up needed uniforms
  const iTimePos = gl.getUniformLocation(prog1, "iTime");
  const iTimeDeltaPos = gl.getUniformLocation(prog1, "iTimeDelta");
  const iFramePos = gl.getUniformLocation(prog1, "iFrame");
  const iResolutionPos = gl.getUniformLocation(prog1, "iResolution");
  const iChannelResolutionPos = gl.getUniformLocation(prog1, "iChannelResolution");
  const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
  gl.uniform2f(iResolutionPos, width, height);
  gl.uniform3fv(iChannelResolutionPos, new Float32Array(resos));
  const prog1Channel0Pos = gl.getUniformLocation(prog1, "iChannel0");
  gl.useProgram(prog2);
  const prog2Channel0Pos = gl.getUniformLocation(prog2, "iChannel0");

  let frame = 0;
  let time = 0;
  let lastTime = 0;

  let setTexture = function(gl, tex, location, spot) {
    gl.activeTexture(gl.TEXTURE0 + spot);
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.uniform1i(location, spot);
  }

  let animate = function() {
    time = performance.now() / 1000;
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    // FIRST RENDER PASS
    gl.useProgram(prog1);
    gl.uniform1f(iTimePos, time);
    gl.uniform1i(iFramePos, frame);
    gl.uniform1f(iTimeDeltaPos, time - lastTime);
    gl.bindFramebuffer(gl.FRAMEBUFFER, rts[0].fbo);
    gl.bindTexture(gl.TEXTURE_2D, rts[1].texture);
    //setTexture(gl, rts[1].texture, prog1Channel0Pos, 0);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    //SECOND RENDER PASS
    gl.useProgram(prog2);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.bindTexture(gl.TEXTURE_2D, rts[0].texture);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    //setTexture(gl, rts[0].texture, prog2Channel0Pos, 0);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    rts.reverse();
    lastTime = time;
    frame++;

    requestAnimationFrame(animate);
  }
  lastTime = performance.now();
  animate();
}


init();

<script type="x-shader/x-fragment" id="shader-fs" src="util/fs">#version 300 es
#ifdef GL_ES
precision highp float;
precision highp int;
precision mediump sampler3D;
#endif
uniform vec3 iChannelResolution[4];
uniform float iTime;
uniform float iTimeDelta;
uniform float timeDelta;
uniform vec2 iResolution;
uniform vec4 iMouse;
uniform int iFrame;
in vec2 uv;
out vec4 fColor;

uniform sampler2D iChannel0;
// The MIT License
// Copyright © 2018 Ian Reichert-Watts
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// SHARED PARAMS (Must be same as Image :/)
const int NUM_PARTICLES = 64;
const float INTERACT_DATA_INDEX = float(NUM_PARTICLES)+1.0;
const float KINETIC_MOUSE_INDEX = INTERACT_DATA_INDEX+1.0;

// SHARED FUNCTIONS (Must be same as Image :/)
vec4 loadData( in float index )
{
    return texture( iChannel0, vec2((index+0.5)/iChannelResolution[0].x,0.0), -100.0 );
}

float floorHeight( in vec3 p )
{
    return (sin(p.z*0.00042)*0.2)+(sin(p.z*0.008)*0.64) + (sin(p.x*0.42+sin(p.z*0.000042)*420.0))*0.42-1.0;
}

// PARAMS
const float PARTICLE_LIFETIME_MIN = 0.02;
const float PARTICLE_LIFETIME_MAX = 4.2;
const float FALL_SPEED = 42.0;
const float JITTER_SPEED = 300.0;
const vec3 WIND_DIR = vec3(0.0,0.0,-1.0);
const float WIND_INTENSITY = 4.2;

// CONST
const float PI = 3.14159;
const float TAU = PI * 2.0;

float randFloat( in float n )
{
    return fract( sin( n*64.19 )*420.82 );
}
vec2 randVec2( in vec2 n )
{
    return vec2(randFloat( n.x*12.95+n.y*43.72 ),randFloat( n.x*16.21+n.y*90.23 ));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    if ( fragCoord.y > iResolution.y-2.0 )
    {
        // Discard top pixels to avoid persistent data getting included in blur
        discard;
    }
    else if ( fragCoord.y < 2.0 )
    {
        if ( fragCoord.y >= 1.0 || fragCoord.x > float(NUM_PARTICLES+4) )
        {
            discard;
        }
        // Store persistent data in bottom pixel row
        if ( fragCoord.x < float(NUM_PARTICLES) )
        {
            vec4 particle;
            float pidx = floor(fragCoord.x);

            if ( iFrame == 0 )
            {
                float padding = 0.01;
                float particleStep = (1.0-(padding*2.0))/float(NUM_PARTICLES);
                particle = vec4(0.0);
                float r1 = randFloat(pidx);
                particle.xy = vec2(padding+(particleStep*pidx), 1.0+(1.0*r1));
                particle.xy *= iResolution.xy;
                particle.a = r1*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
            }
            else
            {
               	vec4 interactData = loadData(INTERACT_DATA_INDEX);

                // Tick particles
        		particle = loadData(pidx);
                vec2 puv = particle.xy / iResolution.x;
                vec4 pbuf = texture(iChannel0, puv);

                // Camera must be the same as Image :/
                float rotYaw = -(interactData.x/iResolution.x)*TAU;
                float rotPitch = (interactData.y/iResolution.y)*PI;
                vec3 rayOrigin = vec3(0.0, 0.1, iTime*80.0);
                float floorY = floorHeight(rayOrigin);
                rayOrigin.y = floorY*0.9 + 0.2;

                vec3 forward = normalize( vec3(sin(rotYaw), rotPitch, cos(rotYaw)) );
                vec3 wup = normalize(vec3((floorY-floorHeight(rayOrigin+vec3(2.0,0.0,0.0)))*-0.2,1.0,0.0));
                vec3 right = normalize( cross( forward, wup ) );
                vec3 up = normalize( cross( right, forward ) );
                mat3 camMat = mat3(right, up, forward);

                vec3 surfforward = normalize( vec3(sin(rayOrigin.z*0.01)*0.042, ((floorY-floorHeight(rayOrigin+vec3(0.0,0.0,-20.0)))*0.2)+0.12, 1.0) );
                vec3 wright = vec3(1.0,0.0,0.0);
                mat3 surfMat = mat3(wright, up, surfforward);

                vec2 centeredCoord = puv-vec2(0.5);
                vec3 rayDir = normalize( surfMat*normalize( camMat*normalize( vec3(centeredCoord, 1.0) ) ) );
                vec3 rayRight = normalize( cross( rayDir, up ) );
                vec3 rayUp = normalize( cross( rayRight, rayDir ) );

                // Wind
                vec2 windShield = (puv-vec2(0.5, 0.0))*2.0;
                float speedScale = 0.0015*(0.1+1.9*(sin(PI*0.5*pow( particle.z/particle.a, 2.0 ))))*iResolution.y;
                particle.x += (windShield.x+WIND_INTENSITY*dot(rayRight, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;
                particle.y += (windShield.y+WIND_INTENSITY*dot(rayUp, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;

                // Jitter
                particle.xy += 0.001*(randVec2( particle.xy+iTime )-vec2(0.5))*iResolution.y*JITTER_SPEED*iTimeDelta;

                // Age
                // Don't age as much when traveling over existing particle trails
                particle.z += (1.0-pbuf.b)*iTimeDelta;

                // Die of old age. Reset
                if ( particle.z > particle.a )
                {
                    float seedX = particle.x*25.36+particle.y*42.92;
                    float seedY = particle.x*16.78+particle.y*93.42;
                    particle = vec4(0.0);
                    particle.x = randFloat( seedX )*iResolution.x;
                    particle.y = randFloat( seedY )*iResolution.y;
                    particle.a = PARTICLE_LIFETIME_MIN+randFloat(pidx)*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
                }
            }
            fragColor = particle;
        }
		else
        {
            float dataIndex = floor(fragCoord.x);
            vec4 interactData = loadData(INTERACT_DATA_INDEX);
            vec4 kineticMouse = loadData(KINETIC_MOUSE_INDEX);

            if ( iMouse.z > 0.0 )
            {
            	vec2 mouseDelta = iMouse.xy-kineticMouse.xy;
                if ( length(iMouse.xy-iMouse.zw) < 4.0 )
                {
                    mouseDelta = vec2(0.0);
                }
                interactData.xy += mouseDelta;
                interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
                kineticMouse = vec4(iMouse.xy, mouseDelta);
            }
            else
            {
                kineticMouse.zw *= 0.9;
                interactData.xy += kineticMouse.zw;
                interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
                kineticMouse.xy = iMouse.xy;
            }
            fragColor = (dataIndex == KINETIC_MOUSE_INDEX) ? kineticMouse : interactData;
        }
    }
    else
    {
        // Draw Particles
        vec2 blurUV = fract( (fragCoord.xy + (fract( float(iFrame)*0.5 )*2.0-0.5)) / iResolution.xy );
        vec2 uv = fragCoord.xy / iResolution.xy;
        fragColor = texture( iChannel0, uv );
        vec4 prevColor = fragColor;

        if ( fragColor.a < 1.0 )
        {
            fragColor = texture( iChannel0, blurUV );
        }
        fragColor.b *= 0.996;

        for ( int i=0; i<NUM_PARTICLES; i++ )
        {
    		vec4 particle = loadData(float(i));
            vec2 delta = fragCoord.xy-particle.xy;
            float dist = length(delta);
            float radius = 0.002*(0.5+2.0*particle.a+abs(sin(1.0*iTime+float(i))))*iResolution.y;
            radius += 4.0*randFloat( particle.x*35.26+particle.y*93.12 )*pow((particle.z/particle.a), 12.0);
            if ( dist < radius )
            {
                // normal
                vec2 dir = delta/dist;
                fragColor.r = dot(dir, vec2(1.0,0.0))*0.5+0.5;
                fragColor.g = dot(dir, vec2(0.0,1.0))*0.5+0.5;
                // height
                float height = sin( dist/radius*PI*0.5 );
                height = pow( height, 8.0 );
                height = 1.0-height;
                fragColor.b = max( height, prevColor.b );
                // age
                fragColor.a = 0.0;
            }
        }
        fragColor.a += 0.1*iTimeDelta;
    }
}void main() {
  vec4 color = vec4(0.0,0.0,0.0,1.0);
  mainImage(color, gl_FragCoord.xy);
  color.w = 1.0;
  fColor = color;
}
</script>





有没有人指出出了什么问题?

最佳答案

问题是您需要在帧缓冲区中使用FLOATHALF_FLOAT纹理。

请注意,能够渲染到FLOATHALF_FLOAT是WebGL2的可选功能,因此您必须检查并启用EXT_color_buffer_floatEXT_color_buffer_half_float

过滤FLOATHALF_FLOAT纹理也是一项可选功能。我不知道Shadertoy是使用NEAREST还是LINEAR。我在下面将内容设置为NEAREST。如果要使用LINEAR,则还需要检查并启用OES_texture_float_linearOES_texture_half_float_linear

如果您好奇我是怎么想的...

首先,我通过添加来验证第二遍是否可以正常工作

 fColor = vec4(p, 0, 1);


在第二遍着色器的底部。

那行得通,所以接下来我在第一遍着色器中尝试了类似的操作

 fragColor = vec4(fragCoord / iResolution, 0, 1);
 return;


那行得通,因此我接下来看了着色器。我看到它分为三部分。一部分跳过了顶行,所以我在其中放置了fragColor = vec4(1,0,0,1),以进行健全性检查,以验证其是否按预期运行

然后下一部分进行粒子模拟,除了足以理解它只是使用最下面一行进行存储之外,我已经跳过了。

最后一部分进行模糊处理,因此我再次输入fragColor = vec4(0,1,0,1)只是为了验证该区域是否变为绿色。它做了。

因此,考虑到应该模糊,我添加了一些代码来使用

gl.bindFramebuffer(gl.FRAMEBUFFER, rts[1].fbo);
gl.enable(gl.SCISSOR_TEST);
gl.scissor(100, 100, 10, 10);
gl.clearColor(.2, .5, .7, .9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.SCISSOR_TEST);


我在渲染循环中尝试过,然后仅在初始化时尝试过。当我看到这个小矩形根本没有受到影响时,它想到了可能是什么问题,那时候我以为是8bit渲染目标。我检查了shadertoy网站上的文档,但文档中没有任何内容(shadertoy.com上的文档极其糟糕)。 This source是从shadertoy.com链接的。它用于iOS应用程序,而不是网站,所以我searched for glTexImage2D看看它们是否全部使用浮点纹理。答案是“是”,因此我尝试了一下,至少得到了一些似乎可以解决问题的结果。

有关代码的一些建议


如果您没有在纹理中放置任何数据,则没有理由将数组传递给gl.texImage2D。只需传递null
没有理由在gl.uniform中使用类型化数组,因此这段代码

const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
gl.uniform3fv(iChannelResolutionPos, new Float32Array(resos));


可能只是

const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
gl.uniform3fv(iChannelResolutionPos, resos);

gl.getShaderInfoLog已经返回一个字符串,因此

const info = String(gl.getShaderInfoLog(shader));


可能只是

const info = gl.getShaderInfoLog(shader);

initProgram代码不检查链接错误。

链接错误可能由于任何原因发生。最常见的是变化不匹配,但是规范实际上说,只要着色器编译失败,只要链接失败,着色器编译就总是可以成功。

gl.linkProgram(prg);
const success = gl.getProgramParameter(gl.LINK_STATUS);
if (!success) {
  console.log(gl.getProgramInfoLog(prg);
}





const fsSource = document.getElementById("shader-fs").text;

class RenderTarget {
  constructor(gl, width, height) {
    this.texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.texture);
    this.level = 0;
    const internalFormat = gl.RGBA32F;
    const border = 0;
    const format = gl.RGBA;
    const type = gl.FLOAT;
    const data = null;

    gl.texImage2D(gl.TEXTURE_2D, this.level, internalFormat, width, height, border, format, type, data);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    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);
    this.fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
  }
}

function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    const info = String(gl.getShaderInfoLog(shader));
    console.log(info, source);
    gl.deleteShader(shader);
    return info;
  }
  return shader;
}

function initProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  return shaderProgram;
}

function initQuad(gl) {
  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  const positions = [-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  return positionBuffer;
}


const vs = `#version 300 es
in vec2 aVertexPosition;
out vec2 uv;
void main() {
  uv = aVertexPosition;
  gl_Position =  vec4(aVertexPosition, 0., 1.0);
}`;


const fs = `#version 300 es
precision highp float;
uniform sampler2D iChannel0;
out vec4 fColor;
void main() {
vec2 p = gl_FragCoord.xy / vec2(420., 320.);
  fColor =  texture(iChannel0, p);
}`;

function init() {
  // Set up canvas and webgl2 context
  const width = 420;
  const height = 320;
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const gl = canvas.getContext("webgl2");
  document.body.appendChild(canvas);
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    return alert('need EXT_color_buffer_float');
  }

  // Compile shaders and set up two render targets
  const prog1 = initProgram(gl, vs, fsSource);
  const prog2 = initProgram(gl, vs, fs);
  const rt1 = new RenderTarget(gl, width, height);
  const rt2 = new RenderTarget(gl, width, height);
  const rts = [rt1, rt2];

  // Bind vertex quad
  gl.useProgram(prog1);
  const quadPos = gl.getAttribLocation(prog1, 'aVertexPosition');
  const quad = initQuad(gl);
  gl.bindBuffer(gl.ARRAY_BUFFER, quad);
  gl.vertexAttribPointer(quadPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(quadPos);
  gl.viewport(0, 0, width, height);
  // Set up needed uniforms
  const iTimePos = gl.getUniformLocation(prog1, "iTime");
  const iTimeDeltaPos = gl.getUniformLocation(prog1, "iTimeDelta");
  const iFramePos = gl.getUniformLocation(prog1, "iFrame");
  const iResolutionPos = gl.getUniformLocation(prog1, "iResolution");
  const iChannelResolutionPos = gl.getUniformLocation(prog1, "iChannelResolution");
  const resos = [width, height, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
  gl.uniform2f(iResolutionPos, width, height);
  gl.uniform3fv(iChannelResolutionPos, new Float32Array(resos));
  const prog1Channel0Pos = gl.getUniformLocation(prog1, "iChannel0");
  gl.useProgram(prog2);
  const prog2Channel0Pos = gl.getUniformLocation(prog2, "iChannel0");

  let frame = 0;
  let time = 0;
  let lastTime = 0;

  let setTexture = function(gl, tex, location, spot) {
    gl.activeTexture(gl.TEXTURE0 + spot);
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.uniform1i(location, spot);
  }

  let animate = function() {
    time = performance.now() / 1000;
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    // FIRST RENDER PASS
    gl.useProgram(prog1);
    gl.uniform1f(iTimePos, time);
    gl.uniform1i(iFramePos, frame);
    gl.uniform1f(iTimeDeltaPos, time - lastTime);
    gl.bindFramebuffer(gl.FRAMEBUFFER, rts[0].fbo);
    gl.bindTexture(gl.TEXTURE_2D, rts[1].texture);
    //setTexture(gl, rts[1].texture, prog1Channel0Pos, 0);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    //SECOND RENDER PASS
    gl.useProgram(prog2);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.bindTexture(gl.TEXTURE_2D, rts[0].texture);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    //setTexture(gl, rts[0].texture, prog2Channel0Pos, 0);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    rts.reverse();
    lastTime = time;
    frame++;

    requestAnimationFrame(animate);
  }
  lastTime = performance.now();
  animate();
}


init();

<script type="x-shader/x-fragment" id="shader-fs" src="util/fs">#version 300 es
#ifdef GL_ES
precision highp float;
precision highp int;
precision mediump sampler3D;
#endif
uniform vec3 iChannelResolution[4];
uniform float iTime;
uniform float iTimeDelta;
uniform float timeDelta;
uniform vec2 iResolution;
uniform vec4 iMouse;
uniform int iFrame;
in vec2 uv;
out vec4 fColor;

uniform sampler2D iChannel0;
// The MIT License
// Copyright © 2018 Ian Reichert-Watts
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// SHARED PARAMS (Must be same as Image :/)
const int NUM_PARTICLES = 64;
const float INTERACT_DATA_INDEX = float(NUM_PARTICLES)+1.0;
const float KINETIC_MOUSE_INDEX = INTERACT_DATA_INDEX+1.0;

// SHARED FUNCTIONS (Must be same as Image :/)
vec4 loadData( in float index )
{
    return texture( iChannel0, vec2((index+0.5)/iChannelResolution[0].x,0.0), -100.0 );
}

float floorHeight( in vec3 p )
{
    return (sin(p.z*0.00042)*0.2)+(sin(p.z*0.008)*0.64) + (sin(p.x*0.42+sin(p.z*0.000042)*420.0))*0.42-1.0;
}

// PARAMS
const float PARTICLE_LIFETIME_MIN = 0.02;
const float PARTICLE_LIFETIME_MAX = 4.2;
const float FALL_SPEED = 42.0;
const float JITTER_SPEED = 300.0;
const vec3 WIND_DIR = vec3(0.0,0.0,-1.0);
const float WIND_INTENSITY = 4.2;

// CONST
const float PI = 3.14159;
const float TAU = PI * 2.0;

float randFloat( in float n )
{
    return fract( sin( n*64.19 )*420.82 );
}
vec2 randVec2( in vec2 n )
{
    return vec2(randFloat( n.x*12.95+n.y*43.72 ),randFloat( n.x*16.21+n.y*90.23 ));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    if ( fragCoord.y > iResolution.y-2.0 )
    {
        // Discard top pixels to avoid persistent data getting included in blur
        discard;
    }
    else if ( fragCoord.y < 2.0 )
    {
        if ( fragCoord.y >= 1.0 || fragCoord.x > float(NUM_PARTICLES+4) )
        {
            discard;
        }
        // Store persistent data in bottom pixel row
        if ( fragCoord.x < float(NUM_PARTICLES) )
        {
            vec4 particle;
            float pidx = floor(fragCoord.x);

            if ( iFrame == 0 )
            {
                float padding = 0.01;
                float particleStep = (1.0-(padding*2.0))/float(NUM_PARTICLES);
                particle = vec4(0.0);
                float r1 = randFloat(pidx);
                particle.xy = vec2(padding+(particleStep*pidx), 1.0+(1.0*r1));
                particle.xy *= iResolution.xy;
                particle.a = r1*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
            }
            else
            {
               	vec4 interactData = loadData(INTERACT_DATA_INDEX);

                // Tick particles
        		particle = loadData(pidx);
                vec2 puv = particle.xy / iResolution.x;
                vec4 pbuf = texture(iChannel0, puv);

                // Camera must be the same as Image :/
                float rotYaw = -(interactData.x/iResolution.x)*TAU;
                float rotPitch = (interactData.y/iResolution.y)*PI;
                vec3 rayOrigin = vec3(0.0, 0.1, iTime*80.0);
                float floorY = floorHeight(rayOrigin);
                rayOrigin.y = floorY*0.9 + 0.2;

                vec3 forward = normalize( vec3(sin(rotYaw), rotPitch, cos(rotYaw)) );
                vec3 wup = normalize(vec3((floorY-floorHeight(rayOrigin+vec3(2.0,0.0,0.0)))*-0.2,1.0,0.0));
                vec3 right = normalize( cross( forward, wup ) );
                vec3 up = normalize( cross( right, forward ) );
                mat3 camMat = mat3(right, up, forward);

                vec3 surfforward = normalize( vec3(sin(rayOrigin.z*0.01)*0.042, ((floorY-floorHeight(rayOrigin+vec3(0.0,0.0,-20.0)))*0.2)+0.12, 1.0) );
                vec3 wright = vec3(1.0,0.0,0.0);
                mat3 surfMat = mat3(wright, up, surfforward);

                vec2 centeredCoord = puv-vec2(0.5);
                vec3 rayDir = normalize( surfMat*normalize( camMat*normalize( vec3(centeredCoord, 1.0) ) ) );
                vec3 rayRight = normalize( cross( rayDir, up ) );
                vec3 rayUp = normalize( cross( rayRight, rayDir ) );

                // Wind
                vec2 windShield = (puv-vec2(0.5, 0.0))*2.0;
                float speedScale = 0.0015*(0.1+1.9*(sin(PI*0.5*pow( particle.z/particle.a, 2.0 ))))*iResolution.y;
                particle.x += (windShield.x+WIND_INTENSITY*dot(rayRight, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;
                particle.y += (windShield.y+WIND_INTENSITY*dot(rayUp, WIND_DIR))*FALL_SPEED*speedScale*iTimeDelta;

                // Jitter
                particle.xy += 0.001*(randVec2( particle.xy+iTime )-vec2(0.5))*iResolution.y*JITTER_SPEED*iTimeDelta;

                // Age
                // Don't age as much when traveling over existing particle trails
                particle.z += (1.0-pbuf.b)*iTimeDelta;

                // Die of old age. Reset
                if ( particle.z > particle.a )
                {
                    float seedX = particle.x*25.36+particle.y*42.92;
                    float seedY = particle.x*16.78+particle.y*93.42;
                    particle = vec4(0.0);
                    particle.x = randFloat( seedX )*iResolution.x;
                    particle.y = randFloat( seedY )*iResolution.y;
                    particle.a = PARTICLE_LIFETIME_MIN+randFloat(pidx)*(PARTICLE_LIFETIME_MAX-PARTICLE_LIFETIME_MIN);
                }
            }
            fragColor = particle;
        }
		else
        {
            float dataIndex = floor(fragCoord.x);
            vec4 interactData = loadData(INTERACT_DATA_INDEX);
            vec4 kineticMouse = loadData(KINETIC_MOUSE_INDEX);

            if ( iMouse.z > 0.0 )
            {
            	vec2 mouseDelta = iMouse.xy-kineticMouse.xy;
                if ( length(iMouse.xy-iMouse.zw) < 4.0 )
                {
                    mouseDelta = vec2(0.0);
                }
                interactData.xy += mouseDelta;
                interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
                kineticMouse = vec4(iMouse.xy, mouseDelta);
            }
            else
            {
                kineticMouse.zw *= 0.9;
                interactData.xy += kineticMouse.zw;
                interactData.y = clamp( interactData.y, -iResolution.y, iResolution.y );
                kineticMouse.xy = iMouse.xy;
            }
            fragColor = (dataIndex == KINETIC_MOUSE_INDEX) ? kineticMouse : interactData;
        }
    }
    else
    {
        // Draw Particles
        vec2 blurUV = fract( (fragCoord.xy + (fract( float(iFrame)*0.5 )*2.0-0.5)) / iResolution.xy );
        vec2 uv = fragCoord.xy / iResolution.xy;
        fragColor = texture( iChannel0, uv );
        vec4 prevColor = fragColor;

        if ( fragColor.a < 1.0 )
        {
            fragColor = texture( iChannel0, blurUV );
        }
        fragColor.b *= 0.996;

        for ( int i=0; i<NUM_PARTICLES; i++ )
        {
    		vec4 particle = loadData(float(i));
            vec2 delta = fragCoord.xy-particle.xy;
            float dist = length(delta);
            float radius = 0.002*(0.5+2.0*particle.a+abs(sin(1.0*iTime+float(i))))*iResolution.y;
            radius += 4.0*randFloat( particle.x*35.26+particle.y*93.12 )*pow((particle.z/particle.a), 12.0);
            if ( dist < radius )
            {
                // normal
                vec2 dir = delta/dist;
                fragColor.r = dot(dir, vec2(1.0,0.0))*0.5+0.5;
                fragColor.g = dot(dir, vec2(0.0,1.0))*0.5+0.5;
                // height
                float height = sin( dist/radius*PI*0.5 );
                height = pow( height, 8.0 );
                height = 1.0-height;
                fragColor.b = max( height, prevColor.b );
                // age
                fragColor.a = 0.0;
            }
        }
        fragColor.a += 0.1*iTimeDelta;
    }
}void main() {
  vec4 color = vec4(0.0,0.0,0.0,1.0);
  mainImage(color, gl_FragCoord.xy);
  color.w = 1.0;
  fColor = color;
}
</script>

09-18 00:22