my first question got closed. Since then I've added some code and made some progress. However, the gradient generated is circular and I currently don't know how to transform it into a square.
Here is my current result:
Target result(along these lines):
Here is my fragment shader:
precision highp float;
varying vec2 vTextureCoord;
uniform float centerX;
uniform float centerY;
uniform vec4 colors[4];
uniform float steps[4];
void main() {
vec3 map = vec3(vTextureCoord, 1);
vec2 uv = map.xy;
float dist = distance(vTextureCoord, vec2(centerX, centerY));
highp vec4 col = colors[0];
for (int i = 1; i < 4; ++i) {
col = mix(col, colors[i], smoothstep(steps[i - 1], steps[i], dist));
}
float factor = max(abs(uv.x - centerX), abs(uv.y - centerY));
float c = 1. - max(abs(uv.x - centerX), abs(uv.y - centerY));
vec4 finalColor = vec4((col.r - factor), (col.g - factor), (col.b - factor), 1.);
gl_FragColor = finalColor;
}
The parameters passed are:
Colors: [[1, 0, 0], [1, 1, 1], [1, 0, 0], [0, 1, 0]]
Steps: [0, 0.29, 0.35, 1]
Below is an example that uses texture coordinates in the range 0 - 1 which makes the center of the gradient at 0.5, 0.5
. Thus to compute the gradient we must normalize the distance from the center from the range 0 - 0.5 to 0 - 1. This is done by dividing by 0.5 or the reciprocal (as in example) multiplying by 2 (as multiplication is always the better option than division)
Also your method of calculating the gradient color at each fragment is computationally expensive. For each gradient (3 in this case) you call smoothstep
and then mix
, yet for each fragment 2 of those calculations do nothing of consequence to the computed color.
The example below reduces the computations by checking if the distance is within a particular gradient, and only if within then computes the color assigning to gl_FragColor
and then breaks out of the loop
I can not workout if you want the gradient to darken to the edges as your first image and code (and accepted answer) suggest, or it is the second image in your question that is the result you want. The example assumes that you want the second image.
const shaders = {
vs: `attribute vec2 vert;
varying vec2 uv;
void main() {
uv = (vert + 1.0) / 2.0; // normalize texture coords
gl_Position = vec4(vert, 0.0, 1.0);
}`,
fs: `precision mediump float;
varying vec2 uv;
uniform vec3 colors[4];
uniform float steps[4];
void main(){
vec2 gradXY = abs(uv - 0.5); // 0.5 is centerX, centerY
float dist = pow(max(gradXY.x, gradXY.y) * 2.0, 2.0);
float start = steps[0];
for (int i = 1; i < 4; i++) {
float end = steps[i];
if (dist >= start && dist <= end) {
gl_FragColor = vec4(mix(colors[i - 1], colors[i], (dist-start) / (end-start)), 1);
break;
}
start = end;
}
}`,};
const F32A = a => new Float32Array(a), UI16A = a => new Uint16Array(a);
const GLBuffer = (data, type = gl.ARRAY_BUFFER, use = gl.STATIC_DRAW, buf) => (gl.bindBuffer(type, buf = gl.createBuffer()), gl.bufferData(type, data, use), buf);
const GLLocs = (shr, type, ...names) => names.reduce((o,name) => (o[name] = (gl[`get${type}Location`])(shr, name), o), {});
const GLShader = (prg, source, type = gl.FRAGMENT_SHADER, shr) => {
gl.shaderSource(shr = gl.createShader(type), source);
gl.compileShader(shr);
gl.attachShader(prg, shr);
}
var W;
const gl = canvas.getContext("webgl");
requestAnimationFrame(render);
addEventListener("resize", render);
const prog = gl.createProgram();
GLShader(prog, shaders.vs, gl.VERTEX_SHADER);
GLShader(prog, shaders.fs);
gl.linkProgram(prog);
gl.useProgram(prog);
const locs = GLLocs(prog, "Uniform", "colors", "steps");
const vert = GLLocs(prog, "Attrib", "vert").vert;
GLBuffer(F32A([-1,-1, 1,-1, 1,1, -1,1]));
GLBuffer(UI16A([1,2,3, 0,1,3]), gl.ELEMENT_ARRAY_BUFFER);
gl.enableVertexAttribArray(vert);
gl.vertexAttribPointer(vert, 2, gl.FLOAT, false, 0, 0);
function render() {
gl.viewport(0, 0, W = canvas.width = Math.min(innerWidth,innerHeight), canvas.height = W);
gl.uniform3fv(locs.colors, F32A([1,1,1, 1,0,0, 1,1,1, 0,0,0]));
gl.uniform1fv(locs.steps, F32A([0, 1/3, 2/3, 1]));
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
body {
margin: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background: black;
}
<canvas id="canvas"></canvas>
The are a number of ambiguities in your question which are addressed in the following
The pow
function in the example line ...
float dist = pow(max(gradXY.x, gradXY.y) * 2.0, 2.0);
... is the an approximation of your use of smoothstep(steps[i - 1], steps[i], dist)
when you calculate the col (assuming the dist range of 0 - 0.5). If you want the full Hermite curve you can replace the line with ...
float distL = max(gradXY.x, gradXY.y) * 2.0;
float dist = distL * distL * (3.0 - 2.0 * distL);
.. and if you want the darkening to the edge as in the questions first image use the following line when calculating the frag color. NOTE assuming colors are vec4
not vec3
make appropriate mods if you use the example code.
FragColor = mix(colors[i - 1], colors[i], (dist-start) / (end-start)) - vec4(vec3(distL * 0.5),0);
or if not using Hermite curve
FragColor = mix(colors[i - 1], colors[i], (dist-start) / (end-start)) - vec4(vec3(dist * 0.5),0);