How to properly blend colors across two triangles and remove diagonal smear

后端 未结 2 534
天涯浪人
天涯浪人 2021-01-22 15:09

I am learning WebGL and I\'ve drawn a full screen quad with colors for each vertex. No lighting or normals or perspective matrix or depth buffer; I\'m just drawing a gradient ba

2条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-01-22 16:13

    Number of dimensions

    You should be passing the 2D coordinate space of the quad to the fragment shader rather than the one dimensional (per channel) color space.

    Then in the fragment shader you can do the color interpolation in 2D space removing the color artifact due to the diagonal line interpolating in 1D.

    The shader snippet for linear color interpolation, where coord2D is the 2D coordinate space

    pixel = vec4(vec3(mix(
            mix(colors[0], colors[1], coord2D.x),
            mix(colors[2], colors[3], coord2D.x),
            coord2D.y
        )), 1);
    

    Improved color interpolation

    When interpolating colors by their RGB values the results can visually darken between opposing hues.

    An easy fix is to use a closer approximation of the sRGB color model by interpolating between the squares of the color channel values. The final output is the square root of the interpolated values.

    The interpolation snippet.

    pixel = vec4(sqrt(vec3(mix(
            mix(colors[0], colors[1], coord2D.x),
            mix(colors[2], colors[3], coord2D.x),
            coord2D.y
        ))) / 255.0, 1);
    

    Note that the color channel values in the uniform colors are in logarithmic space. [R^2, G^2, B^2] and thus range from 0 to 65025.

    Example

    In the example click the canvas to switch between interpolation methods.

    You will note that when using approx ~sRGB that the brightness in the center of the canvas out towards the center edges does not dip as much below the perceivable brightness at the corners.

    Also note that the balance of the transition from the bottom blue and redish to the top orange and white moves down closer the the center. This is because interpolating the RGB model will darken colors that have strong components from 2 or more channels, Reds, greens, blues and blacks will dominate over yellows, cyans, magentas and whites making the interpolation seem to shift and stretch out the RGB primaries.

        var program, colorsLoc, modelLoc, loc, text = " interpolation. Click for ", model = "RGB"; // or sRGB
    const vertSrc = `#version 300 es
        in vec2 verts;
        out vec2 coord2D;
        void main() { 
            coord2D = verts * 0.5 + 0.5; // convert to quad space 0,0 <=> 1, 1
            gl_Position = vec4(verts, 1, 1); 
        }`;
    const fragSrc = `#version 300 es
        #define channelMax 255.0
        // color location indexes 
        #define TR 3
        #define TL 2
        #define BR 1
        #define BL 0
        precision mediump float;
        uniform vec3 colors[4];
        uniform bool isRGB;
        in vec2 coord2D;
        out vec4 pixel;
        void main() {
            if (isRGB) {
                pixel = vec4(vec3(mix(
                        mix(colors[BL], colors[BR], coord2D.x),
                        mix(colors[TL], colors[TR], coord2D.x),
                        coord2D.y
                    )) / channelMax, 1);
             } else {
                pixel = vec4(vec3(sqrt(mix(
                        mix(colors[BL], colors[BR], coord2D.x),
                        mix(colors[TL], colors[TR], coord2D.x),
                        coord2D.y
                    ))) / channelMax, 1);
             }
        }`; 
    const fArr = arr => new Float32Array(arr);
    const colors = [64,140,190, 224,81,141, 247,223,140, 245,245,245];
    const gl = canvas.getContext("webgl2", {premultipliedAlpha: false, antialias: false, alpha: false});
    addEventListener("resize", draw);
    addEventListener("click", draw);
    setup();
    draw();
    function compileShader(src, type, shader = gl.createShader(type)) {
        gl.shaderSource(shader, src);
        gl.compileShader(shader);
        return shader;
    }
    function setup() {
        program = gl.createProgram();
        gl.attachShader(program, compileShader(vertSrc, gl.VERTEX_SHADER));
        gl.attachShader(program, compileShader(fragSrc, gl.FRAGMENT_SHADER));
        gl.linkProgram(program);   
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([0,1,2,0,2,3]), gl.STATIC_DRAW);  
        gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
        gl.bufferData(gl.ARRAY_BUFFER, fArr([-1,-1,1,-1,1,1,-1,1]), gl.STATIC_DRAW);   
        gl.enableVertexAttribArray(loc = gl.getAttribLocation(program, "verts"));
        gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);      
        colorsLoc = gl.getUniformLocation(program, "colors");       
        modelLoc = gl.getUniformLocation(program, "isRGB");    
        gl.useProgram(program);
    }
    function draw() {
        [info.textContent, model] = model != "RGB"? [`RGB${text}~sRGB.`, "RGB"]: [`~sRGB${text}RGB.`, "~sRGB"];
        if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
            [canvas.width, canvas.height] = [innerWidth, innerHeight];
            gl.viewport(0, 0, canvas.width, canvas.height);
        }
        gl.uniform3fv(colorsLoc, fArr(colors.map(v => model=="RGB"? v: v*v)), 0, 12);         
        gl.uniform1i(modelLoc, model=="RGB");   
        gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);                         
    }
    body {
        padding: 0px;
        margin: 0px;
        font-family: arial;
        color: white;
    }
    canvas {
        position: absolute;
        top: 0px;
        left: 0px;
    }
    h2 {
        position: absolute;
        bottom: 0px;
        left: 0px;
        right: 0px;
        text-align: center;
    
    }
    
    

提交回复
热议问题