Search code examples
javaglslshaderlwjgllighting

Why does this lighting code only work for single lights and break when I add multiple lights?


I'm trying to create a simple 2D game engine using lwjgl and java. But I got stuck during the lighting programming, because the code I wrote only works when I have one light in the scene and not when I add multiple. I just can't figure out why so i decided to ask here.

This is the fragment shader with the lighting calculation:

#version 330 core

layout (location = 0) out vec4 color;
layout (origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord;

in DATA {
    vec2 tc;
} fs_in;

uniform sampler2D tex;

float map(float value, float min1, float max1, float min2, float max2) {
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}

void main() {
    vec3 lightColor = vec3(1, 1, 1);
    float range = 700;
    float x = 200;
    float y = 200;
    float ambient = 0.1;
    float intensity = 0.8;
    float alpha = 1.0;

    vec3 totalDiffuse = vec3(0.0);

    for(int i=0;i<1;i++){
        alpha = 1-map(distance(gl_FragCoord.xy, vec2(x*i, y*i)), 0.0, range, 0.0, 1.0);
        totalDiffuse += alpha*lightColor;
    }

    totalDiffuse = max(totalDiffuse, ambient);

    color = vec4(totalDiffuse, 1.0) * texture(tex, fs_in.tc);
}

If I run this code with only one light in the scene, i.e. setting the times the for loop runs to 1, then it works just fine and creates something like this:

But when I change it to loop for example 3 times, you would expect it to create 3 different lights, but actually it just increases the light intensity of the first light like this:

Anybody know why?


Solution

  • The result of the term

    alpha = 1-map(distance(gl_FragCoord.xy, vec2(x*i, y*i)), 0.0, range, 0.0, 1.0);
    

    can be negative.

    This will cause that the totalDiffuse is decreased.

    Clamp alpha to a minimum of 0:

    //totalDiffuse += alpha*lightColor;
    totalDiffuse += max(0.0, alpha) * lightColor;
    

    Note, the distance between the light sources is length(vec2(200, 200)), but the illumination range (radius) of each light source is 700. So anyway the light sources are overlapping.


    I recommend to use the glsl function smoothstep, which performs a Hermite interpolation between two values.

    Try the following:

    for(int i=0; i<3; i++ )
    {
        vec2 pos = vec2(x, y) * float(i);
        float dist = distance(gl_FragCoord.xy, pos);
    
        alpha = smoothstep(50.0, 100.0, dist);
        totalDiffuse += clamp(1.0-alpha, 0.0, 1.0) * lightColor;
    }
    

    in this case the 2nd parameter (100.0) to smoothstep is the maximum radius of the light source and the 1st parameter (50.0) is the radius which is full lit.


    See the WebGL example, where I used smoothstep:

    (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/2000.0);
        gl.uniform2f(progDraw.u_resolution, canvas.width, canvas.height);
        gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
        
        requestAnimationFrame(render);
    }  
    
    initScene();
    
    })();
    <script id="draw-shader-vs" type="x-shader/x-vertex">
    precision mediump float;
    attribute vec2 inPos;
    
    void main() {
        gl_Position = vec4( inPos.xy, 0.0, 1.0 );
    }
    </script>
    
    <script id="draw-shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    uniform float u_time;
    uniform vec2 u_resolution;
    
    vec3 HUEtoRGB(in float H)
    {
        float R = abs(H * 6.0 - 3.0) - 1.0;
        float G = 2.0 - abs(H * 6.0 - 2.0);
        float B = 2.0 - abs(H * 6.0 - 4.0);
        return clamp( vec3(R,G,B), 0.0, 1.0 );
    }
    
    void main() {
        vec2 uv = gl_FragCoord.xy / u_resolution;
    
        vec3 lightColor = vec3(1, 1, 1);
        float range = length(u_resolution) / 4.0;
        float x = u_resolution.x / 4.0;
        float y = u_resolution.y / 4.0;
        float ambient = 0.1;
        float intensity = 0.8;
        float alpha = 1.0;
    
        vec3 totalDiffuse = vec3(0.0);
    
        for(int i=0;i<3;i++)
        {    
            vec2 pos = vec2(x, y) * float(i+1);
            float dist = distance(gl_FragCoord.xy, pos);
            
            alpha = smoothstep(range/4.0, range/2.0, dist);
            totalDiffuse += clamp(1.0-alpha, 0.0, 1.0) * lightColor;
        }
        totalDiffuse = max(totalDiffuse, ambient);
    
        vec4 texcol = vec4( 1.0-uv.x, 1.0-uv.y, uv.x*uv.y, 1.0 );
        gl_FragColor = vec4(totalDiffuse, 1.0) * texcol;
    }
    </script>
    
    <canvas id="ogl-canvas" style="border: none"></canvas>