Почти все возможно с небольшим воображением. Однако то, что невозможно, - это колдование информации из ниоткуда.
Минимальные данные
В стандартном треугольнике отсутствует информация, необходимая для добавления нового интерполированного цвета. Эта информация представляет собой 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>