Lens Flare… (Code – small update & screens)

Seeeeeew… Here be a rundown of how it works:


  1. Knockout everything under a certain threshold. This can be converted to greyscale at this point and SHOULD be blurred before any other step.
  2. Apply the mixer deformation I have in the GPUAnimationFactory to create

    2a) Halo effect

    2b) Ghost images
  3. Mix samples from original image into the halo and ghosts as they are created
  4. Apply chromatic distortion
  5. Mix in lens dirt
  6. Mix everything back together



    Here be my problem. To do this correctly, step one should really be it’s own shader pass (or other option??). How does one best go about this? It’s doing nothing more than manipulating the frame and passing back a texture for the real process.



    Ideas on how to do this appropriately?



    EDIT:

    Here be the code for anyone who wants it. It’s as far as I really want to take it seeing as I have no real need for the effect. Couple ideas for where you could go with it…



    Shader-based rendering of halos (i.e. no texture sampling - using smoothstep, values and coords) It’s interesting what you can actually produce this way… next project is going to be light volumes by rendering markers+normals+depth.



    EDIT 2:

    Made a few changes to the light map generation which dramatically improved the end result. Only file that changed is LensFlareLightMap.frag.



    Here are a few screens:

    Small flare…



    Larger flare…





    Usage:

    [java]FilterPostProcessor fpp = new FilterPostProcessor(assetManager);



    LensFlare lf = new LensFlare(“Textures/lensdirt.png”); // or null if you don’t own a +1 Lens Cloth of Smiting

    lf.setGhostSpacing(0.125f);

    lf.setHaloDistance(0.48f);

    fpp.addFilter(lf);



    viewPort.addProcessor(fpp);[/java]



    Filter:

    LensFlare.java

    [java]package mygame;



    import com.jme3.asset.AssetManager;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.post.Filter;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.ViewPort;

    import com.jme3.texture.FrameBuffer;

    import com.jme3.texture.Image;

    import com.jme3.texture.Image.Format;

    import com.jme3.texture.Texture;

    import com.jme3.texture.Texture.MagFilter;

    import com.jme3.texture.Texture.MinFilter;

    import com.jme3.texture.Texture.WrapMode;

    import com.jme3.texture.Texture2D;

    import java.util.ArrayList;



    public class LensFlare extends Filter{

    RenderManager rm;

    ViewPort vp;

    AssetManager am;

    int w, h;

    Pass lightMap;

    Material matLightMap;

    Texture tex_LensDirt;

    String dirtTexture = null;

    float ghostSpacing = 0.18f;

    float haloDistance = 0.45f;

    float threshold = 0.9f;



    /**
  • Creates a new Lens Flare Filter
  • @param lensDirt String asset key for the lens dirt texture. Default is null

    */

    public LensFlare(String lensDirt) {

    super("LensFlare");

    this.dirtTexture = lensDirt;

    }



    @Override

    public boolean isRequiresDepthTexture() {

    return false;

    }



    @Override

    public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {

    am = manager;

    rm = renderManager;

    this.vp = vp;

    this.w = w;

    this.h = h;



    if (dirtTexture != null) {

    tex_LensDirt = manager.loadTexture(dirtTexture);

    tex_LensDirt.setMinFilter(MinFilter.BilinearNearestMipMap);

    tex_LensDirt.setMagFilter(MagFilter.Bilinear);

    tex_LensDirt.setWrap(WrapMode.Repeat);

    }



    postRenderPasses = new ArrayList<Pass>();



    matLightMap = new Material(am, "MatDefs/LensFlareLightMap.j3md");

    matLightMap.setFloat("Threshold", threshold);



    lightMap = new Pass() {

    @Override

    public boolean requiresDepthAsTexture() {

    return false;

    }

    @Override

    public boolean requiresSceneAsTexture() {

    return true;

    }

    };

    lightMap.init(rm.getRenderer(), w, h, Format.RGBA8, Format.Depth, 1, matLightMap);

    lightMap.getRenderedTexture().setMinFilter(Texture.MinFilter.BilinearNearestMipMap);

    lightMap.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);

    postRenderPasses.add(lightMap);



    material = new Material(manager, "MatDefs/LensFlare.j3md");

    if (dirtTexture != null)

    material.setTexture("LensDirt", tex_LensDirt);

    material.setFloat("Ghost", ghostSpacing);

    material.setFloat("Halo", haloDistance);

    material.setTexture("LightMap", lightMap.getRenderedTexture());

    }



    /**
  • Sets the distance between light ghost images.
  • @param ghostSpacing Values 0.0f to 0.5f. Default is 0.18f

    */

    public void setGhostSpacing(float ghostSpacing) {

    if (ghostSpacing > 0.5f) ghostSpacing = 0.5f;

    else if (ghostSpacing < 0.0f) ghostSpacing = 0.0f;

    this.ghostSpacing = ghostSpacing;

    if (material != null) {

    material.setFloat("Ghost", ghostSpacing);

    }

    }



    /**
  • Gets the current distance between light ghost images.
  • @return ghostSpacing Default is 0.18f

    */

    public float getGhostSpacing() {

    return this.ghostSpacing;

    }



    /**
  • Sets the distance from center screen to halo. Values 0.0f to 0.5f.
  • @param haloDistance Default is 0.45f

    */

    public void setHaloDistance(float haloDistance) {

    if (haloDistance > 0.5f) haloDistance = 0.5f;

    else if (haloDistance < 0.0f) haloDistance = 0.0f;

    this.haloDistance = haloDistance;

    if (material != null) {

    material.setFloat("Halo", haloDistance);

    }

    }



    /**
  • Gets the current distance from center screen to halo. Values 0.0f to 0.5f.
  • @return haloDistance Default is 0.45f

    */

    public float getHaloDistance() {

    return this.haloDistance;

    }



    /**
  • Sets the rgb threshold value used to create the lens flare light map.
  • r < threshold || g < threshold || b < threshold is discarded.
  • @param threshold Default is 0.9f

    */

    public void setLightMapThreshold(float threshold) {

    if (threshold > 0.5f) threshold = 0.5f;

    else if (threshold < 0.0f) threshold = 0.0f;

    this.threshold = threshold;

    if (matLightMap != null) {

    matLightMap.setFloat("Threshold", threshold);

    }

    }



    /**
  • Gets the current rgb threshold value used to create the lens flare light map.
  • @param threshold Default is 0.9f

    /

    public float getLightMapThreshold() {

    return this.threshold;

    }



    @Override

    public Material getMaterial() {

    return material;

    }

    @Override

    public void preFrame(float tpf) {

    }

    @Override

    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {

    }

    }[/java]



    MatDefs x2:

    LensFlareLightMap.j3md

    [java]MaterialDef LensFlare {



    MaterialParameters {

    Int NumSamples : 1

    Float Threshold

    Texture2D Texture

    }



    Technique {

    VertexShader GLSL100: Shaders/LensFlareLightMap.vert

    FragmentShader GLSL100: Shaders/LensFlareLightMap.frag



    WorldParameters {

    WorldViewProjectionMatrix

    WorldViewMatrix

    Resolution

    }

    }

    }[/java]



    LensFlare.j3md

    [java]MaterialDef LensFlare {



    MaterialParameters {

    Int NumSamples

    Texture2D Texture

    Texture2D LensDirt

    Texture2D LightMap

    Float Ghost

    Float Halo

    }



    Technique {

    VertexShader GLSL100: Shaders/LensFlare.vert

    FragmentShader GLSL100: Shaders/LensFlare.frag



    WorldParameters {

    WorldViewProjectionMatrix

    WorldViewMatrix

    Time

    }



    Defines {

    HAS_LENSDIRT : LensDirt

    }

    }

    }[/java]



    Shaders x4:



    LensFlareLightMap.vert

    [java]uniform mat4 g_WorldViewProjectionMatrix;

    varying vec2 texCoord;



    attribute vec2 inTexCoord;

    attribute vec3 inPosition;



    void main() {

    texCoord = inTexCoord;

    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

    }[/java]



    LensFlareLightMap.frag

    [java]varying vec2 texCoord;

    uniform sampler2D m_Texture;

    uniform float m_Threshold;

    float offset = 0.025;



    vec4 getLightMap(in vec2 tc) {

    vec4 color = vec4(0.0);

    for (int x = 0; x < 4; x++) {

    for (int y = 0; y < 4; y++) {

    color += texture2D(m_Texture, texCoord+(0.0025
    float(x)+(0.0025float(y))));

    }

    }

    color /= 16.0;

    color.r = clamp(color.r-m_Threshold,0.0,1.0);

    color.g = clamp(color.g-m_Threshold,0.0,1.0);

    color.b = clamp(color.b-m_Threshold,0.0,1.0);

    if (color.r == 0.0 || color.g == 0.0 || color.b == 0.0) {

    color = vec4(vec3(0.0),1.0);

    } else {

    color = 26.0;

    color.r = 1.0-m_Threshold+color.r;

    color.g = 1.0-m_Threshold+color.g;

    color.b = 1.0-m_Threshold+color.b;

    }

    // color.a = 1.0;

    return color;

    }

    void main(){

    // Get lights only

    vec4 color = getLightMap(texCoord);

    gl_FragColor = color;

    }[/java]



    LensFlare.vert

    [java]uniform mat4 g_WorldViewProjectionMatrix;

    attribute vec3 inPosition;

    attribute vec2 inTexCoord;

    varying vec2 texCoord;



    void main(){

    texCoord = inTexCoord;

    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);



    }[/java]



    LensFlare.frag

    [java]varying vec2 texCoord;

    uniform sampler2D m_Texture;

    uniform sampler2D m_LightMap;

    uniform float m_Ghost;

    uniform float m_Halo;



    #ifdef HAS_LENSDIRT

    uniform sampler2D m_LensDirt;

    #endif



    const vec2 center = vec2(0.5);

    const float threshold = 0.97;

    const float distortion = 0.0035;

    const float intesity = 0.35;

    const int samples = 7;



    vec4 chromaticDistortion(in sampler2D tex, in vec2 sampleVec) {

    float r = texture2D(tex, sampleVec - distortion).r
    intesity;

    float g = texture2D(tex, sampleVec).g
    intesity;

    float b = texture2D(tex, sampleVec + distortion).bintesity;

    return vec4(r, g, b, 1.0);

    }



    float haloMask(in float radius, in float offset, in vec2 tc) {

    vec2 midpoint = -0.1+0.2
    tc+0.5;

    float dist = length(tc - midpoint);

    return smoothstep(radius, radius+offset, dist);

    }



    void main(){

    vec2 p = center - texCoord;

    float len = length§;

    vec2 sampleVec = p * m_Ghost;

    vec2 sampleVec1 = p;

    vec2 haloVec = normalize(sampleVec1) * m_Halo;



    vec4 resultHalo = chromaticDistortion(m_LightMap, texCoord + haloVec);



    vec4 resultFlare = vec4(0.0);



    for (int i = 1; i < samples; i++) {

    vec2 offset = sampleVec * float(i);

    if (i == 3) offset = sampleVec * 0;



    vec4 nFlare = chromaticDistortion(m_LightMap, texCoord + offset);

    resultFlare += nFlare*2.2;

    }

    resultFlare /= float(samples-1);

    resultFlare = clamp(resultFlare,0.0,1.0);



    vec4 result = resultHalo+resultFlare;

    #ifdef HAS_LENSDIRT

    result *= texture2D(m_LensDirt, texCoord)*2.0;

    #endif

    vec4 color = result;

    color.a = color.r;

    float val = haloMask(0.05,0.25,texCoord);

    color *= val;

    color = texture2D(m_Texture, texCoord)+color;



    gl_FragColor = color;

    }[/java]



    The 2 vert shaders probably ended up being the same thing and can be made into one common… or find a standard used in JME that works. I needed them separated at one point for something or other and didn’t bother to compare them because I was way too busy posting that kick ass video up above all this crap.



    @pspeed

    Pay no attention to the ling in the LensFlare.frag shader that reads:



    [java]if (g_Time > 1200000.0) color = vec4(vec3(0.0),1.0);[/java]



    This does NOTHING… especially doesn’t make your game run out of film.

Look inside the FilterPostProcessors, there is some functionality there to create several passes. Look at the BloomFilter it uses several shader passes.



Also, how are you gonna solve the issue of the flare when the sun is out of sight? For instance the flares from the sun should not be visible when you can’t see the sun.

@kwando said:
Look inside the FilterPostProcessors, there is some functionality there to create several passes. Look at the BloomFilter it uses several shader passes.

Also, how are you gonna solve the issue of the flare when the sun is out of sight? For instance the flares from the sun should not be visible when you can't see the sun.


Thanks for this... I'm familiar with how to set up multiple render passes, however I'm not sure how to make that work in this case. I don't actually need to render the scene (the scene is already rendered)... or any geometry... or anything for that matter. I just need to manipulate the current frame to create the lightmap. This is basically:

clamp(currentFrame.rgb-threshold, 0.0, 1.0);

Hopefully into a frameBuffer.

The flare effects are generated from the lightmap (which is generated from the frame). The flare isn't visible unless the sun is... and the intensity of the ghosts and halo are based on how close the sun is to the center of the lens. You can grab the mixer method out of the GPUAnimationFactory shader I wrote and give this a shot to see how it will work. The only image involved in producing the flare is the current frame itself.

Do you plan to simulate dirt and smudges on the lenses also? Do you have in mind what kind of camera is flaring?

@pspeed said:
Do you plan to simulate dirt and smudges on the lenses also? Do you have in mind what kind of camera is flaring?


The lens dirt is in place... though, I'm not totally sure how to change the effect based on camera aspect ratio, because I'm not completely sure what effect it has on the light refraction. This is definitely "Pseudo" in nature... but the tests I'm doing with pre-generated light maps looks really promising!
@t0neg0d said:
The lens dirt is in place... though, I'm not totally sure how to change the effect based on camera aspect ratio, because I'm not completely sure what effect it has on the light refraction. This is definitely "Pseudo" in nature... but the tests I'm doing with pre-generated light maps looks really promising!


I meant like how many lenses, what lens configuration, etc.. I don't know if you have in mind what kind of camera your players are carrying or not. The eye has no lens flare so I figure maybe you thought of this when deciding to give them a camera.

I have a effect like this already coded if you are interested…

@kwando said:
I have a effect like this already coded if you are interested..


Would love to see it for sure...

Here is a quick ROUGH initial take. It's to show how the elements are created... they are adjustable... the ghosting is only in one direction atm... lots of issues... but it's the idea. It is REALLY raw, but maybe will help explain the approach if nothing else:

http://youtu.be/epA4P3YCt94
2 Likes

Setting the sample vector for the flare ghosts to a large enough number overlaps, which gives random ghost size stepping away from the light source. The ghosts actually need halos based on intesity And the true “flare” effect isn’t even there yet



The nice thing about the idea is… if an object is blocking a portion of the light, it is reflected in the flare generation.

My implementation is based on this guys work:

john-chapman.net



Seems pretty similar to what you have done :slight_smile:



It looks quite nice but at least my implementation is bit to slow to be usable :stuck_out_tongue:

@pspeed said:
I meant like how many lenses, what lens configuration, etc.. I don't know if you have in mind what kind of camera your players are carrying or not. The eye has no lens flare so I figure maybe you thought of this when deciding to give them a camera.


I understand what you are asking... :) But, because this is going to be a Pseudo Lens Flare (i.e. I am not basing it in any kind of reality at all)... it is really just to pretty things up. No one actually runs around looking through a camera to begin with (like you said), so there is no real reason to consider the difference between a 35mm lens or 50mm lens... more like... flame on... flame off. Ya get whatcha pay for. :) But, people are used to seeing this in movies and lots of games use it these days. /shrug

I'm sure there are bazillion lens flares, using a bazillion different approaches other people are either a) currently using... or b) could if they wanted to... I'm just sharing my idea as I try and tackle it for outside opinions and ideas to try.
@kwando said:
My implementation is based on this guys work:
http://www.john-chapman.net/content.php?id=18

Seems pretty similar to what you have done :)

It looks quite nice but at least my implementation is bit to slow to be usable :P


I read this one! I like the approach, but I didn't like the technique looking over the shader... WAY to much texture sampling to make it usable :(

Do you have any screen caps of the output?

I think I broke the filter sometime ago, gonna see if I can patch it together :slight_smile:

1 Like

Oops, think that have to wait until tomorrow… Seems like a bad idea to start the new week with to few hours of sleep… -.-

@kwando said:
Oops, think that have to wait until tomorrow.. Seems like a bad idea to start the new week with to few hours of sleep... -.-


Lol... priorities! (and the ones that are forced on us)
@t0neg0d said:
But, people are used to seeing this in movies and lots of games use it these days. /shrug


Movies have lenses but film makers often go through great pains to reduce and eliminate lens flare. I can't think of a game I've seen it in recently but I usually laugh if it's not a camera-based game. There are other ways to make a sun look bright and most games seem to use them instead.

For third person camera games i find lens flare effects looks good since you already should have the feeling of an outside watcher. In the other side for a first person game i don’t like them.



Additionally i thnk for racing games they look good, simulating lens flares for the helmet…



t0neg0d implementation looks pretty decent, not like lens flares in the beginning where every game needed them…

Yeah, in modern game I see the HDR effect more often than a lens flare. E.g. where everything becomes darker when you look at the sun or a large part of the screen is sunlit.

@zzuegg said:
For third person camera games i find lens flare effects looks good since you already should have the feeling of an outside watcher. In the other side for a first person game i don't like them.

Additionally i thnk for racing games they look good, simulating lens flares for the helmet..

t0neg0d implementation looks pretty decent, not like lens flares in the beginning where every game needed them..


I find HDR better in racing games unless it's the third person cameras watching the race. There is almost no lens flare from a helmet. At best you get a reflection of your own face if you are facing directly into the sun (low in the sky) but usually the helmets would be setup for that, then.

But yeah, in first person games I find it laughably distracting. Even to the point of joking, "Gee, I hope I don't run out of film before the game is over..."
@pspeed said:
But yeah, in first person games I find it laughably distracting. Even to the point of joking, "Gee, I hope I don't run out of film before the game is over..."


Well great... you just ruined this filter's surprise. I went through all the effort of limiting the amount of film available and now EVERYONE knows. >.<