Simplex噪声的matlab实现

Simplex噪声的matlab实现

0.Simplex噪声的特点

Simplex噪声是2001年Perlin本人提出的一种新的梯度噪声,主要目的是为了改善之前的Perlin噪声随维度增加计算量指数型增大的缺点。下图就是二维的Perlin噪声,具体实现可以参见:
https://blog.csdn.net/weixin_42943114/article/details/82110468
perlin噪声
与Perlin噪声相比,Simplex噪声最大的特点便是由原来的正方形网格转换成为了三角形网格,这大大减小了计算量,但是却增大了理解的难度。Simplex噪声的计算单元由单形(三角形,四面体,n维n+1面体)构成,其优点是在高维情况下计算量大大减小,由原来的o(2^n)变为o(n^2)。
本文的算法思路参见下面这篇文章:
【图形学】谈谈噪声 https://blog.csdn.net/candycat1992/article/details/50346469

1.二维Simplex噪声的实现

Simplex算法主要步骤为:三角网格的变形与编码,计算各点的梯度权重,加权。

1.1二维Simplex噪声三角网格的编码

对于二维Perlin噪声的网格编码,只需要对所求坐标点取整便可以得到。但是对于三角形网格点的二维simplex的编码,这个编码方式便不再容易。本文用到的思路是通过间接的坐标变换使得三角形网格变换为正方形网格,进行计算。下图便是三角形网格(红色的)变成正方形网格(黑色的)后的示意图,通过变换后的正方形坐标依然可以通过取整的方式来获得对应三角形网格的坐标号。

由于一个正方形网格对应2个三角形网格,所以还需要第二步判断确定具体位置。如果y-floor(y) > x-floor(x)则在上三角部分,关联的三角形三个点为( floor(x)+0 , floor(y)+0 ) , ( floor(x)+0 , floor(y)+1 ) , ( floor(x)+1 , floor(y)+1 ),简记为(0,0)
,(0,1),(1,1)。如果y-floor(y) < x-floor(x)则在下三角部分,关联的三角形记为(0,0),(1,0),(1,1)。

编码

上面的图是晶格点的编码方式,具体判断计算点所在位置可以用下面这张图进行解释:
三角形网格编码
- 先布置计算点
- 接下来把计算点由原来的三角网格变换为正方形网格。确定所需晶格点,并设置随机的梯度向量。坐标变换方法如红字所示。
- 最后再把正方形坐标变回三角形坐标,计算梯度向量和方向向量的点积,以此计算权重。

计算权重的方法和Perlin噪声相似,这个之后再说
点积与权重

1.2二维Simplex噪声计算

对于二维Simplex噪声的计算,可以用以下的算法进行计算:

划分计算点网格
把计算点网格做三角形到正方形的变换
确定正方形网格的范围
给各个网格角点设定随机梯度向量
初始化计算点网格的数值zeros()
循环每一个计算点
    对计算点坐标(正方形坐标)取整,得到正方形网格的关联角点
    正方形网格包含2个三角形网格,筛选出计算点的三角形所在位置(是左上三角还是右下三角)
    循环每一个晶格点(3个)
        求出计算点和晶格角点在三角形网格(即变型前的网格)下的方向向量
        将方向向量与梯度向量做点积,然后做权重计算
    把3个权重相加,得到计算点数值
绘图

其中三角形网格到正方形网格的计算公式为:

  x 4 = x 3 + ( x 3 + y 3 + . . . ) K 34
  y 4 = y 3 + ( x 3 + y 3 + . . . ) K 34
  . . .
其中   K 34 = n + 1 1 n
这个公式在高维情况下也同样使用,n代表维数,在2维时   K 34 = 3 1 2

对于计算点所在的三角形位置,本文采用的是排序法确定的。
假设dx=x4-floor(x4)=0.4,dy=y4-floor(y4)=0.6,对dx和dy排序,得到dy>dx,所以得到的坐标先在y加1,再在x加1。
即三角形角点坐标变化为   ( 0 0 ) ( 0 1 ) ( 1 1 ) 这个顺序
对于三维坐标,假设dx>dz>dy,则最终三角形角点的坐标可以确定为:
  [ 0 0 0 ] [ 1 0 0 ] [ 1 0 1 ] [ 1 1 1 ]
这个方法在matlab里的实现为:

扫描二维码关注公众号,回复: 3024566 查看本文章
function dot4=makedot4(dx4)
    [~,index]=sort(dx4,'descend');%先求出大小顺序
    A=eye(length(dx4)) ;
    A=A(:,index);%然后根据坐标排列
    dot4=[zeros(length(dx4),1),cumsum(A,2)];%累加当做边缘格点
end

之后求正方形网格变回三角形网格的时候,方法和前面介绍的相似,但是K不同
  x 3 = x 4 + ( x 4 + y 4 + . . . ) K 43
  y 3 = y 4 + ( x 4 + y 4 + . . . ) K 43
  . . .
其中   K 43 = 1 n + 1 1 n

再往后的求方向向量与梯度向量的点积,并加权的公式如下:
  ( r 2 | d i s t | 2 ) 4 × d o t ( d i s t , g r a d )
其中dist是方向向量,   | d i s t | 是方向向量的模长。grad则是梯度向量。   r 2 这里取0.5。

1.3二维Simplex噪声matlab实例

接下来贴出用matlab实现Simplex噪声的代码:

clear

%2维噪声
%确定和精度
dx=1/25;
Dim=2;

%划分计算点
x=0:dx:5;
y=0:dx:5;
[TX,TY]=meshgrid(x,y);
X3=zeros(2,length(x)*length(y));
X3(:,1:end)=[TX(1:end);TY(1:end)];%把meshgrid的坐标向量化,变成2行的矩阵。和后面zmat(k)相对应。

%把三角坐标映射到正方形坐标
K34=sqrt(2+1)/2-1/2;
K43=(1/sqrt(2+1)-1)/2;

X4=X3+sum(X3)*K34;

