Global illumination in 2D

If you happen to be working on a 2D top-down game and want some global illumination on top of that, say no more! Actually, if you find something to be terribly wrong or confusing in the code then please tell me!

This project bakes diffuse global illumination for your scene where you define the resolution of the lightmap. It supports as many lightbounces as you like from sky, sun and emissive objects. The algorithm is based on path tracing and accelerated with a distance field in order to reduce waiting times and avoid crashing the program.

The code includes a tab called “ReadMe”, read it!
Potential questions that you might have:
Q: Why does the lighting sometimes look bugged when I have calculated 1 bounce of light?
A: Calculate 1-2 more bounces and it will disappear!

Q: In my game the lighting is really dark, why?
A: Two words, gamma correction! Essentially our screens does not display light in a linear form, so when displaying colors we need to compensate.

Instead of:
gl_FragColor=vec4(Color,1.);
do
gl_FragColor=vec4(pow(Color,vec3(0.45)),1.);
This is correct!

Code:
https://github.com/firelava135/GIDF2D/blob/master/Code.txt

Here is an example:

Did anyone save this code? Link doesn’t work…

@UberGoober couldn’t you just set up a 3d craft scene, but put all the objects on a plane. The light source could be above the plane.

@piinthesky can you make a boilerplate showing using an image on a plane?

@skar not sure exactly what you are after, but this illustrates what i was imagining: a plane with 3D objects sitting on it (cubes in this example) and the standard craft Sun lighting…


-- Demo plane with 3D cubes 
-- PiInTheSky, 3/4/21
-- requires orbitViewer from Cameras library

function setup()
    
    parameter.integer("ambientIntensity",0,255,200)
    parameter.number("sunIntensity",0,10,5.0)       
    
-- Create a new craft scene    
    scene = craft.scene()   
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 1000, HEIGHT/2, 2000)
    v.camera.farPlane=3000
    
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight=scene.sun:get(craft.light)
--    sunLight.position=vec3(WIDTH/2, 1000,0)
    sunLight.intensity=sunIntensity

-- create plane (a flat cube)
    yellow=color(233, 204, 80)
    planeSize=500
    plane=createCube(vec3(0,0,0), vec3(planeSize*2,1, planeSize*2),yellow )
    
-- create many cubes  
    for i=1,1000 do   
        col=color(math.random()*255, math.random()*255, math.random()*255)
        cubeSize=20*math.random()
        y=cubeSize/2
        createCube(vec3(math.random(-planeSize,planeSize),y,math.random(-planeSize,planeSize)), vec3(1,1,1)*cubeSize, col)
    end   
end

function createCube(pos,size,col)
    local c=scene:entity()
    c.position=vec3(pos.x,pos.y,pos.z)
    c.model = craft.model.cube(size)
--    c.material = craft.material("Materials:Specular")
    c.material = craft.material(asset.builtin.Materials.Standard)
    c.material.diffuse=col
    return c
end
   
function update(dt)  
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight.intensity=sunIntensity
    
    scene:update(dt)
end   
 
function draw()
    update(DeltaTime)
    scene:draw()   
end

function touched(touch)  
    v:touched(touch) 
end

I’m trying to find ways to do lighting in 2D not 3D, but I thought you meant to put a 2D image as a texture onto a plane and use the 3D lights on it, but that’s not going to work in the desired way.

https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson6

@skar gotcha, here is a little demo of lighting effects using the inbuilt surfaces: Basic Bricks, Stone Brick and Desert Cliff. Quite fun to see the effect of the varying the different parameters. I expect with Shade one could do a lot more.

-- Demo textures
-- PiInTheSky, 3/4/21
-- requires orbitViewer from Cameras library

function setup()
    stoneBrick=true
    basicBricks=false
    desertCliff=false
    
    parameter.integer("ambientIntensity",0,255,200)
    parameter.number("sunIntensity",0,10,5.0)     
      
    parameter.number("opacity",0,1,1.0)
    parameter.number("normalScale",0,10,1.0)
    parameter.number("roughness",0,10,1.0)
    parameter.number("metalness",0,10,1.0)
    parameter.number("aoMapIntensity",0,10,1.0)
    parameter.number("displacementScale",0,10,1.0)
    
-- Create a new craft scene    
    scene = craft.scene()   
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 1000, HEIGHT/2, 2000)
    v.camera.farPlane=3000
    
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight=scene.sun:get(craft.light)
--    sunLight.position=vec3(WIDTH/2, 1000,0)
    sunLight.intensity=sunIntensity

