Unity Compute Shader - 8 使用噪点图

在之前的章节中,我们生成环形、生成随机运动的小球,都是用了一种伪随机的方式,即每次只要输入的种子数值相同,那么得到的结果就是相同的。在Compute Shader中,还有一种获取随机数的方法,就是使用噪点图
在日常生活中,我们看到的很多东西都可以称为噪点图,例如斑驳的金属表面、树木的表面等,这些噪点图提供了更加自然的随机值。本章中我们就尝试在Compute Shader里生成和使用噪点图

生成噪点图

创建Compute Shader名称为 Noise,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma kernel CSMain

RWTexture2D<float4> Result;
int texResolution;
float time;

float random(float2 pt, float seed)
{
const float a = 12.9898;
const float b = 78.233;
const float c = 43758.543123;
return frac(sin(dot(pt, float2(a, b)) + seed) * c);
}

[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
float4 white = 1;
Result[id.xy] = random(((float2) id.xy) / (float) texResolution, time) * white;
}

创建C#脚本名称为 Noise,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using UnityEngine;
using System.Collections;

public class SimpleNoise : MonoBehaviour
{

public ComputeShader shader;
public int texResolution = 256;

private Renderer rend;
private RenderTexture outputTexture;

private int kernelHandle;

private void Start()
{
outputTexture = new RenderTexture(texResolution, texResolution, 0);
outputTexture.enableRandomWrite = true;
outputTexture.Create();

rend = GetComponent<Renderer>();
rend.enabled = true;

InitShader();
}

private void InitShader()
{
kernelHandle = shader.FindKernel("CSMain");

shader.SetInt("texResolution", texResolution);
shader.SetTexture(kernelHandle, "Result", outputTexture);

rend.material.SetTexture("_MainTex", outputTexture);
}

private void DispatchShader(int x, int y)
{
shader.SetFloat("time", Time.time);
shader.Dispatch(kernelHandle, x, y, 1);
}

private void Update()
{
DispatchShader(texResolution / 8, texResolution / 8);
}
}

将C#代码挂载到Quad上,添加Compute Shader引用,运行后会有一种老式电视机无信号时的效果,如下:

我们还可以改变Compute Shader方法 random 中a、b、c的值,使画面有相应的改变,比如 a = 3; b = 3; c = 10 时,生成的图像会变为如下效果:

我们也注意到了,这样虽然生成了很多随机噪点,但是这些噪点之间会有很明显的边界,并不是平滑过渡的,所以像这样的噪点图在实际中没没有太大的使用价值

在上世纪80年代,Camplin被委托为科幻电影Tron制作更好的模型和特效贴图纹理,他的解决方案就是创造一个平滑变化的量,其返回值具有随机性,而且随着X和Y分量的变化,这个值是平滑变化的

相似的平滑噪点图的生成算法,在制作Shader时时非常有用的,代码库在最下方的参考链接中已经给出,这里,我们可以将 noiseSimplex.cginc 文件拖入unity工程中,在Compute Shader里直接使用

制作木质纹理

接下来,我们就尝试使用这个库来制作一个平滑变化的木纹理,创建C#和Compute Shader脚本,名称都为ProceduralWood,C#脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using UnityEngine;

public class ProceduralWood : MonoBehaviour
{

public ComputeShader shader;
public int texResolution = 256;

private Renderer rend;
private RenderTexture outputTexture;

private int kernelHandle;

public Color paleColor = new Color(0.733f, 0.565f, 0.365f, 1);
public Color darkColor = new Color(0.49f, 0.286f, 0.043f, 1);
public float frequency = 2.0f;
public float noiseScale = 6.0f;
public float ringScale = 0.6f;
public float contrast = 4.0f;

private void Start()
{
outputTexture = new RenderTexture(texResolution, texResolution, 0);
outputTexture.enableRandomWrite = true;
outputTexture.Create();

rend = GetComponent<Renderer>();
rend.enabled = true;

kernelHandle = shader.FindKernel("CSMain");

shader.SetInt("texResolution", texResolution);

DispatchData();
}

private void DispatchData()
{
shader.SetVector("paleColor", paleColor);
shader.SetVector("darkColor", darkColor);
shader.SetFloat("frequency", frequency);
shader.SetFloat("noiseScale", noiseScale);
shader.SetFloat("ringScale", ringScale);
shader.SetFloat("contrast", contrast);

shader.SetTexture(kernelHandle, "Result", outputTexture);

rend.material.SetTexture("_MainTex", outputTexture);

shader.Dispatch(kernelHandle, texResolution / 8, texResolution / 8, 1);
}

private void Update()
{
if (Input.GetKeyUp(KeyCode.A))
{
DispatchData();
}
}
}

