【Water Drop系列】2a – Dynamic rain and its effects

【Water Drop系列】是雨效渲染实现的整套方案的翻译总结,本文是第二篇,主要介绍降雨的基本效果的实现方法。这里是原文链接

本篇雨效渲染文章的主要参考是ATI的“Toy Shop”Demo,这个Demo曾经被Natalya Tatarchuck在很多会议上所提及。不过虽然这个demo是2005年的技术了,但是依然无法在当代的PS3/XBOX360等设备上很好的重现。这篇文章主要介绍作者在“Remember Me”上面尝试复现上述Demo技术的一些努力,实现引擎为UE3.

Rain Effects

Rain splashes / Falling drops splashes

先来讨论下雨滴坠落的实现,而雨滴溅起的水花相对而言实现会容易一点,只需要检测雨滴粒子与场景的碰撞之后,生成一个雨花特效粒子即可。不过碰撞检测比较费,且如此之多的雨滴在地面上碰撞,也很难分清到底哪个雨花是哪个雨滴导致的,基于这一点,雨花跟雨滴的实现可以拆分成两个独立的系统来实现,这样可以兼顾表现跟性能。

大多数游戏实现雨花的方式是从世界的顶部生成一系列随机的垂线,对这些直线(出于优化的考虑,直线最好尽可能的分布在近景处)与一个能够代表场景大致形状的简单几何体进行碰撞检测,并在碰撞点处生成对应的雨花特效,对于那些无法找到一个能够代表场景地形的简单几何体的情况,还可以直接在场景几何体的边缘位置手动放置对应的粒子发射器,生成雨花特效,虽然没有真实雨效那么随机,但至少效果是有了。

本文给出的雨花实现方式有所不同,直接采用类似阴影贴图的方式生成一张雨景深度贴图(以垂直向下的方式得到的正交投影贴图),有了这张贴图之后就不需要模拟场景地形的简单几何体了,可以直接在任意想要的位置生成随机的雨花,基本步骤给出如下:

  • Render a depth map —— 雨景深度贴图(需要为透明物体写入深度)
  • Transfer depth map from GPU to CPU memory —— CPU读取深度贴图
  • Use the depth map to generate random positions following the world geometry —— 在CPU根据深度数据生成随机的雨花生成点
  • Emit the water splash at the generated positions —— 将生成点转换成对应的世界坐标,并生成雨花特效

这种方式得到的雨花,还可以有效的避免室内雨花穿帮问题。

阴影贴图渲染的一切优化手段这里都可以用上(剔除优化,Z双倍加速,只渲染顶点位置,放弃细小物体渲染,使用LOD,shadow cache等等)

出于一些特殊考虑,有些物体不需要进行雨花渲染,有些物体只渲染雨花而不需要在正式场景中渲染,因此这里为物体增加了一些可以交由美术同学控制的选项。

深度图的精度取决于分辨率与覆盖范围,对于一张256x256的贴图,如果用于覆盖20m x 20m的范围的话,那么每个像素将对应于7.8平方厘米,在这种数据下,如果存在某个物件在7.8平方厘米内存在较大的高度差的话,那么采样得到的结果可能就会出现偏差,导致生成的雨花特效的高度与实际情况不匹配,作为性能跟效果的平衡,这一点也是没有办法的。

阴影贴图通常使用正交投影矩阵,这是因为通常来说阴影贴图都是对应于方向光的,但是这里理论上来说也是应该使用正交矩阵,不过由于雨花本来就是近似的,所以使用透视矩阵也是可以的,而如果使用透视矩阵的话,最好使用reversed z来提升深度贴图的精度(浮点数在靠近0的位置,精度最高),此外,这里还给出了各个平台兼容性的一些考虑。

