When using the OpenGL fixed function pipeline for vertex setup, how a fragment program looks like that is compatible to the fixed function vertex setup? I guess that usually
While Gouraud shading calculates the light in the the vertex shader, Phong shading calculates the light in the fragment shader.
The standard OpenGL light model is a Gouraud shading model, with a Blinn-Phong light model (Do not confuse with Phong shading).
The standard OpenGL Blinn-Phong light model is calcualted like this:
Ka ... ambient material
Kd ... difusse material
Ks ... specular material
La ... ambient light
Ld ... diffuse light
Ls ... specular light
sh ... shininess
N ... norlmal vector
L ... light vector (from the vertex postion to the light)
V ... view vector (from the vertex psotion to the eye)
Id = max(dot(N, L), 0.0);
H = normalize(V + L);
NdotH = max(dot(N, H), 0.0);
Is = (sh + 2.0) * pow(NdotH, sh) / (2.0 * 3.14159265);
fs = Ka*La + Id*Kd*Ld + Is*Ks*Ls;
The following function calculates a single directional Blinn-Phong light source:
struct TLightSource
{
vec3 lightDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform TLightSource u_lightSource;
vec3 Light( vec3 eyeV, vec3 N )
{
vec3 lightCol = u_lightSource.ambient;
vec3 L = normalize( -u_lightSource.lightDir );
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;
}
See also the answers to the following questions:
This function can be applied to a vertex shader, as to a fragment shader, too.
The full coding of a gouraud shader program and a phong shader program can be found in the following WebGL example:
(function loadscene() {
var resize, gl, gouraudDraw, phongDraw, vp_size;
var bufCube, bufSphere, bufTorus;
var sliderScale = 100.0;
function render(delteMS){
var ambient_col = hexToRgb( document.getElementById( "ambient_col" ).value );
var diffuse_col = hexToRgb( document.getElementById( "diffuse_col" ).value );
var specular_col = hexToRgb( document.getElementById( "specular_col" ).value );
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 ambientCol = [ambient_col.r*ambient/256.0, ambient_col.g*ambient/256.0, ambient_col.b*ambient/256.0];
var diffuseCol = [diffuse_col.r*diffuse/256.0, diffuse_col.g*diffuse/256.0, diffuse_col.b*diffuse/256.0];
var specularCol = [specular_col.r*ambient/256.0, specular_col.g*ambient/256.0, specular_col.b*ambient/256.0];
var form = document.getElementById( "form" ).value;
var shading = document.getElementById( "shading" ).value;
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.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
//gl.frontFace(gl.CW);
gl.frontFace(gl.CCW);
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.lightDir", [-1.0, -0.5, -2.0] )
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, CalcAng( delteMS, 13.0 ), 0 );
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );
ShaderProgram.SetUniformM44( progDraw.prog, "u_modelMat44", modelMat );
// draw scene
bufObj = form == 0 ? bufCube : form == 1 ? bufSphere : bufTorus;
VertexBuffer.Draw( bufObj );
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() {
document.getElementById( "ambient_col" ).value = "#FFFFFF";
document.getElementById( "diffuse_col" ).value = "#FFFFFF";
document.getElementById( "specular_col" ).value = "#FFFFFF";
document.getElementById( "ambient" ).value = 0.2 * sliderScale;
document.getElementById( "diffuse" ).value = 0.6 * sliderScale;
document.getElementById( "specular" ).value = 0.8 * sliderScale;
document.getElementById( "shininess" ).value = 25.0;
document.getElementById( "shading" ).value = 0;
document.getElementById( "form" ).value = 1;
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 cubePos = [
-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
-1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0 ];
var cubeCol = [ 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ];
var cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ];
var cubePosData = [];
for ( var i = 0; i < cubeHlpInx.length; ++ i ) {
cubePosData.push( cubePos[cubeHlpInx[i]*3], cubePos[cubeHlpInx[i]*3+1], cubePos[cubeHlpInx[i]*3+2] );
}
var cubeNVData = [];
for ( var i1 = 0; i1 < cubeHlpInx.length; i1 += 4 ) {
var nv = [0, 0, 0];
for ( i2 = 0; i2 < 4; ++ i2 ) {
var i = i1 + i2;
nv[0] += cubePosData[i*3]; nv[1] += cubePosData[i*3+1]; nv[2] += cubePosData[i*3+2];
}
for ( i2 = 0; i2 < 4; ++ i2 )
cubeNVData.push( nv[0], nv[1], nv[2] );
}
var cubeColData = [];
for ( var is = 0; is < 6; ++ is ) {
for ( var ip = 0; ip < 4; ++ ip ) {
cubeColData.push( cubeCol[is*3], cubeCol[is*3+1], cubeCol[is*3+2] );
}
}
var cubeInxData = [];
for ( var i = 0; i < cubeHlpInx.length; i += 4 ) {
cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );
}
bufCube = VertexBuffer.Create(
[ { data : cubePosData, attrSize : 3, attrLoc : gouraudDraw.inPos },
{ data : cubeNVData, attrSize : 3, attrLoc : gouraudDraw.inNV },
{ data : cubeColData, attrSize : 3, attrLoc : gouraudDraw.inCol } ],
cubeInxData );
// create sphere
var layer_size = 16, circum_size = 32;
var rad_circum = 1.0;
var rad_tube = 0.5;
var sphere_pts = [];
var sphere_nv = [];
var sphere_col = [];
sphere_pts.push( 0.0, 0.0, -2.0 );
sphere_nv.push( 0.0, 0.0, -1.0 );
//sphere_col.push( 0.8, 0.6, 0.3 );
sphere_col.push( 0.75, 0.75, 0.75 );
for ( var i_l = 1; i_l < layer_size; ++ i_l ) {
var angH = (1.0 - i_l / layer_size) * Math.PI;
var h = Math.cos( angH );
var r = Math.sin( angH );
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var circumX = Math.cos(2 * Math.PI * i_c / circum_size);
var circumY = Math.sin(2 * Math.PI * i_c / circum_size);
sphere_pts.push( r * circumX * 2.0, r * circumY * 2.0, h * 2.0 );
sphere_nv.push( r * circumX, r * circumY, h );
//sphere_col.push( 0.8, 0.6, 0.3 );
sphere_col.push( 0.75, 0.75, 0.75 );
}
}
sphere_pts.push( 0.0, 0.0, 2.0 );
sphere_nv.push( 0.0, 0.0, 1.0 );
//sphere_col.push( 0.8, 0.6, 0.3 );
sphere_col.push( 0.75, 0.75, 0.75 );
var sphere_inx = [];
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
sphere_inx.push( i_c+1, 0, (i_c+1) % circum_size + 1 )
}
for ( var i_l = 0; i_l < layer_size-2; ++ i_l ) {
var l1 = i_l * circum_size + 1;
var l2 = (i_l+1) * circum_size + 1
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var i_n = (i_c+1) % circum_size;
sphere_inx.push( l1+i_c, l1+i_n, l2+i_c, l1+i_n, l2+i_n, l2+i_c );
}
}
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var i_start = 1 + (layer_size-2) * circum_size;
var i_n = (i_c+1) % circum_size;
sphere_inx.push( i_start + i_c, i_start + i_n, sphere_pts.length/3-1 );
}
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 );
// create torus
var circum_size = 32, tube_size = 32;
var rad_circum = 1.4;
var rad_tube = 0.6;
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_c1+i_t, i_c0+i_tn, i_c0+i_tn, i_c1+i_t, i_c1+i_tn )
}
}
bufTorus = VertexBuffer.Create(
[ { data : torus_pts, attrSize : 3, attrLoc : gouraudDraw.inPos },
{ data : torus_nv, attrSize : 3, attrLoc : gouraudDraw.inNV },
{ data : torus_col, attrSize : 3, attrLoc : gouraudDraw.inCol } ],
torus_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, 3, 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 );
}
function hexToRgb (hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
initScene();
})();
<style>
html,body { margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
</style>
<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 lightDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform TLightSource u_lightSource;
vec3 Light( vec3 eyeV, vec3 N )
{
vec3 lightCol = u_lightSource.ambient;
vec3 L = normalize( -u_lightSource.lightDir );
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 );
vertCol = inCol * Light( eyeV, normalV );
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 lightDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform TLightSource u_lightSource;
vec3 Light( vec3 eyeV, vec3 N )
{
vec3 lightCol = u_lightSource.ambient;
vec3 L = normalize( -u_lightSource.lightDir );
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 );
vec3 color = vertCol * Light( eyeV, normalV );
gl_FragColor = vec4( color, 1.0 );
}
</script>
<div>
<form id="gui" name="inputs">
<table>
<tr> <td> <font color= #CCF>ambient</font> </td>
<td> <input type="color" id="ambient_col"></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="color" id="diffuse_col"></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="color" id="specular_col"></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>
<tr> <td> <font color= #CCF>form</font> </td> <td>
<select id="form">>
<option value="0">cube</option>
<option value="1">sphere</option>
<option value="2">torus</option>
</select>
</td> </tr>
<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>
</table>
</form>
</div>
<canvas id="canvas" style="border: none;"></canvas>
Well, in the normal GL fixed function pipeline, lighting and goraud shading are done entirely in the vertex stage of the pipeline (with a little bit of help from the rasterizer), so if you have no texturing, the fragment shader becomes a simple pass-through (gl_FragColor = gl_Color;
) and doesn't actually do anything.
Now you CAN do per-fragment lighting, by writing your own fragment shader that does it, and it may even make sense to do that if you want directional spotlights or some such, but that has nothing to do with the fixed function pipeline.
You might be interested in having a look at ShaderGen. From the description:
ShaderGen is a program that can automatically produce shaders that mimic the results of a set of fixed function state. This tool was used to verify the fixed function shader code that is described in Chapter 9.
By the way, you will most likely encounter subtle driver problems if you mix fixed function and GLSL. This is not the "most used way" to do shading so it has a good change of having bugs (at least it did when I tried it). If what you need works, great! Otherwise, you're likely much better off writing the vertex and fragment shaders in GLSL. From what I heard, the driver of modern GPUs uses shaders under the hood anyway to do fixed function.