WaterFilter reflection artifact/bug

Hello,

I searched the forum about anyone else having this bug since it’s very visible… no success so here it goes.

I implemented reflection into my custom ocean shader since I thought it would be a nice effect, therefore I copied most of the code from SimpleWaterProcessor and the WaterFilter and then an reflection related artifact hit me. So I walked back to the original WaterFilter to verify whether it’s there too. And it is (at least on my systems, recent SVN snapshot from 25.11.2012 and even in a snapshot from 19.11.2011):

pic

Symptoms: at higher camera altitude there emerges a black (or grey, due to other shader effects overlaying) seam near the horizon - the higher the cam the wider the seam. The relfection processing seems to work perfectly only for cam.y==0 or very small altitudes.

Is that a bug? Can you reproduce this? (Simply start the TestPostWater test and increase camera altitude by pressing Q)

Can confirm this.

uh…guys… this the bottom part of the skybox you see… increase your cam frustum far if you want water to go further

Should the skybox have a “water” texture for below the horizon?

Yeah…could be a solution to alleviate the issue.
Just to be clear, this is camera far clipping you see…not just the ocean will be invisible after this limit, but every single object in the scenegraph

Hmm. I somehow feared that something like this is the answer…

So, to understand it right: this issue has to do with the far clipping plane of the reflectionCam? Since the normal ocean geometry (using e.g. SimpleWaterProcessor, no post-processing geometry-less effect like WaterFilter) and even the terrain geometry are visible way after this border where the reflection gives me black…

And when I change my clear color it will give me that, and not black… right?

no.
You may have another issue that i don’t get and that is not the one you showed in the screenshot before.
What’s happening in the screenshot has nothing to do with the reflection render, it’s juts that you can’t see further with your camera so the water just stops.
The grey area you see is the bottom part of the skybox.

now…why don’t you show me your issue, with screenshots?

^^ Haha, well i usually have my 50km ++ frustrum never noticed that before at all XD

@nehon thanks for your patience, I’ll try to show my real issue then (not the one that I believed was identical to mine…):
low altitude
high altitude

(note: I set the reflectionCam viewport’s clear color to red.)

My scene is constructed as follows: I’m using a sky dome that is a perfect hemisphere above xz-plane (in sky bucket so no real dimensions), my ocean is basically a laaarge plane centered at the camera’s location with dimensions as large as the maximum visibility I set to my camera. Onto this ocean plane I apply the reflection map (generating it like SimpleWaterProcessor does) in the same way as SimpleWaterProcessor does (my shader does some other things too therefore I cannot use SimpleWaterProcessor directly). As the ship model and sky reflection show the reflections works, at least near the camera.

But there is a border at which the reflection color obviously turns into the reflectionCam viewports clear color… and that we see this border means that the ocean geometry cannot get clipped yet so this border is nearer to the camera than the camera’s far plane…

Now please help me: is this a known limitation, is it fixable by e.g. taking the camera location into account somewhere, is it my fault, is there something screwed within my scene setup? Do you need additional information for helping me effectively?

You have a geometry for the water? looks like it’s pretty different from the water filter.
Is your reflection camera far clip plane is the same as your view camera far clip plane?

Jepp it’s geometry-based water - the reason why I pulled the WaterFilter for reference was that I believed this artifact somehow arises from the reflection mechanics that seem to be roughly the same even for non-geometry-based rendering (setting up reflectionCam and stuff)…

[java]
reflectionCam.setFrustum(sceneCam.getFrustumNear(),
sceneCam.getFrustumFar(),
sceneCam.getFrustumLeft(),
sceneCam.getFrustumRight(),
sceneCam.getFrustumTop(),
sceneCam.getFrustumBottom());
[/java]

(I even tried to use something like sceneCam.getFrustumFar()*3 in this statement, without any visual improvement)

What if you turn reflection off? the red disappear right?

Jup - I’m currently using my skyBox cubemap for simple environmental “reflections” but they are very limited when it comes to objects in the water…

vec3 vView = normalize(varVertex - pEye); // from camera to vertex (view coords) vec3 vReflect = reflect(vView, vNormal); // from vertex to sky (view coords) // cheap reflection mapping by only using the sky box texture vec4 cSky = textureCube(m_SkyBox, (g_ViewMatrixInverse * vec4(vReflect, 0.0)).xyz);

but at least this works correctly til the horizon giving me the results I expected. When switching to real reflection maps my assumption was to achieve identical results near horizon including the bonus of real reflections of my ship ^^

OK…so how do you setup your reflection cam?
something must be wrong…

Thats “can you please make a testcase” @nehon :wink:

Yea, I thought a testcase would be a good idea - will look into creating one … meanwhile the source code of my mostly copy&pasted (from SimpleWaterProcessor, but without refractions and geometry handling) ReflectionProcessor that is designated to be used as follows

[java]
reflectionProcessor = new ReflectionProcessor(getApp().getRootNode(), 512);
getApp().getViewPort().addProcessor(reflectionProcessor);
// somehwere later…
oceanMat.setTexture(“ReflectionMap”, reflectionProcessor.getReflectionTexture());
[/java]

ReflectionProcessor.java:

[java]
import com.jme3.math.ColorRGBA;
import com.jme3.math.Plane;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D;

import de.mycrobase.ssim.ed.util.TempVars;

public class ReflectionProcessor implements SceneProcessor {

private int texSize;
private Spatial reflectionScene;

// state
private boolean init = false;

private RenderManager rm;
private ViewPort vp;
private ViewPort reflectionView;
private FrameBuffer reflectionBuffer;
private Camera reflectionCam;
private Texture2D reflectionTexture;

private Vector3f targetLocation = new Vector3f();

private Ray ray = new Ray();

private Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
private float reflectionClippingOffset = -5f;
private Plane reflectionClipPlane;

private float savedTpf;

public ReflectionProcessor(Spatial reflectionScene, int texSize) {
    this.reflectionScene = reflectionScene;
    this.texSize = texSize;
    
    reflectionTexture = new Texture2D(getTexSize(), getTexSize(), Format.RGBA8);
    
    reflectionClipPlane = plane.clone();
    reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
}

@Override
public void initialize(RenderManager rm, ViewPort vp) {
    this.rm = rm;
    this.vp = vp;
    
    reflectionCam = new Camera(getTexSize(), getTexSize());
    
    reflectionView = new ViewPort("Reflection-View", reflectionCam);
    reflectionView.setClearFlags(true, true, true);
    reflectionView.setBackgroundColor(ColorRGBA.Black);
    
    reflectionBuffer = new FrameBuffer(getTexSize(), getTexSize(), 1);
    reflectionBuffer.setDepthBuffer(Format.Depth);
    reflectionBuffer.setColorTexture(getReflectionTexture());

    reflectionView.setOutputFrameBuffer(reflectionBuffer);
    reflectionView.addProcessor(new com.jme3.water.ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane));
    reflectionView.attachScene(reflectionScene);
    
    init = true;
}

@Override
public boolean isInitialized() {
    return init;
}

@Override
public void preFrame(float tpf) {
    savedTpf = tpf;
}