// ScreenPosition is the projected position
#if ORTHOGONAL
// We encode the Z device value for orthogonal matrix
// ScreenPosition.w is supposed to be 1.0 in orthogonal projection matrix
OutColor = float4(0, 0, 0, ScreenPosition.z / ScreenPosition.w);
#else // PERSPECTIVE
// Define A = ProjectionMatrix[2][2] and B = ProjectionMatrix[3][2] (row major)
// Standard projection do Z_NDC = A + B / Z => Reversed (1 - Z_NDC) = 1 - A - B / Z
OutColor = float4(0, 0, 0, 1 - A - B / ScreenPosition.w);
#endif
A top view depth map

渲染完成之后,下一步就是将数据从GPU传入CPU,在DX9上有两种做法,一种是阻塞式的,CPU一直等待,直到GPU渲染完成,这种比较浪费,另一种则是直接拿取上一帧渲染的结果来使用,并将本帧渲染的结果交给下一帧使用,即double buffer的实现方式。

主机上这个问题要相对简单一点,因为不需要做任何的同步(即使写错了一个数据对于成千上万的数据而言,也不过是沧海一粟),且传输速度极快(在PS3上的数据为,传输256x256的深度贴图只需要36us)

下一步是根据深度贴图生成雨花粒子的位置,雨花粒子发生器一般会选择放在比较中心的位置,比如角色位置,或者在TPS游戏中,放在相机位置,之后每发射一个雨花粒子,都需要进行如下处理:将这个雨花粒子按照雨景深度贴图的ViewProjection矩阵进行变化,得到uv坐标读取深度值,并进行反向投影,得到最终碰撞点的世界坐标。

// Project current position in our depth map and set world space z location.
Vector4 ShadowPosition = ViewProjMatrix.TransformVector4(Particle->Location);
ShadowPosition = ShadowPosition / ShadowPosition.W;

// Save depth map X Y position for later world space reconstruction
Vector2D PosNDC(Clamp(ShadowPosition.X, -1.0f, 1.0f), Clamp(ShadowPosition.Y, -1.0f, 1.0f));

// If we are out of shadowmap, just kill the pixel (We do this by testing if value change by clamp)
if (PosNDC.X - ShadowPosition.X + PosNDC.Y - ShadowPosition.Y)
{
return ;
}

// Convert to shadow map texel space - apply a clamp mode address mode
ShadowPosition.X = Clamp(ShadowPosition.X * 0.5f + 0.5f, 0.0f, 1.0f);
ShadowPosition.Y = Clamp(ShadowPosition.Y * -0.5f + 0.5f, 0.0f, 1.0f);

int PosX = (int)(ShadowPosition.X * (float)(SizeX - 1));
int PosY = (int)(ShadowPosition.Y * (float)(SizeY - 1));

#if CONSOLE

Data = &Data[(PosY * DepthBuffer->DepthSurfacePitch) + (PosX * 4)]; // Data is the CPU memory containing the depth map
// Big endian on console - D24S8 depth/stencil buffer
unsigned int Val = (Data[0] << 16) + (Data[1] << 8) + Data[2];    // Remove stencil value
float DepthNDC = (float)Val / 16777215.0f; // 2^24 - 1 == 16777215.0f  (for 24bit depth )

#else

Data = &Data[(PosY * SizeX + PosX) * sizeof(Float16Color)];
float DepthDeviceFloat = ((Float16Color*)Data)->A; // Convert to float

// As inversion is not handled inside the projection matrix but in
// the shader we must invert here
if (UsesInvertedZ && ProjMatrix[3][3] < 1.0f) // Orthogonal projection is not inversed
{
    DepthDeviceFloat = 1.0f - DepthDeviceFloat;
}

#endif

Vector4 ReconstructedPositionWS = InverseViewProjection.TransformVector4(Vector4(PosNDC.X, PosNDC.Y, DepthNDC, 1.0f));
Particle->Location = ReconstructedPositionWS/ ReconstructedPositionWS.W;

