You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
20 KiB
622 lines
20 KiB
/*
|
|
* Copyright (c) 2009-2019 jMonkeyEngine
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
package com.jme3.water;
|
|
|
|
import com.jme3.asset.AssetManager;
|
|
import com.jme3.material.Material;
|
|
import com.jme3.math.*;
|
|
import com.jme3.post.SceneProcessor;
|
|
import com.jme3.profile.AppProfiler;
|
|
import com.jme3.renderer.*;
|
|
import com.jme3.renderer.queue.RenderQueue;
|
|
import com.jme3.scene.Geometry;
|
|
import com.jme3.scene.Spatial;
|
|
import com.jme3.scene.shape.Quad;
|
|
import com.jme3.texture.*;
|
|
import com.jme3.texture.Image.Format;
|
|
import com.jme3.texture.Texture.WrapMode;
|
|
import com.jme3.ui.Picture;
|
|
|
|
/**
|
|
*
|
|
* Simple Water renders a simple plane that use reflection and refraction to look like water.
|
|
* It's pretty basic, but much faster than the WaterFilter
|
|
* It's useful if you aim for low specs hardware and still want a good looking water.
|
|
* Usage is :
|
|
* <code>
|
|
* SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
|
|
* //setting the scene to use for reflection
|
|
* waterProcessor.setReflectionScene(mainScene);
|
|
* //setting the light position
|
|
* waterProcessor.setLightPosition(lightPos);
|
|
*
|
|
* //setting the water plane
|
|
* Vector3f waterLocation=new Vector3f(0,-20,0);
|
|
* waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
|
|
* //setting the water color
|
|
* waterProcessor.setWaterColor(ColorRGBA.Brown);
|
|
*
|
|
* //creating a quad to render water to
|
|
* Quad quad = new Quad(400,400);
|
|
*
|
|
* //the texture coordinates define the general size of the waves
|
|
* quad.scaleTextureCoordinates(new Vector2f(6f,6f));
|
|
*
|
|
* //creating a geom to attach the water material
|
|
* Geometry water=new Geometry("water", quad);
|
|
* water.setLocalTranslation(-200, -20, 250);
|
|
* water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
|
|
* //finally setting the material
|
|
* water.setMaterial(waterProcessor.getMaterial());
|
|
*
|
|
* //attaching the water to the root node
|
|
* rootNode.attachChild(water);
|
|
* </code>
|
|
* @author Normen Hansen & Rémy Bouquet
|
|
*/
|
|
public class SimpleWaterProcessor implements SceneProcessor {
|
|
|
|
protected RenderManager rm;
|
|
protected ViewPort vp;
|
|
protected Spatial reflectionScene;
|
|
protected ViewPort reflectionView;
|
|
protected ViewPort refractionView;
|
|
protected FrameBuffer reflectionBuffer;
|
|
protected FrameBuffer refractionBuffer;
|
|
protected Camera reflectionCam;
|
|
protected Camera refractionCam;
|
|
protected Texture2D reflectionTexture;
|
|
protected Texture2D refractionTexture;
|
|
protected Texture2D depthTexture;
|
|
protected Texture2D normalTexture;
|
|
protected Texture2D dudvTexture;
|
|
protected int renderWidth = 512;
|
|
protected int renderHeight = 512;
|
|
protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
|
|
protected float speed = 0.05f;
|
|
protected Ray ray = new Ray();
|
|
protected Vector3f targetLocation = new Vector3f();
|
|
protected AssetManager manager;
|
|
protected Material material;
|
|
protected float waterDepth = 1;
|
|
protected float waterTransparency = 0.4f;
|
|
protected boolean debug = false;
|
|
private Picture dispRefraction;
|
|
private Picture dispReflection;
|
|
private Picture dispDepth;
|
|
private Plane reflectionClipPlane;
|
|
private Plane refractionClipPlane;
|
|
private float refractionClippingOffset = 0.3f;
|
|
private float reflectionClippingOffset = -5f;
|
|
private float distortionScale = 0.2f;
|
|
private float distortionMix = 0.5f;
|
|
private float texScale = 1f;
|
|
private AppProfiler prof;
|
|
|
|
|
|
/**
|
|
* Creates a SimpleWaterProcessor
|
|
* @param manager the asset manager
|
|
*/
|
|
public SimpleWaterProcessor(AssetManager manager) {
|
|
this.manager = manager;
|
|
material = new Material(manager, "Common/MatDefs/Water/SimpleWater.j3md");
|
|
material.setFloat("waterDepth", waterDepth);
|
|
material.setFloat("waterTransparency", waterTransparency / 10);
|
|
material.setColor("waterColor", ColorRGBA.White);
|
|
material.setVector3("lightPos", new Vector3f(1, -1, 1));
|
|
|
|
material.setFloat("distortionScale", distortionScale);
|
|
material.setFloat("distortionMix", distortionMix);
|
|
material.setFloat("texScale", texScale);
|
|
updateClipPlanes();
|
|
|
|
}
|
|
|
|
public void initialize(RenderManager rm, ViewPort vp) {
|
|
this.rm = rm;
|
|
this.vp = vp;
|
|
|
|
loadTextures(manager);
|
|
createTextures();
|
|
applyTextures(material);
|
|
|
|
createPreViews();
|
|
|
|
material.setVector2("FrustumNearFar", new Vector2f(vp.getCamera().getFrustumNear(), vp.getCamera().getFrustumFar()));
|
|
|
|
if (debug) {
|
|
dispRefraction = new Picture("dispRefraction");
|
|
dispRefraction.setTexture(manager, refractionTexture, false);
|
|
dispReflection = new Picture("dispRefraction");
|
|
dispReflection.setTexture(manager, reflectionTexture, false);
|
|
dispDepth = new Picture("depthTexture");
|
|
dispDepth.setTexture(manager, depthTexture, false);
|
|
}
|
|
}
|
|
|
|
public void reshape(ViewPort vp, int w, int h) {
|
|
}
|
|
|
|
public boolean isInitialized() {
|
|
return rm != null;
|
|
}
|
|
float time = 0;
|
|
float savedTpf = 0;
|
|
|
|
public void preFrame(float tpf) {
|
|
time = time + (tpf * speed);
|
|
if (time > 1f) {
|
|
time = 0;
|
|
}
|
|
material.setFloat("time", time);
|
|
savedTpf = tpf;
|
|
}
|
|
|
|
public void postQueue(RenderQueue rq) {
|
|
Camera sceneCam = rm.getCurrentCamera();
|
|
|
|
//update refraction cam
|
|
refractionCam.setLocation(sceneCam.getLocation());
|
|
refractionCam.setRotation(sceneCam.getRotation());
|
|
refractionCam.setFrustum(sceneCam.getFrustumNear(),
|
|
sceneCam.getFrustumFar(),
|
|
sceneCam.getFrustumLeft(),
|
|
sceneCam.getFrustumRight(),
|
|
sceneCam.getFrustumTop(),
|
|
sceneCam.getFrustumBottom());
|
|
refractionCam.setParallelProjection(sceneCam.isParallelProjection());
|
|
|
|
//update reflection cam
|
|
WaterUtils.updateReflectionCam(reflectionCam, plane, sceneCam);
|
|
|
|
//Rendering reflection and refraction
|
|
rm.renderViewPort(reflectionView, savedTpf);
|
|
rm.renderViewPort(refractionView, savedTpf);
|
|
rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
|
|
rm.setCamera(sceneCam, false);
|
|
|
|
}
|
|
|
|
public void postFrame(FrameBuffer out) {
|
|
if (debug) {
|
|
displayMap(rm.getRenderer(), dispRefraction, 64);
|
|
displayMap(rm.getRenderer(), dispReflection, 256);
|
|
displayMap(rm.getRenderer(), dispDepth, 448);
|
|
}
|
|
}
|
|
|
|
public void cleanup() {
|
|
}
|
|
|
|
@Override
|
|
public void setProfiler(AppProfiler profiler) {
|
|
this.prof = profiler;
|
|
}
|
|
|
|
//debug only : displays maps
|
|
protected void displayMap(Renderer r, Picture pic, int left) {
|
|
Camera cam = vp.getCamera();
|
|
rm.setCamera(cam, true);
|
|
int h = cam.getHeight();
|
|
|
|
pic.setPosition(left, h / 20f);
|
|
|
|
pic.setWidth(128);
|
|
pic.setHeight(128);
|
|
pic.updateGeometricState();
|
|
rm.renderGeometry(pic);
|
|
rm.setCamera(cam, false);
|
|
}
|
|
|
|
protected void loadTextures(AssetManager manager) {
|
|
normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.png");
|
|
dudvTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/dudv_map.jpg");
|
|
normalTexture.setWrap(WrapMode.Repeat);
|
|
dudvTexture.setWrap(WrapMode.Repeat);
|
|
}
|
|
|
|
protected void createTextures() {
|
|
reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
|
|
refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
|
|
|
|
reflectionTexture.setMinFilter(Texture.MinFilter.Trilinear);
|
|
reflectionTexture.setMagFilter(Texture.MagFilter.Bilinear);
|
|
|
|
refractionTexture.setMinFilter(Texture.MinFilter.Trilinear);
|
|
refractionTexture.setMagFilter(Texture.MagFilter.Bilinear);
|
|
|
|
depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth);
|
|
}
|
|
|
|
protected void applyTextures(Material mat) {
|
|
mat.setTexture("water_reflection", reflectionTexture);
|
|
mat.setTexture("water_refraction", refractionTexture);
|
|
mat.setTexture("water_depthmap", depthTexture);
|
|
mat.setTexture("water_normalmap", normalTexture);
|
|
mat.setTexture("water_dudvmap", dudvTexture);
|
|
}
|
|
|
|
protected void createPreViews() {
|
|
reflectionCam = new Camera(renderWidth, renderHeight);
|
|
refractionCam = new Camera(renderWidth, renderHeight);
|
|
|
|
// create a pre-view. a view that is rendered before the main view
|
|
reflectionView = new ViewPort("Reflection View", reflectionCam);
|
|
reflectionView.setClearFlags(true, true, true);
|
|
reflectionView.setBackgroundColor(ColorRGBA.Black);
|
|
// create offscreen framebuffer
|
|
reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
|
|
//setup framebuffer to use texture
|
|
reflectionBuffer.setDepthBuffer(Format.Depth);
|
|
reflectionBuffer.setColorTexture(reflectionTexture);
|
|
|
|
//set viewport to render to offscreen framebuffer
|
|
reflectionView.setOutputFrameBuffer(reflectionBuffer);
|
|
reflectionView.addProcessor(new ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane));
|
|
// attach the scene to the viewport to be rendered
|
|
reflectionView.attachScene(reflectionScene);
|
|
|
|
// create a pre-view. a view that is rendered before the main view
|
|
refractionView = new ViewPort("Refraction View", refractionCam);
|
|
refractionView.setClearFlags(true, true, true);
|
|
refractionView.setBackgroundColor(ColorRGBA.Black);
|
|
// create offscreen framebuffer
|
|
refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
|
|
//setup framebuffer to use texture
|
|
refractionBuffer.setDepthBuffer(Format.Depth);
|
|
refractionBuffer.setColorTexture(refractionTexture);
|
|
refractionBuffer.setDepthTexture(depthTexture);
|
|
//set viewport to render to offscreen framebuffer
|
|
refractionView.setOutputFrameBuffer(refractionBuffer);
|
|
refractionView.addProcessor(new RefractionProcessor());
|
|
// attach the scene to the viewport to be rendered
|
|
refractionView.attachScene(reflectionScene);
|
|
}
|
|
|
|
protected void destroyViews() {
|
|
// rm.removePreView(reflectionView);
|
|
rm.removePreView(refractionView);
|
|
}
|
|
|
|
/**
|
|
* Get the water material from this processor, apply this to your water quad.
|
|
* @return the pre-existing Material
|
|
*/
|
|
public Material getMaterial() {
|
|
return material;
|
|
}
|
|
|
|
/**
|
|
* Sets the reflected scene, should not include the water quad!
|
|
* Set before adding processor.
|
|
* @param spat
|
|
*/
|
|
public void setReflectionScene(Spatial spat) {
|
|
reflectionScene = spat;
|
|
}
|
|
|
|
/**
|
|
* returns the width of the reflection and refraction textures
|
|
* @return the width (in pixels)
|
|
*/
|
|
public int getRenderWidth() {
|
|
return renderWidth;
|
|
}
|
|
|
|
/**
|
|
* returns the height of the reflection and refraction textures
|
|
* @return the height (in pixels)
|
|
*/
|
|
public int getRenderHeight() {
|
|
return renderHeight;
|
|
}
|
|
|
|
/**
|
|
* Set the reflection Texture render size,
|
|
* set before adding the processor!
|
|
* @param width
|
|
* @param height
|
|
*/
|
|
public void setRenderSize(int width, int height) {
|
|
renderWidth = width;
|
|
renderHeight = height;
|
|
}
|
|
|
|
/**
|
|
* returns the water plane
|
|
* @return the pre-existing instance
|
|
*/
|
|
public Plane getPlane() {
|
|
return plane;
|
|
}
|
|
|
|
/**
|
|
* Set the water plane for this processor.
|
|
* @param plane
|
|
*/
|
|
public void setPlane(Plane plane) {
|
|
this.plane.setConstant(plane.getConstant());
|
|
this.plane.setNormal(plane.getNormal());
|
|
updateClipPlanes();
|
|
}
|
|
|
|
/**
|
|
* Set the water plane using an origin (location) and a normal (reflection direction).
|
|
* @param origin Set to 0,-6,0 if your water quad is at that location for correct reflection
|
|
* @param normal Set to 0,1,0 (Vector3f.UNIT_Y) for normal planar water
|
|
*/
|
|
public void setPlane(Vector3f origin, Vector3f normal) {
|
|
this.plane.setOriginNormal(origin, normal);
|
|
updateClipPlanes();
|
|
}
|
|
|
|
private void updateClipPlanes() {
|
|
reflectionClipPlane = plane.clone();
|
|
reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
|
|
refractionClipPlane = plane.clone();
|
|
refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset);
|
|
|
|
}
|
|
|
|
/**
|
|
* Set the light Position for the processor
|
|
* @param position
|
|
*/
|
|
//TODO maybe we should provide a convenient method to compute position from direction
|
|
public void setLightPosition(Vector3f position) {
|
|
material.setVector3("lightPos", position);
|
|
}
|
|
|
|
/**
|
|
* Set the color that will be added to the refraction texture.
|
|
* @param color
|
|
*/
|
|
public void setWaterColor(ColorRGBA color) {
|
|
material.setColor("waterColor", color);
|
|
}
|
|
|
|
/**
|
|
* Higher values make the refraction texture shine through earlier.
|
|
* Default is 4
|
|
* @param depth
|
|
*/
|
|
public void setWaterDepth(float depth) {
|
|
waterDepth = depth;
|
|
material.setFloat("waterDepth", depth);
|
|
}
|
|
|
|
/**
|
|
* return the water depth
|
|
* @return the depth
|
|
*/
|
|
public float getWaterDepth() {
|
|
return waterDepth;
|
|
}
|
|
|
|
/**
|
|
* returns water transparency
|
|
* @return the transparency value
|
|
*/
|
|
public float getWaterTransparency() {
|
|
return waterTransparency;
|
|
}
|
|
|
|
/**
|
|
* sets the water transparency default os 0.1f
|
|
* @param waterTransparency
|
|
*/
|
|
public void setWaterTransparency(float waterTransparency) {
|
|
this.waterTransparency = Math.max(0, waterTransparency);
|
|
material.setFloat("waterTransparency", waterTransparency / 10);
|
|
}
|
|
|
|
/**
|
|
* Sets the speed of the wave animation, default = 0.05f.
|
|
* @param speed
|
|
*/
|
|
public void setWaveSpeed(float speed) {
|
|
this.speed = speed;
|
|
}
|
|
|
|
/**
|
|
* returns the speed of the wave animation.
|
|
* @return the speed
|
|
*/
|
|
public float getWaveSpeed(){
|
|
return speed;
|
|
}
|
|
|
|
/**
|
|
* Sets the scale of distortion by the normal map, default = 0.2
|
|
*/
|
|
public void setDistortionScale(float value) {
|
|
distortionScale = value;
|
|
material.setFloat("distortionScale", distortionScale);
|
|
}
|
|
|
|
/**
|
|
* Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5
|
|
*/
|
|
public void setDistortionMix(float value) {
|
|
distortionMix = value;
|
|
material.setFloat("distortionMix", distortionMix);
|
|
}
|
|
|
|
/**
|
|
* Sets the scale of the normal/dudv texture, default = 1.
|
|
* Note that the waves should be scaled by the texture coordinates of the quad to avoid animation artifacts,
|
|
* use mesh.scaleTextureCoordinates(Vector2f) for that.
|
|
*/
|
|
public void setTexScale(float value) {
|
|
texScale = value;
|
|
material.setFloat("texScale", texScale);
|
|
}
|
|
|
|
/**
|
|
* returns the scale of distortion by the normal map, default = 0.2
|
|
*
|
|
* @return the distortion scale
|
|
*/
|
|
public float getDistortionScale() {
|
|
return distortionScale;
|
|
}
|
|
|
|
/**
|
|
* returns how the normal and dudv map are mixed to create the wave effect,
|
|
* default = 0.5
|
|
*
|
|
* @return the distortion mix
|
|
*/
|
|
public float getDistortionMix() {
|
|
return distortionMix;
|
|
}
|
|
|
|
/**
|
|
* returns the scale of the normal/dudv texture, default = 1. Note that the
|
|
* waves should be scaled by the texture coordinates of the quad to avoid
|
|
* animation artifacts, use mesh.scaleTextureCoordinates(Vector2f) for that.
|
|
*
|
|
* @return the textures scale
|
|
*/
|
|
public float getTexScale() {
|
|
return texScale;
|
|
}
|
|
|
|
|
|
/**
|
|
* returns true if the waterprocessor is in debug mode
|
|
* @return true if in debug mode, otherwise false
|
|
*/
|
|
public boolean isDebug() {
|
|
return debug;
|
|
}
|
|
|
|
/**
|
|
* set to true to display reflection and refraction textures in the GUI for debug purpose
|
|
* @param debug
|
|
*/
|
|
public void setDebug(boolean debug) {
|
|
this.debug = debug;
|
|
}
|
|
|
|
/**
|
|
* Creates a quad with the water material applied to it.
|
|
* @param width
|
|
* @param height
|
|
* @return a new Geometry
|
|
*/
|
|
public Geometry createWaterGeometry(float width, float height) {
|
|
Quad quad = new Quad(width, height);
|
|
Geometry geom = new Geometry("WaterGeometry", quad);
|
|
geom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
|
|
geom.setMaterial(material);
|
|
return geom;
|
|
}
|
|
|
|
/**
|
|
* returns the reflection clipping plane offset
|
|
* @return the offset value
|
|
*/
|
|
public float getReflectionClippingOffset() {
|
|
return reflectionClippingOffset;
|
|
}
|
|
|
|
/**
|
|
* sets the reflection clipping plane offset
|
|
* set a nagetive value to lower the clipping plane for relection texture rendering.
|
|
* @param reflectionClippingOffset
|
|
*/
|
|
public void setReflectionClippingOffset(float reflectionClippingOffset) {
|
|
this.reflectionClippingOffset = reflectionClippingOffset;
|
|
updateClipPlanes();
|
|
}
|
|
|
|
/**
|
|
* returns the refraction clipping plane offset
|
|
* @return the offset value
|
|
*/
|
|
public float getRefractionClippingOffset() {
|
|
return refractionClippingOffset;
|
|
}
|
|
|
|
/**
|
|
* Sets the refraction clipping plane offset
|
|
* set a positive value to raise the clipping plane for refraction texture rendering
|
|
* @param refractionClippingOffset
|
|
*/
|
|
public void setRefractionClippingOffset(float refractionClippingOffset) {
|
|
this.refractionClippingOffset = refractionClippingOffset;
|
|
updateClipPlanes();
|
|
}
|
|
|
|
/**
|
|
* Refraction Processor
|
|
*/
|
|
public class RefractionProcessor implements SceneProcessor {
|
|
|
|
RenderManager rm;
|
|
ViewPort vp;
|
|
private AppProfiler prof;
|
|
|
|
public void initialize(RenderManager rm, ViewPort vp) {
|
|
this.rm = rm;
|
|
this.vp = vp;
|
|
}
|
|
|
|
public void reshape(ViewPort vp, int w, int h) {
|
|
}
|
|
|
|
public boolean isInitialized() {
|
|
return rm != null;
|
|
}
|
|
|
|
public void preFrame(float tpf) {
|
|
refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1
|
|
|
|
}
|
|
|
|
public void postQueue(RenderQueue rq) {
|
|
}
|
|
|
|
public void postFrame(FrameBuffer out) {
|
|
}
|
|
|
|
public void cleanup() {
|
|
}
|
|
|
|
@Override
|
|
public void setProfiler(AppProfiler profiler) {
|
|
this.prof = profiler;
|
|
}
|
|
}
|
|
}
|
|
|