openglglslshaderlightphong

Phong Lighting is occasionally too bright on some parts of my terrain (OpenGL)


Using Triangle_Strips and heightmaps I made a mountainous terrain, and used 3D planar projection for texturing. For 3D planar projection, I followed section 1.5 of the following link. https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch01.html

This is how it looks like before Phong Lighting. Unitl texturing, it was ok.

https://i.sstatic.net/myHhY.jpg

This is how it looks after Phong Lighting. Note that I assumed directional light, so I assumed the light direction to face the -y axis direction. I set it as shader.setVec3("light.direction", 0.0f, -0.1f, 0.0f);

https://i.sstatic.net/XfBY3.jpg

Some parts of the terrain suddenly becomes brighter, as if the light becomes strong only in those parts.

I checked whether the normal vectors were correct. So I visualized them by the geometry shader. Here is the result.

https://i.sstatic.net/SQCWL.jpg

As you can see, the normal vectors were actually correct. So something must be wrong in my shader program. Here are the codes. First, the vertex shader.

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

float scale = 0.5;

out vec2 TexX;
out vec2 TexY;
out vec2 TexZ;
out vec3 blend_weights;
out vec3 FragPos;
out vec3 wNormal;

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));    
    blend_weights = abs(aNormal.xyz);
    blend_weights = (blend_weights - 0.2) * 0.7;
    blend_weights = max(blend_weights, 0);
    blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z);

    TexX = aPos.yz * scale;
    TexY = aPos.zx * scale; 
    TexZ = aPos.xy * scale;

    wNormal = mat3(transpose(inverse(model))) * aNormal;

    gl_Position = projection * view * vec4(FragPos, 1.0);
}

This is my fragment shader.

#version 330 core
out vec4 FragColor;

struct Material{

    //aka terrainTexture;
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

struct Light {
    // Direction has to be in world coordinates.
    vec3 direction;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;
uniform Material material;
uniform vec3 viewPos;

in vec3 FragPos;    //world Coordinate!
in vec2 TexX;
in vec2 TexY;
in vec2 TexZ;

in vec3 wNormal;

in vec3 blend_weights;

void main()
{
    vec4 ColorNoLighting = texture(material.diffuse, TexX)*blend_weights.x + texture(material.diffuse, TexY)*blend_weights.y + texture(material.diffuse, TexZ)*blend_weights.z;

    vec3 ambient = light.ambient * ColorNoLighting.xyz; 

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(-light.direction);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * ColorNoLighting.xyz;

    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * ColorNoLighting.xyz;

    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}

Finally, this is how I set all the uniforms.

shader.use();
shader.setInt("material.diffuse", 0);
shader.setInt("material.specular", 1);
shader.setVec3("light.direction", 0.0f, -0.1f, 0.0f);
shader.setVec3("viewPos", camera.Position);
shader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
shader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
shader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
shader.setFloat("material.shininess", 32.0f);
shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", projection);

I don't understand why some parts of my terrain suddenly becomes so bright after phong lighting. Any help would be appreciated. Thanks in advance!


Solution

  • The Lambertian diffuse component of the light is to weak, in compare to the Phong specular highlight.

    In your shader code the strength of the diffuse component is set by the parameter light.diffuse:

    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * ColorNoLighting.xyz;
    

    The strength of the specular component and its size is set by light.specular respectively material.shininess:

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * ColorNoLighting.xyz;
    

    Note, if the shininess parameter is decreased, then the size of the specular highlight increases but it gets smoother.

    Change the parameters to solve your issue.

    e.g.

    shader.setVec3("light.diffuse", 1.0f, 1.0f, 1.0f);
    shader.setVec3("light.specular", 0.5f, 0.5f, 0.5f);
    shader.setFloat("material.shininess", 10.0f);
    

    See the example, which demonstrates the effect of the parameters:

