Water Shader Textures

Started by AGP, January 09, 2011, 09:13:08 PM

Previous topic - Next topic

EgonOlsen

#15
I quote myself:
Quote from: EgonOlsen on January 11, 2011, 12:49:12 PM
Remember that the shader needs a light source to work with. Without one, everything will be black.
Your shader doesn't use a GL light source, mine does. If you don't provide one, everything will be black. Anyway, if it works fine with that one, all is well. I'm sure it's better suited for water then my simple bump mapping shader is. Any screen shots of how the result looks like (just out of interest)?

AGP

#16
Mine isn't the best texture or normal map, but here you go:


AGP

#17
PS: We're working on a better-scaled background! :- )

EDIT: Also, I read the comment about the light this morning. Although there were already two lights (one for the moon and one over Superman), I added four more (one to each side of Superman) to the scene and your shader still looked black.

EgonOlsen

Well, no idea then. Maybe the angle between the light and the surface makes it appear black...who knows. It doesn't really matter now that you seem to have something that works reasonable well.

AGP

But I'm nowhere near satisfied with this water yet. Any chance I could persuade you to try a water shader? I've tried several and the one I think could work well goes black like yours did. It's important to note that this is happening on three very different computers, with distinctly different video cards.

EDIT: Also, the first shader I made work yesterday produced the odd effect of rotating the texture along with the camera. I'm not sure if it was intentional of if I wasn't doing something I was supposed to be doing.

EgonOlsen

Then why not post-pone that shader stuff and go with something easier? A simple sin/cos-based vertex controller effect with a base water texture and and environment map on top might look good enough and will work everywhere.

AGP

#21
Because I'd like to tackle the shader issue at some point and now is as good as any. Truth be told, if the ReflectionHelper would start working I'd be very happy. But also, aren't shaders a lot faster than a simple vertex controller?

EgonOlsen

Yes, they are faster...but that doesn't mean that not using them is a problem. The reflection helper might not very well suited for your application anyway. It works on planar surfaces. If you can tilt the camera, the reflection will go crazy.

EgonOlsen

#23
Cheapo water effect (20min of work, so don't expect too much) without shaders. It skips updating the normals in the controller and calculates them every frame, which is costly. But i didn't have the time to figure out how to do it in the controller.
Textures can be found in http://www.jpct.net/download/misc


import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.GenericVertexController;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureInfo;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.Light;

public class CheapWater {

private World world;

private FrameBuffer buffer;

private Object3D water;

private WaterController wc = null;

public static void main(String[] args) throws Exception {
new CheapWater().loop();
}

public CheapWater() throws Exception {

Config.glForceEnvMapToSecondStage = true;
Config.glUseVBO = true;

world = new World();
world.setAmbientLight(100, 100, 100);

TextureManager tm = TextureManager.getInstance();

tm.addTexture("water", new Texture("water3.jpg"));
tm.addTexture("envmap", new Texture("environment.jpg"));

water = Primitives.getPlane(20, 3);

TextureInfo ti = new TextureInfo(tm.getTextureID("water"));
ti.add(tm.getTextureID("envmap"), TextureInfo.MODE_MODULATE);

wc = new WaterController();

water.setTexture(ti);
water.setEnvmapped(Object3D.ENVMAP_ENABLED);
water.rotateX((float) Math.PI / 2f);
water.rotateMesh();
water.clearRotation();
water.build();
water.compile(true);
water.setTransparency(2);
water.getMesh().setVertexController(wc, false);
water.setTextureMatrix(new Matrix());
world.addObject(water);

world.getCamera().setPosition(0, -50, -50);
world.getCamera().lookAt(water.getTransformedCenter());

Light light=new Light(world);
light.setAttenuation(-1);
light.setIntensity(255, 255, 255);
light.setPosition(new SimpleVector(100,-50,-20));
}

private void loop() throws Exception {
buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_GL_AA_2X);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);

while (!org.lwjgl.opengl.Display.isCloseRequested()) {
buffer.clear(java.awt.Color.BLUE);

wc.update(0.5f);

water.getMesh().applyVertexController();
water.touch();
water.calcNormals(); // This isn't good...it should actually be done in the VertexController...
water.getTextureMatrix().translate(0.0015f, -0.0015f, 0);

world.renderScene(buffer);
world.draw(buffer);
buffer.update();
buffer.displayGLOnly();
Thread.sleep(10);
}
buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
buffer.dispose();
System.exit(0);
}

