How to fill the area below a curve in a glsl fragment shader? - glsl

I'm using the following code to plot a sine-wave curve:
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
const float AMPLITUDE = 0.125;
const float PERIOD = 1.0;
const float VELOCITY = 8.0;
const vec3 COLOR1 = vec3(1.0, 0.5, 0.0);
const vec3 COLOR2 = vec3(1.0, 0.0, 0.0);
#define PI 3.141592653589793
#define TWO_PI 6.283185307179586
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float phase = u_time * VELOCITY / PI;
float curve = AMPLITUDE * sin(uv.x * TWO_PI / PERIOD - phase);
float shape = step(distance(curve + uv.y, 0.5), 1.0 / u_resolution.x);
vec3 color = (1.0 - shape) * COLOR2 + shape * COLOR1;
gl_FragColor = vec4(color, 1.0);
}
Which produces this image:
I would like to fill the area below the curve with COLOR1, as in this image:

You only need to test if curve is greater than uv.y - 0.5. Use the step for this:
step(edge, x)
step generates a step function by comparing x to edge.
For element i of the return value, 0.0 is returned if x[i] < edge[i], and 1.0 is returned otherwise.
e.g:
float shape = step(distance(curve + uv.y, 0.5), 1.0 / u_resolution.x);
float shape = step(uv.y - 0.5, curve);
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {};
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.u_time = gl.getUniformLocation(progDraw, "u_time");
progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform1f(progDraw.u_time, deltaMS/1000.0);
gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
#version 100
attribute vec2 inPos;
void main()
{
//ndcPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
const float AMPLITUDE = 0.125;
const float PERIOD = 1.0;
const float VELOCITY = 8.0;
const vec3 COLOR1 = vec3(1.0, 0.5, 0.0);
const vec3 COLOR2 = vec3(1.0, 0.0, 0.0);
#define PI 3.141592653589793
#define TWO_PI 6.283185307179586
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float phase = u_time * VELOCITY / PI;
float curve = AMPLITUDE * sin(uv.x * TWO_PI / PERIOD - phase);
//float shape = step(distance(curve + uv.y, 0.5), 1.0 / u_resolution.x);
float shape = step(uv.y - 0.5, curve);
vec3 color = mix(COLOR2, COLOR1, shape);
gl_FragColor = vec4(color, 1.0);
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

Related

Rectangular/square gradient in shader

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]
Using texture coordinates
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)
Simplifying the shader
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>
Question ambiguities
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);
A square gradient can be achieved by computing the maximum distance of both axis:
float dist = distance(vTextureCoord, vec2(centerX, centerY));
vec2 distV = vTextureCoord - vec2(centerX, centerY);
float dist = max(abs(distV.x), abs(distV.y));
Complete example:
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {};
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.u_time = gl.getUniformLocation(progDraw, "u_time");
progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform1f(progDraw.u_time, deltaMS/1000.0);
gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
#version 100
attribute vec2 inPos;
varying vec2 ndcPos;
void main()
{
ndcPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 ndcPos; // normaliced device coordinates in range [-1.0, 1.0]
uniform float u_time;
uniform vec2 u_resolution;
#define FILL
void main()
{
vec4 colors[4];
colors[0] = vec4(1.0, 0.0, 0.0, 1.0);
colors[1] = vec4(0.0, 1.0, 0.0, 1.0);
colors[2] = vec4(0.0, 0.0, 1.0, 1.0);
colors[3] = vec4(1.0, 1.0, 1.0, 1.0);
float steps[4];
steps[0] = 0.2;
steps[1] = 0.4;
steps[2] = 0.6;
steps[3] = 0.8;
vec2 uv = ndcPos.xy;
uv.x *= u_resolution.x / u_resolution.y;
vec2 vTextureCoord = uv;
float centerX = 0.0;
float centerY = 0.0;
//float dist = distance(vTextureCoord, vec2(centerX, centerY));
vec2 distV = vTextureCoord - vec2(centerX, centerY);
float dist = max(abs(distV.x), abs(distV.y));
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;
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

How do I replicate a square in GLSL to make a pattern?

I am learning how to create shapes in GLSL code and I recently made a star-like shape in the center of a square using the following code:
#idef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_time;
void main(){
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
vec3 color = vec3(0.0);
float d = 0.0;
// Remap space to -1 to 1.
st = st *2.-1.;
// Make Distance Field
d = length(abs(st)-.3);
d = length(min(abs(st)-.3, 0.));
// Visualize the Distance Field
gl_FragColor = vec4(vec3(fract(d*10.0)), 1.0);
// Draw with the distance field
gl_FragColor = vec4(vec3(step(.3, d)), 1.0);
}
I now want to try to replicate this square design into a bordered tile-like pattern but I don't know how to modify my code to duplicate it into columns. Can anyone help?
Just scale st by the number of tiles and get the fractional part of the result by fract(). For instance:
void main()
{
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
float tiles = 5.0;
st = fract(st * tiles);
// [...]
}
In your example you have to adapt the computation of the distance filed slightly (0.3 -> 0.32):
// Make Distance Field
d = length(abs(st)-.32);
d = length(min(abs(st)-.32, 0.));
See the WebGL example using the fragment shader from your question:
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {};
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.u_time = gl.getUniformLocation(progDraw, "u_time");
progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform1f(progDraw.u_time, deltaMS/1000.0);
gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
#version 100
//precision mediump float;
attribute vec2 inPos;
//varying vec2 ndcPos;
void main()
{
//ndcPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
//varying vec2 ndcPos; // normaliced device coordinates in range [-1.0, 1.0]
uniform float u_time;
uniform vec2 u_resolution;
void main()
{
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
float tiles = 5.0;
st = fract(st * tiles);
vec3 color = vec3(0.0);
float d = 0.0;
// Remap space to -1 to 1.
st = st *2.-1.;
// Make Distance Field
d = length(abs(st)-.32);
d = length(min(abs(st)-.32, 0.));
// Visualize the Distance Field
gl_FragColor = vec4(vec3(fract(d*10.0)), 1.0);
// Draw with the distance field
gl_FragColor = vec4(vec3(step(.3, d)), 1.0);
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

How to do a float to RGBA and back round trip in GLSL (WebGL GLSL ES 1.0)? [duplicate]

Trying to understand the many issues related to the WebGL development for a generic mobile target, now I need to store depth information in a texture attachment for later retrieval and post-processing.
JavaScript:
var depthRB = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthRB);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRB);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
Vertex shader:
precision mediump float;
uniform mat4 u_transformMatrix;
attribute vec3 a_position;
varying float v_depth;
void main() {
vec4 tcoords = u_transformMatrix * vec4(a_position, 1.0);
v_depth = 0.5 * (tcoords.z + 1.0);
gl_Position = tcoords;,
}
Fragment shader:
precision mediump float;
varying float v_depth;
vec4 PackDepth(in float frag_depth) {
vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
vec4 bitMsk = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 enc = fract(frag_depth * bitSh);
enc -= enc.xxyz * bitMsk;
return enc;
}
float UnpackDepth( const in vec4 enc ) {
const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );
float decoded = dot( enc, bit_shift );
return decoded;
}
void main() {
vec4 encoded_depth;
float decoded_depth;
encoded_depth = PackDepth(v_depth);
decoded_depth = UnpackDepth(encoded_depth);
//gl_FragColor = vec4(vec3(decoded_depth), 1.0);
gl_FragColor = encoded_depth;',
}
This is what i get now: left: iPad PRO/Android/desktop Chrome --emulate-shader-precision, middle: desktop FF/Chrome (no flags), right: encoded and decoded (obviously as 256 tones gray-scale)
I tried many different methods for packing/unpacking but none seems to work.
Any advice about what I am doing wrong?
Moreover, i noticed also many examples of the most common WebGL libraries which uses a RGBA texture to store depth information are broken - i believe for the same reason, somewhere an issue in the pack/unpack functions.
EDIT: same issue in Three.js: https://github.com/mrdoob/three.js/issues/9092
Interesting thing, if I use the old mod approach to packing depth, I
get a bunch more precision (at least a couple more bits)
Which is the correct approach to store and retrieve depth information, using mediump precision?
The floting point precision for a variable with the precsion qualifier mediump is guaranteed to 10 bits.
See OpenGL ES Shading Language 1.00 Specification - 4.5.2 Precision Qualifiers, page 33
The required minimum ranges and precisions for precision qualifiers are:
For this reason, only the two highest bytes of the encoded depth have a meaning. The algorithm stores the highest byte in the alpha channel and the second highest byte in the blue color channel. This causes that an RGB view of the encoded depth may look arbitrary.
Further the algorithm has an overflow for the depth of 1.0. This causes that the the depth of 1 is encoded as a completely black color, but black becomes 0.0 when it is decoded.
An algorithm which encodes a depth value in the range from [0.0, 1.0], into 16 bits from b00000000 to b11111111, may look like this (RG color channel):
vec2 PackDepth16( in float depth )
{
float depthVal = depth * (256.0*256.0 - 1.0) / (256.0*256.0);
vec3 encode = fract( depthVal * vec3(1.0, 256.0, 256.0*256.0) );
return encode.xy - encode.yz / 256.0 + 1.0/512.0;
}
float UnpackDepth16( in vec2 pack )
{
float depth = dot( pack, 1.0 / vec2(1.0, 256.0) );
return depth * (256.0*256.0) / (256.0*256.0 - 1.0);
}
This algorithm can be extended to 24 bits or 32 bits:
vec3 PackDepth24( in float depth )
{
float depthVal = depth * (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
vec4 encode = fract( depthVal * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
return encode.xyz - encode.yzw / 256.0 + 1.0/512.0;
}
float UnpackDepth24( in vec3 pack )
{
float depth = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );
return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
}
vec4 PackDepth32( in float depth )
{
depth *= (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
vec4 encode = fract( depth * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
return vec4( encode.xyz - encode.yzw / 256.0, encode.w ) + 1.0/512.0;
}
float UnpackDepth32( in vec4 pack )
{
float depth = dot( pack, 1.0 / vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
}
See the code snippet, which compares the algorithm from the answer (top part) and the algorithm from question (bottom part):
(function onLoad() {
// shader program object
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList, uniformNames ) {
var shaderObjs = [];
for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
if ( shderObj == 0 )
return 0;
shaderObjs.push( shderObj );
}
var progObj = this.LinkProgram( shaderObjs )
if ( progObj != 0 ) {
progObj.unifomLocation = {};
for ( var i_n = 0; i_n < uniformNames.length; ++ i_n ) {
var name = uniformNames[i_n];
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
var shaderScript = document.getElementById(source);
if (shaderScript) {
source = "";
var node = shaderScript.firstChild;
while (node) {
if (node.nodeType == 3) source += node.textContent;
node = node.nextSibling;
}
}
var shaderObj = gl.createShader( shaderStage );
gl.shaderSource( shaderObj, source );
gl.compileShader( shaderObj );
var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : 0;
}
ShaderProgram.LinkProgram = function( shaderObjs ) {
var prog = gl.createProgram();
for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
gl.attachShader( prog, shaderObjs[i_sh] );
gl.linkProgram( prog );
status = gl.getProgramParameter( prog, gl.LINK_STATUS );
if ( !status ) alert("Could not initialise shaders");
gl.useProgram( null );
return status ? prog : 0;
}
function drawScene(){
var canvas = document.getElementById( "ogl-canvas" );
var vp = [canvas.width, canvas.height];
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
ShaderProgram.Use( progDraw );
gl.enableVertexAttribArray( progDraw.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
gl.disableVertexAttribArray( progDraw.pos );
}
var gl;
var prog;
var bufObj = {};
var canvas
function sceneStart() {
container = document.getElementById('container');
canvas = document.getElementById( "ogl-canvas");
resize();
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
], [] );
progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
if ( prog == 0 )
return;
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1, -1, 1, -1, -1, 1, 1, 1 ] ), gl.STATIC_DRAW );
window.onresize = resize;
setInterval(drawScene, 50);
}
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
sceneStart();
})();
<canvas id="ogl-canvas"></canvas>
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vertPos;
void main()
{
vertPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertPos;
vec2 PackDepth16( in float depth )
{
float depthVal = depth * (256.0*256.0 - 1.0) / (256.0*256.0);
vec3 encode = fract( depthVal * vec3(1.0, 256.0, 256.0*256.0) );
return encode.xy - encode.yz / 256.0 + 1.0/512.0;
}
float UnpackDepth16( in vec2 pack )
{
float depth = dot( pack, 1.0 / vec2(1.0, 256.0) );
return depth * (256.0*256.0) / (256.0*256.0 - 1.0);
}
vec4 PackDepth32_orig(in float frag_depth) {
vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
vec4 bitMsk = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 enc = fract(frag_depth * bitSh);
enc -= enc.xxyz * bitMsk;
return enc;
}
float UnpackDepth32_orig( const in vec4 enc ) {
const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );
float decoded = dot( enc, bit_shift );
return decoded;
}
void main()
{
float depthTest = clamp(vertPos.x + 0.5, 0.0, 1.0);
vec2 color1 = clamp(PackDepth16( depthTest ), 0.0, 1.0);
float depth1 = UnpackDepth16( color1 );
vec4 color2 = clamp(PackDepth32_orig( depthTest ), 0.0, 1.0);
float depth2 = UnpackDepth32_orig( color2 );
gl_FragColor = vec4( mix( vec3(depth1), vec3(depth2), step(vertPos.y, 0.0) ), 1.0 );
}
</script>

Problem going from Shadershop functions to glsl functions

It's a bit related to how to convert shadershop formula into glsl .
Only the above answer does not provide any explanation.
What I try is:
Where SineV is:
and SineH is:
This is what I have so far:
#ifdef GL_ES
precision mediump float;
#endif
float scale = 5.0;
uniform vec2 u_resolution;
float sineV(float x) {
x *= scale;
return (sin( x / 0.18 ) + sin( (x - -1.0) / 0.35 ) + 0.25);
}
float sineH(float x) {
x *= scale;
return (sin( (x - 0.18) / 0.37 ) + sin( ((x - 0.18) - -0.31) / 0.45 ) + -0.59) * 0.75 + 0.1;
}
mat2 inverse(mat2 m) {
float det_m = m[0][0]*m[1][1] - m[0][1]*m[1][0];
mat2 inv_m = mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / det_m;
return inv_m;
}
void main() {
mat2 m = mat2(0.0, -1.0,
1.0, 0.0);
vec2 st = gl_FragCoord.xy / u_resolution.xy;
float x1 = st.x;
float x2 = st.y;
vec2 x1x2 = inverse(m) * vec2(x1, x2);
vec2 tmp = m * x1x2;
float x = sineV(x1x2.x - x1x2.y) + sineH(tmp.x - tmp.y);
vec3 color = vec3( x, x, abs(x) );
gl_FragColor = vec4(color, 1.0);
}
But I reached a point where it is just guessing and trial and error.
Hope someone can help.
The matrix in shadershop transforms the input tuple (x1, x2) to (u, v) coordinates.
The fragment shader is executed for each fragment, each fragment is associated to different (u, v) coordinates. You've to calculate the x1 and x2 corresponding to the actual (u, v) coordinate of the fragment. So you've to use the inverse matrix:
(u, v) = m * (x1, x2)
(x1, x2 = inverse(m) * (u, v)
The shader has to sum up result of the f(x1) and the result of the f(x2):
x = f(x1) + f(x2)
The corresponding glsl code is:
vec2 x1x2 = inverse(m) * st.xy;
float x = sineH(x1x2.x) + sineV(x1x2.y);
The mapping of x to the white, blue and black color can be achieved by the empirical formula (I found this out by trial and error):
vec3 color = vec3(x, x, abs(x));
For the full shader code see the example (note, the result is stretched to the canvas):
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {};
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.u_resolution = gl.getUniformLocation(progDraw, "u_resolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
vp_size = [window.innerWidth, window.innerHeight];
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 ndcPos;
void main()
{
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_resolution;
float scale = 5.0;
float sineV(float x) {
x *= scale;
return (sin( x / 0.18 ) + sin( (x - -1.0) / 0.35 ) + 0.25);
}
float sineH(float x) {
x *= scale;
return (sin( (x - 0.18) / 0.37 ) + sin( ((x - 0.18) - -0.31) / 0.45 ) + -0.59) * 0.75 + 0.1;
}
mat2 inverse(mat2 m) {
float det_m = m[0][0]*m[1][1] - m[0][1]*m[1][0];
mat2 inv_m = mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / det_m;
return inv_m;
}
void main()
{
vec2 st = 2.0 * gl_FragCoord.xy / u_resolution.xy - 1.0;
mat2 m = mat2(0.0, -1.0, 1.0, 0.0);
vec2 x1x2 = inverse(m) * st.xy;
float x = sineH(x1x2.x) + sineV(x1x2.y);
vec3 color = vec3(x, x, abs(x));
gl_FragColor = vec4(color, 1.0);
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

GLSL plot disk sector. Problem with clamp'ing angle

My glsl code to plot disk sector:
const float PI = 3.141592653;
vec4 white = vec4(1.0,1.0,1.0,1.0);
vec4 black = vec4(0.0,0.0,0.0,1.0);
vec2 p = (gl_FragCoord.xy * 2.0 - resolution)/min(resolution.x,resolution.y);
float sector(vec2 c, vec2 p, float r, float sa, float alpha){
float l = abs(distance(p,c));
float t = smoothstep(r, r + bl, l);
vec2 uv = p - c;
a = atan(uv.y,uv.x);
t = a >= sa ? t : 1.0;
t = a <= sa + alpha ? t : 1.0;
return t;
}
float t = sector(vec2(0.0,0.1),p,0.1,PI/2.0,PI/3.0);
gl_FragColor = mix(white,black,t);
The code works. But it has a flaw that atan(y,x) returns values in a range of [-π,π].
To circumvent this issue, I am trying to clamp the atan result angle in a range of [0,2π]:
a = clamp(2.0*PI + atan(uv.y,uv.x), 0.0, 2.0*PI);
After above replacement there is nothing that is showed on screen. The disk sector did not show up even for legal atan values. What am I doing wrong?
EDIT
Thanks to the person who answered this topic. I spent too much time on this mistake. In case someone is interested in the solution to this, I made some code changes to reflect that the result angle can be in different unit circle quadrants.
float sector(vec2 c, vec2 p, float r, float sa, float alpha){
float l = abs(distance(p,c));
float t = smoothstep(r, r + bl, l);
vec2 uv = p - c;
float a = atan(uv.y,uv.x);
a = step(sign(a),0.0)*TWO_PI + a;
t = a >= sa ? t : 1.0;
t = a <= sa + alpha ? t : 1.0;
return t;
}
The result of
a = clamp(2.0*PI + atan(uv.y,uv.x), 0.0, 2.0*PI);
is in range [PI, 2*PI], because the result of 2.0*PI + atan(uv.y,uv.x) is in range [PI, 3*PI].
It has to be
a = clamp(PI + atan(uv.y,uv.x), 0.0, 2.0*PI);
But since the result of atan is always in range [-PI, PI], it is sufficient to do:
a = PI + atan(uv.y, uv.x);
If you just want to make the negative range ([0.0, 2*PI]) become positive, then you've to add 2*PI in case when the angle is negative:
e.g.
a = atan(uv.y, uv.x);
if (a < 0.0) a += 2.0*PI;
or
a = atan(uv.y, uv.x);
a = step(sign(a),0.0)*2.0*TWO_PI + a;
or even
a = PI - atan(uv.y, -uv.x);
(function loadscene() {
var canvas, gl, vp_size, prog, bufObj = {};
function initScene() {
canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = gl.createProgram();
for (let i = 0; i < 2; ++i) {
let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
gl.attachShader(progDraw, shaderObj);
gl.linkProgram(progDraw);
}
status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(progDraw));
progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
progDraw.u_time = gl.getUniformLocation(progDraw, "time");
progDraw.u_resolution = gl.getUniformLocation(progDraw, "resolution");
gl.useProgram(progDraw);
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
gl.enableVertexAttribArray( progDraw.inPos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
vp_size = [window.innerWidth, window.innerHeight];
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function render(deltaMS) {
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.uniform1f(progDraw.u_time, deltaMS/2000.0);
gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
requestAnimationFrame(render);
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 ndcPos;
void main()
{
ndcPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 ndcPos; // normaliced device coordinates in range [-1.0, 1.0]
uniform float time;
uniform vec2 resolution;
const float PI = 3.141592653;
float sector(vec2 c, vec2 p, float r, float sa, float alpha){
float bl = 0.1;
float l = abs(distance(p, c ));
float t = smoothstep(r-bl, r + bl, l);
vec2 uv = p - c;
//float a = atan(uv.y, uv.x);
//if (a < 0.0) a += 2.0*PI;
float a = PI - atan(uv.y, -uv.x);
t = a >= sa ? t : 1.0;
t = a <= sa + alpha ? t : 1.0;
return t;
}
void main()
{
vec4 white = vec4(1.0,1.0,1.0,1.0);
vec4 black = vec4(0.0,0.0,0.0,1.0);
vec2 p = (gl_FragCoord.xy * 2.0 - resolution)/min(resolution.x,resolution.y);
float t = sector(vec2(0.0), p, 0.9, 0.0, mod(time, 2.0*PI));
gl_FragColor = mix(white,black,t);
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>