【问题标题】:How to properly blend colors across two triangles and remove diagonal smear如何正确混合两个三角形的颜色并去除对角线拖影
【发布时间】:2020-02-13 16:34:14
【问题描述】:

我正在学习 WebGL,我已经为每个顶点绘制了一个带有颜色的全屏四边形。没有光照或法线或透视矩阵或深度缓冲;我只是在绘制渐变背景。这是我得到的:

看起来不错,但我不禁注意到从右下角到左上角的对角线拖影。我觉得这是对远相反的顶点进行线性插值的产物。我正在绘制两个三角形:左下角和右上角。我想我会使用 OpenGL 而不是 WebGL 得到类似的结果。

给定相同的四种颜色和相同大小的矩形,有没有办法渲染它,使两个三角形之间的边缘不那么明显?也许更多的顶点,或者不同的混合功能?我不确定每个像素的确切颜色应该是什么。我只想知道如何去除对角线拖影。

【问题讨论】:

  • 是的,就是这样。如果您想将有关双线性插值的内容作为包含该链接的答案,我会将其标记为正确答案。谢谢!
  • 我想重新打开这个问题,因为重复的问题已有 9 年历史,不适用于当前的 WebGL,而且答案质量很差,因为它们都没有使用正确的插值。

标签: colors glsl webgl gradient linear-interpolation


【解决方案1】:

问题是右上角的三角形不知道左下角,所以右上角的三角形不包括左下角的任何蓝色(反之亦然)

有几种方法可以解决这个问题。

一种是使用带有线性采样的 2x2 纹理。您必须做一些额外的数学运算才能使插值正确,因为纹理只在像素之间进行插值

+-------+-------+
|       |       |
|   +-------+   |
|   |   |   |   |
+---|---+---|---+
|   |   |   |   |
|   +-------+   |
|       |       |
+-------+-------+

上面是一个 4 像素的纹理,拉伸到 14 x 6。采样发生在像素之间,因此只有这个中心区域才会获得渐变。该区域之外的区域将使用纹理外部的像素进行采样,因此使用CLAMP_TO_EDGE 或在纹理的另一侧使用 REPEAT。

const gl = document.querySelector('canvas').getContext('webgl');

const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];

const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
    gl.TEXTURE_2D,
    0, // mip level
    gl.RGB,  // internal format
    2,  // width,
    2,  // height,
    0,  // border
    gl.RGB, // format
    gl.UNSIGNED_BYTE, // type
    new Uint8Array([...bl, ...br, ...tl, ...tr]));
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.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision mediump float;
varying vec2 v_texcoord;
const vec2 texSize = vec2(2, 2);  // could pass this in
uniform sampler2D tex;
void main() {
  gl_FragColor = texture2D(tex, 
     (v_texcoord * (texSize - 1.0) + 0.5) / texSize);
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');

function createBufferAndSetupAttribute(loc, data) {
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(
      loc,
      2,  // 2 elements per iteration
      gl.FLOAT,  // type of data in buffer
      false,  // normalize
      0,  // stride
      0,  // offset
  );
}

createBufferAndSetupAttribute(positionLoc, [
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
   0,  0,
   1,  0,
   0,  1,
   0,  1,
   1,  0,
   1,  1,
]);

gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

注意:这里的纹理坐标所需的额外数学是相同的示例,没有额外的数学

const gl = document.querySelector('canvas').getContext('webgl');

const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];

const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
    gl.TEXTURE_2D,
    0, // mip level
    gl.RGB,  // internal format
    2,  // width,
    2,  // height,
    0,  // border
    gl.RGB, // format
    gl.UNSIGNED_BYTE, // type
    new Uint8Array([...bl, ...br, ...tl, ...tr]));
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.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
  gl_FragColor = texture2D(tex, v_texcoord);
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');

function createBufferAndSetupAttribute(loc, data) {
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(
      loc,
      2,  // 2 elements per iteration
      gl.FLOAT,  // type of data in buffer
      false,  // normalize
      0,  // stride
      0,  // offset
  );
}

createBufferAndSetupAttribute(positionLoc, [
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
   0,  0,
   1,  0,
   0,  1,
   0,  1,
   1,  0,
   1,  1,
]);

gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

当然,我们可以在 JavaScript 中修复纹理坐标,而不是在片段着色器中进行数学运算

