Skip to content Skip to sidebar Skip to footer

Bump Mapping With Javascript And Glsl

Hey there I`m trying since a while now to add a second texture to my cube and do some bump mapping. But I am a progam beginner, so its kinda hard for me. All my maths for the ma

Solution 1:

With a normal map as in the question, Bump mapping can be performed. At bump mapping the normal vector of a fragment is read from a normal map and used for the light calculations.
In general the incident light vector is transformed into texture space. This is the "orientation" of the normal map on the object (fragment). In order to set up a 3x3 orientation matrix that describes the orientation of the map, the tangent vector and the bi-tangent vector as well as the normal vector must be known. If there is no tangent vector and no bi-tangent vector, the vectors can be approximated by the partial derivative of the vertex position and the texture coordinate in the fragment shader.

So at least the texture coordinate and the normal vector attribute are required. In the fragment shader the calculations are done in world space respectively texture space. The vertex shader is straight forward:

precision highp float;

attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord;

varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
    
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;

void main()
{   
    o_uv          = a_texCoord;
    w_nv          = normalize(mat3(M) * a_normal);
    vec4 worldPos = M * vec4(a_position, 1.0);
    w_pos         = worldPos.xyz;
    gl_Position   = P * V * worldPos;
}

In the fragment shader, the normal vector is read from the normal map:

vec3 mapN = normalize(texture2D(u_normal_map, o_uv.st).xyz * 2.0 - 1.0);

The light vector is transformed to texture space:

vec3 L = tbn_inv * normalize(u_light_pos - w_pos);

With this vector the light calculations can be performed:

float kd   = max(0.0, dot(mapN, L));

To calculate the matrix which transforms form world space to texture space, the partial derivative functions (dFdx, dFdy) are required. This causes that the "OES_standard_derivatives" has to be enabled (or "webgl2" context):

gl = canvas.getContext( "experimental-webgl" );
var standard_derivatives = gl.getExtension("OES_standard_derivatives");

The algorithm to calculate the tangent vector and binormal vector is explained in another answer - How to calculate Tangent and Binormal?.

Final fragment shader:

#extension GL_OES_standard_derivatives : enable
precision mediump float;

varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;

uniform vec3 u_light_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;

void main()
{
    vec3  N       = normalize(w_nv);
    vec3  dp1     = dFdx( w_pos );
    vec3  dp2     = dFdy( w_pos );
    vec2  duv1    = dFdx( o_uv );
    vec2  duv2    = dFdy( o_uv );
    vec3  dp2perp = cross(dp2, N);
    vec3  dp1perp = cross(N, dp1);
    vec3  T       = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3  B       = dp2perp * duv1.y + dp1perp * duv2.y;
    float invmax  = inversesqrt(max(dot(T, T), dot(B, B)));
    mat3  tm      = mat3(T * invmax, B * invmax, N);
    mat3  tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));

    vec3  L    = tbn_inv * normalize(u_light_pos - w_pos);
    vec3  mapN = normalize(texture2D(u_normal_map, o_uv.st).xyz * 2.0 - 1.0); 
    float kd   = max(0.0, dot(mapN, L));

    vec3 color     = texture2D(u_diffuse, o_uv.st).rgb;
    vec3 light_col = (0.0 + kd) * color.rgb;
    gl_FragColor   = vec4(clamp(light_col, 0.0, 1.0), 1.0);
}

And will produce bump mapping like this:

If the tangent vector is know the calculation of tbn_inv matrix can be simplified very much:

mat3 tm = mat3(normalize(w_tv), normalize(cross(w_nv, w_tv)), normalize(w_nv));
mat3 tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));

If you want Parallax mapping like in this Example then a displacement map is required, too.

The white areas on this map are pushed "into" the object. The algorithm is described in detail at LearnOpengl - Parallax Mapping.
The idea is that each fragment is associated to a height of the displacement map. This can be imagined as a rectangular pillar standing on the fragment. The view ray is tracked until a displaced fragment is hit.

For a high performance algorithm samples are taken of the displacement texture. When a fragment is identified, then the corresponding fragment of the e normal map and the diffuse texture is read. This gives a 3 dimensional look of the geometry. Note this algorithm is bot able to handle silhouettes.

Final fragment shader with steep parallax mapping:

#extension GL_OES_standard_derivatives : enable
precision mediump float;

varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;

uniform float u_height_scale;
uniform vec3 u_light_pos;
uniform vec3 u_view_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;
uniform sampler2D u_displacement_map;

vec2 ParallaxMapping (vec2 texCoord, vec3 viewDir)
{
    float numLayers = 32.0 - 31.0 * abs(dot(vec3(0.0, 0.0, 1.0), viewDir));
    float layerDepth = 1.0 / numLayers;

    vec2 P = viewDir.xy / viewDir.z * u_height_scale;
    vec2 deltaTexCoords = P / numLayers;
    vec2 currentTexCoords = texCoord;

    float currentLayerDepth = 0.0;
    float currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
    for (int i=0; i<32; ++ i)
    {
        if (currentLayerDepth >= currentDepthMapValue)
            break;
        currentTexCoords -= deltaTexCoords;
        currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
        currentLayerDepth += layerDepth;
    }

    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
    float afterDepth = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture2D(u_displacement_map, prevTexCoords).r - currentLayerDepth + layerDepth;

    float weight = afterDepth / (afterDepth - beforeDepth);
    return prevTexCoords * weight + currentTexCoords * (1.0 - weight);
}

void main()
{
    vec3  N       = normalize(w_nv);
    vec3  dp1     = dFdx( w_pos );
    vec3  dp2     = dFdy( w_pos );
    vec2  duv1    = dFdx( o_uv );
    vec2  duv2    = dFdy( o_uv );
    vec3  dp2perp = cross(dp2, N);
    vec3  dp1perp = cross(N, dp1);
    vec3  T       = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3  B       = dp2perp * duv1.y + dp1perp * duv2.y;
    float invmax  = inversesqrt(max(dot(T, T), dot(B, B)));
    mat3  tm      = mat3(T * invmax, B * invmax, N);
    mat3  tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));

    vec3 view_dir = tbn_inv * normalize(w_pos - u_view_pos);
    vec2 uv = ParallaxMapping(o_uv, view_dir);
    if (uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0)
        discard;

    vec3  L    = tbn_inv * normalize(u_light_pos - w_pos);
    vec3  mapN = normalize(texture2D(u_normal_map, uv.st).xyz * 2.0 - 1.0); 
    float kd   = max(0.0, dot(mapN, L));

    vec3 color     = texture2D(u_diffuse, uv.st).rgb;
    vec3 light_col = (0.1 + kd) * color.rgb;
    gl_FragColor   = vec4(clamp(light_col, 0.0, 1.0), 1.0);
}

The result is much more impressive:

(function loadscene() {

var gl, progDraw, vp_size;
var bufCube = {};
var diffuse_tex = 1;
var height_tex = 2;
var normal_tex = 3;

function render(deltaMS){

    var height_scale = 0.3 * document.getElementById("height").value / 100.0;
    
    // setup view projection and model
    vp_size = [canvas.width, canvas.height];
    camera.Update( vp_size );
    var prjMat = camera.Perspective();
    var viewMat = camera.LookAt();
    var modelMat = camera.AutoModelMatrix();
        
    gl.viewport( 0, 0, vp_size[0], vp_size[1] );
    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.frontFace(gl.CCW)
    gl.cullFace(gl.BACK)
    gl.enable(gl.CULL_FACE)

    // set up draw shader
    ShProg.Use( progDraw );
    ShProg.SetF3( progDraw, "u_view_pos", camera.pos )
    ShProg.SetF3( progDraw, "u_light_pos", [0.0, 5.0, 5.0] )
    ShProg.SetF1( progDraw, "u_height_scale", height_scale );
    ShProg.SetI1( progDraw, "u_diffuse", diffuse_tex );
    ShProg.SetI1( progDraw, "u_displacement_map", height_tex );
    ShProg.SetI1( progDraw, "u_normal_map", normal_tex );
    ShProg.SetM44( progDraw, "P", prjMat );
    ShProg.SetM44( progDraw, "V", viewMat );
    ShProg.SetM44( progDraw, "M", modelMat );
    
    // draw scene
    VertexBuffer.Draw( bufCube );

    requestAnimationFrame(render);
}

function initScene() {

    canvas = document.getElementById( "canvas");
    gl = canvas.getContext( "experimental-webgl" );
    var standard_derivatives = gl.getExtension("OES_standard_derivatives");  // dFdx, dFdy
    if (!standard_derivatives)
       alert('no standard derivatives support (no dFdx, dFdy)');
    //gl = canvas.getContext( "webgl2" );
    if ( !gl )
    return null;
    
    progDraw = ShProg.Create( 
    [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
        { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
    ] );
    if ( !progDraw.progObj )
        return null;
    progDraw.inPos = ShProg.AttrI( progDraw, "a_position" );
    progDraw.inNV  = ShProg.AttrI( progDraw, "a_normal" );
    progDraw.inUV  = ShProg.AttrI( progDraw, "a_texCoord" );
    
    // create cube
    let Pos = [ -1,-1,1,  1,-1,1,  1,1,1, -1,1,1, -1,-1,-1,  1,-1,-1,  1,1,-1, -1,1,-1 ];
    let Col = [ 1,0,0, 1,0.5,0, 1,0,1, 1,1,0, 0,1,0, 0, 0, 1 ];
    let NV = [ 0,0,1, 1,0,0, 0,0,-1, -1,0,0, 0,1,0, 0,-1,0 ];
    let TV = [ 1,0,0, 0,0,-1, -1,0,0, 0,0,1, 1,0,0, -1,0,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(Pos[cubeHlpInx[i]*3], Pos[cubeHlpInx[i]*3+1], Pos[cubeHlpInx[i]*3+2] );
    var cubeNVData = [];
    for ( var i1 = 0; i1 < 6; ++ i1 ) {
        for ( i2 = 0; i2 < 4; ++ i2 ) cubeNVData.push(NV[i1*3], NV[i1*3+1], NV[i1*3+2]);
    }
    var cubeTVData = [];
    for ( var i1 = 0; i1 < 6; ++ i1 ) {
        for ( i2 = 0; i2 < 4; ++ i2 ) cubeTVData.push(TV[i1*3], TV[i1*3+1], TV[i1*3+2]);
    }
    var cubeColData = [];
    for ( var is = 0; is < 6; ++ is ) {
        for ( var ip = 0; ip < 4; ++ ip ) cubeColData.push(Col[is*3], Col[is*3+1], Col[is*3+2]); 
    }
    var cubeTexData = []
    for ( var i = 0; i < 6; ++ i ) cubeTexData.push( 0, 0, 1, 0, 1, 1, 0, 1 );
    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 : progDraw.inPos },
    { data : cubeNVData,  attrSize : 3, attrLoc : progDraw.inNV },
    //{ data : cubeTVData,  attrSize : 3, attrLoc : progDraw.inTV },
    { data : cubeTexData, attrSize : 2, attrLoc : progDraw.inUV },
    //{ data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol },
    ],
    cubeInxData, gl.TRIANGLES );

    Texture.LoadTexture2D( diffuse_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/woodtiles.jpg" );
    Texture.LoadTexture2D( height_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/toy_box_disp.png" );
    Texture.LoadTexture2D( normal_tex, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/toy_box_normal.png" );
    
    camera = new Camera( [0, 3, 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];
}

function Fract( val ) { 
    return val - Math.trunc( val );
}
function CalcAng( deltaTime, interval ) {
    return Fract( deltaTime / (1000*interval) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, interval, range ) {
    var pos = self.Fract( deltaTime / (1000*interval) ) * 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 Rotate(matA, angRad, axis) {
    var s = Math.sin(angRad), c = Math.cos(angRad);
    var x = axis[0], y = axis[1], z = axis[2]; 
    matB = [
        x*x*(1-c)+c,   x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0,
        y*x*(1-c)+z*s, y*y*(1-c)+c,   y*z*(1-c)-x*s, 0,
        z*x*(1-c)-y*s, z*y*(1-c)+x*s, z*z*(1-c)+c,   0,
        0,             0,             0,             1 ];
    return Multiply(matA, matB);
}    

function Multiply(matA, matB) {
    matC = IdentM44();
    for (var i0=0; i0<4; ++i0 )
        for (var i1=0; i1<4; ++i1 )
            matC[i0*4+i1] = matB[i0*4+0] * matA[0*4+i1] + matB[i0*4+1] * matA[1*4+i1] + matB[i0*4+2] * matA[2*4+i1] + matB[i0*4+3] * matA[3*4+i1]  
    return matC;
}

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]; 
}; 
this.AutoModelMatrix = function() {
    return this.auto_rotate ? Multiply(this.current_model_mat, this.model_mat) : this.model_mat;
};
this.Update = function(vp_size) {
    if (vp_size)
        this.vp = vp_size;
    var current_T = this.Time();
    this.current_model_mat = IdentM44()
    var auto_angle_x = Fract( (current_T - this.rotate_start_T) / 13000.0 ) * 2.0 * Math.PI;
    var auto_angle_y = Fract( (current_T - this.rotate_start_T) / 17000.0 ) * 2.0 * Math.PI;
    this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_x, 0 );
    this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_y, 1 );
};
}

var Texture = {};
Texture.HandleLoadedTexture2D = function( texture, flipY ) {
    gl.activeTexture( gl.TEXTURE0 + texture.unit );
    gl.bindTexture( gl.TEXTURE_2D, texture.obj );
    gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, flipY != undefined && flipY == true );
    gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
    return texture;
}
Texture.LoadTexture2D = function( unit, name ) {
    var texture = {};
    texture.obj = gl.createTexture();
    texture.unit = unit;
    texture.image = new Image();
    texture.image.setAttribute('crossorigin', 'anonymous');
    texture.image.onload = function () {
        Texture.HandleLoadedTexture2D( texture, false )
    }
    texture.image.src = name;
    return texture;
}

var 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) shaderObjs.push(shderObj);
    }
    var prog = {}
    prog.progObj = this.Link(shaderObjs)
    if (prog.progObj) {
        prog.attrInx = {};
        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.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
        }
        prog.uniLoc = {};
        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.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
        }
    }
    return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: 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;
},
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(gl.getProgramInfoLog(prog));
    return status ? prog : null;
} };
  
var VertexBuffer = {
Create: function(attribs, indices, type) {
    var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
    for (var i=0; i<attribs.length; ++i) {
        buffer.buf.push(gl.createBuffer());
        buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc });
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
    }
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    return buffer;
},
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(bufObj.primitive_type, 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 { margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision highp float;

attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord;

varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;
    
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;

void main()
{   
    o_uv          = a_texCoord;
    w_nv          = normalize(mat3(M) * a_normal);
    vec4 worldPos = M * vec4(a_position, 1.0);
    w_pos         = worldPos.xyz;
    gl_Position   = P * V * worldPos;
}
</script>
  
<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;

varying vec3 w_pos;
varying vec3 w_nv;
varying vec2 o_uv;

uniform float u_height_scale;
uniform vec3 u_light_pos;
uniform vec3 u_view_pos;
uniform sampler2D u_diffuse;
uniform sampler2D u_normal_map;
uniform sampler2D u_displacement_map;

vec2 ParallaxMapping (vec2 texCoord, vec3 viewDir)
{
    float numLayers = 32.0 - 31.0 * abs(dot(vec3(0.0, 0.0, 1.0), viewDir));
    float layerDepth = 1.0 / numLayers;

    vec2 P = viewDir.xy / viewDir.z * u_height_scale;
    vec2 deltaTexCoords = P / numLayers;
    vec2 currentTexCoords = texCoord;

    float currentLayerDepth = 0.0;
    float currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
    for (int i=0; i<32; ++ i)
    {
        if (currentLayerDepth >= currentDepthMapValue)
            break;
        currentTexCoords -= deltaTexCoords;
        currentDepthMapValue = texture2D(u_displacement_map, currentTexCoords).r;
        currentLayerDepth += layerDepth;
    }

    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
    float afterDepth = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture2D(u_displacement_map, prevTexCoords).r - currentLayerDepth + layerDepth;

    float weight = afterDepth / (afterDepth - beforeDepth);
    return prevTexCoords * weight + currentTexCoords * (1.0 - weight);
}

void main()
{
    vec3  N       = normalize(w_nv);
    vec3  dp1     = dFdx( w_pos );
    vec3  dp2     = dFdy( w_pos );
    vec2  duv1    = dFdx( o_uv );
    vec2  duv2    = dFdy( o_uv );
    vec3  dp2perp = cross(dp2, N);
    vec3  dp1perp = cross(N, dp1);
    vec3  T       = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3  B       = dp2perp * duv1.y + dp1perp * duv2.y;
    float invmax  = inversesqrt(max(dot(T, T), dot(B, B)));
    mat3  tm      = mat3(T * invmax, B * invmax, N);
    mat3  tbn_inv = mat3(vec3(tm[0].x, tm[1].x, tm[2].x), vec3(tm[0].y, tm[1].y, tm[2].y), vec3(tm[0].z, tm[1].z, tm[2].z));

    vec3 view_dir = tbn_inv * normalize(w_pos - u_view_pos);
    vec2 uv = ParallaxMapping(o_uv, view_dir);
    if (uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0)
        discard;

    vec3  L    = tbn_inv * normalize(u_light_pos - w_pos);
    vec3  mapN = normalize(texture2D(u_normal_map, uv.st).xyz * 2.0 - 1.0); 
    float kd   = max(0.0, dot(mapN, L));

    vec3 color     = texture2D(u_diffuse, uv.st).rgb;
    vec3 light_col = (0.1 + kd) * color.rgb;
    gl_FragColor   = vec4(clamp(light_col, 0.0, 1.0), 1.0);
} 
</script>
  
<body>
  
<div>
  <form id="gui" name="inputs">
      <table>
          <tr>
              <td> <font color=#CCF>height scale</font> </td>
              <td> <input type="range" id="height" min="0" max="100" value="50"/></td>
          </tr>
      </table>
  </form>
</div>


<canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>

Post a Comment for "Bump Mapping With Javascript And Glsl"