Rectangular/square gradient in shader - glsl

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>

Related

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

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>

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>

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>

OpenGL ES3 Shadow map problems

I work on C++ project for Android with OpenGL ES3, so I try to implement the shadow map with directional light, I understand the theory well but I never get it successfully rendered.
first I create the framebuffer which contains the depth map:
glGenFramebuffers(1, &depthMapFBO);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffers(1, GL_NONE);
glReadBuffer(GL_NONE);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
then I create a shader program that compiles the depth shader which is the following:
#version 300 es
precision mediump float;
layout (location = 0) in vec3 position;
layout (location = 4) in ivec4 BoneIDs;
layout (location = 5) in vec4 Weights;
const int MAX_BONES = 100;
uniform mat4 lightSpaceMatrix;
uniform mat4 model;
uniform bool skinned;
uniform mat4 gBones[MAX_BONES];
void main(){
vec4 nPos;
if(skinned){
mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
BoneTransform += gBones[BoneIDs[1]] * Weights[1];
BoneTransform += gBones[BoneIDs[2]] * Weights[2];
BoneTransform += gBones[BoneIDs[3]] * Weights[3];
nPos=BoneTransform * vec4(position, 1.0);
}
else
nPos = vec4(position, 1.0);
vec4 p=model * nPos;
gl_Position = lightSpaceMatrix * p;
}
and draw the scene using this shader program with the light space matrix using the following:
glCullFace(GL_FRONT);
double delta = GetCurrentTime() - firstFrame;
glm::mat4 camInv = glm::inverse(camera->getViewMatrix());
glm::mat4 lightSpaceProjection = glm::ortho(-40.0f, 40.0f, -40.0f, 40.0f, -1.0f, 100.0f);
glm::mat4 lightSpaceView = glm::lookAt(sun->direction, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
lightSpaceMatrix = lightSpaceProjection * (lightSpaceView*camInv) ;
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
directDepthShader.use();
glUniformMatrix4fv(glGetUniformLocation(directDepthShader.getProgramID(), "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
for (mesh_it it = castShadowMeshes.begin(); it != castShadowMeshes.end(); it++) {
it->get()->renderDepth(directDepthShader, delta);
}
glCullFace(GL_BACK);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
finally I render the scene with the regular shader program and bind the depth map to the shadowMap uniform with the following code:
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
phongShader.use();
if (sun != nullptr)
if (sun->castShadow)
glUniformMatrix4fv(glGetUniformLocation(phongShader.getProgramID(), "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
this->setLightsUniforms(phongShader);
this->setViewUniforms(phongShader);
for (mesh_it it = phongMeshes.begin(); it != phongMeshes.end(); it++) {
if (it->get()->hasNormalMap) {
glUniform1i(glGetUniformLocation(phongShader.getProgramID(), "has_normal_map"), 1);
if (directlights.size() > 0) {
for (dlight_it it = this->directlights.begin(); it != this->directlights.end(); ++it) {
GLuint directLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("directLightPos[" + ToString((*it)->index) + "]").c_str());
glUniform3f(directLightPosLoc, (*it)->direction.x, (*it)->direction.y, (*it)->direction.z);
}
}
if (pointlights.size() > 0) {
for (plight_it it = this->pointlights.begin(); it != this->pointlights.end(); ++it) {
GLuint pointLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("pointLightPos[" + ToString((*it)->index) + "]").c_str());
glUniform3f(pointLightPosLoc, (*it)->position.x, (*it)->position.y, (*it)->position.z);
}
}
if (spotlights.size() > 0) {
for (slight_it it = this->spotlights.begin(); it != this->spotlights.end(); ++it) {
GLuint spotLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("spotLightPos[" + ToString((*it)->index) + "]").c_str());
glUniform3f(spotLightPosLoc, (*it)->position.x, (*it)->position.y, (*it)->position.z);
}
}
}
double first = GetCurrentTime() - firstFrame;
it->get()->textures = 0;
if (sun != nullptr)
if (sun->castShadow) {
glUniform1i(glGetUniformLocation(phongShader.getProgramID(), "shadowMap"), it->get()->textures);
glActiveTexture(GL_TEXTURE0 + it->get()->textures);
glBindTexture(GL_TEXTURE_2D, depthMap);
it->get()->textures++;
}
it->get()->Render(phongShader, first, deltaTime);
glBindTexture(GL_TEXTURE_2D, 0);
}
finally the shader vertex and fragment are the following:
Vertex:
#version 300 es
precision mediump float;
#define NR_DIRECT_LIGHTS 0
#define NR_POINT_LIGHTS 0
#define NR_SPOT_LIGHTS 0
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoord;
layout (location = 3) in vec3 tangent;
layout (location = 4) in ivec4 BoneIDs;
layout (location = 5) in vec4 Weights;
const int MAX_BONES = 100;
out vec2 TexCoords;
out vec3 Normal;
out vec3 tDirectLightPos[NR_DIRECT_LIGHTS];
out vec3 tPointLightPos[NR_POINT_LIGHTS];
out vec3 tSpotLightPos[NR_SPOT_LIGHTS];
out vec3 tViewPos;
out vec3 tFragPos;
out vec4 FragPosLightSpace;
// conditions //
uniform bool has_normal_map;
uniform bool skinned;
//
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec3 viewPos;
uniform mat4 lightSpaceMatrix;
uniform mat4 gBones[MAX_BONES];
uniform vec3 directLightPos[NR_DIRECT_LIGHTS];
uniform vec3 pointLightPos[NR_POINT_LIGHTS];
uniform vec3 spotLightPos[NR_SPOT_LIGHTS];
void main(){
TexCoords = texCoord;
vec4 nPos;
vec3 N=transpose(inverse(mat3(model))) * normal;
if(skinned){
mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
BoneTransform += gBones[BoneIDs[1]] * Weights[1];
BoneTransform += gBones[BoneIDs[2]] * Weights[2];
BoneTransform += gBones[BoneIDs[3]] * Weights[3];
nPos=BoneTransform * vec4(position, 1.0);
Normal=(BoneTransform*vec4(N,0.0)).xyz;
}
else{
nPos = vec4(position, 1.0);
Normal=N;
}
gl_Position = projection*view * model * nPos;
vec3 FragPos = vec3(model * nPos);
if(has_normal_map){
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 T = normalize(normalMatrix * tangent);
vec3 N = normalize(N);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N,T);
if (dot(cross(N, T), B) < 0.0)
T = T * -1.0;
mat3 TBN = transpose(mat3(T, B, N));
tViewPos=TBN*viewPos;
tFragPos=TBN*FragPos;
for(int i = 0; i < NR_DIRECT_LIGHTS-2; i++)
tDirectLightPos[i]=TBN*directLightPos[i];
for(int i = 0; i < NR_POINT_LIGHTS-2; i++)
tPointLightPos[i]=TBN*pointLightPos[i];
for(int i = 0; i < NR_SPOT_LIGHTS-2; i++)
tSpotLightPos[i]=TBN*spotLightPos[i];
}
else{
tViewPos=viewPos;
tFragPos=FragPos;
}
FragPosLightSpace = lightSpaceMatrix * vec4(FragPos,1.0);
}
Fragment:
#version 300 es
precision mediump float;
#define NR_DIRECT_LIGHTS 0
#define NR_POINT_LIGHTS 0
#define NR_SPOT_LIGHTS 0
out vec4 glFragColor;
vec2 poissonDisk[4] = vec2[](
vec2( -0.94201624, -0.39906216 ),
vec2( 0.94558609, -0.76890725 ),
vec2( -0.094184101, -0.92938870 ),
vec2( 0.34495938, 0.29387760 )
);
struct SpotLight{
vec3 position;
vec3 direction;
vec3 color;
float constant;
float linear;
float quadratic;
float cutoff;
float outerCutOff;
float intensity;
int castShadow;
};
struct PointLight{
vec3 position;
vec3 color;
float constant;
float linear;
float quadratic;
float intensity;
};
struct DirectLight {
vec3 direction;
vec3 color;
float intensity;
int castShadow;
};
in vec2 TexCoords;
in vec3 Normal;
in vec4 FragPosLightSpace;
in vec3 tDirectLightPos[NR_DIRECT_LIGHTS];
in vec3 tPointLightPos[NR_POINT_LIGHTS];
in vec3 tSpotLightPos[NR_SPOT_LIGHTS];
in vec3 tViewPos;
in vec3 tFragPos;
uniform bool Has_normal_map;
uniform sampler2D mat_diffuse;
uniform sampler2D mat_specular;
uniform sampler2D mat_normal;
uniform sampler2D shadowMap;
uniform vec3 matDiffuse;
uniform vec3 matSpecular;
uniform float shininess;
uniform float far_plane;
uniform DirectLight directLights[NR_DIRECT_LIGHTS];
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLights[NR_SPOT_LIGHTS];
vec3 calcDirectLight(DirectLight,vec3,vec3,vec3,vec3);
vec3 calcPointLight(PointLight,vec3,vec3,vec3,vec3);
vec3 calcSpotLight(SpotLight,vec3,vec3,vec3,vec3);
float directShadowCalculation();
void main(){
vec3 normal;
if(Has_normal_map){
normal=texture(mat_normal, TexCoords).rgb;
normal = normalize(normal * 2.0 - 1.0); // this normal is in tangent space
}
else
normal=normalize(Normal);
vec3 diffColor= matDiffuse+vec3(texture(mat_diffuse, TexCoords));
vec3 specColor= matSpecular+vec3(texture(mat_specular,TexCoords));
vec3 result;
result=vec3(0.0);
for(int i = 0; i < NR_DIRECT_LIGHTS-2; i++)
result += calcDirectLight(directLights[i],normal,tDirectLightPos[i],diffColor,specColor);
for(int i = 0; i < NR_POINT_LIGHTS-2; i++)
result += calcPointLight(pointLights[i],normal,tPointLightPos[i],vec3(0.0,0.2,0.4),specColor);
for(int i = 0; i < NR_SPOT_LIGHTS-2; i++)
result += calcSpotLight(spotLights[i],normal,tSpotLightPos[i],diffColor,specColor);
vec4 color =vec4(result,1.0);
float gamma = 2.2;
color.rgb = pow(color.rgb, vec3(1.0/gamma));
vec4 ambient=vec4(0.2,0.2,0.2,1.0)*vec4(diffColor,1.0);
glFragColor=ambient+color;
}
vec3 calcDirectLight(DirectLight light,vec3 norm,vec3 tLightPos,vec3 diffColor,vec3 specColor){
vec3 lightDir ;
if(Has_normal_map)
lightDir= normalize(tLightPos);
else
lightDir = normalize(light.direction);
float diff = max(dot(lightDir,norm), 0.0);
vec3 diffuse = light.color * diff *diffColor;
vec3 viewDir = normalize(tViewPos- tFragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfwayDir), 0.0), 32.0);
vec3 specular = shininess* spec *specColor* light.color;
vec3 result;
if(light.castShadow==1){
float shadow = directShadowCalculation();
result =light.intensity* ( shadow* (diffuse + specular));
}
else
result =light.intensity* (diffuse + specular);
return result;
}
vec3 calcPointLight(PointLight light,vec3 norm,vec3 tLightPos,vec3 diffColor,vec3 specColor){
vec3 lightDir ;
if(Has_normal_map)
lightDir= normalize(tLightPos-tFragPos);
else
lightDir = normalize(light.position - tFragPos);
float diff = max(dot(lightDir,norm), 0.0);
vec3 diffuse = light.color * diff * diffColor;
vec3 viewDir = normalize(tViewPos- tFragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfwayDir), 0.0), 16.0);
vec3 specular =shininess* specColor * spec * light.color;
vec3 result;
float distance = length(light.position - tFragPos);
float attenuation = 1.0f / (light.constant + light.linear * distance +light.quadratic * (distance * distance));
diffuse *= attenuation;
specular *= attenuation;
result=light.intensity*(diffuse+specular);
return result;
}
vec3 calcSpotLight(SpotLight light,vec3 norm,vec3 tLightPos,vec3 diffColor,vec3 specColor){
vec3 lightDir ;
if(Has_normal_map)
lightDir= normalize(tLightPos-tFragPos);
else
lightDir = normalize(light.position - tFragPos);
float diff = max(dot(lightDir,norm), 0.0);
vec3 diffuse = light.color * diff * diffColor;
vec3 viewDir = normalize(tViewPos- tFragPos);
float spec =0.0;
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(norm, halfwayDir), 0.0), 16.0);
vec3 specular = shininess* light.color * spec * specColor;
// Spotlight (soft edges)
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = (light.cutoff - light.outerCutOff);
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
diffuse *= intensity;
specular *= intensity;
// Attenuation
float distance = length(light.position - tFragPos);
float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
diffuse *= attenuation;
specular *= attenuation;
vec3 result = intensity*(diffuse+specular);
return result;
}
float directShadowCalculation(){
vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
float shadow = 1.0;
for (int i=0;i<4;i++){
if ( texture( shadowMap, -projCoords.xy + poissonDisk[i]/700.0 ).z < -projCoords.z ){
shadow-=0.2;
}
}
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
sorry for all that code but I don't know where is the problem, it takes a week searching and debugging with no progress.
Edit
1- the light position vector is (-3.5f, 8.0f, 1.0f)
2- I changed the directShadowCalculation() to:
float directShadowCalculation(){
vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
float shadow = 1.0;
for (int i=0;i<4;i++){
if ( texture( shadowMap, projCoords.xy + poissonDisk[i]/700.0 ).z < projCoords.z ){
shadow-=0.2;
}
}
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
this is the result
If you have to convert view space coordinates to the local space of the light source, then the lightSpaceMatrix would have to be setup as you do it:
lightSpaceMatrix = lightSpaceProjection * (lightSpaceView*camInv)
Because, you have to convert from view space to world space, by camInv. Then you have to convert the world space coordinates, as seen from the light source (lightSpaceView). And finaly you have to project it lightSpaceProjection.
But you convert directly from world coordinates to the local space of the light source, in the vertex shader:
FragPosLightSpace = lightSpaceMatrix * vec4(FragPos,1.0);
Because of that you have to set up the lightSpaceMatrix like this:
lightSpaceMatrix = lightSpaceProjection * lightSpaceView
The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. It transforms from view (eye) space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) by dividing with the w component of the clip coordinates. The NDC are in range (-1,-1,-1) to (1,1,1).
This is regardless of orthographic or perspective projection.
After dividing the projected fragment position (light space) by its w component, the projCoords are in the range from (-1, -1, -1) to (1, 1, 1).
projCoords = projCoords * 0.5 + 0.5; transforms the XY coordinates to texture coordinates, and transforms the Z coordinate to a depth value in the range [0, 1].
vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
The inversions in the shadow test make no sense, since the content of the texture should be also a depth value in the range [0,1].
The shadow test should look somehow like this:
if ( texture( shadowMap, projCoords.xy ).z < projCoords.z )
{
....
}
If sun->direction is the direction up to the sun, then lightSpaceView has to be set up like this:
glm::mat4 lightSpaceView = glm::lookAt(sun->direction, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
But, If sun->direction is the direction in which the sun shines, then lightSpaceView has to be set up like this:
glm::mat4 lightSpaceView = glm::lookAt(-sun->direction, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
Since you use orthographic projection for the light and the origin of the light space matrix is near the origin of the worldspace, the near plane of the light projection should be far in the back of the light space. Otherwise the objects which are near to the the sun would be clipped, by the near plane of the light projection, when generating the light depth map.
glm::mat4 lightSpaceProjection = glm::ortho(-40.0f, 40.0f, -40.0f, 40.0f, -100.0f, 100.0f);
Since the light calculations are done in view space, you have to convert the light positions from the world space to the view space:
GLuint directLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("directLightPos[" + ToString((*it)->index) + "]").c_str());
glm::vec3 dir = glm::mat3(camera->getViewMatrix()) * (*it)->direction;
glUniform3fv( directLightPosLoc, 1, &dir[0] );
GLuint pointLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("pointLightPos[" + ToString((*it)->index) + "]").c_str());
glm::vec4 pos = camera->getViewMatrix( * glm::vec4((*it)->position.x, (*it)->position.y, (*it)->position.z, 1.0);
glUniform3fv( directLightPosLoc, 1, &pos[0] );
GLuint spotLightPosLoc = glGetUniformLocation(phongShader.getProgramID(), (const GLchar*) ("spotLightPos[" + ToString((*it)->index) + "]").c_str());
glm::vec4 pos = camera->getViewMatrix( * glm::vec4((*it)->position.x, (*it)->position.y, (*it)->position.z, 1.0);
glUniform3fv( spotLightPosLoc, 1, &pos[0] );
See the WebGL example which demonstrates the algorithm:
(function loadscene() {
var sliderScale = 100.0
var gl;
var progShadow;
var progDraw;
var shadowFB;
var bufTorus;
var bufGround;
var canvas;
var vp_size;
var fb_size;
function render(deltaMS){
var ambient = document.getElementById( "ambient" ).value / sliderScale;
var diffuse = document.getElementById( "diffuse" ).value / sliderScale;
var specular = document.getElementById( "specular" ).value / sliderScale;
var shininess = document.getElementById( "shininess" ).value;
canvas = document.getElementById( "scene-canvas" );
var lightPos = [-3.0, 0.0, 2.0];
var lightAnimationMat = RotateAxis( IdentityMat44(), CalcAng( deltaMS, 20.0 ), 2 );
lightPos = Transform( lightPos, lightAnimationMat );
var lightDir = [ -lightPos[0], -lightPos[1], -lightPos[2] ];
var light = Camera.Create( lightPos, [0, 0, 0], [0, 0, 1], 110, [ 5.0, 5.0 ], -20.0, 20.0 );
var camera = Camera.Create( [0, 2.5, 2], [0, 0, 0], [0, 0, 1], 110, [vp_size[0], vp_size[1]], 0.5, 100.0 );
var lightPrjMat = Camera.Ortho( light );
var lightViewMat = Camera.LookAt( light );
var prjMat = Camera.Perspective( camera );
var viewMat = Camera.LookAt( camera );
var modelMat = IdentityMat44();
modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 13.0 ), 0 );
modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 17.0 ), 1 );
groundModelMat = IdentityMat44();
var viewLightDir = TransformVec( lightDir, viewMat );
gl.viewport( 0, 0, fb_size[0], fb_size[1] );
gl.enable( gl.DEPTH_TEST );
shadowFB.Bind( true );
ShaderProgram.Use( progShadow );
ShaderProgram.SetUniformM44( progShadow, "u_projectionMat44", lightPrjMat );
ShaderProgram.SetUniformM44( progShadow, "u_viewMat44", lightViewMat );
ShaderProgram.SetUniformM44( progShadow, "u_modelMat44", modelMat );
ShaderProgram.SetUniformF2( progShadow, "u_depthRange", [light.near, light.far] );
VertexBuffer.Draw( bufTorus );
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
shadowFB.Release( true );
shadowFB.BindTexture( 1 );
ShaderProgram.Use( progDraw );
ShaderProgram.SetUniformM44( progDraw, "u_projectionMat44", prjMat );
ShaderProgram.SetUniformM44( progDraw, "u_viewMat44", viewMat );
ShaderProgram.SetUniformM44( progDraw, "u_lightProjectionMat44", lightPrjMat );
ShaderProgram.SetUniformM44( progDraw, "u_lightViewMat44", lightViewMat );
ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );
ShaderProgram.SetUniformI1( progDraw, "u_depthSampler", 1 );
ShaderProgram.SetUniformF3( progDraw, "u_lightDir", viewLightDir )
ShaderProgram.SetUniformF1( progDraw, "u_ambient", ambient )
ShaderProgram.SetUniformF1( progDraw, "u_diffuse", diffuse )
ShaderProgram.SetUniformF1( progDraw, "u_specular", specular )
ShaderProgram.SetUniformF1( progDraw, "u_shininess", shininess )
VertexBuffer.Draw( bufTorus );
ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", groundModelMat );
VertexBuffer.Draw( bufGround );
requestAnimationFrame(render);
}
function nearestPow2( aSize ){
return Math.pow( 2, Math.round( Math.log( aSize ) / Math.log( 2 ) ) );
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
var size = Math.max(256, Math.max(vp_size[0], vp_size[1]));
size = nearestPow2(size/2);
fb_size = [size, size]
shadowFB = FrameBuffer.Create( fb_size );
}
function initScene() {
document.getElementById( "ambient" ).value = 0.2 * sliderScale;
document.getElementById( "diffuse" ).value = 0.7 * sliderScale;
document.getElementById( "specular" ).value = 0.5 * sliderScale;
document.getElementById( "shininess" ).value = 8.0;
canvas = document.getElementById( "scene-canvas");
vp_size = [canvas.width, canvas.height];
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progShadow = ShaderProgram.Create(
[ { source : "shadow-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "shadow-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
if (!progShadow.progObj)
return null;
progShadow.inPos = ShaderProgram.AttributeIndex( progShadow, "inPos" );
progShadow.inNV = ShaderProgram.AttributeIndex( progShadow, "inNV" );
progShadow.inCol = ShaderProgram.AttributeIndex( progShadow, "inCol" );
progDraw = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
if (!progDraw.progObj)
return null;
progDraw.inPos = ShaderProgram.AttributeIndex( progDraw, "inPos" );
progDraw.inNV = ShaderProgram.AttributeIndex( progDraw, "inNV" );
progDraw.inCol = ShaderProgram.AttributeIndex( progDraw, "inCol" );
// create torus
var circum_size = 32, tube_size = 32;
var rad_circum = 1.0;
var rad_tube = 0.5;
var torus_pts = [];
var torus_nv = [];
var torus_col = [];
var torus_inx = [];
var col = [1, 0.5, 0.0];
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var center = [
Math.cos(2 * Math.PI * i_c / circum_size),
Math.sin(2 * Math.PI * i_c / circum_size) ]
for ( var i_t = 0; i_t < tube_size; ++ i_t ) {
var tubeX = Math.cos(2 * Math.PI * i_t / tube_size)
var tubeY = Math.sin(2 * Math.PI * i_t / tube_size)
var pt = [
center[0] * ( rad_circum + tubeX * rad_tube ),
center[1] * ( rad_circum + tubeX * rad_tube ),
tubeY * rad_tube ]
var nv = [ pt[0] - center[0] * rad_tube, pt[1] - center[1] * rad_tube, tubeY * rad_tube ]
torus_pts.push( pt[0], pt[1], pt[2] );
torus_nv.push( nv[0], nv[1], nv[2] );
torus_col.push( col[0], col[1], col[2] );
var i_cn = (i_c+1) % circum_size
var i_tn = (i_t+1) % tube_size
var i_c0 = i_c * tube_size;
var i_c1 = i_cn * tube_size;
torus_inx.push( i_c0+i_t, i_c0+i_tn, i_c1+i_t, i_c0+i_tn, i_c1+i_t, i_c1+i_tn )
}
}
bufTorus = VertexBuffer.Create(
[ { data : torus_pts, attrSize : 3, attrLoc : progDraw.inPos },
{ data : torus_nv, attrSize : 3, attrLoc : progDraw.inNV },
{ data : torus_col, attrSize : 3, attrLoc : progDraw.inCol } ],
torus_inx
);
var g_l = 8.0;
var g_h = -2.5;
var g_c = [ 0.8, 0.6, 0.8 ];
bufGround = VertexBuffer.Create(
[ { data : [ -g_l, -g_l, g_h, g_l, -g_l, g_h, g_l, g_l, g_h, -g_l, g_l, g_h ], attrSize : 3, attrLoc : progDraw.inPos },
{ data : [ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 ], attrSize : 3, attrLoc : progDraw.inNV },
{ data : [ g_c[0], g_c[1], g_c[2], g_c[0], g_c[1], g_c[2], g_c[0], g_c[1], g_c[2], g_c[0], g_c[1], g_c[2] ], attrSize : 3, attrLoc : progDraw.inCol } ],
[ 0, 1, 2, 0, 2, 3 ]
);
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
var startTime;
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaTime, intervall ) {
return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
}
function IdentityMat44() { return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }
function RotateAxis(matA, angRad, axis) {
var aMap = [ [1, 2], [2, 0], [0, 1] ];
var a0 = aMap[axis][0], a1 = aMap[axis][1];
var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
var matB = IdentityMat44();
for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
for ( var i = 0; i < 3; ++ i ) {
matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
}
return matB;
}
function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
return [ v[0] / len, v[1] / len, v[2] / len ];
}
Transform = function(vec, mat) {
return [
vec[0] * mat[0*4+0] + vec[1] * mat[1*4+0] + vec[2] * mat[2*4+0] + mat[3*4+0],
vec[0] * mat[0*4+1] + vec[1] * mat[1*4+1] + vec[2] * mat[2*4+1] + mat[3*4+1],
vec[0] * mat[0*4+2] + vec[1] * mat[1*4+2] + vec[2] * mat[2*4+2] + mat[3*4+2],
vec[0] * mat[0*4+3] + vec[1] * mat[1*4+3] + vec[2] * mat[2*4+3] + mat[3*4+3] ]
if ( h[3] == 0.0 )
return [0, 0, 0]
return [ h[0]/h[3], h[1]/h[3], h[2]/h[3] ];
}
TransformVec = function(vec, mat) {
return [
vec[0] * mat[0*4+0] + vec[1] * mat[1*4+0] + vec[2] * mat[2*4+0],
vec[0] * mat[0*4+1] + vec[1] * mat[1*4+1] + vec[2] * mat[2*4+1],
vec[0] * mat[0*4+2] + vec[1] * mat[1*4+2] + vec[2] * mat[2*4+2] ]
}
var Camera = {};
Camera.Create = function( pos, target, up, fov_y, vp, near, far ) {
var camera = {};
camera.pos = pos;
camera.target = target;
camera.up = up;
camera.fov_y = fov_y;
camera.vp = vp;
camera.near = near;
camera.far = far;
return camera;
}
Camera.Ortho = function( camera ) {
var fn = camera.far + camera.near;
var f_n = camera.far - camera.near;
var w = camera.vp[0];
var h = camera.vp[1];
var m = IdentityMat44();
m[0] = 2 / w; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 2 / h; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = -2 / f_n; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = -fn / f_n; m[15] = 1;
return m;
}
Camera.Perspective = function( camera ) {
var fn = camera.far + camera.near;
var f_n = camera.far - camera.near;
var r = camera.vp[0] / camera.vp[1];
var t = 1 / Math.tan( Math.PI * camera.fov_y / 360 );
var m = IdentityMat44();
m[0] = t/r; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = t; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = -fn / f_n; m[11] = -1;
m[12] = 0; m[13] = 0; m[14] = -2 * camera.far * camera.near / f_n; m[15] = 0;
return m;
}
Camera.LookAt = function( camera ) {
var mz = Normalize( [ camera.pos[0]-camera.target[0], camera.pos[1]-camera.target[1], camera.pos[2]-camera.target[2] ] );
var mx = Normalize( Cross( camera.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, camera.pos );
var ty = Dot( my, camera.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], camera.pos );
var m = IdentityMat44();
m[0] = mx[0]; m[1] = my[0]; m[2] = mz[0]; m[3] = 0;
m[4] = mx[1]; m[5] = my[1]; m[6] = mz[1]; m[7] = 0;
m[8] = mx[2]; m[9] = my[2]; m[10] = mz[2]; m[11] = 0;
m[12] = tx; m[13] = ty; m[14] = tz; m[15] = 1;
return m;
}
var ShaderProgram = {};
ShaderProgram.Create = function (shaderList) {
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 prog = {}
prog.progObj = this.LinkProgram(shaderObjs)
if (prog.progObj) {
prog.attribIndex = {};
var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
var name = gl.getActiveAttrib(prog.progObj, i_n).name;
prog.attribIndex[name] = gl.getAttribLocation(prog.progObj, name);
}
prog.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
var name = gl.getActiveUniform(prog.progObj, i_n).name;
prog.unifomLocation[name] = gl.getUniformLocation(prog.progObj, name);
}
}
return prog;
}
ShaderProgram.AttributeIndex = function (prog, name) { return prog.attribIndex[name]; }
ShaderProgram.UniformLocation = function (prog, name) { return prog.unifomLocation[name]; }
ShaderProgram.Use = function (prog) { gl.useProgram(prog.progObj); }
ShaderProgram.SetUniformI1 = function (prog, name, val) { if (prog.unifomLocation[name]) gl.uniform1i(prog.unifomLocation[name], val); }
ShaderProgram.SetUniformF1 = function (prog, name, val) { if (prog.unifomLocation[name]) gl.uniform1f(prog.unifomLocation[name], val); }
ShaderProgram.SetUniformF2 = function (prog, name, arr) { if (prog.unifomLocation[name]) gl.uniform2fv(prog.unifomLocation[name], arr); }
ShaderProgram.SetUniformF3 = function (prog, name, arr) { if (prog.unifomLocation[name]) gl.uniform3fv(prog.unifomLocation[name], arr); }
ShaderProgram.SetUniformF4 = function (prog, name, arr) { if (prog.unifomLocation[name]) gl.uniform4fv(prog.unifomLocation[name], arr); }
ShaderProgram.SetUniformM33 = function (prog, name, mat) { if (prog.unifomLocation[name]) gl.uniformMatrix3fv(prog.unifomLocation[name], false, mat); }
ShaderProgram.SetUniformM44 = function (prog, name, mat) { if (prog.unifomLocation[name]) gl.uniformMatrix4fv(prog.unifomLocation[name], false, mat); }
ShaderProgram.CompileShader = function (source, shaderStage) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
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 : null;
}
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 : null;
}
var VertexBuffer = {};
VertexBuffer.Create = function( attributes, indices ) {
var buffer = {};
buffer.buf = [];
buffer.attr = []
for ( var i = 0; i < attributes.length; ++ i ) {
buffer.buf.push( gl.createBuffer() );
buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
}
buffer.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
buffer.inxLen = indices.length;
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
return buffer;
}
VertexBuffer.Draw = function( bufObj ) {
for ( var i = 0; i < bufObj.buf.length; ++ i ) {
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( bufObj.attr[i].loc );
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
for ( var i = 0; i < bufObj.buf.length; ++ i )
gl.disableVertexAttribArray( bufObj.attr[i].loc );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
var FrameBuffer = {};
FrameBuffer.Create = function( vp, texturePlan ) {
var texPlan = texturePlan ? new Uint8Array( texturePlan ) : null;
var fb = gl.createFramebuffer();
var fbsize = Math.max(vp[0], vp[1]);
fbsize = 1 << 31 - Math.clz32(fbsize); // nearest power of 2
fb.width = fbsize;
fb.height = fbsize;
gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
fb.color0_texture = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, fb.color0_texture );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, texPlan );
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 );
fb.renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer( gl.RENDERBUFFER, fb.renderbuffer );
gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height );
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.color0_texture, 0 );
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fb.renderbuffer );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.bindRenderbuffer( gl.RENDERBUFFER, null );
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
fb.Bind = function( clear ) {
gl.bindFramebuffer( gl.FRAMEBUFFER, this );
if ( clear ) {
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
}
};
fb.Release = function( clear ) {
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
if ( clear ) {
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
}
};
fb.BindTexture = function( textureUnit ) {
gl.activeTexture( gl.TEXTURE0 + textureUnit );
gl.bindTexture( gl.TEXTURE_2D, this.color0_texture );
};
return fb;
}
initScene();
})();
html,body {
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
}
#gui {
position : absolute;
top : 0;
left : 0;
}
<script id="shadow-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 inPos;
attribute vec3 inNV;
attribute vec3 inCol;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
varying vec4 vPosPrj;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
void main()
{
vec3 modelNV = mat3( u_modelMat44 ) * normalize( inNV );
vertNV = mat3( u_viewMat44 ) * modelNV;
vertCol = inCol;
vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
vec4 viewPos = u_viewMat44 * modelPos;
vertPos = viewPos.xyz / viewPos.w;
vPosPrj = u_projectionMat44 * viewPos;
gl_Position = vPosPrj;
}
</script>
<script id="shadow-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
varying vec4 vPosPrj;
uniform vec2 u_depthRange;
vec3 PackDepth( 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;
}
void main()
{
float ndc_depth = vPosPrj.z / vPosPrj.w;
float nearZ = u_depthRange.x;
float farZ = u_depthRange.y;
float depth = ndc_depth * 0.5 + 0.5;
gl_FragColor = vec4( PackDepth( depth ).xyz, 1.0 );
}
</script>
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 inPos;
attribute vec3 inNV;
attribute vec3 inCol;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
varying vec4 lightPrj;
varying vec4 vPosPrj;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
uniform mat4 u_lightProjectionMat44;
uniform mat4 u_lightViewMat44;
void main()
{
vec3 modelNV = mat3( u_modelMat44 ) * normalize( inNV );
vertNV = mat3( u_viewMat44 ) * modelNV;
vertCol = inCol;
vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
vec4 lightPos = u_lightViewMat44 * modelPos;
vec4 viewPos = u_viewMat44 * modelPos;
lightPrj = u_lightProjectionMat44 * lightPos;
vertPos = viewPos.xyz / viewPos.w;
vPosPrj = u_projectionMat44 * viewPos;
gl_Position = vPosPrj;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
varying vec4 lightPrj;
varying vec4 vPosPrj;
uniform sampler2D u_depthSampler;
uniform vec3 u_lightDir;
uniform float u_ambient;
uniform float u_diffuse;
uniform float u_specular;
uniform float u_shininess;
float UnpackDepth( 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);
}
float Depth( in sampler2D depthSampler, in vec2 texC )
{
vec3 depthVal = texture2D( depthSampler, texC.st ).xyz;
return UnpackDepth( depthVal.rgb );
}
void main()
{
vec3 ndc_light = lightPrj.xyz / lightPrj.w;
vec2 lightTexC = ndc_light.xy * 0.5 + 0.5;
float lightDepth = ndc_light.z * 0.5 + 0.5;
float testDepth = Depth( u_depthSampler, lightTexC );
float shadow = step( lightDepth-0.01, testDepth ) + step( testDepth, 0.0 );
vec3 color = vertCol;
vec3 lightCol = u_ambient * color;
vec3 normalV = normalize( vertNV );
vec3 lightV = normalize( -u_lightDir );
float NdotL = max( 0.0, dot( normalV, lightV ) );
lightCol += shadow * NdotL * u_diffuse * color;
vec3 eyeV = normalize( -vertPos );
vec3 halfV = normalize( eyeV + lightV );
float NdotH = max( 0.0, dot( normalV, halfV ) );
float kSpecular = ( u_shininess + 2.0 ) * pow( NdotH, u_shininess ) / ( 2.0 * 3.14159265 );
lightCol += shadow * kSpecular * u_specular * color;
gl_FragColor = vec4( lightCol.rgb, 1.0 );
}
</script>
<div><form id="gui" name="inputs"><table>
<tr> <td> <font color= #CCF>ambient</font> </td>
<td> <input type="range" id="ambient" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color= #CCF>diffuse</font> </td>
<td> <input type="range" id="diffuse" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color= #CCF>specular</font> </td>
<td> <input type="range" id="specular" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color= #CCF>shininess</font> </td>
<td> <input type="range" id="shininess" min="0" max="100" value="0"/></td> </tr>
</table></form></div>
<canvas id="scene-canvas" style="border: none;" width="512" height="512"></canvas>
See also
How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader
Transform the modelMatrix