上面的伪代码使用的深度贴图格式为D24S8的,这种格式可以借用硬件的PCF功能,使得结果更为平滑,如果深度贴图只用于被CPU所读取使用的话,也可以将之换成纯粹的深度数据格式。除此之外,考虑到设备兼容性,还需要对不同的格式有一个清晰的了解。

之后就可以根据位置生成雨花粒子了,不过想要模拟完全真实的雨花粒子是比较困难的,参考文献7给出了一种实现方法,总结如下:

雨花的产生是由于雨滴与水面的碰撞,导致水面产生一圈圆形的涟漪,涟漪中心的水面下降的幅度足够大的话,就会出现较强的反弹从而产生水花,而水花大致可以分成两种:一种是垂直于水面的王冠状水花,王冠状水花在于水花与水面还依然衔接在一起;另一种则是迅速与水面脱离的水滴状水花。

这里给出了YouTube上的两个使用高速相机捕捉到的水花生成视频。

简单来说,王冠水花需要有较厚的水面,且水花存在时长要介于10~20ms之间,否则就会成为水滴状水花。

水花的动态效果取决于很多因素,这些因素可以分成两大类:水面的材质属性(粗糙度,坚硬度Rigidity,湿度,倾斜度以及排水度),以及坠落水滴的属性(尺寸,速度等)。粗糙材质对于结果的影响幅度较大,王冠状水花的半径跟高度可以跟雨滴的属性关联在一起,而水滴状水花的水滴数量也跟雨滴下落的速度有关,水滴状水花的水滴分布可以用一个随机模型来描述,更详细的信息可以参考文献7.

游戏中通常不会将细节做得这么深入,比如前面说过的ATI的Demo就是直接使用一个公告板进行不同的缩放来模拟不同的水花。

一种较好的保存两种水花主要特征的实现方式为:使用一种通用的王冠形状,这个形状允许沿着高度跟半径进行缩放,在这个基础上增加一些水滴状水花。不过由于水花的效果通常是由美术同学控制,他们可能对物理特性不太了解也不太关注,考虑到这一点,John David Thornthon为《冰河世纪》提出了一种模块化的装配方式,通过这种方式可以在保留物理性的基础上让美术同学更好的进行效果的控制

最后,关于水花的分布函数,现实中水花的数目跟下坠的水滴数目应该是保持一致的,因此,这里简单的将雨效的强度跟水花的数目关联起来。

其他的水滴效果,比如雨停之后屋檐滑落的水滴以及树梢残留的水滴等导致的水花,可以另外用一套水花系统来控制,可以复用之前的雨景深度图,其他的实现方式都是一致的。

PS3上面,256x256分辨率的深度贴图绘制需要0.32ms,而强降雨情景下的水花渲染需要0.33ms,XBox360的数据分别为0.2ms跟0.25ms。

如果角色身上的雨花难以渲染,可以直接使用特效完成,问题在于可能无法将人物身上的雨花分布跟场景中的雨花分布保持一致。

在上述的实现中,没有考虑光照因素对水花效果的影响,导致在晚上等背光环境中,水花的效果看起来反常一般的亮眼,之前提到的Toy Shop Demo则是通过在vs中进行一次光照平均来减轻这个问题。

Rain/Raindrops

现实中的雨效比较复杂,现存有许多的研究成果。想要实现跟现实世界完全一样的雨效是比较困难的。雨幕在明亮的场景中不如在阴暗的环境中显眼。

雨滴是0.5~10mm左右大小的透明物体,小尺寸的雨滴接近球状,而大尺寸的则接近扁球状。

  • 雨滴的折射(165度固体角,6%的衰减)跟反射特性(亮度增强)

  • 雨滴的FOV远宽于被其遮挡的背景的FOV,导致其亮度与其所遮挡的背景的亮度的关联其实并不是很强烈。

  • 雨滴亮度不怎么受其他雨滴的影响,因为相对于雨滴本身张角覆盖的范围而言,此范围内的其他雨滴所占的面积基本上可以忽略。

