Как правильно смешать цвета по двум треугольникам и удалить диагональный мазок - PullRequest
2 голосов
/ 13 февраля 2020

Я изучаю WebGL и нарисовал полноэкранный квад с цветами для каждой вершины. Нет освещения или нормалей или матрицы перспективы или буфера глубины; Я просто рисую градиентный фон. Вот что я получаю:

full screen gradient

Это выглядит хорошо, но я не могу не заметить диагональный мазок снизу справа вверху слева. Я чувствую, что это артефакт линейной интерполяции крайних противоположных вершин. Я рисую два треугольника: нижний левый и верхний правый. Я думаю, что я получил бы аналогичные результаты, используя OpenGL вместо WebGL.

При тех же четырех цветах и ​​прямоугольнике одинакового размера, есть ли способ отрисовать это так, чтобы край между двумя треугольниками не был так очевиден? Может быть, больше вершин или другая функция смешивания? Я не уверен, какие именно цвета должны быть у каждого пикселя; Я просто хочу знать, как избавиться от диагонального мазка.

Ответы [ 2 ]

4 голосов
/ 14 февраля 2020

Проблема заключается в том, что верхний правый треугольник не знает о нижнем левом углу, поэтому верхний правый треугольник не включает ни одного синего цвета из нижнего левого (и наоборот)

Несколько способов исправить это.

Одним из них является использование текстуры 2х2 с линейной выборкой. Вы должны сделать некоторую дополнительную математику, чтобы получить правильную интерполяцию, потому что текстура интерполируется только между пикселями

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

Выше 4-пиксельная текстура, растянутая до 14 на 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>
1 голос
/ 15 февраля 2020

Количество измерений

Передача 2D координатного пространства квадра в фрагментный шейдер, а не одномерное (на канал) цветовое пространство.

Затем в фрагментном шейдере Вы можете выполнить цветовую интерполяцию в 2D-пространстве, удалив цветовой артефакт из-за диагональной линии, интерполированной в 1D.

Фрагмент шейдера для линейной цветовой интерполяции, где 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 к интерполяция между квадратами значений цветового канала. Окончательный результат - квадрат root интерполированных значений.

Фрагмент интерполяции.

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 приведены в логарифмическом выражении c пространство. [R^2, G^2, B^2] и, следовательно, в диапазоне от 0 до 65025.

Пример

В этом примере щелкните на холсте, чтобы переключаться между методами интерполяции.

Вы заметите, что при использовании ~ ~ sRGB, что яркость в центре холста по направлению к центральным краям не падает так сильно ниже воспринимаемой яркости по углам.

Также обратите внимание, что баланс перехода от нижнего синего до ред sh к вершине оранжевый и белый движутся вниз ближе к центру. Это связано с тем, что интерполяция модели 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...