Compute Shader脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma kernel CSMain

RWTexture2D<float4> Result;
int texResolution;

float4 paleColor;
float4 darkColor;
float frequency;
float noiseScale;
float ringScale;
float contrast;

#include "noiseSimplex.cginc"

[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
float3 pos = (((float3) id) / (float) texResolution) * 2.0;
float n = snoise(pos);
float ring = frac(frequency * pos.z + noiseScale * n);

ring *= contrast * (1.0 - ring);

float delta = pow(abs(ring), ringScale) + n;

Result[id.xy] = lerp(darkColor, paleColor, delta);
}

这里注意,我们使用了 #include "noiseSimplex.cginc"语句来将之前导入到项目的 noiseSimplex.cginc脚本添加了引用,这样才可以使用 snoise 函数

此时我们挂载好C#脚本,添加Compute Shader引用,运行后就能看到生成了一个随机平滑变化的木质纹理,我们还可以在inspector面板调整其参数后,按下键盘A键,改变其纹理外观。效果如下:

创建大理石纹理

接下来,我们可以使用经典的Perlin Noise制作一个大理石纹理,创建C#和Compute Shader脚本,名称为 ProceduralMarble,C#脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using UnityEngine;

public class ProceduralMarble : MonoBehaviour
{
public ComputeShader shader;
public int texResolution = 256;

private Renderer rend;
private RenderTexture outputTexture;

private int kernelHandle;
private bool marble = true;

private void Start()
{
outputTexture = new RenderTexture(texResolution, texResolution, 0);
outputTexture.enableRandomWrite = true;
outputTexture.Create();

rend = GetComponent<Renderer>();
rend.enabled = true;

InitShader();
}

private void InitShader()
{
kernelHandle = shader.FindKernel("CSMain");

shader.SetInt("texResolution", texResolution);
shader.SetTexture(kernelHandle, "Result", outputTexture);

rend.material.SetTexture("_MainTex", outputTexture);

shader.SetBool("marble", marble);
marble = !marble;

shader.Dispatch(kernelHandle, texResolution / 8, texResolution / 8, 1);
}

private void Update()
{
if (Input.GetKeyUp(KeyCode.A))
{
shader.SetBool("marble", marble);
marble = !marble;
shader.Dispatch(kernelHandle, texResolution / 8, texResolution / 8, 1);
}
}
}

Compute Shader脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma kernel CSMain

RWTexture2D<float4> Result;
int texResolution;
int marble;

#include "noiseSimplex.cginc"

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
float3 pos = (((float3)id)/(float)texResolution) * 2.0;
float scale = 800.0;
float3 color;
float noise;

pos *= scale;

if (marble){
float d = perlin(pos.x, pos.y) * scale;
float u = pos.x + d;
float v = pos.y + d;
d = perlin(u, v) * scale;
noise = perlin(pos.x + d, pos.y + d);
color = float3(0.6 * (float3(2,2,2) * noise - float3(noise * 0.1, noise * 0.2 - sin(u / 30.0) * 0.1, noise * 0.3 + sin(v / 40.0) * 0.2)));
}else{
noise = perlin(pos.x, pos.y);
color = float3(1,1,1) * noise;
}

Result[id.xy] = float4( color, 1.0 );
}

在Compute Shader中,我们使用了3次 perlin 函数来进行图像的采样,最后将颜色混合起来,得出了如下效果:

运行后,我们可以按下键盘A键,查看值进行一次采样时的 perlin 函数生成的效果

在本章中,我们了解了怎样使用外部的代码库和使用 perlin 函数多次采样生成不同种类的连续平滑变化的噪点图,在下一章中,我们结合前几章和本章的内容,做一个带遮罩的噪点纹理

参考链接

Noise for GLSL 1.20

webGL demo

noiseSimplex.cginc