基本思路:根据环境(风,光,基础运动)动态生成一张置换贴图用于倒影变化
Flash版本关键代码如下:
声明置换滤镜
//mapBitmap 基于这个对象扭曲目标图像
//mapPoint 一个值,它包含目标显示对象的左上角相对于映射图像左上角的偏移量。
//componentX 通过这个参数将mapBitmap中的颜色或alpha通道设置为映射图像,并基于这个映射图像在x轴方向置换像素。
//componentY 通过这个参数将mapBitmap中的颜色或alpha通道设置为映射图像,并基于这个映射图像在y轴方向置换像素。
//scaleX 这个参数定义了应用置换后在x轴上的偏移量,数值越大,置换效果越明显
//scaleY 这个参数定义了应用置换后在y轴上的偏移量,数值越大,置换效果越明显
//mode 定义滤镜效果超出图像大小时,该部分像素的处理方式。它的可能值为DisplacementMapFilterMode常量。
// WRAP表示置换值折返到图像的另一侧;
// CLAMP的意义和ConvolutionFilter一样,表示置换值重复图像边缘的像素;
// IGNORE表示忽略置换并使用源像素;
// COLOR同样和ConvolutionFilter类似,用指定的颜色和透明度替换置换值。
//color 指定对于超出范围的替换应用什么颜色。置换的有效范围是 0.0 到 1.0。如果 mode 设置为 DisplacementMapFilterMode.COLOR,则使用此参数。
//alpha 指定对于超出范围的替换应用什么 Alpha 值。它被指定为 0.0 到 1.0 之间的标准值。例如,0.25 设置透明度值为 25%。如果 mode 设置为 DisplacementMapFilterMode.COLOR,则使用此参数。
//ex: Offx = (mapBitmap[componentX][x,y]-128)/128*scaleX;
displacementFilter = new DisplacementMapFilter(displacementBitmap, zeroPoint, 1, 2, 256, 256, DisplacementMapFilterMode.COLOR, baseColor, 0.5);
每帧逻辑,加入了风和夜色的影响:
override public function update():void
{
timer += FlxG.elapsed;
if (weather.changed > weatherChanged)
{
//interpolateColor 插值运算 baseColor*(1- weather.darkness)+ weather.darknessColor*weather.darkness
currentBase = 0xFF000000 | Utils.interpolateColor(baseColor, weather.darknessColor, weather.darkness)
//涟漪幅度 WIND_RIPPLE_MULTIPLIER:Number = 25;
var rippleScale:int = int(weather.wind*WIND_RIPPLE_MULTIPLIER);
var xscale:int = rippleScale/2;
var yscale:int = rippleScale;
if(xscale != 2)
xscale = xscale;
//噪声矩阵 rMul, gMul, bMul, aMul, rOff, gOff, bOff, aOff NOISE_BIAS:int = 100;
noiseRange = new ColorTransform(xscale/128,yscale/128,1,1,(128-xscale+(NOISE_BIAS*xscale/128)),(128-yscale+(NOISE_BIAS*yscale/128)),1,1)
weatherChanged = weather.t
}
}
渲染部分:
BitmapData.perlinNoise(baseX:Number, baseY:Number, numOctaves:uint, randomSeed:int, stitch:Boolean, fractalNoise:Boolean, channelOptions:uint=7, grayScale:Boolean=false, offsets:Array=null):void
是FLASH自带的生成柏林噪声的方法。生成多张噪点图叠加得到最终的结果。
baseX, baseY分别是沿X,Y轴生成的杂点频率。
numOctaves 噪点图的张数。
randomSeed 要使用的随机种子数。如果您保持使所有其他参数不变,可以通过改变随机种子值来生成不同的伪随机结果。
stitch 一个布尔值。如果该值为 true,则该方法将尝试平滑图像的转变边缘以创建无缝的纹理,用于作为位图填充进行平铺。
fractalNoise 一个布尔值。如果该值为 true,则该方法将生成碎片杂点;否则,它将生成湍流。带有湍流的图像具有可见的不连续性渐变,可以使其具有更接近锐化的视觉效果,例如火焰或海浪。
channelOptions 一个数字,可以是四个颜色通道值(BitmapDataChannel.RED、BitmapDataChannel.BLUE、BitmapDataChannel.GREEN 和 BitmapDataChannel.ALPHA)的任意组合。您可以使用 logical OR (|) 运算符来组合通道值。
grayScale 一个布尔值。如果该值为 true,则通过将红色、绿色和蓝色通道的每一个值都设置为相同的值来创建一个灰度图像。如果此值设置为 true,则 Alpha 通道值将不会受到影响。
offsets 与每个 octave 的 x 和 y 偏移量相对应的点数组。通过操作这些偏移量值,您可以平滑滚动 perlinNoise 图像的图层。偏移数组中的每个点将影响一个特定的 octave 杂点函数。
override public function draw():void
{
if (timer > 0.1)
{ // Update the water ripple
perlinOffset.y += 1/5;
perlinOffset.x = FlxG.camera.scroll.x*1.5;
displacementBitmap.perlinNoise(32, 4, 1, 12312, false, false, 1|2, true, [perlinOffset]);
displacementBitmap.colorTransform(rect,noiseRange);
// Adjust the base color according to the weather.
displacementFilter.color = currentBase;
timer = 0;
}
var px:BitmapData = pixels;
matrix.identity();
matrix.scale(1, -1);
getScreenXY(_point);
matrix.translate(-_point.x, _point.y);
// Clear the reflection
px.fillRect(rect, currentBase);
Utils.gradientOverlay(px, [0x00000000,0x66000000], 90, 4);
// Flip the screen and copy it to the reflection
px.draw(FlxG.camera.buffer, matrix, transform);
// Draw the lights
var l:Light;
for (var i:int = 0; i < lights.length; i++){
l = lights.members[i] as Light;
l.getScreenXY(_point);
if(l.visible && -64 < _point.x && _point.x < FlxG.width + 64){
l.reflected.alpha = weather.darkness * 0.8;
l.reflected.alpha *= Math.min(1.0, (weather.wind * 10));
l.reflected.drawFrame();
stamp(l.reflected, _point.x - l.reflected.width/2 + 4, (y - l.y) * 0.3);
}
}
// Apply the ripple filter
px.applyFilter(px, rect, zeroPoint, displacementFilter);
dirty = true;
super.draw()
}
unity中的水实现思路:
1.在每个像素点上用Mathf.Perlin(float x, float y)
产生随机值,生成柏林噪声贴图
2.通过shader取贴图通道值作变换
void CalcNoise(float dx, float dy)
{
float y = 0.0F;
while(y < noiseTex.height)
{
float x = 0.0F;
float sample = 0;
while(x < noiseTex.width)
{
float xCoord = x / noiseTex.width * scale;
float yCoord = y / noiseTex.height * scale;
sample = Mathf.PerlinNoise(xCoord + dx, yCoord + dy);//*_force;
pix[(int)(y * noiseTex.width + x)] = new Color(sample, sample, sample);
x++;
}
y++;
}
noiseTex.SetPixels(pix);
noiseTex.Apply();
}
float n = 0;
void Update()
{
n += deltaY;
CalcNoise(0, n);
rend.material.SetTexture("_NoiseTex", noiseTex);
}
比较特殊的一点是因为游戏是像素风的,在取坐标的时候取了下整,保证同一个像素格内的偏移是gg
Shader "ProjZombie/WaterSprite"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
_NoiseTex ("Noise Texture (RG)", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_HeatForce ("Heat Force", range (0,0.1)) = 0.1
_width ("Heat Force", range (1.0,1024.0)) = 256.0
_height ("Heat Force", range (1.0,1024.0)) = 256.0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
// No culling or depth
ZWrite Off
Cull Off
Blend Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
fixed4 _Color;
sampler2D _NoiseTex;
float _HeatForce;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.color = v.color * _Color;
#ifdef PIXELSNAP_ON
o.vertex = UnityPixelSnap (OUT.vertex);
#endif
return o;
}
sampler2D _MainTex;
float _width;
float _height;
//It is executed for each pixel and the output is the color info of the pixel.
fixed4 frag (v2f i) : SV_Target
{
half4 offsetColor1 = tex2D(_NoiseTex, i.uv);
i.uv.x = ceil(i.uv.x*_width)/_width;
i.uv.y = ceil(i.uv.y*_height)/_height;
i.uv.x += offsetColor1.r * _HeatForce;
i.uv.y += offsetColor1.g * _HeatForce;
fixed4 col = tex2D( _MainTex, i.uv);
col.rgb *= col.a;
return col;
}
ENDCG
}
}
}
网友评论