OpenGL Lighting on texture plane is not working - c++

I want to light to the texture plane but this is not work. Light on solid sphere is very well, but texture plane is not light.
Whole Image
Lighting on solid-sphere is working well.
But, lighting on texture plane is not working. (GL_DECAL, GL_REPLACE; I also tried GL_MODULATE)
This is a snippet of my rendering code. (Whole code on GitHub)
Loading texture.
sf::Image image;
if (!image.loadFromFile(path))
return false;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA,
image.getSize().x, image.getSize().y, 0,
GL_RGBA, GL_UNSIGNED_BYTE,
image.getPixelsPtr()
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Initialize
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepth(1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glShadeModel(GL_SMOOTH);
//glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glutSetCursor(GLUT_CURSOR_NONE);
light.Init();
camera.SetPin((GLfloat)width / 2, (GLfloat)height/2);
Display Callback
adjustPerspective();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_LIGHTING);
glPushMatrix();
camera.SetLookAt();
light.On();
// TODO: dsiplay processing
for (auto& obj : display_objs)
{
glPushMatrix();
obj->Draw();
glPopMatrix();
}
glPopMatrix();
// print fps and swap buffers
Light Initialize Function
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
// Set lighting intensity and color
glLightfv(GL_LIGHT0, GL_AMBIENT, qaAmbientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, qaDiffuseLight);
glLightfv(GL_LIGHT0, GL_POSITION, qaLightPosition);
glLightfv(GL_LIGHT0, GL_SPECULAR, qaSpecularLight);
////////////////////////////////////////////////
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0);// set cutoff angle
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dirVector0);
glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 10.0); // set focusing strength
Light.On() Function
glPushMatrix();
glTranslatef(2.0, 10.0, 2.0);
//glRotatef(90, 1, 0, 0);
glLightfv(GL_LIGHT0, GL_POSITION, qaLightPosition);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dirVector0);
glPopMatrix();
glPushMatrix();
glDisable(GL_LIGHTING);
glTranslatef(2.0, 0.0, 2.0);
glRotatef(-90.0, 1.0, 0.0, 0.0);
glutWireCone(tan(80.0 / 180.0 * 3.14159265),10.0,20,20);
glEnable(GL_LIGHTING);
glPopMatrix();
And this is texture plane draw function.
float tile_x = 0.125;
glTranslatef(x, y, z);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex.GetId());
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBegin(GL_QUADS);
// Both of the following cases not work.
glNormal3f(0, -1, 0);
glNormal3f(0, 1, 0);
glTexCoord2f(0.0, 0.0); glVertex3f(0, 0, 0);
glTexCoord2f(height*tile_x, 0.0); glVertex3f(0, 0, width);
glTexCoord2f(height*tile_x, width*tile_x); glVertex3f(height, 0, width);
glTexCoord2f(0.0, width*tile_x); glVertex3f(height, 0, 0);
glEnd();
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
I changed the vector direction, changed the glTexEnvf attribute, changed the order of the code, but did not fix the error. I think there is a fundamental error in my code, but I can not find it. Why is this happening, and how do I fix it?

I want to light to the texture plane but this is not work. Light on solid sphere is very well, but texture plane is not light.
This is an issue caused by the Gouraud Shading model of the OpenGLs standard light model. While Phong shading in common means the technique, which does the light calculations per fragment, at Gouraud Shading, the light calculations are done per vertex. The calculated light is interpolated according to the Barycentric coordinate of the fragment on the primitive.
This means that in your case the light is calculated for the corners of the ground quad. This so calculated light is interpolated for all the fragments in between. The angel of the normal vector at the corners to the light vector tends to 90°. Because of that the entire ground quad looks almost unlit.
Since the light is calculated per vertex, then the light is calculated for more positions than the the 4 corners of the quad and the quality increases. Note, the light on the spheres looks almost perfect, because a sphere consists of a lot of vertices around its shape.
Try the following code, which splits the quad into tiles:
int tiles = 5;
float u_max = height*tile_x;
float v_max = width*tile_x
glBegin(GL_QUADS);
glNormal3f(0, 1, 0);
for (int x=0; x < tiles; ++x)
{
for (int y=0; y < tiles; ++y)
{
x0 = (float)x/(float)tiles;
x1 = (float)(x+1)/(float)tiles;
y0 = (float)y/(float)tiles;
y1 = (float)(y+1)/(float)tiles;
glTexCoord2f(u_max*x0, v_max*y0); glVertex3f(height*x0, 0, widht*y0);
glTexCoord2f(u_max*x1, v_max*y0); glVertex3f(height*x0, 0, widht*y1);
glTexCoord2f(u_max*x1, v_max*y1); glVertex3f(height*x1, 0, widht*y1);
glTexCoord2f(u_max*x0, v_max*y1); glVertex3f(height*x1, 0, widht*y0);
}
}
glEnd();
Of course you can also write your own shader and implement per fragment lighting. But the deprecated fixed function pipeline OpenGL standard light model does not support per fragment lighting.
See the difference in the WebGL example:
(function loadscene() {
var resize, gl, gouraudDraw, phongDraw, vp_size;
var bufSphere = {};
function render(delteMS){
var shading = document.getElementById( "shading" ).value;
var shininess = document.getElementById( "shininess" ).value;
var ambientCol = [0.2, 0.2, 0.2];
var diffuseCol = [0.6, 0.6, 0.6];
var specularCol = [0.8, 0.8, 0.8];
Camera.create();
Camera.vp = vp_size;
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 );
gl.disable(gl.CULL_FACE);
var progDraw = shading == 0 ? gouraudDraw : phongDraw;;
// set up draw shader
ShaderProgram.Use( progDraw.prog );
ShaderProgram.SetUniformM44( progDraw.prog, "u_projectionMat44", Camera.Perspective() );
ShaderProgram.SetUniformM44( progDraw.prog, "u_viewMat44", Camera.LookAt() );
ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.lightPos", [0.0, 0.0, 0.25] )
ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.ambient", ambientCol )
ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.diffuse", diffuseCol )
ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.specular", specularCol )
ShaderProgram.SetUniformF1( progDraw.prog, "u_lightSource.shininess", shininess )
var modelMat = IdentityMat44()
modelMat = RotateAxis( modelMat, -1.5, 0 );
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );
ShaderProgram.SetUniformM44( progDraw.prog, "u_modelMat44", modelMat );
// draw scene
VertexBuffer.Draw( bufSphere );
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
}
function initScene() {
canvas = document.getElementById( "canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return null;
gouraudDraw = {}
gouraudDraw.prog = ShaderProgram.Create(
[ { source : "gouraud-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "gouraud-shader-fs", stage : gl.FRAGMENT_SHADER }
],
[ "u_projectionMat44", "u_viewMat44", "u_modelMat44",
"u_lightSource.lightDir", "u_lightSource.ambient", "u_lightSource.diffuse", "u_lightSource.specular", "u_lightSource.shininess", ] );
if ( gouraudDraw.prog == 0 )
return;
gouraudDraw.inPos = gl.getAttribLocation( gouraudDraw.prog, "inPos" );
gouraudDraw.inNV = gl.getAttribLocation( gouraudDraw.prog, "inNV" );
gouraudDraw.inCol = gl.getAttribLocation( gouraudDraw.prog, "inCol" );
phongDraw = {}
phongDraw.prog = ShaderProgram.Create(
[ { source : "phong-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "phong-shader-fs", stage : gl.FRAGMENT_SHADER }
],
[ "u_projectionMat44", "u_viewMat44", "u_modelMat44",
"u_lightSource.lightDir", "u_lightSource.ambient", "u_lightSource.diffuse", "u_lightSource.specular", "u_lightSource.shininess", ] );
if ( phongDraw.prog == 0 )
return;
phongDraw.inPos = gl.getAttribLocation( phongDraw.prog, "inPos" );
phongDraw.inNV = gl.getAttribLocation( phongDraw.prog, "inNV" );
phongDraw.inCol = gl.getAttribLocation( phongDraw.prog, "inCol" );
// create cube
var layer_size = 16, circum_size = 32;
var rad_circum = 1.0;
var rad_tube = 0.5;
var sphere_pts = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0];
var sphere_nv = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0];
var sphere_col = [0.8, 0.6, 0.3, 0.8, 0.6, 0.3, 0.8, 0.6, 0.3, 0.8, 0.6, 0.3];
var sphere_inx = [0, 1, 2, 0, 2, 3];
bufSphere = VertexBuffer.Create(
[ { data : sphere_pts, attrSize : 3, attrLoc : gouraudDraw.inPos },
{ data : sphere_nv, attrSize : 3, attrLoc : gouraudDraw.inNV },
{ data : sphere_col, attrSize : 3, attrLoc : gouraudDraw.inCol } ],
sphere_inx );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaTime, intervall ) {
return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, intervall, range ) {
var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
function EllipticalPosition( a, b, angRag ) {
var a_b = a * a - b * b
var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
}
glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );
function IdentityMat44() {
var m = new glArrayType(16);
m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 1; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
return m;
};
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 = new glArrayType(16);
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 ];
}
var Camera = {};
Camera.create = function() {
this.pos = [0, 2, 0.0];
this.target = [0, 0, 0];
this.up = [0, 0, 1];
this.fov_y = 90;
this.vp = [800, 600];
this.near = 0.5;
this.far = 100.0;
}
Camera.Perspective = function() {
var fn = this.far + this.near;
var f_n = this.far - this.near;
var r = this.vp[0] / this.vp[1];
var t = 1 / Math.tan( Math.PI * this.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 * this.far * this.near / f_n; m[15] = 0;
return m;
}
Camera.LookAt = function() {
var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
var mx = Normalize( Cross( this.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, this.pos );
var ty = Dot( my, this.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.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 progObj = this.LinkProgram( shaderObjs )
if ( progObj != 0 ) {
progObj.attribIndex = {};
var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( progObj, i_n ).name;
progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
}
progObj.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( progObj, i_n ).name;
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; }
ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; }
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.SetUniformI1 = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF1 = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1f( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF2 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF3 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF4 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform4fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformM33 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix3fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.SetUniformM44 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix4fv( progObj.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 );
}
initScene();
})();
html,body {
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
}
#gui {
position : absolute;
top : 0;
left : 0;
}
<script id="gouraud-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;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
struct TLightSource
{
vec3 lightPos;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform TLightSource u_lightSource;
vec3 Light( vec3 eyeV, vec3 N, vec3 P )
{
vec3 lightCol = u_lightSource.ambient;
vec3 L = normalize( u_lightSource.lightPos-P );
float NdotL = max( 0.0, dot( N, L ) );
lightCol += NdotL * u_lightSource.diffuse;
vec3 H = normalize( eyeV + L );
float NdotH = max( 0.0, dot( N, H ) );
float kSpecular = ( u_lightSource.shininess + 2.0 ) * pow( NdotH, u_lightSource.shininess ) / ( 2.0 * 3.14159265 );
lightCol += kSpecular * u_lightSource.specular;
return lightCol;
}
void main()
{
vec3 modelNV = mat3( u_modelMat44 ) * normalize( inNV );
vertNV = mat3( u_viewMat44 ) * modelNV;
vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
vec4 viewPos = u_viewMat44 * modelPos;
vertPos = viewPos.xyz / viewPos.w;
vec3 eyeV = normalize( -vertPos );
vec3 normalV = normalize( vertNV ) * sign(vertNV.z);
vertCol = inCol * Light( eyeV, normalV, vertPos );
gl_Position = u_projectionMat44 * viewPos;
}
</script>
<script id="gouraud-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
void main()
{
gl_FragColor = vec4( vertCol, 1.0 );
}
</script>
<script id="phong-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;
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;
gl_Position = u_projectionMat44 * viewPos;
}
</script>
<script id="phong-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
struct TLightSource
{
vec3 lightPos;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform TLightSource u_lightSource;
vec3 Light( vec3 eyeV, vec3 N, vec3 P )
{
vec3 lightCol = u_lightSource.ambient;
vec3 L = normalize( u_lightSource.lightPos - P );
float NdotL = max( 0.0, dot( N, L ) );
lightCol += NdotL * u_lightSource.diffuse;
vec3 H = normalize( eyeV + L );
float NdotH = max( 0.0, dot( N, H ) );
float kSpecular = ( u_lightSource.shininess + 2.0 ) * pow( NdotH, u_lightSource.shininess ) / ( 2.0 * 3.14159265 );
lightCol += kSpecular * u_lightSource.specular;
return lightCol;
}
void main()
{
vec3 eyeV = normalize( -vertPos );
vec3 normalV = normalize( vertNV ) * sign(vertNV.z);
vec3 color = vertCol * Light( eyeV, normalV, vertPos );
gl_FragColor = vec4( color, 1.0 );
}
</script>
<form id="gui" name="inputs"><table><tr>
<td><font color= #CCF>Shading:</font></td>
<td><select id="shading">>
<option value="0">Gouraud</option>
<option value="1">Phong</option>
</select></td>
</tr><tr>
<td><font color= #CCF>Shininess:</font></td>
<td><input type="range" id="shininess" min="0" max="100" value="10"/></td>
</tr></table></form>
<canvas id="canvas" style="border: none;" width="100%" height="100%"></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 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>

Creating a Gradient Color in Fragment Shader

I'm trying to achieve making a gradient color as the design apps (Photoshop for example) does, but can't get the exact result i want.
My shader creates very nice 'gradients' but also contains other colors that are different from the colors I want to switch in.
It looks nice but, my aim is adding blending functions later and make a kind of color correction shader. but first of all I have to get the right colors.
Here is my fragment shader
http://player.thebookofshaders.com/?log=171119111216
uniform vec2 u_resolution;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color1 = vec3(1.9,0.55,0);
vec3 color2 = vec3(0.226,0.000,0.615);
float mixValue = distance(st,vec2(0,1));
vec3 color = mix(color1,color2,mixValue);
gl_FragColor = vec4(color,mixValue);
}
And here is
Thanks in advance..
In response to just the title of your question you might also want to consider doing the mix in other color spaces. Your code is mixing in RGB space but you'll get different results in different spaces.
Example
const gl = document.createElement("canvas").getContext("webgl");
gl.canvas.width = 100;
gl.canvas.height = 100;
gl.viewport(0, 0, 100, 100);
const vsrc = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fRGB = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 color = mix(color1, color2, mixValue);
gl_FragColor = vec4(color, 1);
}
`;
const fHSV = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
// from: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
c = vec3(c.x, clamp(c.yz, 0.0, 1.0));
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 hsv1 = rgb2hsv(color1);
vec3 hsv2 = rgb2hsv(color2);
// mix hue in toward closest direction
float hue = (mod(mod((hsv2.x - hsv1.x), 1.) + 1.5, 1.) - 0.5) * mixValue + hsv1.x;
vec3 hsv = vec3(hue, mix(hsv1.yz, hsv2.yz, mixValue));
vec3 color = hsv2rgb(hsv);
gl_FragColor = vec4(color, 1);
}
`;
const fHSL = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
const float Epsilon = 1e-10;
vec3 rgb2hcv(in vec3 RGB)
{
// Based on work by Sam Hocevar and Emil Persson
vec4 P = lerp(vec4(RGB.bg, -1.0, 2.0/3.0), vec4(RGB.gb, 0.0, -1.0/3.0), step(RGB.b, RGB.g));
vec4 Q = mix(vec4(P.xyw, RGB.r), vec4(RGB.r, P.yzx), step(P.x, RGB.r));
float C = Q.x - min(Q.w, Q.y);
float H = abs((Q.w - Q.y) / (6. * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 rgb2hsl(in vec3 RGB)
{
vec3 HCV = rgb2hcv(RGB);
float L = HCV.z - HCV.y * 0.5;
float S = HCV.y / (1 - abs(L * 2. - 1.) + Epsilon);
return vec3(HCV.x, S, L);
}
vec3 hsl2rgb(vec3 c)
{
c = vec3(fract(c.x), clamp(c.yz, 0.0, 1.0));
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 hsl1 = rgb2hsl(color1);
vec3 hsl2 = rgb2hsl(color2);
// mix hue in toward closest direction
float hue = (mod(mod((hsl2.x - hsl1.x), 1.) + 1.5, 1.) - 0.5) * mixValue + hsl1.x;
vec3 hsl = vec3(hue, mix(hsl1.yz, hsl2.yz, mixValue));
vec3 color = hsl2rgb(hsv);
gl_FragColor = vec4(color, 1);
}
`;
const fLAB = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
// from: https://code.google.com/archive/p/flowabs/source
vec3 rgb2xyz( vec3 c ) {
vec3 tmp;
tmp.x = ( c.r > 0.04045 ) ? pow( ( c.r + 0.055 ) / 1.055, 2.4 ) : c.r / 12.92;
tmp.y = ( c.g > 0.04045 ) ? pow( ( c.g + 0.055 ) / 1.055, 2.4 ) : c.g / 12.92,
tmp.z = ( c.b > 0.04045 ) ? pow( ( c.b + 0.055 ) / 1.055, 2.4 ) : c.b / 12.92;
return 100.0 * tmp *
mat3( 0.4124, 0.3576, 0.1805,
0.2126, 0.7152, 0.0722,
0.0193, 0.1192, 0.9505 );
}
vec3 xyz2lab( vec3 c ) {
vec3 n = c / vec3( 95.047, 100, 108.883 );
vec3 v;
v.x = ( n.x > 0.008856 ) ? pow( n.x, 1.0 / 3.0 ) : ( 7.787 * n.x ) + ( 16.0 / 116.0 );
v.y = ( n.y > 0.008856 ) ? pow( n.y, 1.0 / 3.0 ) : ( 7.787 * n.y ) + ( 16.0 / 116.0 );
v.z = ( n.z > 0.008856 ) ? pow( n.z, 1.0 / 3.0 ) : ( 7.787 * n.z ) + ( 16.0 / 116.0 );
return vec3(( 116.0 * v.y ) - 16.0, 500.0 * ( v.x - v.y ), 200.0 * ( v.y - v.z ));
}
vec3 rgb2lab(vec3 c) {
vec3 lab = xyz2lab( rgb2xyz( c ) );
return vec3( lab.x / 100.0, 0.5 + 0.5 * ( lab.y / 127.0 ), 0.5 + 0.5 * ( lab.z / 127.0 ));
}
vec3 lab2xyz( vec3 c ) {
float fy = ( c.x + 16.0 ) / 116.0;
float fx = c.y / 500.0 + fy;
float fz = fy - c.z / 200.0;
return vec3(
95.047 * (( fx > 0.206897 ) ? fx * fx * fx : ( fx - 16.0 / 116.0 ) / 7.787),
100.000 * (( fy > 0.206897 ) ? fy * fy * fy : ( fy - 16.0 / 116.0 ) / 7.787),
108.883 * (( fz > 0.206897 ) ? fz * fz * fz : ( fz - 16.0 / 116.0 ) / 7.787)
);
}
vec3 xyz2rgb( vec3 c ) {
vec3 v = c / 100.0 * mat3(
3.2406, -1.5372, -0.4986,
-0.9689, 1.8758, 0.0415,
0.0557, -0.2040, 1.0570
);
vec3 r;
r.x = ( v.r > 0.0031308 ) ? (( 1.055 * pow( v.r, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.r;
r.y = ( v.g > 0.0031308 ) ? (( 1.055 * pow( v.g, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.g;
r.z = ( v.b > 0.0031308 ) ? (( 1.055 * pow( v.b, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.b;
return r;
}
vec3 lab2rgb(vec3 c) {
return xyz2rgb( lab2xyz( vec3(100.0 * c.x, 2.0 * 127.0 * (c.y - 0.5), 2.0 * 127.0 * (c.z - 0.5)) ) );
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 lab1 = rgb2lab(color1);
vec3 lab2 = rgb2lab(color2);
vec3 lab = mix(lab1, lab2, mixValue);
vec3 color = lab2rgb(lab);
gl_FragColor = vec4(color, 1);
}
`;
function draw(gl, shaders, color1, color2, label) {
const programInfo = twgl.createProgramInfo(gl, shaders);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
color1: color1,
color2: color2,
});
gl.drawArrays(gl.POINTS, 0, 1);
const div = document.createElement("div");
const img = new Image();
img.src = gl.canvas.toDataURL();
div.appendChild(img);
const inner = document.createElement("span");
inner.textContent = label;
div.appendChild(inner);
document.body.appendChild(div);
}
const color1 = [1.0, 0.55, 0];
const color2 = [0.226, 0.000, 0.615];
draw(gl, [vsrc, fRGB], color1, color2, "rgb");
draw(gl, [vsrc, fHSV], color1, color2, "hsv");
draw(gl, [vsrc, fHSV], color1, color2, "hsl");
draw(gl, [vsrc, fLAB], color1, color2, "lab");
img { border: 1px solid black; margin: 2px; }
span { display: block; }
div { display: inline-block; text-align: center; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
I would also suggest passing your colors in in a ramp texture. An Nx1 texture and using
color = texture2D(
rampTexture,
vec2((mixValue * (rampWidth - 1.) + .5) / rampWidth, 0.5)).rgb;
Then you can easily blend across 2 colors, 3 colors, 20 colors. You can space out the colors as well by repeating colors etc..
Example:
const gl = document.createElement("canvas").getContext("webgl");
gl.canvas.width = 100;
gl.canvas.height = 100;
gl.viewport(0, 0, 100, 100);
const vsrc = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fsrc = `
precision mediump float;
uniform sampler2D rampTexture;
uniform float rampWidth;
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 color = texture2D(
rampTexture,
vec2((mixValue * (rampWidth - 1.) + .5) / rampWidth, 0.5)).rgb;
gl_FragColor = vec4(color, 1);
}
`;
const programInfo = twgl.createProgramInfo(gl, [vsrc, fsrc]);
gl.useProgram(programInfo.program);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
function draw(gl, ramp, label) {
const width = ramp.length;
gl.bindTexture(gl.TEXTURE_2D, tex);
const level = 0;
const internalFormat = gl.RGB;
const height = 1;
const border = 0;
const format = gl.RGB;
const type = gl.UNSIGNED_BYTE;
const rampData = new Uint8Array([].concat(...ramp).map(v => v * 255));
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
format, type, rampData);
twgl.setUniforms(programInfo, {
rampTexture: tex,
rampWidth: width,
});
gl.drawArrays(gl.POINTS, 0, 1);
const div = document.createElement("div");
const img = new Image();
img.src = gl.canvas.toDataURL();
div.appendChild(img);
const inner = document.createElement("span");
inner.textContent = label;
div.appendChild(inner);
document.body.appendChild(div);
}
const color1 = [1.0, 0.55, 0];
const color2 = [0.226, 0.000, 0.615];
const r = [1, 0, 0];
const g = [0, 1, 0];
const b = [0, 0, 1];
const w = [1, 1, 1];
draw(gl, [color1, color2], "color1->color2");
draw(gl, [r, g], "red->green");
draw(gl, [r, g, b], "r->g->b");
draw(gl, [r, b, r, b, r], "r->b->r->b->r");
draw(gl, [g, b, b, b, g], "g->b->b->b->g");
img { border: 1px solid black; margin: 2px; }
span { display: block; }
div { display: inline-block; text-align: center; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
Note: a 1 dimensional 256x1 texture is how Chrome, Firefox, and Android render both linear and radial gradients. See src
Ther is a simple mistake when you set up the color value. You used 1.9 for the value of red color channel instead of 1.0, when you set up the orange color.
Change your code to:
vec3 color1 = vec3(1.0, 0.55, 0.0); // 1.0 insted of 1.9
Note, the final color channels are clamped to [0, 1], but since you use mix to interpolate the colors, a color channel above 1.0 raises the part of the color in the gradient.
Preview:
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 progObj = this.LinkProgram( shaderObjs )
if ( progObj != 0 ) {
progObj.attribIndex = {};
var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( progObj, i_n ).name;
progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
}
progObj.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( progObj, i_n ).name;
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; }
ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; }
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.SetUniformF2 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
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 );
ShaderProgram.SetUniformF2( progDraw, "u_resolution", vp )
gl.enableVertexAttribArray( progDraw.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progDraw.pos );
}
var gl;
var prog;
var bufObj = {};
function sceneStart() {
var canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl", { premultipliedAlpha: true } );
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;
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 );
setInterval(drawScene, 50);
}
<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;
uniform vec2 u_resolution;
void main()
{
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color1 = vec3(1.0,0.55,0);
vec3 color2 = vec3(0.226,0.000,0.615);
float mixValue = distance(st,vec2(0,1));
vec3 color = mix(color1,color2,mixValue);
gl_FragColor = vec4(color,1.0);
}
</script>
<body onload="sceneStart();">
<canvas id="ogl-canvas" style="border: none;" width="256" height="256"></canvas>
</body>

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