由于雨滴的折射效果,想要正确的渲染雨滴,就需要渲染一个带有折射反射以及内部反射的球形,这就非常的不划算了。

雨滴下降过程中,会经历一个快速的形变扭曲。一颗雨滴的降落速度基本上是恒定的,小尺寸雨滴大概是3m/s,而大尺寸雨滴可以达到9M/S。由于视觉暂留效应,人眼或者相机捕捉到的雨滴降落的效果通常会带有运动模糊从而形成一条垂直的透明带,而这种带状细丝的形状也不是恒定不变的,通常会随着空间跟时间而变化,具体可以参考文献[8]

雨滴的效果跟很多参数有关。远处的降雨模型可以使用Simple Photometric Model,近处的要考虑的东西就老鼻子多了

想要在游戏中完全复刻现实中的雨效,在当前的硬件水平上是不太可能的。

因此游戏中实现雨效通常会考虑如下的两种方式:粒子系统或者大尺寸贴图。

粒子系统通常会使用方片模型来表示雨束。粒子系统模拟出来的雨效可以得到较好的动态效果,能够被风力所驱动且在GPU上的性能表现也很不错。通常为了提升性能,这个特效系统会直接挂在相机上,以减少需要管理的粒子数目,比如《太空潜艇》使用了一个view frustum的粒子生成器来实现。粒子系统的缺点在于可扩展性不太好,强降雨需要通过增加粒子数目来实现,这会导致性能受到较大影响。

大尺寸贴图方案则是通过对贴图进行uv动画来完成。这种方案没有粒子系统那种强降雨状态性能受损的不足,代价则是其效果表现不够真实(深度数据跟运动数据都不够)。Toy Shop Demo的是通过屏幕空间后处理完成的,通过对不同的uv变换速度以及深度的多层贴图采样来增强真实感(采样的时候会根据屏幕空间的背景像素的位置来决定使用那一层贴图)[3]。这种方案的缺陷在于当相机移动的时候,镜头的效果就会变得很蹩脚,比如当镜头朝下观看的时候,就会看到雨束是水平流动的了。

《Flight simulator 2004》这个游戏别出心裁的使用了两个椎体加上四层贴图来模拟雨束效果,很好的避免了前面提到的镜头移动导致的穿帮。这种做法在镜头向上或者向下观看的时候,可以看到雨束来自于一个起点或者回归到一个终点,跟平时透视投影的效果非常接近,增强了真实感。这里用到的四层贴图每一层的雨束都比上一层的雨束要小,且移动的速度也比上一层的更慢(毕竟距离更远,下降的高度也就更高了,不能同时落地),通过这种方式可以很好的模拟视差效果。

本文要介绍的方法跟《Flight simulator 2004》的方法比较接近。

将镜头前方的雨景区域分割成四层。

每一层都使用一张相同的提前进行过运动模糊处理的雨束贴图。

这里贴图所附着的模型不再是前面的两个椎体组成的形状了,而是下图所示的半椎体半柱体的形状。这个形状的中心与相机重合,渲染的时候使用的是内面。

参考文献12中会将雨效的透明度存储在顶点色中,从而实现对雨效透明度的调整,不过本文给出的方案并没有采取这种做法。

为了模拟雨滴坠落时的动态效果以及近大远小近疏远密的真实感,对于每一层贴图的采样都会根据速度与尺寸的不同使用不同的uv缩放,另外,为了模拟风力作用效果,还会对uv的采样方向进行旋转。

float2 SinT = sin(Time.xx * 2.0f * Pi / speed.xy) * scale.xy;
// rotate and scale UV
float4 Cosines = float4(cos(SinT), sin(SinT));
float2 CenteredUV = UV - float2(0.5f, 0.5f);
float4 RotatedUV = float4(dot(Cosines.xz*float2(1,-1), CenteredUV)
, dot(Cosines.zx, CenteredUV)
, dot(Cosines.yw*float2(1,-1), CenteredUV)
, dot(Cosines.wy, CenteredUV) ) + 0.5f);
float4 UVLayer12 = ScalesLayer12 * RotatedUV.xyzw;

