Hello Guest

Author Topic: 2D Dynamic Lighting  (Read 3977 times)

0 Members and 1 Guest are viewing this topic.

Fruckert

  • Member
  • Posts: 60
  • Total Square
    • View Profile
2D Dynamic Lighting
« on: January 25, 2015, 02:12:09 PM »
I'm playing around with Sprite Lamp for a project, and I just managed to finish implementing a directional light.
I'll post progress on here as the lighting engine goes.
But right now, I think it looks pretty sweet.

The shader, which is probably pretty terrible:
Code: [Select]
// Otter dynamic-lighting shader.
// Based off of the default Sprite Lamp shader, and the standard Unity shader.
// Note: I don't really know what I'm doing.
//- Cody Gillmer, @Fruckert

#version 130

uniform sampler2D diffuseMap;
uniform sampler2D normalDepthMap;
uniform sampler2D specGlossMap;
uniform sampler2D emissiveMap;
uniform sampler2D aoMap;

uniform vec3 dirLightDirection;
uniform vec4 dirLightColour;
uniform vec4 upAmbientColour;
uniform vec4 loAmbientColour;

uniform float celShadingLevel;

uniform float lightWrap;

uniform float ambientOcclusionStrength;

uniform float specularExponent;
uniform float specularStrength;

uniform float emissiveStrength;

uniform float amplifyDepth;

void main()
{
    //Texture lookups at the beginning.
    vec4 diffuseColour = texture2D(diffuseMap, gl_TexCoord[0].xy);
    vec4 normalDepth = texture2D(normalDepthMap, gl_TexCoord[0].xy);
    vec4 specGlossValues = texture2D(specGlossMap, gl_TexCoord[0].xy);
    vec4 emissiveColour = texture2D(emissiveMap, gl_TexCoord[0].xy);
    vec4 ambientOcclusion = texture2D(aoMap, gl_TexCoord[0].xy);
   
    //If we're transparent enough, just leave.
    if (diffuseColour.a <= 0.1)
    {
        discard;
    }
   
    //* Ambient
    vec4 ambientResult;
   
    ambientOcclusion = (ambientOcclusion * ambientOcclusionStrength) + (1.0 - ambientOcclusionStrength);
   
    float upness = normalDepth.y * 0.5 + 0.5; //"Upfactor": 1 = straight up, 0 = straight down, 0.5 = horizontal
   
    vec4 ambientColour = (loAmbientColour * (1.0 - upness) + upAmbientColour * upness) * ambientOcclusion;
   
    ambientResult = ambientColour * diffuseColour + vec4(emissiveColour.rgb * emissiveStrength, 0.0);
    //*/
   
    //* Shadows
    float depthColour = normalDepth.a;
    vec3 lightDirection = normalize(dirLightDirection);
   
    //We calculate shadows here. Magic numbers incoming (FIXME).
    float shadowMult = 1.0;
    vec3 moveVec = lightDirection * 0.006;
    float thisHeight = depthColour * amplifyDepth;
   
    vec3 tapPos = vec3(gl_TexCoord[0].xy, thisHeight + 0.1);

    //This loop traces along the light ray and checks if that ray is inside the depth map at each point.
    //If it is, darken that pixel a bit.
    for (int i = 0; i < 8; i++)
    {
        tapPos += moveVec;
        float tapDepth = texture2D(normalDepthMap, tapPos.xy).a * amplifyDepth;
        if (tapDepth > tapPos.z)
        {
            shadowMult -= 0.125;
        }
    }
    shadowMult = clamp(shadowMult, 0.0, 1.0);
    //*/
   
    //* Directional
    vec3 normalDirection = (normalDepth.rgb - 0.5) * 2.0;
    normalDirection.b *= -1.0;
    normalDirection = normalize(normalDirection);
   
    float normalDotLight = dot(normalDirection, lightDirection);
   
    float diffuseLevel = clamp(normalDotLight + lightWrap, 0, lightWrap + 1.0) / (lightWrap + 1.0) * shadowMult;
    //*/
   
    //* Specular
    float specularLevel;
   
    if (normalDotLight < 0.0)
    {
        //Light's on the other side, no specular.
        specularLevel = 0.0;
    }
    else
    {
        //TODO: Good thing we're orthographic, otherwise we'd have to change this!
        vec3 viewDirection = vec3(0, 0, -1);
        specularLevel = pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), specularExponent * specGlossValues.a) * 0.4;
    }
    //*/
   
    //* Cel Shading
    if (celShadingLevel >= 2.0)
    {
        diffuseLevel = floor(diffuseLevel * celShadingLevel) / (celShadingLevel - 0.5);
        specularLevel = floor(specularLevel * celShadingLevel) / (celShadingLevel - 0.5);
    }
    //*/
   
    //* Final Colour
    vec3 diffuseReflection = dirLightColour.rgb * diffuseLevel * diffuseColour.rgb;
    vec3 specularReflection = dirLightColour.rgb * specularLevel * specGlossValues.rgb * specularStrength;
   
    gl_FragColor = vec4(diffuseReflection + specularReflection, diffuseColour.a) + ambientResult;
    //*/
}


I used a custom class to implement the shader:
Code: [Select]
/// <summary>
/// Shader for dynamic lighting.
/// </summary>
public class LightingShader : Shader
{
    /// <summary>
    /// What direction the directional light is coming from.
    /// </summary>
    public static Vector3 DirLightDirection = Vector3.Forward;

    /// <summary>
    /// What colour the directional light is.
    /// </summary>
    public static Color DirLightColour = new Color(0.8f, 0.8f, 0.8f);

    /// <summary>
    /// The upper ambient colour.
    /// </summary>
    public static Color UpperAmbient = new Color(0.3f, 0.3f, 0.3f);

    /// <summary>
    /// The lower ambient colour.
    /// </summary>
    public static Color LowerAmbient = new Color(0.1f, 0.1f, 0.1f);

    /// <summary>
    /// How many cel levels the shading has.
    /// Below 2 is disabled.
    /// </summary>
    public static float CelLevel = 6f;

    private TextureSet _textures;
    private float _lightWrap, _aoStrength, _specExponent, _specStrength, _emissiveStrength, _amplifyDepth;

    /// <summary>
    /// The textures that the shader uses to shade the final image.
    /// </summary>
    public TextureSet Textures
    {
        get { return _textures; }
        set
        {
            _textures = value;

            SetParameter("diffuseMap", _textures.Diffuse);
            SetParameter("normalDepthMap", _textures.NormalDepth);

            if (_textures.SpecularGloss != null)
                SetParameter("specGlossMap", _textures.SpecularGloss);

            if (_textures.Emissive != null)
                SetParameter("emissiveMap", _textures.Emissive);

            if (_textures.AmbientOcclusion != null)
                SetParameter("aoMap", _textures.AmbientOcclusion);
        }
    }

    public float AOStrength
    {
        get { return _aoStrength; }
        set
        {
            _aoStrength = value;
            SetParameter("ambientOcclusionStrength", _aoStrength);
        }
    }

    public float LightWrap
    {
        get { return _lightWrap; }
        set
        {
            _lightWrap = value;
            SetParameter("lightWrap", _lightWrap);
        }
    }

    public float SpecularExponent
    {
        get { return _specExponent; }
        set
        {
            _specExponent = value;
            SetParameter("specularExponent", _specExponent);
        }
    }

    public float SpecularStrength
    {
        get { return _specStrength; }
        set
        {
            _specStrength = value;
            SetParameter("specularStrength", _specStrength);
        }
    }

    public float EmissiveStrength
    {
        get { return _emissiveStrength; }
        set
        {
            _emissiveStrength = value;
            SetParameter("emissiveStrength", _emissiveStrength);
        }
    }

    public float AmplifyDepth
    {
        get { return _amplifyDepth; }
        set
        {
            _amplifyDepth = value;
            SetParameter("amplifyDepth", _amplifyDepth);
        }
    }

    //*/
    public LightingShader(TextureSet textures, float aoStrength = 0f, float lightWrap = 0f, float specExponent = 10f, float specStrength = 1f, float amplifyDepth = 0f)
        :base("Assets/LightingShader.frag")
    {
        Textures = textures;
        AOStrength = aoStrength;
        LightWrap = lightWrap;
        SpecularExponent = specExponent;
        SpecularStrength = specStrength;
        AmplifyDepth = amplifyDepth;
    }
    //*/

    /// <summary>
    /// Updates the lighting shader's static parameters.
    /// </summary>
    public void UpdateStatics()
    {
        SetParameter("celShadingLevel", CelLevel);
        SetParameter("dirLightDirection", DirLightDirection);
        SetParameter("dirLightColour", DirLightColour);
        SetParameter("upAmbientColour", UpperAmbient);
        SetParameter("loAmbientColour", LowerAmbient);
    }
}

public class TextureSet
{
    public Texture Diffuse, NormalDepth, SpecularGloss, Emissive, AmbientOcclusion;

    /// <summary>
    /// A set of textures.
    /// </summary>
    /// <param name="setPath"></param>
    public TextureSet(string setPath)
    {
        Diffuse = new Texture(setPath + "_Diffuse.png");

        if (File.Exists(setPath + "_NormalDepth.png"))
            NormalDepth = new Texture(setPath + "_NormalDepth.png");

        if (File.Exists(setPath + "_Specular.png"))
            SpecularGloss = new Texture(setPath + "_Specular.png");

        if (File.Exists(setPath + "_Emissive.png"))
            Emissive = new Texture(setPath + "_Emissive.png");

        if (File.Exists(setPath + "_AO.png"))
            AmbientOcclusion = new Texture(setPath + "_AO.png");
    }
}




Download

Controls:
Arrow keys - move character
WASD - move light X/Y
RF - move light Z up/down
TG - change cel levels
Q - reset
« Last Edit: January 25, 2015, 03:30:47 PM by Fruckert »

Kyle

  • Administrator
  • Member
  • Posts: 258
    • View Profile
Re: 2D Dynamic Lighting
« Reply #1 on: January 28, 2015, 05:53:39 PM »
Whoa this looks awesome!  Gonna have to play around with this later ;D

Fruckert

  • Member
  • Posts: 60
  • Total Square
    • View Profile
Re: 2D Dynamic Lighting
« Reply #2 on: January 28, 2015, 06:39:37 PM »
Thank you!
Right now we're having a liiittle bit of trouble getting an environment put in, but the shader works, and so does the custom Shader subclass.
Here's an update on the LightingShader class. Now it's better documented and possibly has some other changes, idunno

Code: [Select]
/// <summary>
/// Shader for dynamic lighting.
/// </summary>
public class LightingShader : Shader
{
    /// <summary>
    /// What direction the directional light is coming from.
    /// </summary>
    public static Vector3 DirLightDirection = Vector3.Forward;

    /// <summary>
    /// What colour the directional light is.
    /// </summary>
    public static Color DirLightColour = new Color(0.8f, 0.8f, 0.8f);

    /// <summary>
    /// The upper ambient colour.
    /// </summary>
    public static Color UpperAmbient = new Color(0.3f, 0.3f, 0.3f);

    /// <summary>
    /// The lower ambient colour.
    /// </summary>
    public static Color LowerAmbient = new Color(0.1f, 0.1f, 0.1f);

    /// <summary>
    /// How many cel levels the shading has.
    /// Below 2 is disabled.
    /// </summary>
    public static float CelLevel = 6f;

    private TextureSet _textures;
    private float _lightWrap, _aoStrength, _specExponent, _specStrength, _emissiveStrength, _amplifyDepth;

    /// <summary>
    /// The textures that the shader uses to shade the final image.
    /// </summary>
    public TextureSet Textures
    {
        get { return _textures; }
        set
        {
            _textures = value;

            SetParameter("diffuseMap", _textures.Diffuse);
            SetParameter("normalDepthMap", _textures.NormalDepth);

            if (_textures.SpecularGloss != null)
                SetParameter("specGlossMap", _textures.SpecularGloss);

            if (_textures.Emissive != null)
                SetParameter("emissiveMap", _textures.Emissive);

            if (_textures.AmbientOcclusion != null)
                SetParameter("aoMap", _textures.AmbientOcclusion);
        }
    }

    /// <summary>
    /// How strong the Ambient Occlusion is.
    /// </summary>
    /// <remarks>Should be set between 0.0f and 1.0f</remarks>
    public float AOStrength
    {
        get { return _aoStrength; }
        set
        {
            _aoStrength = value;
            SetParameter("ambientOcclusionStrength", _aoStrength);
        }
    }

    /// <summary>
    /// Higher values light away-facing pixels more.
    /// </summary>
    /// <remarks>Should be set between 0.0f and 1.0f</remarks>
    public float LightWrap
    {
        get { return _lightWrap; }
        set
        {
            _lightWrap = value;
            SetParameter("lightWrap", _lightWrap);
        }
    }

    /// <summary>
    /// Multiplied by the alpha channel of the spec map to get the specular exponent.
    /// </summary>
    /// <remarks>Should be set between 1.0f and 50.0f</remarks>
    public float SpecularExponent
    {
        get { return _specExponent; }
        set
        {
            _specExponent = value;
            SetParameter("specularExponent", _specExponent);
        }
    }

    /// <summary>
    /// Multiplier that affects brightness of the specular highlights.
    /// </summary>
    /// <remarks>Should be set between 0.0f and 5.0f</remarks>
    public float SpecularStrength
    {
        get { return _specStrength; }
        set
        {
            _specStrength = value;
            SetParameter("specularStrength", _specStrength);
        }
    }

    /// <summary>
    /// The emissive map is multiplied by this.
    /// </summary>
    /// <remarks>Should be set between 0.0f and 1.0f</remarks>
    public float EmissiveStrength
    {
        get { return _emissiveStrength; }
        set
        {
            _emissiveStrength = value;
            SetParameter("emissiveStrength", _emissiveStrength);
        }
    }

    /// <summary>
    /// Affects the "severity" of the depth map. Mostly affects shading and soft shadows.
    /// </summary>
    /// <remarks>Should be set between 0.0f and 1.0f</remarks>
    public float AmplifyDepth
    {
        get { return _amplifyDepth; }
        set
        {
            _amplifyDepth = value;
            SetParameter("amplifyDepth", _amplifyDepth);
        }
    }

    //*/
    /// <summary>
    /// Creates a new LightingShader.
    /// </summary>
    /// <param name="textures">The textures the shader will use to calculate lighting.</param>
    /// <param name="aoStrength">How strong the Ambient Occlusion is.</param>
    /// <param name="lightWrap">Higher values will light away-facing pixels more. 0.0 - 1.0</param>
    /// <param name="specExponent">Multiplied by the alpha channel of the spec map to get the specular exponent. 1.0 - 50.0</param>
    /// <param name="specStrength">Multiplier that affects brightness of specular highlights. 0.0 - 5.0</param>
    /// <param name="amplifyDepth">Affects the depth math's "strength". Mostly affects shadows. 0.0 - 1.0</param>
    public LightingShader(TextureSet textures, float aoStrength = 0f, float lightWrap = 0f, float specExponent = 10f, float specStrength = 1f, float amplifyDepth = 0f)
        :base("Assets/LightingShader.frag")
    {
        Textures = textures;
        AOStrength = aoStrength;
        LightWrap = lightWrap;
        SpecularExponent = specExponent;
        SpecularStrength = specStrength;
        AmplifyDepth = amplifyDepth;
    }
    //*/

    /// <summary>
    /// Updates the lighting shader's static parameters.
    /// </summary>
    public void UpdateStatics()
    {
        SetParameter("celShadingLevel", CelLevel);
        SetParameter("dirLightDirection", DirLightDirection);
        SetParameter("dirLightColour", DirLightColour);
        SetParameter("upAmbientColour", UpperAmbient);
        SetParameter("loAmbientColour", LowerAmbient);
    }
}

And here's the latest version of the shader:
Code: [Select]
// Otter dynamic-lighting shader.
// Based off of the default Sprite Lamp shader, and the standard Unity shader.
// Note: I don't really know what I'm doing.
//- Cody Gillmer, @Fruckert

#version 130

uniform sampler2D diffuseMap;
uniform sampler2D normalDepthMap;
uniform sampler2D specGlossMap;
uniform sampler2D emissiveMap;
uniform sampler2D aoMap;

uniform vec3 dirLightDirection;
uniform vec4 dirLightColour;
uniform vec4 upAmbientColour;
uniform vec4 loAmbientColour;

uniform float celShadingLevel;

uniform float lightWrap;

uniform float ambientOcclusionStrength;

uniform float specularExponent;
uniform float specularStrength;

uniform float emissiveStrength;

uniform float amplifyDepth;