const gl = document.querySelector('canvas').getContext('webgl');

const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];

const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
    gl.TEXTURE_2D,
    0, // mip level
    gl.RGB,  // internal format
    2,  // width,
    2,  // height,
    0,  // border
    gl.RGB, // format
    gl.UNSIGNED_BYTE, // type
    new Uint8Array([...bl, ...br, ...tl, ...tr]));
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.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
  gl_FragColor = texture2D(tex, v_texcoord);
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');

function createBufferAndSetupAttribute(loc, data) {
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(
      loc,
      2,  // 2 elements per iteration
      gl.FLOAT,  // type of data in buffer
      false,  // normalize
      0,  // stride
      0,  // offset
  );
}

createBufferAndSetupAttribute(positionLoc, [
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
   0.25,  0.25,
   0.75,  0.25,
   0.25,  0.75,
   0.25,  0.75,
   0.75,  0.25,
   0.75,  0.75,
]);

gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

另一种方法是根据这些角自己进行插值(这实际上是在执行纹理采样器在上一个示例中所做的,即 4 种颜色的双线性插值)。

const gl = document.querySelector('canvas').getContext('webgl');

const tl = [254/255, 217/255, 138/255];
const tr = [252/255, 252/255, 252/255];
const bl = [ 18/255, 139/255, 184/255];
const br = [203/255,  79/255, 121/255];

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform vec3 tl;
uniform vec3 tr;
uniform vec3 bl;
uniform vec3 br;

void main() {
  vec3 l = mix(bl, tl, v_texcoord.t);
  vec3 r = mix(br, tr, v_texcoord.t);
  vec3 c = mix(l, r, v_texcoord.s);
  gl_FragColor = vec4(c, 1);
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');

const tlLoc = gl.getUniformLocation(program, 'tl');
const trLoc = gl.getUniformLocation(program, 'tr');
const blLoc = gl.getUniformLocation(program, 'bl');
const brLoc = gl.getUniformLocation(program, 'br');

function createBufferAndSetupAttribute(loc, data) {
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(
      loc,
      2,  // 2 elements per iteration
      gl.FLOAT,  // type of data in buffer
      false,  // normalize
      0,  // stride
      0,  // offset
  );
}

createBufferAndSetupAttribute(positionLoc, [
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
   0,  0,
   1,  0,
   0,  1,
   0,  1,
   1,  0,
   1,  1,
]);

gl.useProgram(program);
gl.uniform3fv(tlLoc, tl);
gl.uniform3fv(trLoc, tr);
gl.uniform3fv(blLoc, bl);
gl.uniform3fv(brLoc, br);
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

【讨论】:

    【解决方案2】:

    维数

    您应该将四边形的 2D 坐标空间传递给片段着色器,而不是一维(每个通道)颜色空间。

    然后在片段着色器中,您可以在 2D 空间中进行颜色插值,消除由于 1D 中对角线插值而导致的颜色伪影。

    线性颜色插值的shader sn-p,其中coord2D是2D坐标空间

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

    改进的颜色插值

    当通过 RGB 值对颜色进行插值时,结果可能会在相反的色调之间在视觉上变暗。

    一个简单的解决方法是通过在颜色通道值的平方之间进行插值来使用更接近 sRGB 颜色模型的近似值。最终输出是插值的平方根。

    插值 sn-p.

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

    请注意,统一colors 中的颜色通道值在对数空间中。 [R^2, G^2, B^2],因此范围从 0 到 65025。

    示例

    在示例中单击画布以在插值方法之间切换。

    您会注意到,当使用大约 ~sRGB 时,画布中心向外朝向中心边缘的亮度不会低于角落处可感知的亮度。

    还要注意,从底部的蓝色和红色到顶部的橙色和白色的过渡平衡向下移动,靠近中心。这是因为插值 RGB 模型会使具有来自 2 个或更多通道的强分量的颜色变暗,红色、绿色、蓝色和黑色将超过黄色、青色、洋红色和白色,从而使插值看起来会移动并拉伸 RGB 原色。

        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;
    
    }
    <canvas id="canvas"></canvas>
    <h2 id="info"></h2>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-19
      • 1970-01-01
      • 2022-06-16
      • 1970-01-01
      • 1970-01-01
      • 2015-02-04
      • 1970-01-01
      相关资源
      最近更新 更多