Hi,
I wanted to improve the look of my fluid blocks. By default they are translucent and you don’t really get a feeling of ‘depth’.
I don’t use voxel lighting, so I created a FluidDepthFilter
to simulate this effect, and I would like some feedback on it. I am not a shader/filter expert and some things are still a bit ‘magic’ to me.
Result:
In short, what I did:
- create a filter that creates 2 depth textures and send them to the fragment shader. One depth texture of the fluid objects and one depth texture of the scene without the fluid objects.
- in the shader calculate the depth difference between the scene and the fluid
- mix the color of the fluid depending on the depth.
Scene depth texture without the fluid objects:
Scene depth texture of only the fluid objects:
(I inverted the colors to make the depth texture more obvious)
FluidDepthFilter
:
public class FluidDepthFilter extends Filter {
private RenderManager renderManager;
private ViewPort viewPort;
private Material material;
private FrameBuffer fluidDepthBuffer;
private FrameBuffer sceneDepthBuffer;
private final GeometryList fluidGeometryList = new GeometryList(new TransparentComparator());
@Override
protected boolean isRequiresDepthTexture() {
return false;
}
@Override
protected boolean isRequiresSceneTexture() {
return true;
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
material = new Material(manager, "Mats/FluidDepth.j3md");
material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
material.setColor("FadeColor", new ColorRGBA().setAsSrgb(0.2f, 0.404f, 0.698f, 1.0f));
material.setFloat("FadeDepth", 6.0f);
fluidDepthBuffer = new FrameBuffer(w, h, 1);
sceneDepthBuffer = new FrameBuffer(w, h, 1);
Texture2D fluidDepthTexture = new Texture2D(w, h, 1, Image.Format.Depth);
Texture2D sceneDepthTexture = new Texture2D(w, h, 1, Image.Format.Depth);
fluidDepthBuffer.setDepthTexture(fluidDepthTexture);
sceneDepthBuffer.setDepthTexture(sceneDepthTexture);
material.setTexture("FluidDepthTexture", fluidDepthTexture);
material.setTexture("SceneDepthTexture", sceneDepthTexture);
this.renderManager = renderManager;
this.viewPort = vp;
}
@Override
protected Material getMaterial() {
return material;
}
@Override
protected void postQueue(RenderQueue queue) {
Renderer renderer = renderManager.getRenderer();
if (fluidGeometryList.size() > 0) {
renderer.setFrameBuffer(fluidDepthBuffer);
renderer.clearBuffers(true, true, true);
renderManager.renderGeometryList(fluidGeometryList);
renderer.setFrameBuffer(viewPort.getOutputFrameBuffer());
}
GeometryList filteredSceneGeometries = new GeometryList(new OpaqueComparator());
for (Spatial scene : viewPort.getScenes()) {
scene.depthFirstTraversal(new SceneGraphVisitorAdapter() {
@Override
public void visit(Geometry geom) {
if (!contains(geom, fluidGeometryList)) {
filteredSceneGeometries.add(geom);
}
}
});
}
renderer.setFrameBuffer(sceneDepthBuffer);
renderer.clearBuffers(true, true, true);
renderManager.renderGeometryList(filteredSceneGeometries);
renderer.setFrameBuffer(viewPort.getOutputFrameBuffer());
}
private static boolean contains(Geometry geometry, GeometryList geometryList) {
for (Geometry g : geometryList) {
if (g.equals(geometry)) {
return true;
}
}
return false;
}
public void addFluidGeometry(Geometry waterGeometry) {
fluidGeometryList.add(waterGeometry);
fluidGeometryList.sort();
}
}
FluidDepth.j3md
MaterialDef WaterDepth Textured {
MaterialParameters {
// the depth when the FadeColor is fully visible
Float FadeDepth
// the color the fluid will blend to
Vector4 FadeColor
// samples for aa
Int NumSamples
// the rendered scene
Texture2D Texture
// the depth texture of the fluid geometries that fade
Texture2D FluidDepthTexture
// the depth texture of the scene without the geometries that fade
Texture2D SceneDepthTexture
}
Technique {
VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Post/Post.vert
FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Mats/FluidDepth.frag
WorldParameters {
FrustumNearFar
}
}
}
FluidDepth.frag
#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/MultiSample.glsllib"
uniform vec4 m_FadeColor;
uniform float m_FadeDepth;
uniform sampler2D m_Texture;
uniform vec2 g_FrustumNearFar;
uniform sampler2D m_WaterDepthTexture;
uniform sampler2D m_SceneDepthTexture;
varying vec2 texCoord;
void main(){
vec4 scene = getColor(m_Texture, texCoord);
float depth = getDepth(m_SceneDepthTexture, texCoord).r;
float waterDepth = getDepth(m_WaterDepthTexture, texCoord).r;
// logic from DepthOfField.frag:
// z_buffer_value = a + b / z;
//
// Where:
// a = zFar / ( zFar - zNear )
// b = zFar * zNear / ( zNear - zFar )
// z = distance from the eye to the object
//
// Which means:
// zb - a = b / z;
// z * (zb - a) = b
// z = b / (zb - a)
//
float a = g_FrustumNearFar.y / (g_FrustumNearFar.y - g_FrustumNearFar.x);
float b = g_FrustumNearFar.y * g_FrustumNearFar.x / (g_FrustumNearFar.x - g_FrustumNearFar.y);
float distanceScene = b / (depth - a);
float distanceWater = b / (waterDepth - a);
float depthDifference = distanceScene - distanceWater;
float depthMixFactor = clamp(depthDifference / m_FadeDepth, 0, 1.0);
// depthMixFactor = 0 == surface
// depthMixFactor = 1 == bottom (m_FadeDepth)
if (distanceWater < distanceScene) {
gl_FragColor = mix(scene, m_FadeColor, depthMixFactor);
} else {
gl_FragColor = scene;
}
//
// DEBUG
//
// render depthMixFactor
//gl_FragColor = vec4(depthMixFactor, depthMixFactor, depthMixFactor, 1.0);
// render depth texture
// the value is inverted to make it more obvious
//gl_FragColor = vec4(1.0 - depth, 1.0 - depth, 1.0 - depth, 1.0);
// render the water depth texture
// the value is inverted to make it more obvious
//gl_FragColor = vec4(1.0 - waterDepth, 1.0 - waterDepth, 1.0 - waterDepth, 1.0);
// render the scene
//gl_FragColor = scene;
}
Is my way of handling this correct? Am I doing any unnecessary processing? Any tips or tricks to improve or make it look better?
note: the code still needs to be cleaned up.