void main()
{
    //Texture lookups at the beginning.
    vec4 diffuseColour = texture2D(diffuseMap, gl_TexCoord[0].xy);
    vec4 normalDepth = texture2D(normalDepthMap, gl_TexCoord[0].xy);
    vec4 specGlossValues = texture2D(specGlossMap, gl_TexCoord[0].xy);
    vec4 emissiveColour = texture2D(emissiveMap, gl_TexCoord[0].xy);
    vec4 ambientOcclusion = texture2D(aoMap, gl_TexCoord[0].xy);
   
    //If we're transparent enough, just leave.
    if (diffuseColour.a <= 0.1)
    {
        discard;
    }
   
    //* Ambient
    vec4 ambientResult;
   
    ambientOcclusion = (ambientOcclusion * ambientOcclusionStrength) + (1.0 - ambientOcclusionStrength);
   
    float upness = normalDepth.y * 0.5 + 0.5; //"Upfactor": 1 = straight up, 0 = straight down, 0.5 = horizontal
   
    vec4 ambientColour = (loAmbientColour * (1.0 - upness) + upAmbientColour * upness) * ambientOcclusion;
   
    ambientResult = ambientColour * diffuseColour + vec4(emissiveColour.rgb * emissiveStrength, 0.0);
    //*/
   
    //* Shadows
    float depthColour = normalDepth.a;
    vec3 lightDirection = normalize(dirLightDirection);
   
    //We calculate shadows here. Magic numbers incoming (FIXME).
    float shadowMult = 1.0;
    vec3 moveVec = lightDirection * 0.006;
    float thisHeight = depthColour * amplifyDepth;
   
    vec3 tapPos = vec3(gl_TexCoord[0].xy, thisHeight + 0.1);

    //This loop traces along the light ray and checks if that ray is inside the depth map at each point.
    //If it is, darken that pixel a bit.
    for (int i = 0; i < 8; i++)
    {
        tapPos += moveVec;
        float tapDepth = texture2D(normalDepthMap, tapPos.xy).a * amplifyDepth;
        if (tapDepth > tapPos.z)
        {
            shadowMult -= 0.125;
        }
    }
    shadowMult = clamp(shadowMult, 0.0, 1.0);
    //*/
   
    //* Directional
    vec3 normalDirection = (normalDepth.rgb - 0.5) * 2.0;
    normalDirection.b *= -1.0;
    normalDirection = normalize(normalDirection);
   
    float normalDotLight = dot(normalDirection, lightDirection);
   
    float diffuseLevel = clamp(normalDotLight + lightWrap, 0, lightWrap + 1.0) / (lightWrap + 1.0) * shadowMult;
    //*/
   
    //* Specular
    float specularLevel;
   
    if (normalDotLight < 0.0)
    {
        //Light's on the other side, no specular.
        specularLevel = 0.0;
    }
    else
    {
        //TODO: Good thing we're orthographic, otherwise we'd have to change this!
        vec3 viewDirection = vec3(0, 0, -1);
        specularLevel = pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), specularExponent * specGlossValues.a) * 0.4;
    }
    //*/
   
    //* Cel Shading
    if (celShadingLevel >= 2.0)
    {
        diffuseLevel = floor(diffuseLevel * celShadingLevel) / (celShadingLevel - 0.5);
        specularLevel = floor(specularLevel * celShadingLevel) / (celShadingLevel - 0.5);
    }
    //*/
   
    //* Final Colour
    vec3 diffuseReflection = dirLightColour.rgb * diffuseLevel * diffuseColour.rgb;
    vec3 specularReflection = dirLightColour.rgb * specularLevel * specGlossValues.rgb * specularStrength;
   
    gl_FragColor = vec4(diffuseReflection + specularReflection, diffuseColour.a) + ambientResult;
    //*/
}

Right now it's set up specifically to work with Sprite Lamp, but I'm going to be modifying the workflow later to be more generic (i hope).

My original plan with this thing was to slap it into a dll and make it so you only have to mess with the classes, but for some reason SFML's Shader class kept giving me a NullArgumentException when I gave it an embedded resource stream. It worked fine otherwise, though.
« Last Edit: January 30, 2015, 06:07:28 PM by Fruckert »

Fruckert

  • Member
  • Posts: 60
  • Total Square
    • View Profile
Re: 2D Dynamic Lighting
« Reply #3 on: February 01, 2015, 07:55:16 PM »
Thanks for that static AddParameter method.
Very helpful.

Been in a bit of a rough patch, so I haven't gotten much done.
I've been pondering multiple lights, and I think some kind of multiple surface rendering method, like that displacement map thing, would be for the best.
I'll be properly organizing this later, though.

Kyle

  • Administrator
  • Member
  • Posts: 258
    • View Profile
Re: 2D Dynamic Lighting
« Reply #4 on: February 02, 2015, 02:12:56 AM »
Awesome! Yeah I really try to eliminate areas of using strings since typos can really ruin your day.  Havent had a chance to check this out yet, trying to get some other stuff done this week.  Very interested to see where you take this though :O