I’ve found yet another odd rendering bug in my game, which (as usual) I’ve distilled down to a short test app. The test app is closely based on jme3test.water.TestSimpleWater: basically, a scene consisting of an unlit box, a skybox, and a horizontal water quad.
The camera starts out 1 unit above the water, in a horizontal orientation. I assert that the image reflected in the water is incorrect because the box is not visible in the reflected image and the reflected skyline does not match the directly visible skyline above it.
If you’re unconvinced, press the ‘N’ key to enable flyCam, then jiggle the mouse. The moment you adjust the camera’s orientation, the reflected image changes suddenly.
I’d appreciate any insight into why SimpleWater seems to fail for this case. Here’s my test app:
[java]
package test;
A simple JME3 app to demonstrate simple water rendering issues.
*/
public class TestSimple
extends SimpleApplication
implements ActionListener {
public static void main(String[] args) {
TestSimple app = new TestSimple();
app.setShowSettings(false);
app.start();
}
@Override
public void simpleInitApp() {
setDisplayStatView(false);
Node sceneNode = new Node("scene node");
rootNode.attachChild(sceneNode);
/*
* Add a logo box and sky cube to the scene.
*/
Material logoMaterial = new Material(assetManager,
"Common/MatDefs/Misc/Unshaded.j3md");
Texture logoTexture =
assetManager.loadTexture("Interface/Logo/Monkey.jpg");
logoMaterial.setTexture("ColorMap", logoTexture);
Box boxMesh = new Box(1f, 1f, 1f);
Geometry boxGeometry = new Geometry("Box", boxMesh);
boxGeometry.setMaterial(logoMaterial);
sceneNode.attachChild(boxGeometry);
Spatial skyCube = SkyFactory.createSky(assetManager,
"Textures/Sky/Bright/BrightSky.dds", false);
sceneNode.attachChild(skyCube);
/*
* Add a water processor.
*/
SimpleWaterProcessor waterProcessor =
new SimpleWaterProcessor(assetManager);
//waterProcessor.setDebug(true);
waterProcessor.setLightPosition(new Vector3f(33f, 12f, -29f));
waterProcessor.setReflectionScene(sceneNode);
viewPort.addProcessor(waterProcessor);
/*
* Add a square water quad to the scene.
*/
Spatial waterQuad = assetManager.loadModel(
"Models/WaterTest/WaterTest.mesh.xml");
waterQuad.setLocalScale(40f);
waterQuad.setMaterial(waterProcessor.getMaterial());
rootNode.attachChild(waterQuad);
/*
* The camera starts horizontal, one unit above the water,
* looking toward the top of the cube.
*
* Press the 'N' key to enable camera movement.
*/
flyCam.setEnabled(false);
cam.setLocation(new Vector3f(0f, 1f, 10f));
cam.setRotation(new Quaternion(0f, 1f, 0f, 0f));
inputManager.addMapping("fly", new KeyTrigger(KeyInput.KEY_N));
inputManager.addListener(this, "fly");
}
@Override
public void onAction(String actionString, boolean ongoing, float unused) {
if (ongoing && “fly”.equals(actionString)) {
flyCam.setEnabled(true);
}
}
}
[/java]
Just a tip: use imgur.com for your images and then we don’t have to wait for dropbox (timing out for me right now). Also, you can embed the images right in the post and save us some clicks.
This appears to reverse the ray if it doesn’t intersect the plane of the water.
Perhaps the intent was to ensure that the ray and the plane intersect.
In my case, the plane and ray are both horizontal, so they never intersect, even
after the ray gets reversed. Could this be the cause of the issue I reported?
Yeah though I had updated this thread.
I looked into it, but couldn’t find a suitable solution.
the reverse ray is to ensure the correctness of the reflection when the camera is looking away from the water. That what’s this code does. The problem is that in the particular case where the camera direction is strictly parallel to the watr plane, there is no intersection and every thing is screwed up, because all the code assumes that there is that intersection.
Actually the whole processor rely on this assumption.
One way to alleviate it would be to “rotate” the camera direction used for the ray by an epsilon…but that felt really hackish and I couldn’t resolve to commit it.
Thanks for your analysis, @nehon. I agree that rotating the camera by epsilon would not be a great solution.
It looks to me like targetLocation is used solely to aim reflectionCam. If reflectionCam were aimed using setRotation() instead, the code would be simpler and more robust. I’ll dig into the math and see if I can work out how to reflect a quaternion in a plane.
I didn’t figure out how to reflect a quaternion. However, I did work out a simple solution using Camera.setAxes(). Basically, you reflect each of sceneCam’s axes in the plane, negate the up axis,
and then apply the reflected axes to reflectionCam.
Here’s a patch:
Index: SimpleWaterProcessor.java
--- SimpleWaterProcessor.java Base (BASE)
+++ SimpleWaterProcessor.java Locally Modified (Based On LOCAL)
@@ -120,9 +120,6 @@
private Plane refractionClipPlane;
private float refractionClippingOffset = 0.3f;
private float reflectionClippingOffset = -5f;
- private Vector3f vect1 = new Vector3f();
- private Vector3f vect2 = new Vector3f();
- private Vector3f vect3 = new Vector3f();
private float distortionScale = 0.2f;
private float distortionMix = 0.5f;
private float texScale = 1f;
@@ -221,22 +218,21 @@
sceneCam.getFrustumTop(),
sceneCam.getFrustumBottom());
reflectionCam.setParallelProjection(false);
- // tempVec and calcVect are just temporary vector3f objects
- vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
- float planeDistance = plane.pseudoDistance(vect1);
- vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
- vect3.set(vect1.subtractLocal(vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
- // now set the up vector
- reflectionCam.lookAt(targetLocation, vect3);
- if (inv) {
- reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
- }
+
+ Vector3f sceneTarget = sceneCam.getLocation().add(sceneCam.getDirection());
+ Vector3f reflectDirection = plane.reflect(sceneTarget, null);
+ reflectDirection.subtractLocal(loc);
//we are rendering a sub part of the scene so the camera planeState may never be reseted to 0.
After looking at the code, I have an additional concern. It has to do with this line:
reflectionCam.setParallelProjection(false);
It seems to me that reflectionCam ought to use parallel projection if (and only if) sceneCam does. In that case, the line would look something like this:
You’re right.
I guess the problem never arise because parallel projection is seldom used in a “classic” scene that involve water. But could be…
I’ll fix it