在之前的章节中,我们生成环形、生成随机运动的小球,都是用了一种伪随机的方式,即每次只要输入的种子数值相同,那么得到的结果就是相同的。在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