用Compute Shader生成噪声图

用Compute Shader生成噪声图

效果展示

老规矩先来张效果图吧(今天又学到一个无用的小知识,上传图片把问号到后面的代码删掉就可以得到一个没有水印的图片了——CSDN 博客添加无水印图片的方法)。
在这里插入图片描述
这里用了Compute Shader来生成噪声,并且可以保存到本地,目前的缺陷是生成的噪声没有实现平铺,并且只有2D的噪声,不知道以后会不会补上。
虽然网上已经有珠玉在前,这里还是简单地讲一下各个噪声的计算原理,方便日后自己回看。

Perlin 噪声

在这里插入图片描述
其用晶体格的方式生成,相信下面这张图片在各个讲柏林噪声都能看到,因为这个图就已经把原理展示得很明了了,我这里借用一下。
在这里插入图片描述
输入一个点,我们找到和它相邻的那些晶格顶点(二维下有4个),计算该点到各个晶格顶点的距离向量,再分别与顶点上的梯度向量做点乘,得到4个点乘结果(这里只关注二维)。
使用缓和曲线(ease curves)来计算它们的权重和。在原始的Perlin噪声实现中,缓和曲线是 S ( t ) = 3 t 2 − 2 t 3 S\left ( t\right )=3t^{2}-2t^{3} S(t)=3t22t3,在2002年的论文6中,Perlin改进为 S ( t ) = 6 t 5 − 15 t 4 + 10 t 3 S\left ( t\right )=6t^{5}−15t^{4}+10t^{3} S(t)=6t515t4+10t3。——【图形学】谈谈噪声

代码和网上其它的柏林噪声生成代码一致:

float2 hash22(float2 p)
{
    p = float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)));
    return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
}

float Perlin(float2 p)
{
    float2 pi = floor(p);//返回小于等于x的最大整数。
    float2 pf = frac(p);//返回输入值的小数部分。
    
    //float2 w = pf * pf * (3.0 - 2.0 * pf);
    float2 w = pf * pf * pf * (6 * pf * pf - 15 * pf + 10);
     
    return lerp(lerp(dot(hash22(pi + float2(0.0, 0.0)), pf - float2(0.0, 0.0)),
                    dot(hash22(pi + float2(1.0, 0.0)), pf - float2(1, 0.0)), w.x),
                lerp(dot(hash22(pi + float2(0.0, 1.0)), pf - float2(0.0, 1.0)),
                    dot(hash22(pi + float2(1.0, 1.0)), pf - float2(1.0, 1.0)), w.x), w.y);
}

Value 噪声

在这里插入图片描述
Value 噪音算法比起柏林噪音,少了一个梯度的概念,因此会更容易理解。——如何生成一张 Value Noise 算法图片(包括 Perlin Noise)
主要思想就是计算出输入点相对于四个晶格顶点的插值,并使用缓和曲线来平滑结果
代码如下:

float2 hash21(float2 p)
{
    float h = dot(p, float2(127.1, 311.7));
    return -1.0 + 2.0 * frac(sin(h) * 43758.5453123);
}

float ValueNoise(float2 p)
{
    float2 pi = floor(p);
    float2 pf = frac(p);
    
    //float2 w = pf * pf * (3.0 - 2.0 * pf);
    float2 w = pf * pf * pf * (6 * pf * pf - 15 * pf + 10);
     
    return lerp(lerp(hash21(pi + float2(0.0, 0.0)),hash21(pi + float2(1.0, 0.0)), w.x),
                lerp(hash21(pi + float2(0.0, 1.0)), hash21(pi + float2(1.0, 1.0)), w.x),w.y);
}

Simplex 噪声

在这里插入图片描述
Simpex噪声比其他三种噪声的生成方式都要复杂,我也理解的不是很清楚,还是推荐去看看冯乐乐的【图形学】谈谈噪声

代码如下:

float SimplexNoise(float2 p)
{
    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
    const float K2 = 0.211324865; // (3-sqrt(3))/6;
    
    float2 i = floor(p + (p.x + p.y) * K1);
    
    float2 a = p - (i - (i.x + i.y) * K2);
    float2 o = (a.x < a.y) ? float2(0, 1) : float2(1, 0);
    float2 b = a - o + K2;
    float2 c = a - 1 + 2 * K2;
    
    float3 h = max(0.5 - float3(dot(a, a), dot(b, b), dot(c, c)), 0);
    float3 n = pow(h, 4) * float3(dot(a, hash22(i)), dot(b, hash22(i + o)),
        dot(c, hash22(i + 1)));
    
    return dot(float3(70, 70, 70), n);
}

Worley 噪声

在这里插入图片描述
Worley 噪声,又名Voronoi/Cell噪音。

Worley算法分六步:
1、确定输入点所在的晶胞
2、对该晶胞产生可重复的随机数生成器
3、确定在该晶胞内生成特征点的数量
4、随机的将3所生成的特征点放置到晶胞中
5、计算输入点到该晶胞内所有特征点的距离最小值
6、查找并计算输入点到所在晶胞直接相邻晶胞内特征点的距离最小值,并与5中生成的最小值比较,返回最小值

——Worley Noise(一)

代码如下:

float2 random2(float2 p)
{
    return frac(sin(float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)))) *
        43758.5453);
}

float Worley(float2 p)
{
    float min_dist = 1000;
    float2 pi = floor(p);
    float2 pf = frac(p);
    float num = scale / 10;
    
    for (int m = -1; m <= 1; m++)
    {
        for (int n = -1; n <= 1; n++)
        {
            float2 sp = (pi + float2(m, n));
            float2 pos = 0;
            float factor = num;
            pos = (sp + factor) % factor;
            
            sp += random2(pos);
            float dist = distance(p, sp);
            min_dist = min(min_dist, dist);
        }
    }
    return min_dist;
}

这里我试了一下想生成可以平铺的Worley噪声,但是只有在某些Scale值下才可以平铺,怎么试都不太对,以后再研究研究。

所有代码

C#:

using System.IO;
using UnityEditor;
using UnityEngine;

public class NoiseTool : EditorWindow
{
    [MenuItem("Tools/噪声工具")]
    static void AddWindow()
    {
        Rect wr = new Rect(0, 0, 400, 600);
        NoiseTool window = (NoiseTool)EditorWindow.GetWindowWithRect(typeof(NoiseTool), wr, true, "Noise Tool");
        window.Show();
    }

    public enum NoiseType { Perlin, Value, Simplex, Worley };
    public enum GenerationMode { None, Abs, Sin };
    public enum TextureSize {x64=64,x128=128, x256=256,x512=512,x1024=1024,x2048=2048};

    private ComputeShader computeShader;
    private NoiseType noiseType = NoiseType.Perlin;
    private GenerationMode generation = GenerationMode.None;
    private RenderTextureFormat format=RenderTextureFormat.ARGB32;
    private TextureSize size = TextureSize.x512;
    private float scale = 10f;

    RenderTexture renderTexture;
    int kernel;
    Texture2D texture;
    string path = "Assets/test.tga";

    private void OnGUI()
    {
        computeShader = EditorGUILayout.ObjectField("Compute Shader:", computeShader,
            typeof(ComputeShader), true) as ComputeShader;
        noiseType =(NoiseType)EditorGUILayout.EnumPopup("噪声类型:", noiseType);
        generation = (GenerationMode)EditorGUILayout.EnumPopup("生成类型:", generation);
        format = (RenderTextureFormat)EditorGUILayout.EnumPopup("贴图类型:", format);
        size=(TextureSize)EditorGUILayout.EnumPopup("贴图大小:", size);
        scale = EditorGUILayout.Slider("噪声大小:",scale, 1f, 40f);

        if (GUILayout.Button("生成噪声图"))
        {
            if(computeShader==null)
            {
                ShowNotification(new GUIContent("Compute Shader 不能为空!"));
            }
            else
            {
                Init();
            }
        }
        if(renderTexture!=null)
        {
            int x = 390;
            Rect rect = new Rect(5, 180, x, x);
            GUI.DrawTexture(rect, renderTexture);
        }
        if (GUILayout.Button("保存"))
        {
            if(renderTexture == null)
            {
                ShowNotification(new GUIContent("贴图为空!"));
            }
            else
            {
                SaveTexture();
                AssetDatabase.Refresh();
                ShowNotification(new GUIContent("保存成功!"));
            }
        }
    }

    private RenderTexture CreateRT(int size)
    {
        RenderTexture renderTexture = new RenderTexture(size, size, 0, format);
        renderTexture.enableRandomWrite = true;
        renderTexture.wrapMode = TextureWrapMode.Repeat;
        renderTexture.Create();
        return renderTexture;
    }

    void Init()
    {
        renderTexture = CreateRT((int)size);
        kernel = computeShader.FindKernel("PerlinNoise");
        computeShader.SetTexture(kernel, "Texture", renderTexture);
        computeShader.SetInt("size", (int)size);
        computeShader.SetFloat("scale", scale * 10f);
        computeShader.SetInt("Type", (int)noiseType);
        computeShader.SetInt("State", (int)generation);
        computeShader.Dispatch(kernel, (int)size / 8, (int)size / 8, 1);
    }