private static class WaterController extends GenericVertexController {

private static final long serialVersionUID = 1L;

private float degreeAdd = 0;

public void update(float inc) {
degreeAdd += inc;
}

public void apply() {
SimpleVector[] source = this.getSourceMesh();
SimpleVector[] dest = this.getDestinationMesh();

int end = source.length;

for (int i = 0; i < end; i++) {
SimpleVector s = source[i];
SimpleVector d = dest[i];
float sin = (float) Math.sin((((((degreeAdd + s.x + s.z)/10f) % 360))));
d.set(s.x, s.y + sin*2, s.z);

//@todo
// Update the normals here too. Formula?
}
}
}

}


AGP

#24
Thanks a lot, I will try this out and report.

But I'd still like to make the shader thing work (that's the appeal to me!).

AGP

It's definately not fast, but I love that it moves like water on the shores. If it could be sped up, and you could solve the normals formula (to maybe look like it moves more three-dimensionally and less planar), I would have to find another excuse to play with shaders on jpct. :- )


EgonOlsen

#26
Here's an improved version. It pre-calculated the normals in the controller based on a formula that i pulled out of my nose. With that change, performance is like this on my machine (Core2 Quad @3.2Ghz, AMD Radeon 5870):

1600 polygons/precalc normals: 4500fps
1600 polygons/real normals: 2500fps

10000 polygons/precalc normals: 2300fps
10000 polygons/real normals: 30fps

...so precalc normals should be fast enough IMHO.

Here's the code. You can now apply an additional scale that changes the waves' height as well as a damping factor that makes the waves narrow or wide.

import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.GenericVertexController;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureInfo;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.Light;

public class CheapWater {

private World world;

private FrameBuffer buffer;

private Object3D water;

private WaterController wc = null;

public static void main(String[] args) throws Exception {
new CheapWater().loop();
}

public CheapWater() throws Exception {

Config.glForceEnvMapToSecondStage = true;
Config.glUseVBO = true;
                Config.glDynamicBatchSize=4000;

world = new World();
world.setAmbientLight(100, 100, 100);

TextureManager tm = TextureManager.getInstance();

tm.addTexture("water", new Texture("water3.jpg"));
tm.addTexture("envmap", new Texture("environment2.jpg"));

water = Primitives.getPlane(40, 1.5f);

TextureInfo ti = new TextureInfo(tm.getTextureID("water"));
ti.add(tm.getTextureID("envmap"), TextureInfo.MODE_MODULATE);

water.setTexture(ti);
water.setEnvmapped(Object3D.ENVMAP_ENABLED);
water.rotateX((float) Math.PI / 2f);
water.rotateMesh();
water.clearRotation();
water.build();
water.compile(true);
water.setTransparency(2);
wc = new WaterController(water, 5, 10, false);
water.getMesh().setVertexController(wc, false);
world.addObject(water);

world.getCamera().setPosition(0, -50, -50);
world.getCamera().lookAt(water.getTransformedCenter());

Light light = new Light(world);
light.setAttenuation(-1);
light.setIntensity(255, 255, 255);
light.setPosition(new SimpleVector(100, -50, -20));
}

private void loop() throws Exception {
buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_GL_AA_2X);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);

long s = System.nanoTime() / 1000000L;
long ss = System.currentTimeMillis();
int fps = 0;

