水面的制作方式也有很多种,最简单的莫过于采用纹理贴图。
如果嫌弃静态的水面,可以准备多张纹理贴图,使用帧动画让水面看起来是流动的。
当然,也可以使用着色器技术,通过俗称“滚UV”的方式让一张图片看起来是流动的。
当然,也可以使用着色器技术,通过俗称“滚UV”的方式让一张图片看起来是流动的。
更复杂一些的做法,是制作一个平面来代表水面,然后用着色器再其表面绘制场景的反射贴图。另外,还可以利用算法让平面波动起来,这样显得更加真实。
jME3的 jme3-effects 模块中的 SimpleWaterProcessor 就是基于这个原理实现的。
带有水的 jme3 场景都可以使用 com.JME3.water.SimpleWaterProcessor (它实现了 SceneProcessor 接口)。
为了达到水的效果,jme3 使用着色器和一种特殊材质 Common/MatDefs/Water/SimpleWater.j3md。水面是一个四边形,我们使用法线贴图和 dU/dV 贴图来模拟波浪。
// 创建SimpleWaterProcessor
SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
waterProcessor.setReflectionScene(mainScene);
//设置水平面
Vector3f waterLocation=new Vector3f(0,-6,0);
waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
viewPort.addProcessor(waterProcessor);
// 设置属性
waterProcessor.setWaterDepth(40); // transparency of water
waterProcessor.setDistortionScale(0.05f); // strength of waves
waterProcessor.setWaveSpeed(0.05f); // speed of waves
// 通过设置纹理坐标的大小来定义波的大小
Quad quad = new Quad(400,400);
quad.scaleTextureCoordinates(new Vector2f(6f,6f));
// 在四边形上创建水的几何形状
Geometry water=new Geometry("water", quad);
water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
water.setLocalTranslation(-200, -6, 250);
water.setShadowMode(ShadowMode.Receive);
water.setMaterial(waterProcessor.getMaterial());
rootNode.attachChild(water);
相关设置:
//可以通过降低渲染大小来获得更高的性能:
waterProcessor.setRenderSize(128,128);
//水越深,越透明
waterProcessor.setWaterDepth(40);
// 失真程度越高,波就越大。
waterProcessor.setDistortionScale(0.05f);
//较低的波速使水更平静。
waterProcessor.setWaveSpeed(0.05f);
//如果你的场景没有光源,你可以设置水的光线方向:
waterProcessor.setLightDirection( new Vector3f(0.55f, -0.82f, 0.15f));
// 你可以从处理器中得到一个默认的水平面,而不是创建一个四边形并指定一个平面:
Geometry waterPlane = waterProcessor.createWaterGeometry(10, 10);
waterPlane.setLocalTranslation(-5, 0, 5);
waterPlane.setMaterial(waterProcessor.getMaterial());
官方例子:
反色技术使得水面看起来更加真实,但说白了依然是一层纸。如果想体验水下场景,就需要额外的渲染技术了。
在jME3中,WaterFilter 用于模拟真实的水体,它是一种后期特效。其核心算法与 SimpleWaterFilter 类似,也是实时计算水面反射。除此之外,当玩家把摄像机移到水面以下时,还能够实现水下的特效。
WaterFilter 须配合 FilterPostProcessor 一起使用。WaterFilter 是理想的海洋和湖泊,尤其是水下场景。如果只需要一个小的简单的水面,比如一个水槽或一个浅的喷泉,SimpleWaterProcessor 已经足够了。
这种效果是延迟渲染过程的一部分,利用了预先计算的位置缓冲区和后退缓冲区(表示屏幕在视图空间中的像素位置的纹理,以及渲染场景的纹理)。
经过一些计算,这允许为屏幕上的每个像素重建世界空间中的位置。如果一个像素低于给定的水高度,那么我们将其渲染为一个蓝色像素!不完全是这样,我们想要波浪,我们想要涟漪,我们想要泡沫,我们想要反射和折射。
在 GameDev.net 上的文章描述了这些效果是如何实现的,但主要思想是从高度贴图生成波纹,从普通贴图生成涟漪,当水深低于一定高度时融入泡沫纹理,用巧妙的消色算法计算折射颜色,然后通过计算菲涅耳项显示反射和镜面效果(就像简单的水效果)。此外,这种效果允许混合的水岸与地面,以避免硬边的经典水效应的基础上网格或四边形。
Jme3的默认行为是使用前向呈现过程,因此没有可以利用的位置缓冲区呈现。但是在 FilterPostProcessor 中将主场景渲染到帧缓冲区时,我们可以将硬件深度缓冲区写入纹理,几乎不需要额外的开销。有几种方法可以从深度缓冲区重建像素的世界空间位置。计算成本高于仅从位置缓冲区获取位置,但所需的带宽和内存要低得多。现在我们有了纹理渲染的场景,我们可以重建每个像素在世界空间中的位置。
在 simpleInitApp () 方法中,将场景附加到 rootNode,通常是带有天空的地形。记住要加一个 directional light,因为水依赖于光的方向矢量。WaterFilter 需要一个附加了场景的节点,这个节点应该反映在水中,并且需要从光源的方向获得矢量信息。
private FilterPostProcessor fpp;
private WaterFilter water;
private Vector3f lightDir = new Vector3f(-4.9f, -1.3f, 5.9f);
private float initialWaterHeight = 0.8f;
...
public void simpleInitApp() {
...
fpp = new FilterPostProcessor(assetManager);
water = new WaterFilter(rootNode, lightDir);
water.setWaterHeight(initialWaterHeight);
fpp.addFilter(water);
viewPort.addProcessor(fpp);
...
}
通常你让水反映所有附加到 rootNode 的东西。但是,您也可以将一个自定义节点(rootNode 的一个子节点)提供给只附加了一个场景节点子集的 WaterFilter 构造函数。如果你有很多远离水面的节点,或者被覆盖的节点,而且这些节点永远不会被反映出来,那么这将是一个相关的优化。
如果你想要波浪效果,在更新循环中设置水的高度。我们重用 initialWaterHeight 变量,并根据时间重新设置 waterHeight 值。这就引起了波浪。
private float time = 0.0f;
private float waterHeight = 0.0f;
@Override
public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
time += tpf;
waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f;
water.setWaterHeight(initialWaterHeight + waterHeight);
}
所有这些效果都是可选的,每个 setter 也有一个 getter。
方法 | 波浪 | 默认值 |
---|---|---|
water.setWaterHeight(-6); | 使用这种水高的方法来引起波浪。 | 0.0f |
water.setMaxAmplitude(0.3f); | 最高的浪有多高。 | 1.0f |
water.setWaveScale(0.008f); | 设置波浪高度图的比例因子。值越小,波浪越大! | 0.005f |
water.setWindDirection(new Vector2f(0,1)) | 设置风向,也就是波浪移动的方向 | Vector2f(0.0f, -1.0f) |
water.setSpeed(0.7f); | 波浪移动的速度。静水设置为0.0 f。 | 1.0f |
String path = "Textures/waveheight.png"; Texture2D t = (Texture2D)manager.loadTexture(path); water.setHeightTexture(t) |
这幅高度图描述了海浪的形状 | "Common/MatDefs/Water/Textures/heightmap.jpg" |
String path = "Textures/wavenormals.png"; Texture2D t = (Texture2D)manager.loadTexture(path); water.setNormalTexture(t) |
这张法线图描述了波的形状 | "Common/MatDefs/Water/Textures/gradient_map.jpg" |
water.setUseRipples(false); | 开启关闭涟漪效果。 | true |
water.setNormalScale(0.5f) | 设置应用于法线贴图的法线比例因子。数值越高,波纹就越多。 | 1.0f |
方法 | 颜色 | 默认值 |
water.setLightDirection(new Vector3f(-0.37f,-0.50f,-0.78f)) | 通常把它设置成和光源的方向一样。如果太阳在移动,用这个来设置光的方向。 | WaterFilter()的构造函数 |
water.setLightColor(ColorRGBA.White) | 通常把这个设置成和光源的颜色一样。 | ColorRGBA.White |
water.setWaterColor(ColorRGBA.Brown.mult(2.0f)); | 设置水的主色 | 绿蓝色:ColorRGBA(0.0f,0.5f,0.5f,1.0f) |
water.setDeepWaterColor(ColorRGBA.Brown); | 设置深水色。 | 暗蓝色:ColorRGBA(0.0f, 0.0f,0.2f,1.0f) |
water.setWaterTransparency(0.2f); | 设置颜色消失的速度。用这个来控制水的清澈程度(例如0.05f)或者混浊程度(0.2f)。 | 0.1f |
water.setColorExtinction(new Vector3f(10f,20f,30f)); | 设置在什么深度折射下颜色消失。这三个值按顺序是 RGB(红、绿、蓝)。利用这些参数使水变得“浑浊”。 | Vector3f(5f,20f,30f) |
方法 | 支撑物 | 默认值 |
water.setCenter(Vector3f.ZERO); water.setRadius(260); | 将滤水器的中心和半径限制在一个半球内。用于湖泊和较小的水体。如果未设置,则为海洋。 | unused |
water.setShoreHardness(1.0f); | 设定海岸和水域之间的过渡应该有多柔软。高值意味着海岸和水域之间的过渡更加困难。 | 0.1f |
water.setUseHQShoreline(false); | 使海岸线具有更好的质量 | true |
方法 | 泡沫 | 默认值 |
water.setUseFoam(false); | 打开或关闭白色泡沫 | true |
water.setFoamHardness(0.5f) | 设置泡沫与岸边混合的程度,以避免硬边的水面。 | 1.0f |
water.setFoamExistence(new Vector3f(0.5f,5f,1.0f)) | 这三个数值描述了什么深度的泡沫开始消失,在什么深度它是完全看不见的,在什么高度的海浪出现泡沫(+ whaterHeight 加上水的高度)。 | Vector3f(0.45f,4.35f,1.0f) |
water.setFoamTexture( (Texture2D) manager.loadTexture("Textures/foam.png") ) | 这种泡沫材质将用于 WrapMode.Repeat。 | "Common/MatDefs/Water/Textures/foam.jpg" |
方法 | 光照 | 默认值 |
water.setSunScale(1f); | 设置太阳在光线的反射作用下在水面上应该显示的大小。 | 3.0f |
water.setUseSpecular(false) | 开启关闭镜面效果 | true |
water.setShininess(0.8f) | 设置水反射的亮度 | 0.7f |
water.setUseRefraction(true) | 开关或关闭折射效果。 | true |
water.setRefractionConstant(0.2f); | 值越低,水面上的反射越少。这个常数与用来计算菲涅耳项的折射率有关。 | 0.3f |
water.setRefractionStrength(-0.1) | 此值修改当前的菲涅耳项。若想削弱反射则使用更大的值。若想加强放射则使用小于0的值。 | 0.0f |
water.setReflectionMapSize(256) | 设置反射图的大小。越高,质量越好,但效果越慢 | 512 |
直接添加带有水声的音频节点来实现这个效果:
AudioNode waves = new AudioNode(assetManager, "Sounds/Environment/Ocean Waves.ogg", false);
waves.setLooping(true);
audioRenderer.playSource(waves);