@Override
public void postQueue(RenderQueue rq) {
    Camera sceneCam = rm.getCurrentCamera();
    ray.setOrigin(sceneCam.getLocation());
    ray.setDirection(sceneCam.getDirection());
    
    boolean inv = false;
    if (!ray.intersectsWherePlane(plane, targetLocation)) {
        ray.setDirection(ray.getDirection().negateLocal());
        ray.intersectsWherePlane(plane, targetLocation);
        inv = true;
    }
    Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
    reflectionCam.setLocation(loc);
    reflectionCam.setFrustum(sceneCam.getFrustumNear(),
            sceneCam.getFrustumFar(),
            sceneCam.getFrustumLeft(),
            sceneCam.getFrustumRight(),
            sceneCam.getFrustumTop(),
            sceneCam.getFrustumBottom());
    reflectionCam.setParallelProjection(false);
    
    TempVars vars = TempVars.get();
    
    vars.vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
    float planeDistance = plane.pseudoDistance(vars.vect1);
    vars.vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
    vars.vect3.set(vars.vect1.subtractLocal(vars.vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
    
    reflectionCam.lookAt(targetLocation, vars.vect3);
    if (inv) {
        reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
    }
    
    vars.release();
    
    rm.renderViewPort(reflectionView, savedTpf);
    rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
    rm.setCamera(sceneCam, false);
}

@Override
public void postFrame(FrameBuffer out) {
    // nothing
}

@Override
public void reshape(ViewPort vp, int w, int h) {
    // nothing
}

@Override
public void cleanup() {
    init = false;
    
    //rm.removePreView(reflectionView);
    
    // TODO: do clean up
}

public int getTexSize() {
    return texSize;
}

public Texture2D getReflectionTexture() {
    return reflectionTexture;
}

}
[/java]

Edit: oh I see I referenced a custom TempVars class … works exactly as jME’s.

I visualized the reflection map output and it looks ok (pic) given the fact that my sky dome exists only above y=0… I modified TestSimpleWater to have a water plane that exeeds the maximum visibility and the artifact did not occur there (their sky map covers the parts below y=0 too but this parts where not visible in the reflections) … so something is different, maybe my shaders:

Ocean.vert

uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_WorldViewMatrix; uniform mat3 g_NormalMatrix; uniform mat4 g_ViewMatrix; uniform mat4 g_WorldMatrix;

uniform vec4 g_LightColor;
uniform vec4 g_LightPosition;

uniform vec3 g_CameraPosition;

attribute vec3 inPosition;
attribute vec3 inNormal;

varying vec3 varNormal; // view coords
varying vec3 varVertex; // view coords
varying vec4 varLightDir; // view coords
varying vec4 varFoo; //for projection

float waveFalloff(float dist) {
const float maxDist = 1000.0;
return 1.0 - clamp(dist / maxDist, 0.0, 1.0);
}

void main() {
varNormal = normalize(g_NormalMatrix * inNormal);

vec4 mPosition = vec4(inPosition, 1.0);
vec3 wPosition = (g_WorldMatrix * mPosition).xyz;
wPosition.y = 0.0;

// this mPosition stuff is used to flatten the waves with certain distance to viewer
float dist = waveFalloff(length(g_CameraPosition - wPosition));
//float dist = waveFalloff(length(vec3(-50.0, 50.0, +50.0) - wPosition));
mPosition.y *= dist;

gl_Position = g_WorldViewProjectionMatrix * mPosition;
varVertex = (g_WorldViewMatrix * mPosition).xyz;

vec4 ScreenPos = gl_Position;
ScreenPos.x = 0.5 * (ScreenPos.w + ScreenPos.x);
ScreenPos.y = 0.5 * (ScreenPos.w + ScreenPos.y);
ScreenPos.z = ScreenPos.w;
varFoo = ScreenPos;

// ..snip..

}

Ocean.frag

uniform mat4 g_ViewMatrix;
uniform mat4 g_ViewMatrixInverse;

uniform vec4 g_LightColor;

uniform vec3 g_CameraPosition;

uniform vec4 m_WaterColor;
uniform float m_R0;
uniform float m_Shininess;
uniform float m_ShininessFactor;
uniform samplerCube m_SkyBox;
uniform sampler2D m_ReflectionMap;

varying vec3 varNormal; // view coords
varying vec3 varVertex; // view coords
varying vec4 varLightDir; // view coords
varying vec4 varFoo; //for projection

void main() {

vec3 vNormal = normalize(varNormal);
vec3 pEye = vec3(0.0, 0.0, 0.0);
vec3 vView = normalize(varVertex - pEye); // from camera to vertex (view coords)
vec3 vReflect = reflect(vView, vNormal); // from vertex to sky (view coords)

//vec4 cMirror = vec4(1.0, 0.5, 0.0, 1.0);
vec4 projCoord = varFoo / varFoo.w;
//projCoord =(projCoord+1.0)*0.5;
//projCoord = clamp(projCoord, 0.0, 1.0);

vec4 cMirror = vec4(texture2D(m_ReflectionMap, vec2(projCoord.x,1.0-projCoord.y)).rgb, 1.0);

// ..snip..

gl_FragColor = cFinal;

}

Oh wait!! it brings back memories.
The problem is that your skybox is clipped ( I mean clipped by the arbitrary clip plane) and it shouldn’t.
I fixed this issue on the ReflectionProcessor a while back, by handling the skyBucket before applying the clipping.
see http://code.google.com/p/jmonkeyengine/source/browse/trunk/engine/src/core-effects/com/jme3/water/ReflectionProcessor.java#81

Are you sure? Because I stumbled on your fix in the forums and I tried to make sure my ReflectionProcessor uses your fixed in line 18:

[java]
@Override
public void initialize(RenderManager rm, ViewPort vp) {
this.rm = rm;
this.vp = vp;

    reflectionCam = new Camera(getTexSize(), getTexSize());

    reflectionView = new ViewPort(“Reflection-View”, reflectionCam);
    reflectionView.setClearFlags(true, true, true);
    reflectionView.setBackgroundColor(ColorRGBA.Black);

    reflectionBuffer = new FrameBuffer(getTexSize(), getTexSize(), 1);
    reflectionBuffer.setDepthBuffer(Format.Depth);
    reflectionBuffer.setColorTexture(getReflectionTexture());

    reflectionView.setOutputFrameBuffer(reflectionBuffer);
    reflectionView.addProcessor(
          new com.jme3.water.ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane)); // <==
    reflectionView.attachScene(reflectionScene);

    init = true;
}

[/java]

ok well for some reason it doesn’t work in your implementation. Try in debug mode, and check that the postQueue of the jme3 ReflectionProcessor is called before the actual rendering of your relfection viewport.

Your skybox is in the skybucket right?