GLSL spotlight projection volume

In my open source project I have setup a deferred rendering pipeline using Qt3D. So far so good, but now I'd like to move forward by adding spotlights projection volume. (e.g. as if there is smoke in the scene)
Like this:
The fragment shader I'm using is at the end of the question.
I've read that for each fragment I should do ray marching from the light position and find the intersections with a cone, but I have no idea how to translate this into GLSL.
I can easily add a uniform with the depth map (from camera point of view) coming from the GBuffer, but I don't know if that's of any help.
Since my GLSL knowledge is very limited, please reply with actual code, not a lengthy mathematical explanation that I won't be able to understand/translate into code. Please be patient with me.
uniform sampler2D color;
uniform sampler2D position;
uniform sampler2D normal;
uniform vec2 winSize;
out vec4 fragColor;
const int MAX_LIGHTS = 102;
const int TYPE_POINT = 0;
const int TYPE_DIRECTIONAL = 1;
const int TYPE_SPOT = 2;
struct Light {
int type;
vec3 position;
vec3 color;
float intensity;
vec3 direction;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
float cutOffAngle;
};
uniform Light lightsArray[MAX_LIGHTS];
uniform int lightsNumber;
void main()
{
vec2 texCoord = gl_FragCoord.xy / winSize;
vec4 col = texture(color, texCoord);
vec3 pos = texture(position, texCoord).xyz;
vec3 norm = texture(normal, texCoord).xyz;
vec3 lightColor = vec3(0.0);
vec3 s;
float att;
for (int i = 0; i < lightsNumber; ++i) {
att = 1.0;
if ( lightsArray[i].type != TYPE_DIRECTIONAL ) {
s = lightsArray[i].position - pos;
if (lightsArray[i].constantAttenuation != 0.0
|| lightsArray[i].linearAttenuation != 0.0
|| lightsArray[i].quadraticAttenuation != 0.0) {
float dist = length(s);
att = 1.0 / (lightsArray[i].constantAttenuation + lightsArray[i].linearAttenuation * dist + lightsArray[i].quadraticAttenuation * dist * dist);
}
s = normalize( s );
if ( lightsArray[i].type == TYPE_SPOT ) {
if ( degrees(acos(dot(-s, normalize(lightsArray[i].direction))) ) > lightsArray[i].cutOffAngle)
att = 0.0;
}
} else {
s = normalize(-lightsArray[i].direction);
}
float diffuse = max( dot( s, norm ), 0.0 );
lightColor += att * lightsArray[i].intensity * diffuse * lightsArray[i].color;
}
fragColor = vec4(col.rgb * lightColor, col.a);
}
This is how a spotlight looks like with the original shader above:
[EDIT - SOLVED] Thanks to Rabbid76 excellent answer and precious support
This is the modified code to see the cone projection:
#version 140
uniform sampler2D color;
uniform sampler2D position;
uniform sampler2D normal;
uniform vec2 winSize;
out vec4 fragColor;
const int MAX_LIGHTS = 102;
const int TYPE_POINT = 0;
const int TYPE_DIRECTIONAL = 1;
const int TYPE_SPOT = 2;
struct Light {
int type;
vec3 position;
vec3 color;
float intensity;
vec3 direction;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
float cutOffAngle;
};
uniform Light lightsArray[MAX_LIGHTS];
uniform int lightsNumber;
uniform mat4 inverseViewMatrix; // defined by camera position, camera target and up vector
void main()
{
vec2 texCoord = gl_FragCoord.xy / winSize;
vec4 col = texture(color, texCoord);
vec3 pos = texture(position, texCoord).xyz;
vec3 norm = texture(normal, texCoord).xyz;
vec3 lightColor = vec3(0.0);
vec3 s;
// calculate unprojected fragment position on near plane and line of sight relative to view
float nearZ = -1.0;
vec3 nearPos = vec3( (texCoord.x - 0.5) * winSize.x / winSize.y, texCoord.y - 0.5, nearZ ); // 1.0 is camera near
vec3 los = normalize( nearPos );
// ray definition
vec3 O = vec3( inverseViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ) ); // translation part of the camera matrix, which is equal to the camera position
vec3 D = (length(pos) > 0.0) ? normalize(pos - O) : (mat3(inverseViewMatrix) * los);
for (int i = 0; i < lightsNumber; ++i)
{
float att = 1.0;
if ( lightsArray[i].type == TYPE_DIRECTIONAL )
{
s = normalize( -lightsArray[i].direction );
}
else
{
s = lightsArray[i].position - pos;
if (lightsArray[i].type != TYPE_SPOT
&& (lightsArray[i].constantAttenuation != 0.0
|| lightsArray[i].linearAttenuation != 0.0
|| lightsArray[i].quadraticAttenuation != 0.0))
{
float dist = length(s);
att = 1.0 / (lightsArray[i].constantAttenuation + lightsArray[i].linearAttenuation * dist + lightsArray[i].quadraticAttenuation * dist * dist);
}
s = normalize( s );
if ( lightsArray[i].type == TYPE_SPOT )
{
// cone definition
vec3 C = lightsArray[i].position;
vec3 V = normalize(lightsArray[i].direction);
float cosTh = cos( radians(lightsArray[i].cutOffAngle) );
// ray - cone intersection
vec3 CO = O - C;
float DdotV = dot( D, V );
float COdotV = dot( CO, V );
float a = DdotV * DdotV - cosTh * cosTh;
float b = 2.0 * (DdotV * COdotV - dot( D, CO ) * cosTh * cosTh);
float c = COdotV * COdotV - dot( CO, CO ) * cosTh * cosTh;
float det = b * b - 4.0 * a * c;
// find intersection
float isIsect = 0.0;
vec3 isectP = vec3(0.0);
if ( det >= 0.0 )
{
vec3 P1 = O + (-b - sqrt(det)) / (2.0 * a) * D;
vec3 P2 = O + (-b + sqrt(det)) / (2.0 * a) * D;
float isect1 = step( 0.0, dot(normalize(P1 - C), V) );
float isect2 = step( 0.0, dot(normalize(P2 - C), V) );
if ( isect1 < 0.5 )
{
P1 = P2;
isect1 = isect2;
}
if ( isect2 < 0.5 )
{
P2 = P1;
isect2 = isect1;
}
isectP = (length(P1 - O) < length(P2 - O)) ? P1 : P2;
isIsect = mix( isect2, 1.0, isect1 );
if ( length(pos) != 0.0 && length(isectP - O) > length(pos - O))
isIsect = 0.0;
}
float dist = length( isectP - C.xyz );
float limit = degrees(acos(dot(-s, normalize(lightsArray[i].direction))) );
if (isIsect > 0 || limit <= lightsArray[i].cutOffAngle)
{
att = 1.0 / dot( vec3( 1.0, dist, dist * dist ),
vec3(lightsArray[i].constantAttenuation,
lightsArray[i].linearAttenuation,
lightsArray[i].quadraticAttenuation) );
}
else
att = 0.0;
}
}
float diffuse = max( dot( s, norm ), 0.0 );
lightColor += att * lightsArray[i].intensity * diffuse * lightsArray[i].color;
}
fragColor = vec4(col.rgb * lightColor, col.a);
}
Uniforms passed to the shader are:
qml: lightsArray[0].type = 0
qml: lightsArray[0].position = QVector3D(0, 10, 0)
qml: lightsArray[0].color = #ffffff
qml: lightsArray[0].intensity = 0.8
qml: lightsArray[0].constantAttenuation = 1
qml: lightsArray[0].linearAttenuation = 0
qml: lightsArray[0].quadraticAttenuation = 0
qml: lightsArray[1].type = 2
qml: lightsArray[1].position = QVector3D(0, 3, 0)
qml: lightsArray[1].color = #008000
qml: lightsArray[1].intensity = 0.5
qml: lightsArray[1].constantAttenuation = 2
qml: lightsArray[1].linearAttenuation = 0
qml: lightsArray[1].quadraticAttenuation = 0
qml: lightsArray[1].direction = QVector3D(-0.573576, -0.819152, 0)
qml: lightsArray[1].cutOffAngle = 15
qml: lightsNumber = 2
Screenshot:
For a primitive visualization of the light cone of a spot light, you have to do a intersection of the line of sight and the light cone. The following algorithm works in a perpectiv view and the caluclations ar done in view (eye) sapce. The algorithm does not care about the geometry of the scene and does not do any depth test or shadow test, it only is a overlayerd visualization of the light cone.
The line of sight in a perspective view can be deifned by a points and a direction. Since the calculations is done in view (eye) space, the point is the point of view (the origin of the view frustum) which is vec3(0.0). The direction can easily be determined, by the intersection of the line of sight and the near plane of the camera frustum. This can easily be calculated if the projected XY-coordinate of the fragment is known in normalized device coordinates (the lower left point is (-1,-1) and the upper right point is (1,1) see the answer to this question).
float aspect = .....; // ratio of the view port (widht/length)
float fov = .....; // filed of view angle in radians (angle of the camera frustum on the Y-axis)
vec2 ndcPos = .....; // fragment position in NDC space from (-1,-1) to (1,1)
vec3 tanFov = tan( fov * 0.5 );
vec3 los = normalize( vec3( ndcPos.x * aspect * tanFov, ndcPos.y * tanFov, -1.0 ) );
The light cone is defined by the origin of the light source, the direction where the light source points to, and the full angle of the light cone. The position and the direction have to be up in view space. The angle has to be set up in radians.
vec3 vLightPos = .....; // position of the light source in view space
vec3 vLightDir = .....; // direction of the light in view space
float coneAngle = .....; // full angle of the light cone in radians
How to calculate the intersection point(s) of a ray and a cone can be found in the answer to Stackoverflow question Points of intersection of vector with cone and in the following paper: Intersection of a ray and a cone.
The following code calculates a intersection of a ray and a cone as defined above. The result point is stored in isectP. The variable isIsect of the type float is set to 1.0 if there is a intersection, and is set to 0.0 else.
// ray definition
vec3 O = vec3(0.0);
vec3 D = los;
// cone definition
vec3 C = vLightPos;
vec3 V = vLightDir;
float cosTh = cos( coneAngle * 0.5 );
// ray - cone intersection
vec3 CO = O - C;
float DdotV = dot( D, V );
float COdotV = dot( CO, V );
float a = DdotV*DdotV - cosTh*cosTh;
float b = 2.0 * (DdotV*COdotV - dot( D, CO )*cosTh*cosTh);
float c = COdotV*COdotV - dot( CO, CO )*cosTh*cosTh;
float det = b*b - 4.0*a*c;
// find intersection
float isIsect = 0.0;
vec3 isectP = vec3(0.0);
if ( det >= 0.0 )
{
vec3 P1 = O + (-b-sqrt(det))/(2.0*a) * D;
vec3 P2 = O + (-b+sqrt(det))/(2.0*a) * D;
float isect1 = step( 0.0, dot(normalize(P1-C), V) );
float isect2 = step( 0.0, dot(normalize(P2-C), V) );
P1 = mix( P2, P1, isect1 );
isectP = P2.z < 0.0 && P2.z > P1.z ? P2 : P1;
isIsect = mix( isect2, 1.0, isect1 ) * step( isectP.z, 0.0 );
}
For the full GLSL code, see the following WebGL example:
(function loadscene() {
var sliderScale = 100.0
var gl, canvas, vp_size, camera, progDraw, progLightCone, bufTorus = {}, bufQuad = {}, drawFB;
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;
var cutOffAngle = document.getElementById( "cutOffAngle" ).value;
// setup view projection and model
vp_size = [canvas.width, canvas.height];
var prjMat = camera.Perspective();
var viewMat = camera.LookAt();
var modelMat = IdentM44();
modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 13.0 ), 0 );
modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 17.0 ), 1 );
var lightPos = [0.95, 0.95, -1.0];
var lightDir = [-1.0, -1.0, -3.0];
var lightCutOffAngleRad = cutOffAngle * Math.PI / 180.0;
var lightAtt = [0.7, 0.1, 0.5];
drawFB.Bind( true );
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 );
ShProg.Use( progDraw );
ShProg.SetM44( progDraw, "u_projectionMat44", prjMat );
ShProg.SetM44( progDraw, "u_viewMat44", viewMat );
ShProg.SetF3( progDraw, "u_light.position", lightPos );
ShProg.SetF3( progDraw, "u_light.direction", lightDir );
ShProg.SetF1( progDraw, "u_light.ambient", ambient );
ShProg.SetF1( progDraw, "u_light.diffuse", diffuse );
ShProg.SetF1( progDraw, "u_light.specular", specular );
ShProg.SetF1( progDraw, "u_light.shininess", shininess );
ShProg.SetF3( progDraw, "u_light.attenuation", lightAtt );
ShProg.SetF1( progDraw, "u_light.cutOffAngle", lightCutOffAngleRad );
ShProg.SetM44( progDraw, "u_modelMat44", modelMat );
bufObj = bufTorus;
gl.enableVertexAttribArray( progDraw.inPos );
gl.enableVertexAttribArray( progDraw.inNV );
gl.enableVertexAttribArray( progDraw.inCol );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progDraw.inPos, 3, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.nv );
gl.vertexAttribPointer( progDraw.inNV, 3, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.col );
gl.vertexAttribPointer( progDraw.inCol, 3, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progDraw.pos );
gl.disableVertexAttribArray( progDraw.nv );
gl.disableVertexAttribArray( progDraw.col );
drawFB.Release( true );
gl.viewport( 0, 0, canvas.width, canvas.height );
var texUnitDraw = 2;
drawFB.BindTexture( texUnitDraw );
ShProg.Use( progLightCone );
ShProg.SetI1( progLightCone, "u_colorAttachment0", texUnitDraw );
ShProg.SetF2( progLightCone, "u_depthRange", [ camera.near, camera.far ] );
ShProg.SetF2( progLightCone, "u_vp", camera.vp );
ShProg.SetF1( progLightCone, "u_fov", camera.fov_y * Math.PI / 180.0 );
ShProg.SetF3( progLightCone, "u_light.position", lightPos );
ShProg.SetF3( progLightCone, "u_light.direction", lightDir );
ShProg.SetF3( progLightCone, "u_light.attenuation", lightAtt );
ShProg.SetF1( progLightCone, "u_light.cutOffAngle", lightCutOffAngleRad );
gl.enableVertexAttribArray( progLightCone.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
gl.vertexAttribPointer( progLightCone.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
gl.drawElements( gl.TRIANGLES, bufQuad.inxLen, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progLightCone.inPos );
requestAnimationFrame(render);
}
function initScene() {
canvas = document.getElementById( "glow-canvas");
vp_size = [canvas.width, canvas.height];
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
document.getElementById( "ambient" ).value = 0.25 * sliderScale;
document.getElementById( "diffuse" ).value = 1.0 * sliderScale;
document.getElementById( "specular" ).value = 1.0 * sliderScale;
document.getElementById( "shininess" ).value = 10.0;
document.getElementById( "cutOffAngle" ).value = 30.0;
progDraw = ShProg.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = ShProg.AttrI( progDraw, "inPos" );
progDraw.inNV = ShProg.AttrI( progDraw, "inNV" );
progDraw.inCol = ShProg.AttrI( progDraw, "inCol" );
if ( progDraw == 0 )
return;
progLightCone = ShProg.Create(
[ { source : "light-cone-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "light-cone-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progLightCone.inPos = ShProg.AttrI( progDraw, "inPos" );
if ( progDraw == 0 )
return;
var circum_size = 32, tube_size = 32;
var rad_circum = 1.5;
var rad_tube = 0.8;
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.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufTorus.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( torus_pts ), gl.STATIC_DRAW );
bufTorus.nv = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufTorus.nv );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( torus_nv ), gl.STATIC_DRAW );
bufTorus.col = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufTorus.col );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( torus_col ), gl.STATIC_DRAW );
bufTorus.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufTorus.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( torus_inx ), gl.STATIC_DRAW );
bufTorus.inxLen = torus_inx.length;
bufQuad.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ] ), gl.STATIC_DRAW );
bufQuad.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );
bufQuad.inxLen = 6;
camera = new Camera( [0, 4, 0.0], [0, 0, 0], [0, 0, 1], 90, vp_size, 0.5, 100 );
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];
var fbsize = Math.max(vp_size[0], vp_size[1]);
fbsize = 1 << 31 - Math.clz32(fbsize); // nearest power of 2
var fb_rect = [fbsize, fbsize];
drawFB = FrameBuffer.Create( fb_rect );
}
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaMS, intervall ) {
return Fract( deltaMS / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( deltaMS, intervall, range ) {
var pos = self.Fract( deltaMS / (1000*intervall) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
function IdentM44() { 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 = matA.slice(0);
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 ];
}
Camera = function( pos, target, up, fov_y, vp, near, far ) {
this.Time = function() { return Date.now(); }
this.pos = pos;
this.target = target;
this.up = up;
this.fov_y = fov_y;
this.vp = vp;
this.near = near;
this.far = far;
this.orbit_mat = this.current_orbit_mat = this.model_mat = this.current_model_mat = IdentM44();
this.mouse_drag = this.auto_spin = false;
this.auto_rotate = true;
this.mouse_start = [0, 0];
this.mouse_drag_axis = [0, 0, 0];
this.mouse_drag_angle = 0;
this.mouse_drag_time = 0;
this.drag_start_T = this.rotate_start_T = this.Time();
this.Ortho = function() {
var fn = this.far + this.near;
var f_n = this.far - this.near;
var w = this.vp[0];
var h = this.vp[1];
return [
2/w, 0, 0, 0,
0, 2/h, 0, 0,
0, 0, -2/f_n, 0,
0, 0, -fn/f_n, 1 ];
};
this.Perspective = function() {
var n = this.near;
var f = this.far;
var fn = f + n;
var f_n = f - n;
var r = this.vp[0] / this.vp[1];
var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
return [
t/r, 0, 0, 0,
0, t, 0, 0,
0, 0, -fn/f_n, -1,
0, 0, -2*f*n/f_n, 0 ];
};
this.LookAt = function() {
var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
var mx = Normalize( Cross( this.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, this.pos );
var ty = Dot( my, this.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos );
return [mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1];
};
}
var FrameBuffer = {};
FrameBuffer.Create = function( vp, texturePlan ) {
var texPlan = texturePlan ? new Uint8Array( texturePlan ) : null;
var fb = gl.createFramebuffer();
fb.width = vp[0];
fb.height = vp[1];
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 );
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.viewport( 0, 0, this.width, this.height );
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;
}
var ShProg = {};
ShProg.Create = function( shaderList ) {
var shaderObjs = [];
for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
var shderObj = this.Compile( shaderList[i_sh].source, shaderList[i_sh].stage );
if ( shderObj == 0 )
return 0;
shaderObjs.push( shderObj );
}
var progObj = this.Link( shaderObjs )
if ( progObj != 0 ) {
progObj.attrInx = {};
var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( progObj, i_n ).name;
progObj.attrInx[name] = gl.getAttribLocation( progObj, name );
}
progObj.uniLoc = {};
var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( progObj, i_n ).name;
progObj.uniLoc[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShProg.AttrI = function( progObj, name ) { return progObj.attrInx[name]; }
ShProg.UniformL = function( progObj, name ) { return progObj.uniLoc[name]; }
ShProg.Use = function( progObj ) { gl.useProgram( progObj ); }
ShProg.SetI1 = function( progObj, name, val ) { if(progObj.uniLoc[name]) gl.uniform1i( progObj.uniLoc[name], val ); }
ShProg.SetF1 = function( progObj, name, val ) { if(progObj.uniLoc[name]) gl.uniform1f( progObj.uniLoc[name], val ); }
ShProg.SetF2 = function( progObj, name, arr ) { if(progObj.uniLoc[name]) gl.uniform2fv( progObj.uniLoc[name], arr ); }
ShProg.SetF3 = function( progObj, name, arr ) { if(progObj.uniLoc[name]) gl.uniform3fv( progObj.uniLoc[name], arr ); }
ShProg.SetF4 = function( progObj, name, arr ) { if(progObj.uniLoc[name]) gl.uniform4fv( progObj.uniLoc[name], arr ); }
ShProg.SetM44 = function( progObj, name, mat ) { if(progObj.uniLoc[name]) gl.uniformMatrix4fv( progObj.uniLoc[name], false, mat ); }
ShProg.Compile = 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;
}
ShProg.Link = 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;
}
initScene();
})();
html,body { margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
<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 clip_space_pos;
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;
gl_Position = u_projectionMat44 * viewPos;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
struct Light {
vec3 position;
vec3 direction;
float ambient;
float diffuse;
float specular;
float shininess;
vec3 attenuation;
float cutOffAngle;
};
uniform Light u_light;
void main()
{
vec3 color = vertCol;
vec3 lightCol = u_light.ambient * color;
vec3 normalV = normalize( vertNV );
vec3 lightV = normalize( u_light.position - vertPos );
float lightD = length( u_light.position - vertPos );
float cosL = dot( normalize( u_light.direction ), -lightV );
float inCone = step( cos( u_light.cutOffAngle * 0.5 ), cosL );
float att = 1.0 / dot( vec3( 1.0, lightD, lightD*lightD ), u_light.attenuation );
float NdotL = max( 0.0, dot( normalV, lightV ) );
lightCol += NdotL * u_light.diffuse * color * inCone * att;
vec3 eyeV = normalize( -vertPos );
vec3 halfV = normalize( eyeV + lightV );
float NdotH = max( 0.0, dot( normalV, halfV ) );
float kSpecular = ( u_light.shininess + 2.0 ) * pow( NdotH, u_light.shininess ) / ( 2.0 * 3.14159265 );
lightCol += kSpecular * u_light.specular * color * inCone * att;
gl_FragColor = vec4( lightCol.rgb, 1.0 );
}
</script>
<script id="light-cone-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vertPos;
void main()
{
vertPos.xy = inPos.xy;
gl_Position = vec4( inPos, 0.0, 1.0 );
}
</script>
<script id="light-cone-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertPos;
uniform sampler2D u_colorAttachment0;
uniform vec2 u_depthRange;
uniform vec2 u_vp;
uniform float u_fov;
struct Light {
vec3 position;
vec3 direction;
float ambient;
float diffuse;
float specular;
float shininess;
vec3 attenuation;
float cutOffAngle;
};
uniform Light u_light;
void main()
{
vec4 texCol = texture2D( u_colorAttachment0, vertPos.st * 0.5 + 0.5 );
vec3 vLightPos = u_light.position;
vec3 vLightDir = normalize( u_light.direction );
float tanFOV = tan(u_fov*0.5);
vec3 nearPos = vec3( vertPos.x * u_vp.x/u_vp.y * tanFOV, vertPos.y * tanFOV, -1.0 );
//vec2 texCoord = gl_FragCoord.xy / u_vp;
//vec3 nearPos = vec3( (texCoord.x-0.5) * u_vp.x/u_vp.y, texCoord.y-0.5, -u_depthRange.x );
vec3 los = normalize( nearPos );
// ray definition
vec3 O = vec3(0.0);
vec3 D = los;
// cone definition
vec3 C = vLightPos;
vec3 V = vLightDir;
float cosTh = cos( u_light.cutOffAngle * 0.5 );
// ray - cone intersection
vec3 CO = O - C;
float DdotV = dot( D, V );
float COdotV = dot( CO, V );
float a = DdotV*DdotV - cosTh*cosTh;
float b = 2.0 * (DdotV*COdotV - dot( D, CO )*cosTh*cosTh);
float c = COdotV*COdotV - dot( CO, CO )*cosTh*cosTh;
float det = b*b - 4.0*a*c;
// find intersection
float isIsect = 0.0;
vec3 isectP = vec3(0.0);
if ( det >= 0.0 )
{
vec3 P1 = O + (-b-sqrt(det))/(2.0*a) * D;
vec3 P2 = O + (-b+sqrt(det))/(2.0*a) * D;
float isect1 = step( 0.0, dot(normalize(P1-C), V) );
float isect2 = step( 0.0, dot(normalize(P2-C), V) );
if ( isect1 < 0.5 )
{
P1 = P2;
isect1 = isect2;
}
if ( isect2 < 0.5 )
{
P2 = P1;
isect2 = isect1;
}
isectP = ( P1.z > -u_depthRange.x || (P2.z < -u_depthRange.x && P1.z < P2.z ) ) ? P2 : P1;
isIsect = mix( isect2, 1.0, isect1 ) * step( isectP.z, -u_depthRange.x );
}
float dist = length( isectP - vLightPos.xyz );
float att = 1.0 / dot( vec3( 1.0, dist, dist*dist ), u_light.attenuation );
gl_FragColor = vec4( mix( texCol.rgb, vec3(1.0, 1.0, 1.0), isIsect * att * 0.5 ), 1.0 );
}
</script>
<div><form id="gui" name="inputs">
<table>
<tr> <td> <font color=#40f040>ambient</font> </td>
<td> <input type="range" id="ambient" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color=#40f040>diffuse</font> </td>
<td> <input type="range" id="diffuse" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color=#40f040>specular</font> </td>
<td> <input type="range" id="specular" min="0" max="100" value="0"/></td> </tr>
<tr> <td> <font color=#40f040>shininess</font> </td>
<td> <input type="range" id="shininess" min="1" max="100" value="0"/></td> </tr>
<tr> <td> <font color=#40f040>cut off angle</font> </td>
<td> <input type="range" id="cutOffAngle" min="1" max="180" value="0"/></td> </tr>
</table>
</form>
</div>
<canvas id="glow-canvas" style="border: none;"></canvas>