水面

本篇教程资料请点击这里下载

水面纹理

水面的制作方式也有很多种,最简单的莫过于采用纹理贴图。

image.png

如果嫌弃静态的水面,可以准备多张纹理贴图,使用帧动画让水面看起来是流动的。

image.png

当然,也可以使用着色器技术,通过俗称“滚UV”的方式让一张图片看起来是流动的。

当然,也可以使用着色器技术,通过俗称“滚UV”的方式让一张图片看起来是流动的。

水面反射

更复杂一些的做法,是制作一个平面来代表水面,然后用着色器再其表面绘制场景的反射贴图。另外,还可以利用算法让平面波动起来,这样显得更加真实。

image.png

jME3的 jme3-effects 模块中的 SimpleWaterProcessor 就是基于这个原理实现的。

SimpleWaterProcessor

带有水的 jme3 场景都可以使用 com.JME3.water.SimpleWaterProcessor (它实现了 SceneProcessor 接口)。

为了达到水的效果,jme3 使用着色器和一种特殊材质 Common/MatDefs/Water/SimpleWater.j3md。水面是一个四边形,我们使用法线贴图和 dU/dV 贴图来模拟波浪。

  1. 每一帧,我们渲染三个纹理贴图:
    • 对于水面(反射) ,我们拍摄一张环境快照,将其倒置,并将其夹在可见的水面上。请注意,我们实际上并没有使用“水”纹理贴图: 水的“纹理”只是一种扭曲的反射。
    • 对于“波浪式”扭曲(折射) ,我们使用法线映射的导数,即 dU/dV 映射。
    • 对于水(深度)的模糊,我们使用地形 z 缓冲区的深度图。
  2. 在着色器中,我们将所有的纹理贴图添加到一起。
    • 对于波浪的“颠簸”位移,我们使用一个法线贴图和一个随时间相互移动的 du/dv 贴图来创建波浪效应。
    • 对于水面上的光反射矢量,我们使用菲涅耳公式和法向矢量。
    • 我们添加了镜面照明。
  3. 水下焦散效果,我们使用溅射纹理。-WIP/TODO

使用

// 创建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 类似,也是实时计算水面反射。除此之外,当玩家把摄像机移到水面以下时,还能够实现水下的特效。

image.png

WaterFilter 须配合 FilterPostProcessor 一起使用。WaterFilter 是理想的海洋和湖泊,尤其是水下场景。如果只需要一个小的简单的水面,比如一个水槽或一个浅的喷泉,SimpleWaterProcessor 已经足够了。

原理

这种效果是延迟渲染过程的一部分,利用了预先计算的位置缓冲区和后退缓冲区(表示屏幕在视图空间中的像素位置的纹理,以及渲染场景的纹理)。

经过一些计算,这允许为屏幕上的每个像素重建世界空间中的位置。如果一个像素低于给定的水高度,那么我们将其渲染为一个蓝色像素!不完全是这样,我们想要波浪,我们想要涟漪,我们想要泡沫,我们想要反射和折射。

在 GameDev.net 上的文章描述了这些效果是如何实现的,但主要思想是从高度贴图生成波纹,从普通贴图生成涟漪,当水深低于一定高度时融入泡沫纹理,用巧妙的消色算法计算折射颜色,然后通过计算菲涅耳项显示反射和镜面效果(就像简单的水效果)。此外,这种效果允许混合的水岸与地面,以避免硬边的经典水效应的基础上网格或四边形。

Jme3的默认行为是使用前向呈现过程,因此没有可以利用的位置缓冲区呈现。但是在 FilterPostProcessor 中将主场景渲染到帧缓冲区时,我们可以将硬件深度缓冲区写入纹理,几乎不需要额外的开销。有几种方法可以从深度缓冲区重建像素的世界空间位置。计算成本高于仅从位置缓冲区获取位置,但所需的带宽和内存要低得多。现在我们有了纹理渲染的场景,我们可以重建每个像素在世界空间中的位置。

使用 Water Filter

在 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 构造函数。如果你有很多远离水面的节点,或者被覆盖的节点,而且这些节点永远不会被反映出来,那么这将是一个相关的优化。

选项: 波浪(waves)

如果你想要波浪效果,在更新循环中设置水的高度。我们重用 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);
}

各种效果

image.png

所有这些效果都是可选的,每个 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);