shader生成噪声图

shader中噪声图的生成

这边文章主要讲噪声图的生成原理,以及噪声图在shader中的应用。

什么是噪声图

简单直白地说人话,噪声图就是一张有着如同云雾一般混乱画面的图片。
请添加图片描述

噪声的生成原理有什么用?

一般情况下,我们需要用噪声图时,直接在网上找就行了。但是有时候,我们可能找不到需要的类型的图,或者是移植了shadertoy上的shader代码,由于shadertoy上很多都是在用实时计算,在拿来应用时,可能需要将保存成图片来优化计算量。

常规代码中的随机数生成

在常规的cpu上执行的代码,我们一般用这种形式的算法生成随机数:
随机数 = (随机数种子 + a ) 模除 b。
使用这种随机数生成算法,需要给定一个初始的随机数种子。之后,每次生成随机数,将使用上一个随机数作为这次随机的随机数种子。
公式很简单,主要是a和b的值需要选的好,这样生成的随机数才会比较像随机。
这个随机数计算方法放在噪声图生成中有个严重的问题。大家想想看是什么?

悬浮鼠标到此查看

一元方程式随机数

噪声图的生成是一个2维图像。但是为了方便理解算法,我们先在考虑在一维中研究算法。

1算法的输入值

对于每个像素点来说,各自特有不与其他像素点相同的数据,其中很自然就能想到的就是像素点的坐标。

2输出范围限定

接下来的这些图片来自于
https://thebookofshaders.com/10/?lan=ch
可以到这个网页上自己修改尝试各种写法。
生成随机数必然有一个范围,在数学计算中,y = f(x),y的值永远处于一定范围内,很容易就能想到三角函数。
我们先不考虑像素点的纵坐标,使用横坐标作为输入值,用三角函数来做计算:

请添加图片描述

3取整

随机数是一串离散的点,所以我们对输入的x值取整:
y = sin(floor(x));
这时,我们已经有了一个大致的算法,能够获取一串随机数,并且不像cpu算法那样,需要等待上一个计算结果,能够直接根据当前的x值得出随机数。
请添加图片描述
这里的y值,在数学逻辑上是保证了绝对不会有周期性的
为什么

4少许优化

给三角函数加上倍数系数和固定系数,可以调节振幅和频率。
通过加一个取小数部分的操作,可以让这个算法看起来更随机一点,并且将取值范围从-a,a变成0,a。这种优化没有固定公式,可以随意组合。
请添加图片描述

离散点之间过度

使用上面的这种算法,可以获得一系列由像素点坐标来确定的离散随机数值。放到2维图中,可以制作成噪点图。
但是这种噪点图和我们想要的噪声图有一些差距。原因便是噪声图虽然是随机的,但是相邻的像素点之间的值是接近的,也就是说,噪声图存在一个平滑过度的关系。
所以,我们不以像素点作为单位来取噪点,使用较大的间隔获取噪点,然后在噪点之间做平滑过度。
平滑过度的计算一般使用shader中内置的smoothstep函数即可。请添加图片描述

很多细致的shader作品中,会使用更符合作品需求的平滑计算函数,如果你看到一个shader代码中,有类似这种算式,不用头疼,基本上是在构造它自己的平滑公式。不确定的可以放进一些能展示数学函数图像的软件里,可以获得和smoothstep类似的图像。

2维图像

取噪点,将一副图网格化。对一个晶格的四个顶点做平滑过度,一张2维的随机图像就生成了。
在这里插入图片描述

在使用fbm前,我们先看看之前得到的图像,明显细节不够丰富。
请添加图片描述

什么是分形?

fbm是通过叠加更改了振幅和频率后的图像来丰富细节的。每次迭代由于公式的形式不变,只变了振幅和频率,因此借用音乐中的概念,将其成为一个“八度”(octave)请添加图片描述请添加图片描述

请添加图片描述请添加图片描述

在unity中生成噪声图,并保存为图片。

unity中渲染图像,如果只是在编辑器中使用,可以用computeshader,能够方便地借助gpu进行高并发计算。

创建computershader:
请添加图片描述
computeShader代码:

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
float random (float2 full_uv) {
    return frac(sin(dot(full_uv.xy,
                        float2(12.9898,78.233)))*
        43758.5453123);
}

float noise (in float2 full_uv) {
    float2 i = floor(full_uv);
    float2 f = frac(full_uv);

    // Four corners in 2D of a tile
    float a = random(i);
    float b = random(i + float2(1.0, 0.0));
    float c = random(i + float2(0.0, 1.0));
    float d = random(i + float2(1.0, 1.0));

    float2 u = f * f * (3.0 - 2.0 * f);

    return lerp(a, b, u.x) +
            (c - a)* u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

#define OCTAVES 6
float fbm (in float2 full_uv) {
    // Initial values
    float value = 0.0;
    float amplitude = .5;
    float frequency = 0.;
    //
    // Loop of octaves
    for (int i = 0; i < OCTAVES; i++) {
        value += amplitude * noise(full_uv);
        full_uv *= 2.;
        amplitude *= .5;
    }
    return value;
}

[numthreads(8,8,1)]
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{    
    Result[id.xy] = float4(fbm(float2(float(id.x), float(id.y) / 32)), 0, 0, 1);
}

脚本中调用computeShader修改RenderTexture纹理的数据,并保存为图片:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.UI;

public class noiseCreator : MonoBehaviour
{
    public ComputeShader cs;
    private RenderTexture rt;
    // Start is called before the first frame update
    void Start()
    {
        int kernelHandle = cs.FindKernel("CSMain");

        int width = 1024;
        int height = 1024;
        rt = new RenderTexture(width ,height, 0, RenderTextureFormat.RFloat);
        rt.enableRandomWrite = true;
        rt.Create();
        cs.SetTexture(kernelHandle, "Result", rt);
        // cs.Dispatch(kernelHandle, width / 8, height / 8, 1);
        cs.Dispatch(kernelHandle, width / 8, height / 8, 1);

        GetComponent<RawImage>().material.mainTexture = rt;
    }

    public void save() {
        Texture2D t = new Texture2D(rt.width, rt.height, TextureFormat.R8, false);

        var previous = RenderTexture.active;
        RenderTexture.active = rt;

        t.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        RenderTexture.active = previous;

        t.Apply();
        var bytes = t.EncodeToTGA();
        System.IO.File.WriteAllBytes("fbm.tga", bytes);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值