为了能让雨效能表现出正确的遮挡效果(比如室内无雨),这里采用的方法是根据前面划分的区域,根据高度图选定对应的深度数值(如何选定?这里的高度图是为camera前的每一个区域所单独生成的,对高度图采样的目的是为了实现雨点前后错落的效果,制造更强的立体感)作为雨滴的深度,并根据射线方向重建雨滴的虚拟世界坐标,高度图最好通过程序的方式渐进生成。(如果只是选取个别点进行采样,感觉精度会有较大损失,而且在相机移动的时候,可能会出现雨效的频繁切换与抖动)

为了避免前面提到的生硬过渡问题,这里给出了软切的实现方法,即通过渐进式的调整雨滴的透明度来实现雨幕从无到有之间的切换,从而避免pop in/out的问题。这种方案的缺陷在于如果相机朝向地面,就会导致雨幕消失(因为雨幕所处的深度被地表所挡住,其实也是采样点数目过少导致的弊端)

对于室内雨景遮挡的实现,可以通过雨景深度图与采样点的位置进行比对来实现,通过硬件PCF的方式还能够提升效果跟效率。

出于对性能的考虑,这里只对前两层进行遮挡剔除计算,因此雨景深度贴图只需要覆盖前两层即可,没看懂伪代码中将occlusionDistance跟深度测试结果相乘的目的在哪里

// Background Pixel depth - in view space
float Depth = CalcSceneDepth(ScreenPosition);
// Layers Depth tests :
float2 VirtualDepth = 0;
// Constant are based on layers distance
VirtualDepth.x = tex2D(Heightmap, UVLayer12.xy).r * RainDepthRange.x + RainDepthStart.x;
VirtualDepth.y = tex2D(Heightmap, UVLayer12.zw).r * RainDepthRange.y + RainDepthStart.y;
// Mask using virtual position and the scene depth
float2 OcclusionDistance = saturate((Depth - VirtualDepth) * 10000.0f);
// Calc virtual position
float3 Dir = normalize(PixelPosition);   // Cylinder is link to camera
float3 VirtualPosition1WS = CameraPositionWS.xyz + Dir * DepthLayers.x;
float3 VirtualPosition2WS = CameraPositionWS.xyz + Dir * DepthLayers.y;
// Mask using virtual layer depth and the depth map
// RainDepthMapTest use the same projection matrix than
// the one use for render depth map
float2 Occlusion= 0;
Occlusion.x = RainDepthMapTest(VirtualWPos1);
Occlusion.y = RainDepthMapTest(VirtualWPos2);
Occlusion*= OcclusionDistance;

根据layer的距离与全分辨率深度buffer的关系,还会进行一次平滑的遮挡处理,以减轻前面使用低分辨率做遮挡测试带来的瑕疵。

// Depth is in view space
// RainDepthStart contain the start distance of each layer
// RainDepthRange contain the area size of each layer
float4 Mask = saturate((Depth - RainDepthStart) / RainDepthRange);

为了避免后面两层雨效出现重复纹理,以及出于效果优化的考虑,还会使用两张动态生成的低分辨率动画贴图来对效果进行叠加。

