Как мне контролировать цвет между вершинами с помощью шейдера? - PullRequest
0 голосов
/ 18 марта 2020

Есть ли способ управления цветом между вершинами с помощью шейдера? Как и в классических c уроках с нарисованным на экране треугольником - вершинам назначаются красный, зеленый и синий цвета соответственно. Значения между ними интерполируются. Есть ли способ контролировать цвет этого среднего уровня, кроме изменения геометрии и добавления большего количества вершин внутри?

Ответы [ 2 ]

1 голос
/ 19 марта 2020

Самый распространенный способ раскрасить треугольник - использовать текстуру . Вторым наиболее распространенным способом было бы добавить вершины. Как заметил @derhass, теоретически вы можете создать фрагментный шейдер, который немного различает цвета середины. Однако для этого потребуется предоставить фрагментному шейдеру больше данных, поскольку фрагментный шейдер не знает, какой пиксель треугольника рисуется. Таким образом, вы в конечном итоге добавите больше данных в свою геометрию для достижения sh, что, даже если технически вы не добавите больше вершин.

Кроме того, любое решение, которое вы придумали, скорее всего будет довольно негибким, если использование текстуры (самый распространенный способ окраски чего-либо) дает вам массу гибкости. Например, вы можете создать фрагментный шейдер, который позволит вам выбрать 1 новый цвет в середине треугольника.

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

const vs = `
attribute vec4 position;
attribute vec4 color;
attribute vec3 corner;

varying vec4 v_color;
varying vec3 v_corner;

void main() {
  gl_Position = position;
  v_color = color;
  v_corner = corner;
}
`;

const fs = `
precision highp float;

varying vec4 v_color;
varying vec3 v_corner;

// could be a uniform
const vec4 centerColor = vec4(0, 1, 0, 1);

void main() {
  vec3 center = vec3(1.0 / 3.0);
  float edge = distance(center, v_corner) / 0.75;
  gl_FragColor = mix(centerColor, v_color, edge);
}
`;

const prg = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(prg, 'position');
const colorLoc = gl.getAttribLocation(prg, 'color');
const cornerLoc = gl.getAttribLocation(prg, 'corner');

function createBufferAndSetupAttribute(gl, loc, data) {
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  // normally these would happen at render time
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
}

createBufferAndSetupAttribute(gl, posLoc, [
  0,  1, 0,
  1, -1, 0,
 -1, -1, 0,
]);
createBufferAndSetupAttribute(gl, colorLoc, [
  1, 0, 0,
  1, 0, 1,
  0, 0, 1,
]);
createBufferAndSetupAttribute(gl, cornerLoc, [
  1, 0, 0,
  0, 1, 0,
  0, 0, 1,
]);

gl.useProgram(prg);
gl.drawArrays(gl.TRIANGLES, 0, 3);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>

Позже вы решите, что хотите 2 цвета, один 1/3 пути от первой точки вниз по линии от первой точки до средней точки другого 2 очка и еще 2/3 по этой линии. Вам нужно будет написать совершенно новый шейдер. Если бы вы использовали текстуру, вы бы просто изменили текстуру.

0 голосов
/ 19 марта 2020

Почти все возможно с небольшим воображением. Однако то, что невозможно, - это колдование информации из ниоткуда.

Минимальные данные

В стандартном треугольнике отсутствует информация, необходимая для добавления нового интерполированного цвета. Эта информация представляет собой 2D-координату (например, координату текстуры) и цвет.

Затем можно использовать 2D-координату, чтобы смешать дополнительный цвет.

Структура данных

In В примере координаты карты добавляются к каждой вершине в качестве атрибута map вместе с вершиной color и положением (vert 2D в примере). Дополнительный цвет добавляется как единообразное midColor, что ограничивает его всеми треугольниками, имеющими одинаковый внутренний цвет.

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

Смешение

Самое основное смешение c может предполагать, что отображение относится к началу координат, причем начало координат является центром треугольника. Затем просто используйте расстояние от начала координат, чтобы смешать средний цвет с интерполированными цветами пикселей.

// midColor is uniform 
// colV and colM are varying, and hold the color and internal color 2D mapping
gl_FragColor = vec4(mix(midColor, colV, length(colM)), 1);

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

Вершинный шейдер

attribute vec2 vert;
attribute vec3 color;
attribute vec2 map;

uniform float time;
varying vec3 colV;
varying vec2 colM;

void main() {
    float z = sin(time) * vert.x;
    gl_Position = vec4(cos(time) * vert.x, vert.y, z * 0.5 + 0.5, z  + 1.0);
    colV = color;
    colM = map;
}

Фрагмент шейдера

Микширование контролируется определителями smoothStart и smoothEnd