    private void OnDisable()
    {
        if (renderTexture == null) return;
        renderTexture.Release();
    }

    void SaveTexture()
    {
        RenderTexture previous = RenderTexture.active;
        RenderTexture.active = renderTexture;
        texture = new Texture2D(renderTexture.width, renderTexture.height);
        texture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
        texture.Apply();
        RenderTexture.active = previous;

        byte[] bytes = texture.EncodeToTGA();
        File.WriteAllBytes(path, bytes);
    }
}

Compute Shader:

#pragma kernel PerlinNoise

RWTexture2D<float4> Texture;
float scale;
int Type = 0;
int State = 0;
int size;

float2 hash22(float2 p)
{
    p = float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)));
    return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
}
			
float2 hash21(float2 p)
{
    float h = dot(p, float2(127.1, 311.7));
    return -1.0 + 2.0 * frac(sin(h) * 43758.5453123);
}

float Perlin(float2 p)
{
    float2 pi = floor(p);
    float2 pf = frac(p);
    
    //float2 w = pf * pf * (3.0 - 2.0 * pf);
    float2 w = pf * pf * pf * (6 * pf * pf - 15 * pf + 10);
     
    return lerp(lerp(dot(hash22(pi + float2(0.0, 0.0)), pf - float2(0.0, 0.0)),
                    dot(hash22(pi + float2(1.0, 0.0)), pf - float2(1, 0.0)), w.x),
                lerp(dot(hash22(pi + float2(0.0, 1.0)), pf - float2(0.0, 1.0)),
                    dot(hash22(pi + float2(1.0, 1.0)), pf - float2(1.0, 1.0)), w.x), w.y);
}

float PerlinSum(float2 p)
{
    float f = 0;
    p = p * 8;
    float2x2 m = float2x2(1.6, 1.2, -1.2, 1.6);
    
    f += 1.0 * (State > 0 ? abs(Perlin(p)) : Perlin(p));
    p = mul(m, p);
    f += 0.5 * (State > 0 ? abs(Perlin(p)) : Perlin(p));
    p = mul(m, p);
    f += 0.25 * (State > 0 ? abs(Perlin(p)) : Perlin(p));
    p = mul(m, p);
    f += 0.125 * (State > 0 ? abs(Perlin(p)) : Perlin(p));
    p = mul(m, p);
    f += 0.0625 * (State > 0 ? abs(Perlin(p)) : Perlin(p));
    
    p = mul(m, p);
    if (State > 1)
        f = sin(f + p.x / 32.0);
    return f;
}

float ValueNoise(float2 p)
{
    float2 pi = floor(p);
    float2 pf = frac(p);
    
    //float2 w = pf * pf * (3.0 - 2.0 * pf);
    float2 w = pf * pf * pf * (6 * pf * pf - 15 * pf + 10);
     
    return lerp(lerp(hash21(pi + float2(0.0, 0.0)),hash21(pi + float2(1.0, 0.0)), w.x),
                lerp(hash21(pi + float2(0.0, 1.0)), hash21(pi + float2(1.0, 1.0)), w.x),w.y);
}

float ValueSum(float2 p)
{
    float f = 0;
    p = p * 4;
    f += State > 0 ? abs(ValueNoise(p)) : ValueNoise(p);
    p = 2 * p;
    f += 0.5 * (State > 0 ? abs(ValueNoise(p)) : ValueNoise(p));
    p = 2 * p;
    f += 0.25 * (State > 0 ? abs(ValueNoise(p)) : ValueNoise(p));
    p = 2 * p;
    f += 0.125 * (State > 0 ? abs(ValueNoise(p)) : ValueNoise(p));
    p = 2 * p;
    f += 0.0625 * (State > 0 ? abs(ValueNoise(p)) : ValueNoise(p));
    
    p = 2 * p;
    if (State > 1)
        f = sin(f + p.x / 32.0);
    return f;
}

float SimplexNoise(float2 p)
{
    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
    const float K2 = 0.211324865; // (3-sqrt(3))/6;
    
    float2 i = floor(p + (p.x + p.y) * K1);
    
    float2 a = p - (i - (i.x + i.y) * K2);
    float2 o = (a.x < a.y) ? float2(0, 1) : float2(1, 0);
    float2 b = a - o + K2;
    float2 c = a - 1 + 2 * K2;
    
    float3 h = max(0.5 - float3(dot(a, a), dot(b, b), dot(c, c)), 0);
    float3 n = pow(h, 4) * float3(dot(a, hash22(i)), dot(b, hash22(i + o)),
        dot(c, hash22(i + 1)));
    
    return dot(float3(70, 70, 70), n);
}

