如何在WebGL中使用缩放着色器?

时间:2017-10-12 16:33:30

标签: javascript webgl shader scaling

我试图在WebGL中使用缩放着色器(特别是super-xbr),但是我无法正确地将数据传递给着色器,因此它进行了正确的插值。我已经将GLSL着色器转换为相应的顶点和片段着色器,并且在我尝试使用它们进行缩放时,它们都运行良好。

这就是我定义gl_Position以获得两倍大的图像(这可能是我犯错的地方):

        // Added 2x multiplication to the original code
        vec2 zeroToOne = a_position / u_resolution * 2.0;
        vec2 zeroToTwo = zeroToOne * 2.0;
        vec2 clipSpace = zeroToTwo - 1.0;
        gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

这可以完成工作,但着色器不会插入图像。这是结果(顶部是具有最近邻居插值的源,底部是程序的结果):

Test result

这是近似的预期结果:

enter image description here

程序本身基本上是WebGL2 fundamentals tutorial的副本,它执行基本的矩形绘制。我改变的唯一一件事就是让画布大两倍。

下面的完整代码(在高dpi监视器上可以更好地查看结果)

!(function(window, document, undefined) {
    "use strict";

    function main() {
        var image = document.getElementsByTagName('img')[0]; // MUST BE SAME DOMAIN!!!
        image.onload = function() {
            render(image);
        }
    }

    function render(image) {
        // Get A WebGL context
        /** @type {HTMLCanvasElement} */
        var canvas = document.getElementById("canvas");
        canvas.width = image.naturalWidth * 2;
        canvas.height = image.naturalHeight * 2;
        canvas.style.width = image.naturalWidth + 'px';
        canvas.style.height = image.naturalHeight + 'px';        
        var gl = canvas.getContext("webgl2");
        if (!gl) {
            return;
        }

        // setup GLSL program
        var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

        // look up where the vertex data needs to go.
        var positionLocation = gl.getAttribLocation(program, "a_position");
        var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");

        // Create a buffer to put three 2d clip space points in
        var positionBuffer = gl.createBuffer();

        // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        // Set a rectangle the same size as the image.
        setRectangle(gl, 0, 0, image.width, image.height);

        // provide texture coordinates for the rectangle.
        var texcoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 0.0,
            1.0, 0.0,
            0.0, 1.0,
            0.0, 1.0,
            1.0, 0.0,
            1.0, 1.0,
        ]), gl.STATIC_DRAW);

        // Create a texture.
        var texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        // Set the parameters so we can render any size image.
        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);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        // Upload the image into the texture.
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        // lookup uniforms
        var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

        //webglUtils.resizeCanvasToDisplaySize(gl.canvas);

        // Tell WebGL how to convert from clip space to pixels
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

        // Clear the canvas
        gl.clearColor(0, 0, 0, 0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        // Tell it to use our program (pair of shaders)
        gl.useProgram(program);

        // Turn on the position attribute
        gl.enableVertexAttribArray(positionLocation);

        // Bind the position buffer.
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

        // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
        var size = 2; // 2 components per iteration
        var type = gl.FLOAT; // the data is 32bit floats
        var normalize = false; // don't normalize the data
        var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
        var offset = 0; // start at the beginning of the buffer
        gl.vertexAttribPointer(
            positionLocation, size, type, normalize, stride, offset)

        // Turn on the texcord attribute
        gl.enableVertexAttribArray(texcoordLocation);

        // Bind the position buffer.
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

        // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
        var size = 2; // 2 components per iteration
        var type = gl.FLOAT; // the data is 32bit floats
        var normalize = false; // don't normalize the data
        var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
        var offset = 0; // start at the beginning of the buffer
        gl.vertexAttribPointer(
            texcoordLocation, size, type, normalize, stride, offset)

        // set the resolution
        gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

        // Draw the rectangle.
        var primitiveType = gl.TRIANGLES;
        var offset = 0;
        var count = 6;
        gl.drawArrays(primitiveType, offset, count);
    }

    function setRectangle(gl, x, y, width, height) {
        var x1 = x;
        var x2 = x + width;
        var y1 = y;
        var y2 = y + height;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            x1, y1,
            x2, y1,
            x1, y2,
            x1, y2,
            x2, y1,
            x2, y2,
        ]), gl.STATIC_DRAW);
    }

    document.addEventListener('DOMContentLoaded', main);

})(window, document, undefined);
       html, body
        {
            margin: 0;
            padding: 0;
        }

        body 
        {
            display: flex;
            flex-direction: column;
        }

        .container,
        .canvas
        {
            margin: auto;
        }

        img
        {
            max-width: 100%;
            height: auto;
            image-rendering: pixelated;
        }
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
    <script id="2d-vertex-shader" type="x-shader/x-vertex">#version 300 es

        #ifdef GL_ES
        #define COMPAT_PRECISION mediump
        #else
        #define COMPAT_PRECISION
        #endif

        #if __VERSION__ >= 130
        #define COMPAT_VARYING out
        #define COMPAT_ATTRIBUTE in
        #define COMPAT_TEXTURE texture
        #else
        #define COMPAT_VARYING varying
        #define COMPAT_ATTRIBUTE attribute
        #define COMPAT_TEXTURE texture2D
        #endif

        COMPAT_ATTRIBUTE vec2 a_position;
        COMPAT_ATTRIBUTE vec2 a_texCoord;
        COMPAT_VARYING vec4 TEX0;
        COMPAT_VARYING vec4 t1;
        COMPAT_VARYING vec4 t2;
        COMPAT_VARYING vec4 t3;
        COMPAT_VARYING vec4 t4;
        COMPAT_VARYING vec4 t5;
        COMPAT_VARYING vec4 t6;
        COMPAT_VARYING vec4 t7;

        uniform COMPAT_PRECISION int FrameDirection;
        uniform COMPAT_PRECISION int FrameCount;
        uniform COMPAT_PRECISION vec2 OutputSize;
        uniform COMPAT_PRECISION vec2 TextureSize;
        uniform COMPAT_PRECISION vec2 InputSize;
        uniform COMPAT_PRECISION vec2 u_resolution;

        // compatibility #defines
        #define vTexCoord TEX0.xy
        #define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
        #define OutSize vec4(OutputSize, 1.0 / OutputSize)

        void main()
        {
            //gl_Position = MVPMatrix * VertexCoord;

            // convert the rectangle from pixels to 0.0 to 1.0
            vec2 zeroToOne = a_position / u_resolution * 2.0;

            // convert from 0->1 to 0->2
            vec2 zeroToTwo = zeroToOne * 2.0;

            // convert from 0->2 to -1->+1 (clipspace)
            vec2 clipSpace = zeroToTwo - 1.0;

            gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);


            TEX0.xy = a_texCoord.xy * 1.0004;
            vec2 ps = vec2(1.0/ SourceSize.x, 1.0/ SourceSize.y);
            float dx = ps.x;
            float dy = ps.y;

            //    A1 B1 C1
            // A0  A  B  C C4
            // D0  D  E  F F4
            // G0  G  H  I I4
            //    G5 H5 I5

            t1 = vTexCoord.xxxy + vec4( -dx, 0, dx,-2.0*dy); // A1 B1 C1
            t2 = vTexCoord.xxxy + vec4( -dx, 0, dx,    -dy); //  A  B  C
            t3 = vTexCoord.xxxy + vec4( -dx, 0, dx,      0); //  D  E  F
            t4 = vTexCoord.xxxy + vec4( -dx, 0, dx,     dy); //  G  H  I
            t5 = vTexCoord.xxxy + vec4( -dx, 0, dx, 2.0*dy); // G5 H5 I5
            t6 = vTexCoord.xyyy + vec4(-2.0*dx,-dy, 0,  dy); // A0 D0 G0
            t7 = vTexCoord.xyyy + vec4( 2.0*dx,-dy, 0,  dy); // C4 F4 I4

        }
    </script>
    <script id="2d-fragment-shader" type="x-shader/x-fragment">#version 300 es

        #ifdef GL_ES
        #ifdef GL_FRAGMENT_PRECISION_HIGH
        precision highp float;
        #else
        precision mediump float;
        #endif
        #define COMPAT_PRECISION mediump
        #else
        #define COMPAT_PRECISION
        #endif

        #define mul(a,b) (b*a)

        // Constants used with gamma correction.
        #define InputGamma 2.4
        #define OutputGamma 2.2

        #define GAMMA_IN(color) pow(color, vec3(InputGamma, InputGamma, InputGamma))
        #define GAMMA_OUT(color) pow(color, vec3(1.0 / OutputGamma, 1.0 / OutputGamma, 1.0 / OutputGamma))


        #if __VERSION__ >= 130
        #define COMPAT_VARYING in
        #define COMPAT_TEXTURE texture
        out COMPAT_PRECISION vec4 FragColor;
        #else
        #define COMPAT_VARYING varying
        #define FragColor gl_FragColor
        #define COMPAT_TEXTURE texture2D
        #endif

        uniform COMPAT_PRECISION int FrameDirection;
        uniform COMPAT_PRECISION int FrameCount;
        uniform COMPAT_PRECISION vec2 OutputSize;
        uniform COMPAT_PRECISION vec2 TextureSize;
        uniform COMPAT_PRECISION vec2 InputSize;
        uniform sampler2D Texture;
        COMPAT_VARYING vec4 TEX0;
        COMPAT_VARYING vec4 t1;
        COMPAT_VARYING vec4 t2;
        COMPAT_VARYING vec4 t3;
        COMPAT_VARYING vec4 t4;
        COMPAT_VARYING vec4 t5;
        COMPAT_VARYING vec4 t6;
        COMPAT_VARYING vec4 t7;

        // compatibility #defines
        #define Source Texture
        #define vTexCoord TEX0.xy
        #define texture(c, d) COMPAT_TEXTURE(c, d)
        #define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
        #define OutSize vec4(OutputSize, 1.0 / OutputSize)

        const float coef           = 2.0;
        const vec4 eq_threshold  = vec4(15.0, 15.0, 15.0, 15.0);
        const float y_weight        = 1.0;
        const float u_weight        = 7.0;
        const float v_weight        = 6.0;
        const mat3x3 yuv          = mat3x3(0.299, 0.587, 0.114, -0.169, -0.331, 0.499, 0.499, -0.418, -0.0813);
        const mat3x3 yuv_weighted = mat3x3(y_weight*yuv[0], u_weight*yuv[1], v_weight*yuv[2]);
        const vec4 delta         = vec4(0.5, 0.5, 0.5, 0.5);
        const float sharpness      = 0.65;

        float lum(vec3 A, vec3 B)
        {
            return abs(dot(A-B, yuv_weighted[0]));
        }

        vec4 df(vec4 A, vec4 B)
        {
            return vec4(abs(A-B));
        }

        bvec4 eq(vec4 A, vec4 B)
        {
            return lessThan(df(A, B) , vec4(15.0, 15.0, 15.0, 15.0));
        }

        bvec4 eq2(vec4 A, vec4 B)
        {
            return lessThan(df(A, B) , vec4(2.0, 2.0, 2.0, 2.0));
        }

        bvec4 eq3(vec4 A, vec4 B)
        {
            return lessThan(df(A, B) , vec4(5.0, 5.0, 5.0, 5.0));
        }


        vec4 weighted_distance(vec4 a, vec4 b, vec4 c, vec4 d, vec4 e, vec4 f, vec4 g, vec4 h)
        {
            return (df(a,b) + df(a,c) + df(d,e) + df(d,f) + 4.0*df(g,h));
        }

        bvec4 and(bvec4 A, bvec4 B)
        {
            return bvec4(A.x && B.x, A.y && B.y, A.z && B.z, A.w && B.w);
        }

        bvec4 or(bvec4 A, bvec4 B)
        {
            return bvec4(A.x || B.x, A.y || B.y, A.z || B.z, A.w || B.w);
        }

        void main()
        {
            bvec4 edr, edr_left, edr_up, px; // px = pixel, edr = edge detection rule
            bvec4 interp_restriction_lv1, interp_restriction_lv2_left, interp_restriction_lv2_up;
            bvec4 nc, nc30, nc60, nc45; // new_color
            vec4 fx, fx_left, fx_up, final_fx; // inequations of straight lines.

            vec2 fp = fract(vTexCoord*SourceSize.xy);

            vec3 A1 = texture(Source, t1.xw).rgb;
            vec3 B1 = texture(Source, t1.yw).rgb;
            vec3 C1 = texture(Source, t1.zw).rgb;

            vec3 A  = texture(Source, t2.xw).rgb;
            vec3 B  = texture(Source, t2.yw).rgb;
            vec3 C  = texture(Source, t2.zw).rgb;

            vec3 D  = texture(Source, t3.xw).rgb;
            vec3 E  = texture(Source, t3.yw).rgb;
            vec3 F  = texture(Source, t3.zw).rgb;

            vec3 G  = texture(Source, t4.xw).rgb;
            vec3 H  = texture(Source, t4.yw).rgb;
            vec3 I  = texture(Source, t4.zw).rgb;

            vec3 G5 = texture(Source, t5.xw).rgb;
            vec3 H5 = texture(Source, t5.yw).rgb;
            vec3 I5 = texture(Source, t5.zw).rgb;

            vec3 A0 = texture(Source, t6.xy).rgb;
            vec3 D0 = texture(Source, t6.xz).rgb;
            vec3 G0 = texture(Source, t6.xw).rgb;

            vec3 C4 = texture(Source, t7.xy).rgb;
            vec3 F4 = texture(Source, t7.xz).rgb;
            vec3 I4 = texture(Source, t7.xw).rgb;

            vec4 b = mul( mat4x3(B, D, H, F), yuv_weighted[0] );
            vec4 c = mul( mat4x3(C, A, G, I), yuv_weighted[0] );
            vec4 e = mul( mat4x3(E, E, E, E), yuv_weighted[0] );
            vec4 a = c.yzwx;
            vec4 d = b.yzwx;
            vec4 f = b.wxyz;
            vec4 g = c.zwxy;
            vec4 h = b.zwxy;
            vec4 i = c.wxyz;

            vec4 i4 = mul( mat4x3(I4, C1, A0, G5), yuv_weighted[0] );
            vec4 i5 = mul( mat4x3(I5, C4, A1, G0), yuv_weighted[0] );
            vec4 h5 = mul( mat4x3(H5, F4, B1, D0), yuv_weighted[0] );
            vec4 f4 = h5.yzwx;


            vec4 Ao = vec4( 1.0, -1.0, -1.0, 1.0 );
            vec4 Bo = vec4( 1.0,  1.0, -1.0,-1.0 );
            vec4 Co = vec4( 1.5,  0.5, -0.5, 0.5 );
            vec4 Ax = vec4( 1.0, -1.0, -1.0, 1.0 );
            vec4 Bx = vec4( 0.5,  2.0, -0.5,-2.0 );
            vec4 Cx = vec4( 1.0,  1.0, -0.5, 0.0 );
            vec4 Ay = vec4( 1.0, -1.0, -1.0, 1.0 );
            vec4 By = vec4( 2.0,  0.5, -2.0,-0.5 );
            vec4 Cy = vec4( 2.0,  0.0, -1.0, 0.5 );

            // These inequations define the line below which interpolation occurs.
            fx      = (Ao*fp.y+Bo*fp.x);
            fx_left = (Ax*fp.y+Bx*fp.x);
            fx_up   = (Ay*fp.y+By*fp.x);

            //this is the only way I can keep these comparisons straight '-_-
            bvec4 block1 = and(not(eq(h,h5)) , not(eq(h,i5)));
            bvec4 block2 = and(not(eq(f,f4)) , not(eq(f,i4)));
            bvec4 block3 = and(not(eq(h, d)) , not(eq(h, g)));
            bvec4 block4 = and(not(eq(f, b)) , not(eq(f, c)));
            bvec4 block5 = and(eq(e, i) , or(block2 , block1));

            bvec4 block_comp = or(block4 , or(block3 , or(block5 , or(eq(e,g) , eq(e,c)))));

            interp_restriction_lv1      = and(notEqual(e,f) , and(notEqual(e,h) , block_comp));
            interp_restriction_lv2_left = and(notEqual(e,g) , notEqual(d,g));
            interp_restriction_lv2_up   = and(notEqual(e,c) , notEqual(b,c));

            vec4 fx45 = smoothstep(Co - delta, Co + delta, fx);
            vec4 fx30 = smoothstep(Cx - delta, Cx + delta, fx_left);
            vec4 fx60 = smoothstep(Cy - delta, Cy + delta, fx_up);


            edr      = and(lessThan((weighted_distance( e, c, g, i, h5, f4, h, f) + 3.5) , weighted_distance( h, d, i5, f, i4, b, e, i)) , interp_restriction_lv1);
            edr_left = and(lessThanEqual((coef*df(f,g)) , df(h,c)) , interp_restriction_lv2_left);
            edr_up   = and(greaterThanEqual(df(f,g) , (coef*df(h,c))) , interp_restriction_lv2_up);

            nc45 = and( edr ,                bvec4(fx45));
            nc30 = and( edr , and(edr_left , bvec4(fx30)));
            nc60 = and( edr , and(edr_up   , bvec4(fx60)));

            px = lessThanEqual(df(e,f) , df(e,h));

            vec3 res = E;


            vec3 n1, n2, n3, n4, s, aa, bb, cc, dd;


            n1 = B1; n2 = B; s = E; n3 = H; n4 = H5;
            aa = n2-n1; bb = s-n2; cc = n3-s; dd = n4-n3;

            vec3 t = (7. * (bb + cc) - 3. * (aa + dd)) / 16.;

            vec3 m;
            m.x = (s.x < 0.5) ? 2.*s.x : 2.*(1.0-s.x);
            m.y = (s.y < 0.5) ? 2.*s.y : 2.*(1.0-s.y);
            m.z = (s.z < 0.5) ? 2.*s.z : 2.*(1.0-s.z);

                m = min(m, sharpness*abs(bb));
                m = min(m, sharpness*abs(cc));

            t = clamp(t, -m, m);


            vec3 s1 = (2.*fp.y-1.)*t + s;

            n1 = D0; n2 = D; s = s1; n3 = F; n4 = F4;
            aa = n2-n1; bb = s-n2; cc = n3-s; dd = n4-n3;

            t = (7. * (bb + cc) - 3. * (aa + dd)) / 16.;

            m.x = (s.x < 0.5) ? 2.*s.x : 2.*(1.0-s.x);
            m.y = (s.y < 0.5) ? 2.*s.y : 2.*(1.0-s.y);
            m.z = (s.z < 0.5) ? 2.*s.z : 2.*(1.0-s.z);

                m = min(m, sharpness*abs(bb));
                m = min(m, sharpness*abs(cc));

            t = clamp(t, -m, m);

            vec3 s0 = (2.*fp.x-1.)*t + s;


            nc = or(nc30 , or(nc60 , nc45));

            float blend = 0.0;
            vec3 pix;

            vec4 r1 = mix(e, f, edr);

            bool yeseq3=false;
            bvec4 yes;

            if ( all(eq3(r1,e)) )
            {
                yeseq3 = true;
                pix = res = s0;
            }
            else
            {
                pix = res = E;
            }

            yes = and(bvec4(yeseq3) , eq2(e, mix(f, h, px)));

            vec4 final45 = vec4(nc45) * fx45;
            vec4 final30 = vec4(nc30) * fx30;
            vec4 final60 = vec4(nc60) * fx60;

            vec4 maximo = max(max(final30, final60), final45);

                 if (nc.x) {pix = px.x ? F : H; blend = maximo.x; if (yes.x)  pix = res = s0; else res=E;}
            else if (nc.y) {pix = px.y ? B : F; blend = maximo.y; if (yes.y)  pix = res = s0; else res=E;}
            else if (nc.z) {pix = px.z ? D : B; blend = maximo.z; if (yes.z)  pix = res = s0; else res=E;}
            else if (nc.w) {pix = px.w ? H : D; blend = maximo.w; if (yes.w)  pix = res = s0; else res=E;}

            res = GAMMA_IN(res);
            pix = GAMMA_IN(pix);

            res = mix(res, pix, blend);

           FragColor = vec4(clamp( GAMMA_OUT(res), 0.0, 1.0 ), 1.0);
        }
    </script>
    <div class="container">
        <img src="data:image/jpeg;base64,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" alt="testimage" />
    </div>
    <div class="canvas">
        <canvas id="canvas"></canvas>
    </div>

0 个答案:

没有答案