while (!org.lwjgl.opengl.Display.isCloseRequested()) {
buffer.clear(java.awt.Color.BLUE);

long ticks = ((System.nanoTime() / 1000000L) - s) / 10;

if (ticks > 0) {
s = System.nanoTime() / 1000000L;
wc.update(0.5f * (float) ticks);
water.getMesh().applyVertexController();
}

world.renderScene(buffer);
world.draw(buffer);
buffer.update();
buffer.displayGLOnly();
fps++;
if (System.currentTimeMillis() - ss >= 1000) {
System.out.println(fps + "fps");
fps = 0;
ss = System.currentTimeMillis();
}
}
buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
buffer.dispose();
System.exit(0);
}

private static class WaterController extends GenericVertexController {

private float scale = 0;

private float damping = 0;

private SimpleVector[] preCalcNormals = null;

private SimpleVector[] preCalcNormalsNeg = null;

private float[] lastHeight = null;

private static final long serialVersionUID = 1L;

private float degreeAdd = 0;

private Object3D water = null;

private float lastUpdate = 0;

private boolean realNormals = false;

public WaterController(Object3D water, float scale, float damping, boolean realNormals) {
this.scale = scale;
this.water = water;
this.realNormals = realNormals;
this.damping = damping;
water.setTextureMatrix(new Matrix());
}

/**
* This calculates some normals...these are rather fake and in no way
* comparable to real surface normals. But they should do the trick...
*/
public boolean setup() {
SimpleVector ax = new SimpleVector(-1, 0, 1).normalize();
preCalcNormals = new SimpleVector[(int) (100f * scale)];
preCalcNormalsNeg = new SimpleVector[(int) (100f * scale)];
int end = preCalcNormals.length;
for (int i = 0; i < end; i++) {
float height = -1f + (((float) i) / (end / 2f));
SimpleVector n = new SimpleVector(0, -1, 0);
SimpleVector n2 = new SimpleVector(0, -1, 0);
Matrix m = new Matrix();
Matrix m2 = new Matrix();
if (height <= 0) {
float val = (float) Math.sqrt((height + 1) * (Math.PI / 7f));
m.rotateAxis(ax, val);
m2.rotateAxis(ax, -val);
} else {
float val = (float) Math.sqrt((1 - height) * (Math.PI / 7f));
m.rotateAxis(ax, val);
m2.rotateAxis(ax, -val);
}
n.rotate(m);
n2.rotate(m);
preCalcNormals[i] = n;
preCalcNormalsNeg[i] = n2;
}

SimpleVector[] source = this.getSourceMesh();
lastHeight = new float[source.length];

for (int i = 0; i < source.length; i++) {
lastHeight[i] = 0;
}

return true;
}

public void update(float inc) {
degreeAdd += inc;
lastUpdate = inc;
}

public void apply() {
SimpleVector[] source = this.getSourceMesh();
SimpleVector[] dest = this.getDestinationMesh();

SimpleVector[] destNormals = this.getDestinationNormals();

int end = source.length;
int nEnd = preCalcNormals.length;

for (int i = 0; i < end; i++) {
SimpleVector s = source[i];
SimpleVector d = dest[i];
float sin = (float) Math.sin((degreeAdd + s.x + s.z) / damping);
d.set(s.x, s.y + sin * scale, s.z);

int iHeight = (int) ((sin + 1) * (nEnd / 2));

if (lastHeight[i] > sin) {
destNormals[i].set(preCalcNormalsNeg[iHeight]);
} else {
destNormals[i].set(preCalcNormals[iHeight]);
}

lastHeight[i] = sin;
}

water.touch();
if (realNormals) {
water.calcNormals();
}
float tr = lastUpdate / 333f;
water.getTextureMatrix().translate(tr, -tr, 0);
}
}

}


AGP

Setup() isn't being called. Where should it be?

EgonOlsen

It's called by the engine, you don't have to call it yourself.

AGP

Oh, I see that it's part of the IVertexController interface, sorry.

Is it defaulting to pre-calculated? And although the look on the shore is that of moving water, the water surface itself doesn't move (it's a smooth surface).