#define smoothStart 0.15
#define smoothEnd 0.5
uniform vec3 midColor;
varying vec3 colV;
varying vec2 colM;
void main() {
    vec3 mixed = sqrt(
        mix(
            midColor * midColor, 
            colV * colV,
            smoothstep(smoothStart, smoothEnd, length(colM - vec2(0, 1.0 / 3.0)))
        )
    );
    gl_FragColor = vec4(mixed, 1);
}`;

Пример кода

const CONTEXT = "webgl";
const GL_OPTIONS = {alpha: false, depth: false, premultpliedAlpha: false, preserveDrawingBufer: true};
Math.TAU = Math.PI * 2;
Math.sinWave = (phase, period = 1, min = -1, max = 1) => Math.sin((phase * Math.TAU) / period) * (max - min) + min;
const GL_SETUP = {
    get context() { return  this.gl = canvas.getContext(CONTEXT, GL_OPTIONS) },
    get vertexSrc() { return `${CONTEXT === "webgl2" ? "#version 300 es" : ""}
        #define aspect ${(innerHeight / innerWidth).toFixed(4)}
        attribute vec2 vert;
        attribute vec3 color;
        attribute vec2 map;
        
        uniform float time;
        varying vec3 colV;
        varying vec2 colM;
        
        void main() {
            float z = sin(time) * vert.x;
            gl_Position = vec4(cos(time) * vert.x * aspect, vert.y, z * 0.5 + 0.5, z  + 1.0);
            colV = color;
            colM = map;
        }`;
    },
    get fragmentSrc() { return `${CONTEXT === "webgl2" ? "#version 300 es" : ""}
        precision highp float;
        #define smoothStart 0.15
        #define smoothEnd 0.5
        uniform vec3 midColor;
        varying vec3 colV;
        varying vec2 colM;
        void main() {
            vec3 mixed = sqrt(mix(midColor * midColor, colV * colV, smoothstep(smoothStart, smoothEnd,length(colM - vec2(0, 1.0 / 3.0)))));
            gl_FragColor = vec4(mixed, 1);
        }`;
    },
    get locations() { return ["A_vert", "A_color", "A_map", "U_midColor", "U_time"] },  
    compileAndAttach(program, src, type = this.gl.VERTEX_SHADER) {
        const gl = this.gl, shader = gl.createShader(type);
        gl.shaderSource(shader, src);
        gl.compileShader(shader);   
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error("WebGL shader compile error\n" + gl.getShaderInfoLog(shader)) }  
        gl.attachShader(program, shader);
    },  
    createShader() {
        const gl = this.gl, locations = {}, program = gl.createProgram();
        this.compileAndAttach(program, this.vertexSrc);
        this.compileAndAttach(program, this.fragmentSrc, gl.FRAGMENT_SHADER);
        gl.linkProgram(program);
        gl.useProgram(program);
        for(const desc of this.locations) {
            const [type, name] = desc.split("_");
            locations[name] = gl[`get${type==="A" ? "Attrib" : "Uniform"}Location`](program, name);
        }
        return {program, locations, gl};
    },   
    get shader() {
        const gl = this.gl;
        const shader = this.createShader();
        for (const [name, data] of Object.entries(this.buffers)) {
            const {use = gl.STATIC_DRAW, type = gl.FLOAT, buffer, size = 2, normalize = false} = data;
            gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ARRAY_BUFFER, buffer, use);
            gl.enableVertexAttribArray(shader.locations[name]);
            gl.vertexAttribPointer(shader.locations[name], size, type, normalize, 0, 0);
        }
        return shader;
    },  
    get buffers() {
        return {
            vert: {buffer: this.verticies},
            color: {buffer: this.vertCols, size: 3},
            map: {buffer: this.mapping},
        };
    },
    get verticies() { return new Float32Array([0,-0.5, 0.5,0.5, -0.5,0.5]) },
    get vertCols() { return new Float32Array([1,0,0, 0,1,0, 0,0,1]) },
    get mapping() { return new Float32Array([0,-1, 1,1, -1,1]) },
  
};

const gl = GL_SETUP.context;
const shader = GL_SETUP.shader;
const W = gl.canvas.width = innerWidth, H = gl.canvas.height = innerHeight;
const midColor = new Float32Array([1,0,0]);
gl.viewport(0, 0, W, H);


requestAnimationFrame(mainLoop);
function mainLoop(time) {
    gl.clear(gl.COLOR_BUFFER_BIT);
    midColor[0] = Math.sinWave(time / 1000, 1, 0.5, 1);
    midColor[1] = Math.sinWave(time / 1000, 2, 0.5, 1);
    midColor[2] = Math.sinWave(time / 1000, 3, 0.5, 1);
    gl.uniform1f(shader.locations.time, time / 1000);
    gl.uniform3fv(shader.locations.midColor, midColor);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    requestAnimationFrame(mainLoop);
}
canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}
<canvas id="canvas"></canvas>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...