Given a convex Polygon as a set of edges (in this case on the plane from [[-1,1],[-1,1]] like this:
edges = [
[[-0.8, 0],[-0.3,0.6]],
[[-0.3,0.6],[0,0.8]],
[[0,0.8],[0.7,0.6]],
[[0.7,0.6],[0.6,-0.9]],
[[0.6,-0.9],[-0.8,0]]
]
How can I color the inner area depeding on how far every every spot inside is from the closest edge. I want to achieve an effect like in the following blender documentation: https://docs.blender.org/manual/en/latest/render/shader_nodes/textures/voronoi.html (under "Distance to Edge")
I really have no idea how I would realize such irregular gradients. The most promising idea I had was to cut the inner area into pieces which could then be filled with linear gradients, but I had no luck so far.
I also tried looking up the according source code from blender, but that didn't really help me either: https://projects.blender.org/blender/blender/src/branch/main/source/blender/gpu/shaders/material/gpu_shader_material_tex_voronoi.glsl
My two renderings of the polygon, in 2d context and in webgl context look like this.
edges = [
[[-0.8, 0],[-0.3,0.6]],
[[-0.3,0.6],[0,0.8]],
[[0,0.8],[0.7,0.6]],
[[0.7,0.6],[0.6,-0.9]],
[[0.6,-0.9],[-0.8,0]]
]
const canvas = document.querySelector("canvas");
var ctx = canvas.getContext('2d');
ctx.strokeStyle = '#fff';
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
edges.forEach(edge => {
let x = (canvas.width * 0.5) + ( edge[0][0] * 0.5 * canvas.width);
let y = (canvas.height * 0.5) - ( edge[0][1] * 0.5 * canvas.height);
console.log("Move to " + x + ", " + y)
ctx.moveTo(x, y);
x = (canvas.width * 0.5) + ( edge[1][0] * 0.5 * canvas.width);
y = (canvas.height * 0.5) - ( edge[1][1] * 0.5 * canvas.height);
console.log("Line to " + x + ", " + y)
ctx.lineTo(x,y);
});
ctx.closePath();
ctx.stroke();
<canvas width="640" height="360"></canvas>
edges = [
[[-0.8, 0],[-0.3,0.6]],
[[-0.3,0.6],[0,0.8]],
[[0,0.8],[0.7,0.6]],
[[0.7,0.6],[0.6,-0.9]],
[[0.6,-0.9],[-0.8,0]]
]
vertices = [];
edges.forEach(edge => {
vertices.push(edge[0][0], edge[0][1], 0.);
vertices.push(edge[1][0], edge[1][1], 0.);
});
//
// start here
//
const canvas = document.querySelector("canvas");
// Initialize the GL context
const gl = canvas.getContext("webgl");
// Only continue if WebGL is available and working
if (gl === null) {
alert(
"Unable to initialize WebGL. Your browser or machine may not support it.",
);
}
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
// Create an empty buffer object
var vertex_buffer = gl.createBuffer();
// Bind appropriate array buffer to it
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
// Pass the vertex data to the buffer
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Unbind the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, null);
/*=================== Shaders ====================*/
// Vertex shader source code
var vertCode =
'attribute vec3 coordinates;' +
'void main(void) {' +
' gl_Position = vec4(coordinates, 1.0);' +
'}';
// Create a vertex shader object
var vertShader = gl.createShader(gl.VERTEX_SHADER);
// Attach vertex shader source code
gl.shaderSource(vertShader, vertCode);
// Compile the vertex shader
gl.compileShader(vertShader);
// Fragment shader source code
var fragCode =
'void main(void) {' +
'gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);' +
'}';
// Create fragment shader object
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
// Attach fragment shader source code
gl.shaderSource(fragShader, fragCode);
// Compile the fragmentt shader
gl.compileShader(fragShader);
// Create a shader program object to store
// the combined shader program
var shaderProgram = gl.createProgram();
// Attach a vertex shader
gl.attachShader(shaderProgram, vertShader);
// Attach a fragment shader
gl.attachShader(shaderProgram, fragShader);
// Link both the programs
gl.linkProgram(shaderProgram);
// Use the combined shader program object
gl.useProgram(shaderProgram);
/*======= Associating shaders to buffer objects ======*/
// Bind vertex buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
// Get the attribute location
var coord = gl.getAttribLocation(shaderProgram, "coordinates");
// Point an attribute to the currently bound VBO
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
// Enable the attribute
gl.enableVertexAttribArray(coord);
/*============ Drawing the triangle =============*/
// Clear the canvas
gl.clearColor(0.5, 0.5, 0.5, 1);
// Enable the depth test
gl.enable(gl.DEPTH_TEST);
// Clear the color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Set the view port
gl.viewport(0,0,canvas.width,canvas.height);
// Draw the triangle
gl.drawArrays(gl.LINES, 0, 10);
<canvas width="640" height="360"></canvas>
Using WebGl it's quite easy to draw a single-colored triangle if you send three vertices to the fragment shader.
Now what you might not know is that you can also assign each vertex a different color. So if we give vertices A and B a black and vertex C a white color, WebGl will interpolate between the colors and display a gradient instead.
As with WebGl it's all about triangles we first need to triangulate your polygon. This is done by calculating the weighted centroid and ultimately connecting each side of the polygon to that center point.
That's it - we have 5 triangles we can color independently!
Here's an example:
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
function polygonCentroid(vertices) {
let a = 0;
let x = 0;
let y = 0;
for (let i = 0; i < vertices.length; i++) {
let s = i === vertices.length - 1 ? 0 : i + 1,
v0 = vertices[i],
v1 = vertices[s],
f = v0[0] * v1[1] - v1[0] * v0[1];
a += f;
x += (v0[0] + v1[0]) * f;
y += (v0[1] + v1[1]) * f;
}
let d = a * 3;
return [x / d, y / d];
}
let edges = [
[-0.8, 0],
[-0.3, 0.6],
[0, 0.8],
[0.7, 0.6],
[0.6, -0.9]
];
let center = polygonCentroid(edges);
let colorMesh = [];
for (let a = 0; a < edges.length; a++) {
colorMesh.push(edges[a][0]);
colorMesh.push(edges[a][1]);
colorMesh.push(0);
colorMesh.push(0);
colorMesh.push(0);
if (a != edges.length - 1) {
colorMesh.push(edges[a + 1][0]);
colorMesh.push(edges[a + 1][1]);
} else {
colorMesh.push(edges[0][0]);
colorMesh.push(edges[0][1]);
}
colorMesh.push(0);
colorMesh.push(0);
colorMesh.push(0);
colorMesh.push(center[0]);
colorMesh.push(center[1]);
colorMesh.push(1);
colorMesh.push(1);
colorMesh.push(1);
}
let vertexSource = `
attribute vec2 position;
attribute vec3 color;
varying vec3 _color;
void main() {
gl_Position = vec4(position.x, position.y , 0, 1.0);
_color = color;
}`;
let fragmentSource = `
precision mediump float;
varying vec3 _color;
void main() {
gl_FragColor = vec4(_color, 1.0);
}`;
function createShader(source, type) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function createProgram(vertexSource, fragmentSource) {
let program = gl.createProgram();
let vertexShader = createShader(vertexSource, gl.VERTEX_SHADER);
let fragmentShader = createShader(fragmentSource, gl.FRAGMENT_SHADER);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
return program;
}
let triangleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorMesh), gl.STATIC_DRAW);
program = createProgram(vertexSource, fragmentSource);
let attrPosition = gl.getAttribLocation(program, "position");
let attrColor = gl.getAttribLocation(program, "color");
gl.enableVertexAttribArray(attrPosition);
gl.enableVertexAttribArray(attrColor);
gl.useProgram(program);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleBuffer);
gl.vertexAttribPointer(attrPosition, 2, gl.FLOAT, false, 4 * (2 + 3), 0);
gl.vertexAttribPointer(attrColor, 3, gl.FLOAT, false, 4 * (2 + 3), 4 * 2);
gl.drawArrays(gl.TRIANGLES, 0, 15);
<canvas width="640" height="360"></canvas>