Lua Examples

This page provides some examples of Lua bake scripts that can be provided to a Lua render pass in order to achieve some commonly used effects.

Occlusion

This example shows a very simple use of occlusion. It bakes red for surfaces that are in occlusion relative to the first light source, and green otherwise.

function bake()
    -- Check for occluders between here and light source number one.
    occ = shootOccRay(getPoint(), getLightDir(1))
    if (occ > 0) then
        return {1,0,0,1}  -- Red
    else
        return {0,1,0,1}  -- Green
    end
end

Radiosity Normal Maps (RNM)

This example shows a typical setup for baking indirect illumination to Radiosity Normal Maps. Note that there are multiple different standard definitions in existence for the three RNM basis vectors.

function setup()
    -- Sampling hemispherically in tangent space.
    bset("gather.sampletype", "indirect illumination")
    bset("gather.space", "tangent space")
    bset("gather.distribution", "equiareal")
    bset("gather.minsamples", 300)
    bset("gather.maxsamples", 600)
    bset("gather.coneangle", 180)

    bset("output.size", 9)
end

function bake()
    -- Basis definition.
    basis1 = vec3(0.8165, 0.0, 0.577)
    basis2 = vec3(-0.408, 0.707, 0.577)
    basis3 = vec3(-0.408, -0.707, 0.577)

    rgb1 = getSampleProjectedSum(basis1, false)
    rgb2 = getSampleProjectedSum(basis2, false)
    rgb3 = getSampleProjectedSum(basis3, false)

    n = getSampleCount()
    rgb1 = (rgb1 * 2.0) / n
    rgb2 = (rgb2 * 2.0) / n
    rgb3 = (rgb3 * 2.0) / n

    result = {rgb1[1], rgb1[2], rgb1[3],
              rgb2[1], rgb2[2], rgb2[3],
              rgb3[1], rgb3[2], rgb3[3]}

    return result
end

Spherical Harmonics (SH)

This example shows a typical setup for baking indirect illumination to Spherical Harmonics. It uses three SH, one for each color channel. Each SH has two bands. This makes a total of 12 components. The bake() function here is actually implicit. Unless you want to rearrange the components or perform other operations, then removing the bake() function completely will render the same result.

shbands = 2;
shbasiscount = shbands*shbands;

function setup()
    -- Sampling spherically in world space.
    bset("gather.sampletype", "indirect illumination")
    bset("gather.space", "world space")
    bset("gather.distribution", "equiareal")
    bset("gather.minsamples", 700)
    bset("gather.maxsamples", 1000)
    bset("gather.coneangle", 360)

    bset("basis.type", "sh")
    bset("basis.rgb", true)
    bset("basis.sh.bands", shbands)

    bset("output.size", 3 * shbasiscount)
end

function bake()
    red = getBasisCoefficients(1)
    grn = getBasisCoefficients(2)
    blu = getBasisCoefficients(3)

    result = {}
    for i = 1, shbasiscount do
        result[i] = red[i]
    end
    for i = 1, shbasiscount do
        result[i + shbasiscount] = grn[i]
    end
    for i = 1, shbasiscount do
        result[i + 2*shbasiscount] = blu[i]
    end

    return result
end

Dominant light direction

This example demonstrates a Dominant Light Direction pass. Unlike the fixed RNM basis vectors, it projects lighting onto the normal direction and the direction of the most incoming lighting. The dominant light direction is derived from the second SH band. Note that the example is working in tangent space, taking only the upper hemisphere into account.

function setup()
    -- Sampling hemispherically in tangent space.
    bset("gather.sampletype", "indirect illumination")
    bset("gather.space", "tangent space")
    bset("gather.distribution", "equiareal")
    bset("gather.minsamples", 300)
    bset("gather.maxsamples", 600)
    bset("gather.coneangle", 180)

    bset("basis.type", "sh")
    bset("basis.rgb", true)
    bset("basis.sh.bands", 4)
    bset("output.size", 8)
end

function bake()
    -- Tangent space directions to get illumination from.
    normal = vec3(0.0, 0.0, 1.0)
    lightDir = dominantLight()

    -- Project indirect light onto those directions.
    if lightDir == vec3(0.0, 0.0, 0.0) then
        rgb1 = {0.0, 0.0, 0.0} -- No dominant light direction, no light.
    else
        rgb1 = getSampleProjectedSum(lightDir, false)
    end
    
    rgb2 = getSampleProjectedSum(normal, false)

    n = getSampleCount()
    rgb1 = (rgb1 * 2.0) / n
    rgb2 = (rgb2 * 2.0) / n
    
    -- Output indirect lighting projected on normal and dominant light direction.
    -- Dominant light direction stored in LDR alpha channel.
    result = {rgb1[1], rgb1[2], rgb1[3], lightDir[1]/2+0.5,
              rgb2[1], rgb2[2], rgb2[3], lightDir[2]/2+0.5}

    return result
end

function dominantLight()
    coefficients = getBasisCoefficients(0)

    -- Get dominant light direction from second band sh coefficients.
    dir = vec3(coefficients[4], coefficients[2], math.max(0.0, coefficients[3]))

    if length(dir) > 0.000001 then
        return normalize(dir)
    else
        -- Can't find a proper direction, at least not in this hemisphere.
        return vec3(0.0, 0.0, 0.0)
    end
end

Direct lighting

This example demonstrates how to include direct lighting from certain light sources. You can add a light loop in the bake() function that projects the direct light contribution onto the basis. This example shows how it can be done with RNM, but the same principle applies to other basis types like SH.

function setup()
    -- Sampling hemispherically in tangent space.
    bset("gather.sampletype", "indirect illumination")
    bset("gather.space", "tangent space")
    bset("gather.distribution", "equiareal")
    bset("gather.minsamples", 300)
    bset("gather.maxsamples", 600)
    bset("gather.coneangle", 180)

    bset("output.size", 9)
end

function bake()
    -- Basis definition.
    basis1 = vec3(0.8165, 0.0, 0.577)
    basis2 = vec3(-0.408, 0.707, 0.577)
    basis3 = vec3(-0.408, -0.707, 0.577)

    rgb1 = getSampleProjectedSum(basis1, false)
    rgb2 = getSampleProjectedSum(basis2, false)
    rgb3 = getSampleProjectedSum(basis3, false)

    n = getSampleCount()
    rgb1 = (rgb1 * 2.0) / n
    rgb2 = (rgb2 * 2.0) / n
    rgb3 = (rgb3 * 2.0) / n

    -- Direct light loop.
    nlights = getLights()
    for i = 1, nlights do
            if not (getLightInd(i)) then
            -- Add lambert shaded light source contibution for each basis vector.
            col = getLightCol(i)
            weight = getLightLambertianReflectance(i, gatherToWorld(basis1))
            if (weight > 0) then
                rgb1 = rgb1 + col * weight
            end
            weight = getLightLambertianReflectance(i, gatherToWorld(basis2))
            if (weight > 0) then
                rgb2 = rgb2 + col * weight
            end
            weight = getLightLambertianReflectance(i, gatherToWorld(basis3))
            if (weight > 0) then
                rgb3 = rgb3 + col * weight
            end
        end
    end

    result = {rgb1[1], rgb1[2], rgb1[3],
              rgb2[1], rgb2[2], rgb2[3],
              rgb3[1], rgb3[2], rgb3[3]}

    return result
end