From 5671358657e6059fc7b0d58ee70e15f0d0e961d6 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Wed, 29 Feb 2012 22:26:37 +0000 Subject: [PATCH] Soft particles implementation. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9223 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../Common/MatDefs/Misc/Particle.j3md | 26 ++++ .../Common/MatDefs/Misc/SoftParticle.frag | 50 +++++++ .../Common/MatDefs/Misc/SoftParticle.vert | 53 +++++++ .../post/filters/TranslucentBucketFilter.java | 75 ++++++++++ engine/src/core/com/jme3/post/Filter.java | 13 +- .../com/jme3/post/FilterPostProcessor.java | 25 ++-- .../jme3test/effect/TestSoftParticles.java | 130 ++++++++++++++++++ 7 files changed, 359 insertions(+), 13 deletions(-) create mode 100644 engine/src/core-data/Common/MatDefs/Misc/SoftParticle.frag create mode 100644 engine/src/core-data/Common/MatDefs/Misc/SoftParticle.vert create mode 100644 engine/src/test/jme3test/effect/TestSoftParticles.java diff --git a/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md b/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md index e28d41ab3..73674f9bf 100644 --- a/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md +++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md @@ -4,6 +4,10 @@ MaterialDef Point Sprite { Texture2D Texture Float Quadratic Boolean PointSprite + + //only used for soft particles + Texture2D DepthTexture + Float Softness // Texture of the glowing parts of the material Texture2D GlowMap @@ -58,6 +62,28 @@ MaterialDef Point Sprite { } } + Technique SoftParticles{ + + VertexShader GLSL100 : Common/MatDefs/Misc/SoftParticle.vert + FragmentShader GLSL100 : Common/MatDefs/Misc/SoftParticle.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + WorldMatrix + CameraPosition + } + + RenderState { + Blend AlphaAdditive + DepthWrite Off + } + + Defines { + USE_TEXTURE : Texture + } + } + Technique FixedFunc { RenderState { Blend AlphaAdditive diff --git a/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.frag b/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.frag new file mode 100644 index 000000000..d3108b54e --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.frag @@ -0,0 +1,50 @@ +uniform sampler2D m_DepthTexture; +uniform float m_Softness; // Power used in the contrast function +varying vec2 vPos; // Position of the pixel +varying vec2 projPos;// z and w valus in projection space + +#ifdef USE_TEXTURE +uniform sampler2D m_Texture; +varying vec4 texCoord; +#endif + +varying vec4 color; + +float Contrast(float d){ + float val = clamp( 2.0*( (d > 0.5) ? 1.0-d : d ), 0.0, 1.0); + float a = 0.5 * pow(val, m_Softness); + return (d > 0.5) ? 1.0 - a : a; +} + +float stdDiff(float d){ + return clamp((d)*m_Softness,0.0,1.0); +} + + +void main(){ + if (color.a <= 0.01) + discard; + + vec4 c = vec4(1.0,1.0,1.0,1.0);//color; + #ifdef USE_TEXTURE + #ifdef POINT_SPRITE + vec2 uv = mix(texCoord.xy, texCoord.zw, gl_PointCoord.xy); + #else + vec2 uv = texCoord.xy; + #endif + c = texture2D(m_Texture, uv) * color; + #endif + + + float depthv = texture2D(m_DepthTexture, vPos).x*2.0-1.0; // Scene depth + depthv*=projPos.y; + float particleDepth = projPos.x; + + float zdiff =depthv-particleDepth; + if(zdiff<=0.0){ + discard; + } + // Computes alpha based on the particles distance to the rest of the scene + c.a = c.a * stdDiff(zdiff);// Contrast(zdiff); + gl_FragColor =c; +} \ No newline at end of file diff --git a/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.vert b/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.vert new file mode 100644 index 000000000..4f0dfd5bf --- /dev/null +++ b/engine/src/core-data/Common/MatDefs/Misc/SoftParticle.vert @@ -0,0 +1,53 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec3 inPosition; +attribute vec4 inColor; +attribute vec4 inTexCoord; + +varying vec4 color; +// z and w values in projection space +varying vec2 projPos; +varying vec2 vPos; // Position of the pixel in clip space + + + +#ifdef USE_TEXTURE +varying vec4 texCoord; +#endif + +#ifdef POINT_SPRITE +uniform mat4 g_WorldViewMatrix; +uniform mat4 g_WorldMatrix; +uniform vec3 g_CameraPosition; +uniform float m_Quadratic; +const float SIZE_MULTIPLIER = 4.0; +attribute float inSize; +#endif + +void main(){ + vec4 pos = vec4(inPosition, 1.0); + + gl_Position = g_WorldViewProjectionMatrix * pos; + color = inColor; + + projPos = gl_Position.zw; + // projPos.x = 0.5 * (projPos.x) + 0.5; + + // Transforms the vPosition data to the range [0,1] + vPos = (gl_Position.xy / gl_Position.w + 1.0) / 2.0; + + #ifdef USE_TEXTURE + texCoord = inTexCoord; + #endif + + #ifdef POINT_SPRITE + vec4 worldPos = g_WorldMatrix * pos; + float d = distance(g_CameraPosition.xyz, worldPos.xyz); + gl_PointSize = max(1.0, (inSize * SIZE_MULTIPLIER * m_Quadratic) / d); + + //vec4 worldViewPos = g_WorldViewMatrix * pos; + //gl_PointSize = (inSize * SIZE_MULTIPLIER * m_Quadratic)*100.0 / worldViewPos.z; + + color.a *= min(gl_PointSize, 1.0); + #endif +} \ No newline at end of file diff --git a/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java b/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java index 47be41382..c377a581e 100644 --- a/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java +++ b/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java @@ -5,6 +5,7 @@ package com.jme3.post.filters; import com.jme3.asset.AssetManager; +import com.jme3.effect.ParticleEmitter; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.post.Filter; @@ -12,8 +13,14 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; + +import com.jme3.scene.Spatial; import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter @@ -22,11 +29,25 @@ import com.jme3.texture.Texture2D; */ public final class TranslucentBucketFilter extends Filter { + private final static Logger logger = Logger.getLogger(TranslucentBucketFilter.class.getName()); private RenderManager renderManager; + private boolean enabledSoftParticles = false; + private Texture depthTexture; + private ViewPort viewPort; + + public TranslucentBucketFilter() { + super("TranslucentBucketFilter"); + } + + public TranslucentBucketFilter(boolean enabledSoftParticles) { + this(); + this.enabledSoftParticles = enabledSoftParticles; + } @Override protected void initFilter(AssetManager manager, RenderManager rm, ViewPort vp, int w, int h) { this.renderManager = rm; + this.viewPort = vp; material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md"); material.setColor("Color", ColorRGBA.White); Texture2D tex = processor.getFilterTexture(); @@ -37,6 +58,26 @@ public final class TranslucentBucketFilter extends Filter { material.clearParam("NumSamples"); } renderManager.setHandleTranslucentBucket(false); + if (enabledSoftParticles && depthTexture != null) { + initSoftParticles(vp, true); + } + } + + private void initSoftParticles(ViewPort vp, boolean enabledSP) { + if (depthTexture != null) { + for (Spatial scene : vp.getScenes()) { + makeSoftParticleEmitter(scene, enabledSP && enabled); + } + } + + } + + @Override + protected void setDepthTexture(Texture depthTexture) { + this.depthTexture = depthTexture; + if (enabledSoftParticles && depthTexture != null) { + initSoftParticles(viewPort, true); + } } /** @@ -48,6 +89,11 @@ public final class TranslucentBucketFilter extends Filter { return false; } + @Override + protected boolean isRequiresDepthTexture() { + return enabledSoftParticles; + } + @Override protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { renderManager.setCamera(viewPort.getCamera(), false); @@ -63,6 +109,8 @@ public final class TranslucentBucketFilter extends Filter { if (renderManager != null) { renderManager.setHandleTranslucentBucket(true); } + + initSoftParticles(viewPort, false); } @Override @@ -76,5 +124,32 @@ public final class TranslucentBucketFilter extends Filter { if (renderManager != null) { renderManager.setHandleTranslucentBucket(!enabled); } + initSoftParticles(viewPort, enabledSoftParticles); + } + + private void makeSoftParticleEmitter(Spatial scene, boolean enabled) { + if (scene instanceof Node) { + Node n = (Node) scene; + for (Spatial child : n.getChildren()) { + makeSoftParticleEmitter(child, enabled); + } + } + if (scene instanceof ParticleEmitter) { + ParticleEmitter emitter = (ParticleEmitter) scene; + if (enabled) { + enabledSoftParticles = enabled; + + emitter.getMaterial().selectTechnique("SoftParticles", renderManager); + emitter.getMaterial().setTexture("DepthTexture", processor.getDepthTexture()); + emitter.setQueueBucket(RenderQueue.Bucket.Translucent); + + logger.log(Level.INFO, "Made particle Emitter {0} soft.", emitter.getName()); + } else { + emitter.getMaterial().clearParam("DepthTexture"); + emitter.getMaterial().selectTechnique("Default", renderManager); + // emitter.setQueueBucket(RenderQueue.Bucket.Transparent); + logger.log(Level.INFO, "Particle Emitter {0} is not soft anymore.", emitter.getName()); + } + } } } diff --git a/engine/src/core/com/jme3/post/Filter.java b/engine/src/core/com/jme3/post/Filter.java index ef9a6ff88..12babf581 100644 --- a/engine/src/core/com/jme3/post/Filter.java +++ b/engine/src/core/com/jme3/post/Filter.java @@ -40,6 +40,7 @@ import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; import java.io.IOException; import java.util.Collection; @@ -236,7 +237,7 @@ public abstract class Filter implements Savable { * cleanup this filter * @param r */ - protected final void cleanup(Renderer r) { + protected final void cleanup(Renderer r) { processor = null; if (defaultPass != null) { defaultPass.cleanup(r); @@ -269,8 +270,6 @@ public abstract class Filter implements Savable { protected void cleanUpFilter(Renderer r) { } - ; - /** * Must return the material used for this filter. * this method is called every frame. @@ -278,6 +277,14 @@ public abstract class Filter implements Savable { * @return the material used for this filter. */ protected abstract Material getMaterial(); + + /** + * Override if you want to do something special with the depth texture; + * @param depthTexture + */ + protected void setDepthTexture(Texture depthTexture){ + getMaterial().setTexture("DepthTexture", depthTexture); + } /** * Override this method if you want to make a pre pass, before the actual rendering of the frame diff --git a/engine/src/core/com/jme3/post/FilterPostProcessor.java b/engine/src/core/com/jme3/post/FilterPostProcessor.java index 2e48f0f0e..d97e8e91d 100644 --- a/engine/src/core/com/jme3/post/FilterPostProcessor.java +++ b/engine/src/core/com/jme3/post/FilterPostProcessor.java @@ -77,7 +77,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable { private int originalHeight; private int lastFilterIndex = -1; private boolean cameraInit = false; - + /** * Create a FilterProcessor * @param assetManager the assetManager @@ -98,8 +98,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable { * @param filter the filter to add */ public void addFilter(Filter filter) { - filters.add(filter); - filter.setProcessor(this); + filters.add(filter); if (isInitialized()) { initFilter(filter, viewPort); @@ -148,14 +147,17 @@ public class FilterPostProcessor implements SceneProcessor, Savable { * @param vp */ private void initFilter(Filter filter, ViewPort vp) { - filter.init(assetManager, renderManager, vp, width, height); + filter.setProcessor(this); if (filter.isRequiresDepthTexture()) { - if (!computeDepth && renderFrameBuffer != null) { + if (!computeDepth && renderFrameBuffer != null) { depthTexture = new Texture2D(width, height, Format.Depth24); renderFrameBuffer.setDepthTexture(depthTexture); } computeDepth = true; - filter.getMaterial().setTexture("DepthTexture", depthTexture); + filter.init(assetManager, renderManager, vp, width, height); + filter.setDepthTexture(depthTexture); + } else { + filter.init(assetManager, renderManager, vp, width, height); } } @@ -281,9 +283,9 @@ public class FilterPostProcessor implements SceneProcessor, Savable { } else if (renderFrameBufferMS != null) { sceneBuffer = renderFrameBufferMS; } - renderFilterChain(renderer, sceneBuffer); + renderFilterChain(renderer, sceneBuffer); renderer.setFrameBuffer(outputBuffer); - + //viewport can be null if no filters are enabled if (viewPort != null) { renderManager.setCamera(viewPort.getCamera(), false); @@ -356,8 +358,11 @@ public class FilterPostProcessor implements SceneProcessor, Savable { //reseting the viewport camera viewport to its initial value viewPort.getCamera().resize(originalWidth, originalHeight, true); viewPort.getCamera().setViewPort(left, right, bottom, top); - viewPort.setOutputFrameBuffer(outputBuffer); + viewPort.setOutputFrameBuffer(outputBuffer); viewPort = null; + for (Filter filter : filters) { + filter.cleanup(renderer); + } } } @@ -484,7 +489,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable { * For internal use only
* returns the depth texture of the scene * @return - */ + */ public Texture2D getDepthTexture() { return depthTexture; } diff --git a/engine/src/test/jme3test/effect/TestSoftParticles.java b/engine/src/test/jme3test/effect/TestSoftParticles.java new file mode 100644 index 000000000..d779a6fab --- /dev/null +++ b/engine/src/test/jme3test/effect/TestSoftParticles.java @@ -0,0 +1,130 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.TranslucentBucketFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +/** + * + * @author Nehon + */ +public class TestSoftParticles extends SimpleApplication { + + private boolean softParticles = true; + private FilterPostProcessor fpp; + private TranslucentBucketFilter tbf; + + public static void main(String[] args) { + TestSoftParticles app = new TestSoftParticles(); + app.start(); + } + + @Override + public void simpleInitApp() { + + cam.setLocation(new Vector3f(-7.2221026f, 4.1183004f, 7.759811f)); + cam.setRotation(new Quaternion(0.06152846f, 0.91236454f, -0.1492115f, 0.37621948f)); + + flyCam.setMoveSpeed(10); + + + // -------- floor + Box b = new Box(Vector3f.ZERO, 10, 0.1f, 10); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Gray); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + Box b2 = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom2 = new Geometry("Box", b2); + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.DarkGray); + geom2.setMaterial(mat2); + rootNode.attachChild(geom2); + geom2.setLocalScale(0.1f, 0.2f, 1); + + fpp = new FilterPostProcessor(assetManager); + tbf = new TranslucentBucketFilter(true); + fpp.addFilter(tbf); + viewPort.addProcessor(fpp); + + + Material material = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + material.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + + material.setFloat("Softness", 3f); // + + + //Fire + ParticleEmitter fire = new ParticleEmitter("Fire", ParticleMesh.Type.Triangle, 30); + fire.setMaterial(material); + fire.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.1f)); + fire.setImagesX(2); + fire.setImagesY(2); // 2x2 texture animation + fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red + fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow + fire.setStartSize(0.6f); + fire.setEndSize(0.01f); + fire.setGravity(0, -0.3f, 0); + fire.setLowLife(0.5f); + fire.setHighLife(3f); + fire.setLocalTranslation(0, 0.2f, 0); + + rootNode.attachChild(fire); + + + ParticleEmitter smoke = new ParticleEmitter("Smoke", ParticleMesh.Type.Triangle, 30); + smoke.setMaterial(material); + smoke.setShape(new EmitterSphereShape(Vector3f.ZERO, 5)); + smoke.setImagesX(1); + smoke.setImagesY(1); // 2x2 texture animation + smoke.setStartColor(new ColorRGBA(0.1f, 0.1f, 0.1f,1f)); // dark gray + smoke.setEndColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0.3f)); // gray + smoke.setStartSize(3f); + smoke.setEndSize(5f); + smoke.setGravity(0, -0.001f, 0); + smoke.setLowLife(100f); + smoke.setHighLife(100f); + smoke.setLocalTranslation(0, 0.1f, 0); + smoke.emitAllParticles(); + + rootNode.attachChild(smoke); + + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if(isPressed && name.equals("toggle")){ + // tbf.setEnabled(!tbf.isEnabled()); + softParticles = !softParticles; + if(softParticles){ + viewPort.addProcessor(fpp); + }else{ + viewPort.removeProcessor(fpp); + } + } + } + }, "toggle"); + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + +}