Seeeeeew… Here be a rundown of how it works:
- Knockout everything under a certain threshold. This can be converted to greyscale at this point and SHOULD be blurred before any other step.
- Apply the mixer deformation I have in the GPUAnimationFactory to create
2a) Halo effect
2b) Ghost images
- Mix samples from original image into the halo and ghosts as they are created
- Apply chromatic distortion
- Mix in lens dirt
- 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.0025float(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).rintesity;
float g = texture2D(tex, sampleVec).gintesity;
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.2tc+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.