void RainLowPixelShader(...){  // Mask with magic values (detail are not provide as this is "artistic" feature)// Layer 3float2 NoiseUV = tex2D(DistortionTexture, DistoUV.xy).xy+ tex2D(DistortionTexture, DistoUV.zw).xy;    NoiseUV = NoiseUV * UV.y * 2.0f + float2(1.5f, 0.7f)*UV.xy+ float2(0.1f, -0.2f) * Time;       float LayerMask3 = tex2D(NoiseTexture, NoiseUV) + 0.32f;    LayerMask3 = saturate(pow(2.0f * Layer1, 2.95f) * 0.6f);    // Layer 4    float LayerMask4 = tex2D(NoiseTexture, BlendUV.xy)+ tex2D(NoiseTexture, BlendUV.zw) + 0.37f;// Background Pixel depth - in view spacefloat Depth = CalcSceneDepth(ScreenPosition);// Layers Depth tests :float2 VirtualDepth = 0;// Constant are based on layers distanceVirtualDepth.x = tex2D(Heightmap, UVLayer12.xy).r * RainDepthRange.x + RainDepthStart.x;VirtualDepth.y = tex2D(Heightmap, UVLayer12.zw).r * RainDepthRange.y + RainDepthStart.y;// Mask using virtual position and the scene depthfloat2 OcclusionDistance = saturate((Depth - VirtualDepth) * 10000.0f);// Calc virtual positionfloat3 Dir = normalize(PixelPosition);   // Cylinder is link to camerafloat3 VirtualPosition1WS = CameraPositionWS.xyz + Dir * DepthLayers.x;float3 VirtualPosition2WS = CameraPositionWS.xyz + Dir * DepthLayers.y;// Mask using virtual layer depth and the depth map// RainDepthMapTest use the same projection matrix than// the one use for render depth mapfloat2 Occlusion= 0;Occlusion.x = RainDepthMapTest(VirtualWPos1);Occlusion.y = RainDepthMapTest(VirtualWPos2);Occlusion*= OcclusionDistance;OutColor = float4(Occlusion.xy, LayerMask3, LayerMask4);
}
// Depth is in view space
float Depth = CalcSceneDepth(ScreenPosition);
// RainDepthMin contain the start distance of each layer
// RainDepthRange contain the area size of each layer
// RainOpacities allow to control opacity of each layer (useful with lightning
// or to mask layer)
float4 Mask = RainOpacities * saturate((Depth - RainDepthStart) / RainDepthRange);

float2 MaskLowUV = ScreenPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);
float4 MaskLow = tex2D(RainLowTexture, MaskLowUV);

float4 Values;
Values.x = tex2D(RainTexture, CylUVLayer1.xy);
Values.y = tex2D(RainTexture, CylUVLayer1.zw);
Values.z = tex2D(RainTexture, CylUVLayer2.xy);
Values.w = tex2D(RainTexture, CylUVLayer2.zw);

// The merge of all mask: occlusion, pattern, distance is perform here
float RainColor = dot(Values, Mask * MaskLow);

float3 FinalColor = RainColor.xxx * 0.09f * RainIntensity;

给美术同学提供了一个不透明度参数,可以允许自由的参数设定,此外,当闪电来临时,也可以调整这个参数生成更接近真实的效果。提供的雨效强度参数也可以用来制造不同程度的降雨效果,实现方式为对雨幕贴图上的雨点进行移除,因此雨幕贴图给出的应该是最大强度的降雨效果。

出于优化的考虑,整个过程分成两个pass完成,第一个是低分辨率pass,分辨率为原分辨率的1/4,第二个是全分辨率pass。全分辨率pass中会处理其他所有所有相关的后处理操作,比如motion blur,DOF,color grading,tonemapping等,从而避免重复劳动。

两种设备上的消耗大概为2ms左右。

这种实现的问题在于透过半透物体看到的雨效与透过完全透明物体看到的雨效是一样的。

可以考虑添加一张与雨幕贴图相匹配的强度贴图,用于实现对不同雨滴强度的精确控制

float3 SiTransmissionDirection (float fromIR, float toIR,
float3 incoming, float3 normal)
{
float eta = fromIR/toIR; // relative index of refraction
float c1 = -dot (incoming, normal); // cos(theta1)
    float cs2 = 1.-eta*eta*(1.-c1*c1); // cos^2(theta2)
    float3 v = (eta*incoming + (eta*c1-sqrt(cs2))*normal);
    if (cs2 < 0.) v = 0; // total internal reflection
    return v;
}