    (function loadscene() {
    
    var sliderScale = 100.0
    var gl;
    var canvas;
    var vp_size;
    var progDraw;
    var bufTorus = {};
    
    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 = Math.max(1, document.getElementById( "shininess" ).value / 2);
        document.getElementById( "ambient_val" ).innerHTML = ambient;
        document.getElementById( "diffuse_val" ).innerHTML = diffuse;
        document.getElementById( "specular_val" ).innerHTML = specular;
        document.getElementById( "shininess_val" ).innerHTML = shininess;
    
        Camera.create();
        Camera.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 );
    
        // set up draw shader
        ShaderProgram.Use( progDraw );
        ShaderProgram.SetUniformMat44( progDraw, "u_projectionMat44", Camera.Perspective() );
        ShaderProgram.SetUniformMat44( progDraw, "u_viewMat44", Camera.LookAt() );
        ShaderProgram.SetUniform3f( progDraw, "u_lightDir", [-1.0, -0.5, -2.0] )
        ShaderProgram.SetUniformFloat( progDraw, "u_ambient", ambient )
        ShaderProgram.SetUniformFloat( progDraw, "u_diffuse", diffuse )
        ShaderProgram.SetUniformFloat( progDraw, "u_specular", specular )
        ShaderProgram.SetUniformFloat( progDraw, "u_shininess", shininess )
        var modelMat = IdentityMat44()
        modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 13.0 ), 0 );
        modelMat = RotateAxis( modelMat, CalcAng( deltaMS, 17.0 ), 1 );
        ShaderProgram.SetUniformMat44( progDraw, "u_modelMat44", modelMat );
    
        // draw scene
        VertexBuffer.Draw( bufTorus );
    
        requestAnimationFrame(render);
    }
    
    function initScene() {
    
        document.getElementById( "ambient" ).value = 0.2 * sliderScale;
        document.getElementById( "diffuse" ).value = 0.75 * sliderScale;
        document.getElementById( "specular" ).value = 0.75 * sliderScale;
        document.getElementById( "shininess" ).value = 20.0;
        
        canvas = document.getElementById( "phong-canvas");
        vp_size = [canvas.width, canvas.height];
        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 }
          ],
          [ "u_projectionMat44", "u_viewMat44", "u_modelMat44", 
            "u_lightDir", "u_ambient", "u_diffuse", "u_specular", "u_shininess", ] );
        progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
        progDraw.inNV  = gl.getAttribLocation( progDraw, "inNV" );
        progDraw.inCol = gl.getAttribLocation( progDraw, "inCol" );
        if ( progDraw == 0 )
            return;
    
        // 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
        );
    
        window.onresize = resize;
        resize();
        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];
    }
    
    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 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 ];
    }
    
    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 = matA.slice(0);
        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;
    } 
    
    // 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.SetUniformInt = function( progObj, name, val ) { gl.uniform1i( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniformFloat = function( progObj, name, val ) { gl.uniform1f( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniform2f = function( progObj, name, arr ) { gl.uniform2fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniform3f = function( progObj, name, arr ) { gl.uniform3fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformMat44 = function( progObj, name, mat ) { gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
    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;
    }
    
    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 { 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;
    
    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;
    
    uniform vec3  u_lightDir;
    uniform float u_ambient;
    uniform float u_diffuse;
    uniform float u_specular;
    uniform float u_shininess;
    
    void main()
    {
        vec3 color      = vertCol;
        vec3 lightCol   = u_ambient * color;
        vec3  normalV   = normalize( vertNV );
        vec3  lightV    = normalize( -u_lightDir );
        vec3  eyeV      = normalize( -vertPos );
        
        // Lambertian
        float NdotL     = max( 0.0, dot( normalV, lightV ) );
        lightCol       += NdotL * u_diffuse * color;
        
        // Phong
        vec3  reflectV  = reflect(-lightV, normalV);
        float VdotR     = max( 0.0, dot( eyeV, reflectV ) );
        float kSpecular = ( u_shininess + 2.0 ) * pow( VdotR, u_shininess ) / ( 2.0 * 3.14159265 );
        
        lightCol       += kSpecular * u_specular * color;
        gl_FragColor    = vec4( lightCol.rgb, 1.0 );
    }
    </script>
    
    <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> <td> <font color= #CCF><span id="ambient_val">0</span></font> </td> </tr>
            <tr> <td> <font color=#40f040>diffuse</font> </td> 
                    <td> <input type="range" id="diffuse" min="0" max="100" value="0"/></td> <td> <font color= #CCF><span id="diffuse_val">0</span></font> </td></tr>
            <tr> <td> <font color=#40f040>specular</font> </td> 
                    <td> <input type="range" id="specular" min="0" max="100" value="0"/></td> <td> <font color= #CCF><span id="specular_val">0</span></font> </td></tr>
            <tr> <td> <font color=#40f040>shininess</font> </td> 
                    <td> <input type="range" id="shininess" min="0" max="100" value="0"/></td> <td> <font color= #CCF><span id="shininess_val">0</span></font> </td></tr>
        </table>
    </form>
    
    <canvas id="phong-canvas" style="border: none;"></canvas>