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?
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>