// Reimplemented - Not in the shaderX 5 article
float3 SiReflect (float3 view, float3 normal)
{
return 2*dot (view, normal)*normal - view;
}
// In the main shader:
// Retrieve the normal map normal for rain drops:
float3 vNormalTS = tex2Dproj( tBump, texCoord ).xyz;
vNormalTS = SiComputeNormalATI2N( vNormalTS );
// Compute normal in world space:
float3x3 mTangentToWorld = float3x3( normalize( i.vTangent ),
normalize( i.vBinormal ), normalize( i.vNormal ));
float3   vNormalWS       = normalize( mul( mTangentToWorld, vNormalTS ));
// Compute the reflection vector:
float3 vReflectionWS = SiReflect( vViewWS, vNormalWS );
(...)
// Environment contribution:
float3 cReflection = texCUBE( tEnvironment, vReflectionWS );
(...)
// Approximate fresnel term
float fFresnel = SiComputeFresnelApprox( vNormalWS, vViewWS );
// Compute refraction vector: 0.754 = 1.003 (air) / 1.33 (water)
float3 vRefractWS = SiTransmissionDirection( 1.003, 1.33, vViewWS, vNormalWS );
// Refraction contribution:
float3 cRefraction = texCUBE( tEnvironment, vRefractWS );
(...)
cResult = saturate( (cReflection * fFresnel * 0.25f) +
cRefraction * (1.0f - (fFresnel * 0.75 )));

这里给出了雨滴光照实现的一些优秀方法。

Droplets / wall glides / additional rain

除了雨效之外,雨水与场景的交互效果也很重要,这些效果通常都是由美术同学预先设定好的,不太适合根据雨水的强度进行动态设置(顶多使用一个blend进行fade in/out),Toy Shop demo给出了一些程序式的实现方法。

玻璃上的水流通过贴图采样+uv动画实现,贴图采样通常会使用不同的参数进行两次采样,之后在此基础上还会根据需要进行一次你去处理,考虑到折射效果,可能还需要跟环境贴图相结合。

玻璃上的水流通过贴图采样+uv动画实现,贴图采样通常会使用不同的参数进行两次采样,之后在此基础上还会根据需要进行一次你去处理,考虑到折射效果,可能还需要跟环境贴图相结合。

屋檐的水滴通常是由特效来实现,目标是最好能做到通用。

这个效果的实现方式跟玻璃上的水流一致,区别在于需要考虑水流侵入墙体的视觉感。

根据情况是使用特效面片还是使用网格,网格效果更好且特效面片在有些情况中可能会造成较多的overdraw?(是因为动态效果需要多层叠加的原因吧)

根据情况采用不同的雨效实现方案

Camera droplets

镜头雨效是增强真实感的重要元素,当镜头往上抬起来的时候,就需要考虑镜头受雨点影响带来的效果变化,一种方法是通过后处理来跟一个扭曲贴图进行blend,blend需要考虑镜头方向的变化,这种方法的弊端在于:
1.效果不够动态
2.消耗较高(全屏)

这里给出的第二种方案是屏幕粒子系统,粒子系统具有可控性好渲染性能高的优点。

屏幕空间粒子系统的实现不是很好做,一种可行的方法是将粒子系统贴在相机空间的近平面上, 并将之转换函数跟相机相绑定,这种方法的缺陷在于更改FOV之后会导致效果的变化(就像屏幕被拉伸一样),不过这种影响基本上是可以忽略的,伪代码给出如下:

if (ParticleSystem->UseAsCameraLensEffect())
{
// View matrix transform from world to view.
// Here we want that's the view transform
// has no effect as if we were spanwing in view space.
// So use inverse of the view matrix.
ParticleSystem->LocalToWorld = View->InvViewMatrix;
}

出于对性能的考虑,上面还进行了特效的裁剪。

整个效果大概需要花费0.4ms左右

Rain effects control panel

这里给出供美术同学使用的一些参数的定义,详情请参见原文。

这里给出了雨滴相关参数与降水量的一个关联公式,其中重要的是不同参数选择对于曲线的影响:

现实世界中的雨滴尺寸实际上是不一致的,存在一定的分布规律,且雨的强度越大,分布的范围也就越广。

游戏中,前面给出的一些规律没有太大的使用价值,这里给出的实现方式为根据雨效强度线性提升雨滴数目,主要分成三类雨:

这里给出的参数可以根据雨效强度进行插值。

Reference

[1] Barrero, “Relic’s FX system: Making big battles comes Alives”, http://www.slideshare.net/proyZ/relics-fx-system

[2] Tatarchuck, “Artist directable real-time rain rendering in city environments ” http://www.ati.com/developer/gdc/2006/GDC06-Advanced_D3D_Tutorial_Day-Tatarchuk-Rain.pdf

[3] Tatarchuck, “Rendering Multiple Layers of Rain with a Post-Processing Composite Effect”, ShaderX 5

[4] Tatarchuck, “Artist-Directable Real-Time Rain Rendering in City Environments”, http://developer.amd.com/wordpress/media/2012/10/Tatarchuk-Isidoro-Rain%28EGWNph06%29.pdf

[5] Alexander, Johnson, “The Art and Technology Behind Bioshock’s Special Effects”, http://gdcvault.com/play/289/The-Art-and-Technology-Behind

[6] Garg, K. Nayar, “Photometric Model of a Rain Drop”, http://www1.cs.columbia.edu/CAVE/publications/pdfs/Garg_TR04.pdf

[7] Garg, Krishnan, K. Nayar , “Material Based Splashing of Water Drops”, http://www1.cs.columbia.edu/CAVE/projects/mat_splash/

[8] Garg, K. Nayar , “Photorealistic Rendering of Rain Streaks”, http://www1.cs.columbia.edu/CAVE/projects/rain_ren/rain_ren.php

[9] Thornton, “Directable Simulation of Stylized Water Splash Effects in 3D Space”, http://nguyendangbinh.org/Proceedings/Siggraph/2006/cd2/content/sketches/0106-thornton.pdf

[10] Persson, “Graphics Gems for Games – Findings from Avalanche Studios”, http://www.humus.name/index.php?page=Articles

[11] Garg, Krishnan, K. Nayar , “Vision and Rain”, http://www1.cs.columbia.edu/CAVE/publications/pdfs/Garg_IJCV07.pdf

[12] Wang, Wade, “Rendering falling rain and sown”, http://www.ofb.net/~niniane/rainsnow/rainsnow-sketch.pdf

[13] Tariq, “Rain”, http://developer.download.nvidia.com/SDK/10/direct3d/Source/rain/doc/RainSDKWhitePaper.pdf

[14] Feng, Tang, Dong, Chou, “Real-Time Rain Simulation”

[15] Glossary of Meteorology, http://amsglossary.allenpress.com/glossary/search?id=rain1

[16] Rain – Wikipedia, http://en.wikipedia.org/wiki/Rain

[17] Marshall, Palmer, “The distribution of raindrops with size”, http://journals.ametsoc.org/doi/pdf/10.1175/1520-0469%281948%29005%3C0165%3ATDORWS%3E2.0.CO%3B2

[18] Kircher, “Lighting & Simplifying Saints Row: The Third”, http://twvideo01.ubm-us.net/o1/vault/gdc2012/slides/Programming%20Track/Kircher_Lighting_and_Simplifying_Saints_Row_The_Third.pdf

[19] NASA, “Precipitation, Fog And Icing”, https://standards.nasa.gov/released/1001/1001_7.pdf

[20] Rousseau, Jolivet, Ghazanfarpour, “Realistic real-time rain rendering”, http://www.pierrerousseau.fr/rain.html

你可能感兴趣的:(【Water Drop系列】2a – Dynamic rain and its effects)