%设定正方形坐标角点各个随机向量
maxX4=max(X4,[],2);
numX=ceil(maxX4)+1;
uxmat=(rand([numX',Dim])-0.5)*2;%直接正方形是不是没用的点太多了?'
%这里用numX代表每个网格点,Dim代表网格点的坐标。比如uxmat(:,:,1)代表x方向梯度,uxmat(:,:,2)代表y方向梯度


zmat=zeros(length(x),length(y));%预设随机云图
r2=0.5;

%判断5个关联点的位置,求权重
n=zeros(Dim+1,1);
u=zeros(Dim,1);
for k=1:length(x)*length(y)

    dotX4=X4(:,k);
    dx4=dotX4-floor(dotX4);
    dot4=makedot4(dx4)+floor(dotX4);%生成5个关联点坐标,参见下面makedot4()函数

    %变回三角网格
    dotX3=dotX4+sum(dotX4)*K43;%计算点
    dot3=dot4+ones(Dim,1)*sum(dot4)*K43;


    %n1,先求距离向量r,再求r*u,再求(0.5-r^2)^4*r*u权重
    dist=dotX3*ones(1,Dim+1)-dot3;
    u(1:end)=uxmat(dot4(1,1)+1,dot4(2,1)+1,:);%2个维度各取一个数,组成向量%不能为0
    n(1)=(r2-sum(dist(:,1).^2))^4*dot(dist(:,1),u);
    %n2
    u(1:end)=uxmat(dot4(1,2)+1,dot4(2,2)+1,:);%2个维度各取一个数,组成向量
    n(2)=(r2-sum(dist(:,2).^2))^4*dot(dist(:,2),u);
    %n3
    u(1:end)=uxmat(dot4(1,3)+1,dot4(2,3)+1,:);%2个维度各取一个数,组成向量
    n(3)=(r2-sum(dist(:,3).^2))^4*dot(dist(:,3),u);

    %n=n1+n2+n3
    zmat(k)=sum(n);

end
%绘图
pcolor(TX,TY,zmat)
shading interp

function dot4=makedot4(dx4)
    [~,index]=sort(dx4,'descend');%先求出大小顺序
    A=eye(length(dx4)) ;
    A=A(:,index);%然后根据坐标排列
    dot4=[zeros(length(dx4),1),cumsum(A,2)];%累加当做边缘格点
end

最后图片:
Simplex噪声

2.可平铺Simplex循环噪声

在上一篇关于Perlin噪声里讲过,如果想要制作循环噪声,只需要把边界条件变成重复的便可以实现。但是这一方法到了Simplex噪声里却不适用,因为Simplex噪声的网格形状变成了三角形,所以想要构造循环噪声便有了一定的难度。

如下图所示,通过重复边界条件构造出来的图像平铺图案是菱形而不是正方形
一类可平铺图案的尝试

所以这里采用另一种方法构造可平铺图形,即n维可平铺图案可以由2n维图案的一个圆上得到。
举个例子,对于1维可平铺图案,只需要在任意二维噪声上取一个圆便可以构造出来
这里写图片描述
比如说这里初始设置的计算点坐标为x=R*cos(u),y=R*sin(u),这样通过在二维平面上的计算得到的一维图形一定可平铺的。而且实际上不限于只取圆形作为初始计算点坐标。

程序实例如下:

%循环噪声
%确定半径和精度
R=1.2;
da=pi/64;
%划分计算点
a=0:da:2*pi-da;
X3=R*cos(a)+R+1;Y3=R*sin(a)+R+1;

%把三角坐标映射到正方形坐标
K34=sqrt(2+1)/2-1/2;
K43=(1/sqrt(2+1)-1)/2;

X4=X3+sum([X3;Y3])*K34;
Y4=Y3+sum([X3;Y3])*K34;

%设定正方形坐标角点各个随机向量
maxX4=max(max(X4));maxY4=max(max(Y4));
numX=ceil(maxX4)+1;numY=ceil(maxY4)+1;
uxmat=(rand(numX+1,numY+1)-0.5)*2;
uymat=(rand(numX+1,numY+1)-0.5)*2;%未完待续,直接正方形是不是没用的点太多了?

zmat=zeros(1,length(a));%预设随机云图

r=sqrt(0.5);
%判断三个关联点的位置,求权重

for k=1:length(a)
    dotX4=X4(k);dotY4=Y4(k);
    dx4=dotX4-floor(dotX4);
    dy4=dotY4-floor(dotY4);
    if dx4>=dy4
        dot4_1=[floor(dotX4),floor(dotY4)];
        dot4_2=[floor(dotX4)+1,floor(dotY4)];
        dot4_3=[floor(dotX4)+1,floor(dotY4)+1];
    else
        dot4_1=[floor(dotX4),floor(dotY4)];
        dot4_2=[floor(dotX4),floor(dotY4)+1];
        dot4_3=[floor(dotX4)+1,floor(dotY4)+1];
    end

    %变回三角网格
    dotX3=dotX4+sum([dotX4;dotY4])*K43;%这个用一个公式代替?
    dotY3=dotY4+sum([dotX4;dotY4])*K43;



    dot3_1=[dot4_1(1)+sum(dot4_1)*K43,...
        dot4_1(2)+sum(dot4_1)*K43];
    dot3_2=[dot4_2(1)+sum(dot4_2)*K43,...
        dot4_2(2)+sum(dot4_2)*K43];
    dot3_3=[dot4_3(1)+sum(dot4_3)*K43,...
        dot4_3(2)+sum(dot4_3)*K43];


    %n1,先求距离向量r,再求r*u,再求(0.5^2-r^2)^4*r*u权重
    dist=[dotX3,dotY3]-dot3_1;
    u=[uxmat(dot4_1(1)+1,dot4_1(2)+1),uymat(dot4_1(1)+1,dot4_1(2)+1)];%这里采用最小值为0的假设
    n1=(r^2-dist(1)^2-dist(2)^2)^4*(dist(1)*u(1)+dist(2)*u(2));
    %n2
    dist=[dotX3,dotY3]-dot3_2;
    u=[uxmat(dot4_2(1)+1,dot4_2(2)+1),uymat(dot4_2(1)+1,dot4_2(2)+1)];
    n2=(r^2-dist(1)^2-dist(2)^2)^4*(dist(1)*u(1)+dist(2)*u(2));
    %n3
    dist=[dotX3,dotY3]-dot3_3;
    u=[uxmat(dot4_3(1)+1,dot4_3(2)+1),uymat(dot4_3(1)+1,dot4_3(2)+1)];
    n3=(r^2-dist(1)^2-dist(2)^2)^4*(dist(1)*u(1)+dist(2)*u(2));
    %n
    n=n1+n2+n3;
    zmat(k)=n;

end
x=linspace(0,1,length(a)+1);
hold on
plot(x(1:end-1),zmat)
plot(x(1:end-1)+1,zmat)
plot(x(1:end-1)+2,zmat)
hold off

最终的效果图如下:
这里写图片描述

所以可以看到一维可平铺的效果还是很好的。

对于二维可平铺图形,要用到四维平面去构造二维平面的点,这个我还没有实现,有能力的可以参考下面的连接自己尝试。
http://ronvalstar.nl/creating-tileable-noise-maps

猜你喜欢

转载自blog.csdn.net/weixin_42943114/article/details/82179405