-- create plane (a flat cube)
    yellow=color(233, 204, 80)
    planeSize=500
    e=createCube(vec3(0,0,0), vec3(planeSize*2, planeSize*2, 1), yellow )

    -- Load the standard material (physically based rendering)
    m = craft.material(asset.builtin.Materials.Standard)
--    m = craft.material(asset.builtin.Materials.Specular)
    e.material = m
    
    -- Surface color
    m.diffuse = color(255, 255, 255)
    -- Opacity (0.0 fully transparent, 1.0 fully opaque)
    m.blendMode=NORMAL
    m.opacity = opacity
    
    if basicBricks then 
        -- Surface color texture map
        m.map = "Surfaces:Basic Bricks Color"
        -- Normal map for small scale surface details
        m.normalMap = "Surfaces:Basic Bricks Normal"
        -- A texture that controls the roughness
        m.roughnessMap = "Surfaces:Basic Bricks Roughness"
        -- The ambient occlusion map, used for self-occluding shadows
        m.aoMap = "Surfaces:Basic Bricks AO"
        -- A texture that controls how metallic the surface is
        m.metalnessMap = nil
        -- The displacement map which modifies vertex positions based on normals
        m.displacementMap = nil
    end
    
    if stoneBrick then 
        -- Surface color texture map
        m.map = "Surfaces:Stone Brick Color"
        -- Normal map for small scale surface details
        m.normalMap = "Surfaces:Stone Brick Normal"
        -- A texture that controls the roughness
        m.roughnessMap = "Surfaces:Stone Brick Roughness"
        -- The ambient occlusion map, used for self-occluding shadows
        m.aoMap = "Surfaces:Stone Brick AO"
        -- A texture that controls how metallic the surface is
        m.metalnessMap = "Surfaces:Stone Brick Metalness"
        -- The displacement map which modifies vertex positions based on normals
        m.displacementMap = "Surfaces:Stone Brick Height"
    end    
    
    if desertCliff then
        -- Surface color texture map
        m.map = "Surfaces:Desert Cliff Color"
        -- Normal map for small scale surface details
        m.normalMap = "Surfaces:Desert Cliff Normal"
        -- A texture that controls the roughness
        m.roughnessMap = "Surfaces:Desert Cliff Roughness"
        -- The ambient occlusion map, used for self-occluding shadows
        m.aoMap = "Surfaces:Desert Cliff AO"
        -- A texture that controls how metallic the surface is
        m.metalnessMap = "Surfaces:Desert Cliff Metalness"
        -- The displacement map which modifies vertex positions based on normals
        m.displacementMap = "Surfaces:Desert Cliff Height"
    end

    -- The environment map (a CubeTexture), specular illumination
    m.envMap = nil
    
    -- Texture map offset and repeat (tiling)
    m.offsetRepeat = vec4(0.0, 0.0, 3.0, 3.0)
    -- Base offset of the displacement map
    m.displacementBias = 0
    -- Scale of the displacement map
    m.displacementScale = displacementScale
       
end

function createCube(pos,size,col)
    local c=scene:entity()
    c.position=vec3(pos.x,pos.y,pos.z)
    c.model = craft.model.cube(size)
--    c.material = craft.material("Materials:Specular")
    c.material = craft.material(asset.builtin.Materials.Standard)
    c.material.diffuse=col
    return c
end
   
function update(dt)  
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight.intensity=sunIntensity
    
    -- Opacity (0.0 fully transparent, 1.0 fully opaque)
    m.opacity = opacity  
    -- How intense the normal map effect is in tangent space (also used for flipping)
    m.normalScale = vec2(1, 1)*normalScale
    -- How rough the material is
    m.roughness = roughness
    -- How metallic the material is
    m.metalness = metalness
    -- How intense the aoMap is
    m.aoMapIntensity = aoMapIntensity
    -- Scale of the displacement map
    m.displacementScale = displacementScale
    
    e.material = m
    
    scene:update(dt)
end   
 
function draw()
    update(DeltaTime)
    scene:draw()   
end

function touched(touch)  
    v:touched(touch) 
end

This is really great, thanks for setting it up. It’s as I thought though, this solution probably is not what i want to do. I’ll have to play with it some to see if it’s the best solution but it seems to me we should be able to use Mesh and Shader to have a 2D light effect without having to use craft 3D

@UberGoober @skar I don’t have the original code anymore, but I have created a better version on shadertoy: https://www.shadertoy.com/view/stcSzN. If you are just going to bake the lighting, then the lack of motion vectors in the shader won’t be any problem.