float SimplexSum(float2 p)
{
    float f = 0;
    p = p * 4;
    f += State > 0 ? abs(SimplexNoise(p)) : SimplexNoise(p);
    p = 2 * p;
    f += 0.5 * (State > 0 ? abs(SimplexNoise(p)) : SimplexNoise(p));
    p = 2 * p;
    f += 0.25 * (State > 0 ? abs(SimplexNoise(p)) : SimplexNoise(p));
    p = 2 * p;
    f += 0.125 * (State > 0 ? abs(SimplexNoise(p)) : SimplexNoise(p));
    p = 2 * p;
    f += 0.0625 * (State > 0 ? abs(SimplexNoise(p)) : SimplexNoise(p));
    
    p = 2 * p;
    if (State>1)
        f = sin(f + p.x / 32.0); 
    return f;
}

float2 random2(float2 p)
{
    return frac(sin(float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)))) *
        43758.5453);
}

float Worley(float2 p)
{
    float min_dist = 1000;
    float2 pi = floor(p);
    float2 pf = frac(p);
    
    
    float num = scale / 10;
    
    for (int m = -1; m <= 1; m++)
    {
        for (int n = -1; n <= 1; n++)
        {
            float2 sp = (pi + float2(m, n));
            float2 pos = 0;
            float factor = num;
            pos = (sp + factor) % factor;
         
            sp += random2(pos);
            float dist = distance(p, sp);
            min_dist = min(min_dist, dist);
        }
    }
    return min_dist;
}

int2 Tiling(int2 pos)
{
    pos.x = pos.x % 256;
    pos.y = pos.y % 256;
    return pos;
}

[numthreads(8,8,1)]
void PerlinNoise(uint3 id : SV_DispatchThreadID)
{
    //float r = Perlin(id.xy / scale);
    float r = 0;
    if (Type == 0)
    {
        r = PerlinSum(id.xy / scale);
        //r = PerlinSum(Tiling(id.xy) / scale);
    }
    else if (Type == 1)
        r = ValueSum(id.xy / scale);
    else if (Type == 2)
        r = SimplexSum(id.xy / scale);
    r = r * 0.5 + 0.5;
    
    if (Type == 3)
    {
        float num = scale / 10;
        num = (float) size / num;
        r = Worley(id.xy/num);
        r = 1 - r;
    }
    
    Texture[id.xy] = float4(r, r, r, 1);
}

参考文章

【图形学】谈谈噪声
如何生成一张 Value Noise 算法图片(包括 Perlin Noise)
Voronoi 噪音入门(又名Worley/Cell噪音,都是一个意思)
Worley Noise(一)

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用ComputeShader来计算波形的值,并将结果传递到GPU上进行绘制。下面是一些简单的示例代码: 首先,需要定义一个ComputeShader: ```hlsl #pragma kernel CSMain RWTexture2D<float4> result; [numthreads(8, 8, 1)] void CSMain (uint3 id : SV_DispatchThreadID) { float x = (float)id.x / (float)result.GetWidth(); float y = (float)id.y / (float)result.GetHeight(); float value = sin(x * 10.0f) * cos(y * 10.0f); result[id.xy] = float4(value, value, value, 1.0f); } ``` 这个ComputeShader的作用是计算在屏幕上绘制一个波形所需的值。在这个示例中,使用了sin和cos函数来计算波形的值,并将结果存储在一个float4类型的纹理中。 接下来,需要在C#代码中创建一个RenderTexture,并将其绑定到ComputeShader中: ```csharp public class WaveformRenderer : MonoBehaviour { public ComputeShader computeShader; public Material material; public int textureSize = 512; private RenderTexture waveformTexture; private void Start() { waveformTexture = new RenderTexture(textureSize, textureSize, 0, RenderTextureFormat.ARGBFloat); waveformTexture.enableRandomWrite = true; waveformTexture.Create(); int kernelHandle = computeShader.FindKernel("CSMain"); computeShader.SetTexture(kernelHandle, "result", waveformTexture); computeShader.Dispatch(kernelHandle, textureSize / 8, textureSize / 8, 1); material.SetTexture("_MainTex", waveformTexture); } private void OnRenderImage(RenderTexture source, RenderTexture destination) { Graphics.Blit(source, destination, material); } } ``` 在这个示例中,使用了一个Material来将计算出的纹理绘制到屏幕上。在Start()方法中,首先创建了一个RenderTexture,并将其设置为可写。然后将其绑定到ComputeShader中,并使用Dispatch()方法来执行ComputeShader。最后,将计算出的纹理存储在Material中,并在OnRenderImage()方法中将其绘制到屏幕上。 这就是一个简单的使用ComputeShader绘制波形的示例。你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值