Unity Shader 夜视仪效果实现思路


夜视仪效果

在玩射击类游戏的时候,有时候玩家可以开启夜视仪的功能,这里总结一下这种夜视效果的实现方法,同理,刺客信条里的鹰眼效果,也是类似的做法,使用屏幕后处理的方法去做即可。对于夜视仪的效果:

要实现的就是三个东西:整体的色调改变、闪烁的黑色噪点、屏幕扭曲(畸变)。

在顶点着色器里,要先将剪裁空间顶点坐标转变为屏幕空间坐标:

1
o.screenPos = ComputeScreenPos(o.vertex);

镜头畸变

去百度就可以搜到好几种常用的镜头畸变方法,这里使用桶形畸变的方法,直接使用别人总结出来的数学公式即可,就是先把纹理坐标的原点从左下角移到中心,将纹理坐标基于距离进行扭曲之后再移回左下角:

1
2
3
4
5
fixed2 center = i.uv - 0.5;
half radius = pow(center.x, 2) + (center.y, 2);
half distortion = 1 + sqrt(radius) * radius * _Distortion;
half2 uvScreen = center * distortion * _Scale + 0.5;
fixed4 screen = tex2D(_MainTex, uvScreen);

画面亮度饱和度颜色

1
2
3
4
5
6
7
8
9
screen *= _Brightness;

fixed4 luminance = Luminance(screen.rgb);//经验公式得到色彩明度
screen = lerp(luminance, screen, _Saturation);

fixed4 grey = fixed4(0.5,0.5,0.5,1);//得到0对比度的值
screen = lerp(grey, screen, _Contrast);//做插值

screen *= _VisionColor;

闪烁的黑色噪点

使用一个噪点图,然后对它进行采样:

1
2
3
4
5
6
7
float2 uvNoise = i.uv * _NoiseAmount;
//闪烁效果,在C#脚本里面对RandomValue取随机值
uvNoise.x -= sin(_RandomValue);
uvNoise.y += sin(_RandomValue + 1);

fixed noise = tex2D(_NoiseMap, uvNoise).r;
screen *= noise;

对于这个噪点图我们不一定需要美术去帮我们做,也可以自己生成,我们也能自己去控制噪点的大小以及密集程度。首先使用柏林噪声去生成一个噪点图:

1
2
3
4
5
6
7
8
9
10
11
12
float[,] noiseMap = new float[mapWidth, mapHeight];
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
float sampleX = x / noiseScale;//控制噪声密集程度
float sampleY = y / noiseScale;

float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
noiseMap[x, y] = perlinValue;
}
}

然后根据这里的值(范围是0~1),去生成ColorMap,对于每一个点的像素,我们都用一个从黑色到白色的lerp,lerp的值就是刚刚存的噪点值:

1
2
3
4
5
6
7
8
9
Color[] colorMap = new Color[mapWidth * mapHeight];
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
colorMap[y * mapWidth + x] = Color.Lerp(Color.black, Color.white, noiseMap[x, y]);
}
}
return colorMap;

最后就是生成一个噪点图texture,然后在着色器里面把它拿去采样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Texture2D generatedTexture;

public void GenerateTexture()
{
Color[] colorMap = new Color[textureHeight * textureWidth];
colorMap = GenerateNoiseTexture(textureWidth, textureHeight, noiseScale);

generatedTexture = new Texture2D(textureWidth, textureHeight);
generatedTexture.filterMode = FilterMode.Bilinear;
generatedTexture.wrapMode = TextureWrapMode.Repeat;
generatedTexture.SetPixels(colorMap);
generatedTexture.Apply();
}

private void Start()
{
//获取屏幕Quad的宽和高,作为NoiseTexture的大小
textureWidth = Screen.width;
textureHeight = Screen.height;
GenerateTexture();
}

对于Texture2D.SetPixels方法,Unity对它的描述是:

“This function takes a color array and changes the pixel colors of the whole mip level of the texture. Call Apply to actually upload the changed pixels to the graphics card”

它接收的是一个一维数组,返回的数组放在2D的数组中,像素被从左到右,从上到下的方式排列。数组的大小是这个mip等级的宽乘以高。这也对应了我们之前设置ColorMap的方式。

暗角效果

很多情况下为了给人是从夜视仪的视角去看,还可以做一个暗角效果,就是让屏幕的四个角边缘渐变成黑色:

1
2
3
half circle = distance(i.screenPos.xy, fixed2(0.5, 0.5));
fixed vignette = 1 - saturate(pow(circle, _VignetteFalloff));
screen *= pow(vignette, _VignetteIntensity);

首先求得每一个像素到屏幕中心的距离,这个数值越往外面越大,可以使用VignetteFalloff去控制从黑到白的渐变范围,然后要把数值限制在0~1内,因为中心是0,所以要把数值反转一下,最后再对这个暗角值做乘方,就可以控制它的漆黑程度。

最后效果:

哎感觉hexo在文章里放视频好麻烦,这样有点看不出黑色噪点闪烁的效果。。。


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC