基于分形的山脉河流生成

自相似

所谓自相似,是一种尺度变换下的不变性(scale-invariance),即在不同尺度下观察分形可以看到近似相同的形象,若把整个对象的局部放大,再把局部的局部放大,都可以看到相似的结构特征。但是这种自相似并不像整形的相似那么严格,允许相似中的不相似,不需要也不可能完全相同。比如,科赫曲线,整体是闭合的,但任一部分都不是封闭曲线。分形自相似意味着部分与整体有一样的复杂性:一样曲折、琐碎、纷乱、不规整、不光滑。并且,分形的部分与部分之间也是相似的,下图中的植物就是自然界中自相似的一个例子。“山重水复疑无路”就是从审美的角度对山水分形中的自相似的描述,以至于让外人只看到相同之处而难以了解细微的差别,便生出迷路的疑惑。

Midpoint Displacement in One Dimension


一维的Midpoint Displacement方法常用于山脊的生成

伪代码如下

Start with a single horizontal line segment.
Repeat for a sufficiently large number of times

 {
        Repeat over each line segment in the scene 
       {
                 Find the midpoint of the line segment.
                 Displace the midpoint in Y by a random amount.
                 Reduce the range for random numbers.
        }
}


在Unity具体实现如下

  void Generate1DMountain(Vector3 start, Vector3 end, int iterateTime, float roughless)
         {
                   if (iterateTime > 0)
                  {
                            float rand = Random.Range(0f, 2.999f) * roughless;
                            Vector3 mid = new Vector3(0.5f * start.x + 0.5f * end.x, 0.5f * start.y + 0.5f * end.y + rand, 0);
                            --iterateTime;
                            Generate1DMountain(start, mid, iterateTime, roughless * iterateTime * iterateTime * 0.01f);
                            points.Add(mid);
                            Generate1DMountain(mid, end, iterateTime, roughless * iterateTime * iterateTime * 0.01f);
                   }
         }


当然也可以写成非递归的

  float GetMidHeight(int i, int stride, Vector3[] points)
         {
                   return 0.5f * (points[i - stride].y + points[i + stride].y);
         }
         Vector3[] Generater1DMoutainIterative(Vector3 start, Vector3 end, int iterateTime, float heightScale, float h)
         {
                   int length = CalculateArrayLenght(iterateTime);
                   Vector3[] points = new Vector3[length];
                   points[0] = start;
                   points[length - 1] = end;
                   float gap = Mathf.Abs(end.x - start.x) / length;
                   //Debug.Log("gap: " + gap);
                   for(int i=0; i< length; i++)
                   {
                            points[i] = new Vector3(start.x + i*gap, 0f, 0f);
                   }
                   float ratio, scale;
                   ratio = (float)Mathf.Pow(2.0f, -h);
                   scale = heightScale * ratio;
                   int stride = length / 2;
                   while(stride != 0)
                   {
                            for (int i = stride; i < length; i += stride)
                            {
                                     points[i].y = scale * Random.Range(-4f, 4f) + GetMidHeight(i, stride, points);
                                     i += stride;
                            }
                            scale *= ratio;
                            Debug.Log("Stride: " + stride);
                            stride >>= 1;
                   }
         //       DumpAllPoint(points);
                   return points;
         }



在OnGizmos里面绘制一下

 void OnDrawGizmos()
 {
        Gizmos.color = Color.red;
        for (int i = 0; i < newPoints.Length - 1; i++)
        {
             Gizmos.DrawLine(newPoints[i], newPoints[i + 1]);
        }
  }


结果



iteration = 5 Roughness = 0.2


iteration = 15 Roughness = 0.8



通过调整roughness,可以得到不同” 锯齿感” 的山脊,也许还可以用来生成闪电的曲线?



只用这么简单的代码就可以生成这么复杂的信息,通过这个方法,有一个专门的技术称为 fractal image compression,就是通关过简单的一些递归函数和参数来生成图像而不是存储图像本身,具体可以参考一下这本书 <<Chaos and Fractals, New Frontiers of Science>>。

Midpoint in 3D

三维情况下的Midpoint其实只是将线段换成了三角形,如下图所示




需要注意的是,这每次细分都是上下文相关的



也即是说相邻边的midpoint displacement 值一定是相等的。在实现上,要达到这种目的,就引入一个hashtable,存储的是边和midpoint displacement,当要对一条边进行细分的时候,首先去hashtable里面去找是否有存,如果已经存在,就用已有的值,如果没有就算一个随机值,并且添加到hashtable中去。

下面看下具体实现,首先是数据结构的定义

public class EdgeMidHashTable
{
	List<Edge> edges;
	List<Node> midNodes;

	public EdgeMidHashTable()
	{
		edges = new List<Edge>();
		midNodes = new List<Node>();
	}

	public void Clear()
	{
		edges.Clear();
		midNodes.Clear();
	}

	public void Add(Edge edge, Node node)
	{
		edges.Add(edge);
		midNodes.Add(node);
	}

	public bool IsContainsEdge(Edge edge)
	{
		return edges.Contains(edge);
	}

	public Node GetEdgeMidNode(Edge edge)
	{
		int index = GetEdgeIndex(edge);
		return midNodes[index];
	}

	int GetEdgeIndex(Edge edge)
	{
		for(int i = 0; i< edges.Count; i++)
		{
			if(edges[i].Equals(edge))
			{
				return i;
			}
		}
		return -1;
	}
}

public class Edge
{
	int startNodeIndex;
	int endNodeIndex;
	public EdgeLabel label;
	public Node StartNode;
	public Node EndNode;
	public Edge()
	{ }
	public Edge(Node Start, Node End)
	{
		label = EdgeLabel.Neutral;
		startNodeIndex = Start.vertexIndex;
		StartNode = Start;
		endNodeIndex = End.vertexIndex;
		EndNode = End;
	}

	public override bool Equals(object other)
	{
		if(other == null)
		{
			return false;
		}
		Edge otherEdge = (Edge)other;

		if ((StartNode.vertexIndex == otherEdge.StartNode.vertexIndex && EndNode.vertexIndex == otherEdge.EndNode.vertexIndex) ||
			(StartNode.vertexIndex == otherEdge.EndNode.vertexIndex && EndNode.vertexIndex == otherEdge.StartNode.vertexIndex))
		{
			return true;
		}

		return false;
	}

	public Edge(Node Start, Node End, EdgeLabel _label)
	{
		label = _label;
		startNodeIndex = Start.vertexIndex;
		StartNode = Start;
		endNodeIndex = End.vertexIndex;
		EndNode = End;
	}

	public Vector3 GetEdgeCenter()
	{
		return (StartNode.position + EndNode.position) * 0.5f;
	}

	public override int GetHashCode()
	{
		return this.label.GetHashCode();
	}

}

public class Node
{
	public Vector3 position;
	public int vertexIndex = -1;

	public Node(Vector3 _pos)
	{
		position = _pos;
	}

	public override bool Equals(object other)
	{
		if (other == null)
		{
			return false;
		}
		Node otherNode = (Node)other;

		if (this.vertexIndex == otherNode.vertexIndex)
		{
			return true;
		}

		return false;
	}
}

public class Triange
{
	public Node[] vertices = new Node[3];
	public Triange(Vector3 Vertex0, Vector3 Vertex1, Vector3 Vertex2)
	{
		vertices[0] = new Node(Vertex0);
		vertices[1] = new Node(Vertex1);
		vertices[2] = new Node(Vertex2);
	}

	public Triange(Node node0, Node node1, Node node2)
	{
		vertices[0] = node0;
		vertices[1] = node1;
		vertices[2] = node2;
	}
}






细分关键代码

void SplitTriangle(Triange triangle,  int recursiveTime)
	{
		Vector3 VertexPos0 = triangle.vertices[0].position;
		Vector3 VertexPos1 = triangle.vertices[1].position;
		Vector3 VertexPos2 = triangle.vertices[2].position;
		float RandomScale = Vector3.Distance(VertexPos0, VertexPos1);
		float rand0 = 0f;
		float rand1 = 0f;
		float rand2 = 0f;
		Node midNode0 = new Node(Vector3.zero);
		Node midNode1 = new Node(Vector3.zero);
		Node midNode2 = new Node(Vector3.zero);

		midNode0 = CalculateMidNode(RandomScale, triangle.vertices[0], triangle.vertices[1]);
		midNode1 = CalculateMidNode(RandomScale, triangle.vertices[1], triangle.vertices[2]);
		midNode2 = CalculateMidNode(RandomScale, triangle.vertices[0], triangle.vertices[2]);

		Triange triangle0 = new Triange(triangle.vertices[0], midNode0, midNode2);
		Triange triangle1 = new Triange(midNode0, triangle.vertices[1], midNode1);
		Triange triangle2 = new Triange(midNode2, midNode1, triangle.vertices[2]);
		Triange triangle3 = new Triange(midNode0, midNode1, midNode2);

		if(recursiveTime >0)
		{
			recursiveTime--;
			SplitTriangle2(triangle0, recursiveTime);
			SplitTriangle2(triangle1, recursiveTime);
			SplitTriangle2(triangle2, recursiveTime);
			SplitTriangle2(triangle3, recursiveTime);
		}
		else if(recursiveTime == 0)
		{
			FinalTriangleList.Add(triangle0);
			FinalTriangleList.Add(triangle1);
			FinalTriangleList.Add(triangle2);
			FinalTriangleList.Add(triangle3);
		}
	}




看下生成结果

迭代4次


迭代5次





生成Mesh看一下

public void GenerateMesh()
	{
		meshVertices = new List<Vector3>();
		meshTriangles = new List<int>();

		for (int i = 0; i < nodes.Count; i++)
		{
			meshVertices.Add(nodes[i].position);
		}

		for (int i = 0; i < FinalTriangleList.Count; i++)
		{
			meshTriangles.Add(FinalTriangleList[i].vertices[0].vertexIndex);
			meshTriangles.Add(FinalTriangleList[i].vertices[1].vertexIndex);
			meshTriangles.Add(FinalTriangleList[i].vertices[2].vertexIndex);
		}

		Mesh mesh = new Mesh();
		GetComponent<MeshFilter>().mesh = mesh;

		mesh.vertices = meshVertices.ToArray();
		mesh.triangles = meshTriangles.ToArray();
		mesh.RecalculateNormals();
	}


运行结果






SQUIG CURVES算法生成河流


SQUIG CURVES也是一种三角形的细分算法,这种算法把三角形的边分为三种entry, exit 和neutral,entry边表示水流的流入,exit边表示水流的流出,neutral不会接触到水流。

边的细分遵循下面的规则

Entry边细分成一条entry边和一条neutral边;

Exit边细分成一条Exit边和一条neutral边;

neutral边细分成一条neutral边和一条neutral边;

重合的边要么是一条entry和一条exit,要么全是neutral。




根据上面的规则,细分一个三角形可能出现四种情况。



代码实现如下

首先看SquigTriange的定义

public class SquigTriangle
{
	public Edge[] edges = new Edge[3];
	public SquigTriangle()
	{

	}
	public SquigTriangle(Edge edge0, Edge edge1, Edge edge2)
	{
		edges[0] = edge0;
		edges[1] = edge1;
		edges[2] = edge2;
	}

	public SquigTriangle(Node node0, Node node1, Node node2)
	{
		edges[0] = new Edge(node0, node1);
		edges[1] = new Edge(node1, node2);
		edges[2] = new Edge(node2, node0);
	}

	/// <summary>
	/// Construct by nodes and labels.label0 ->Edge(node0, node1); label1 -> Edge(node1, node2); label2 -> Edge(node2, node0)
	/// </summary>

	public SquigTriangle(Node node0, Node node1, Node node2, EdgeLabel label0, EdgeLabel label1, EdgeLabel label2)
	{
		edges[0] = new Edge(node0, node1, label0);
		edges[1] = new Edge(node1, node2, label1);
		edges[2] = new Edge(node2, node0, label2);
	}

	public int  GetEnteryEdgeIndex()
	{
		for(int i = 0; i< 3; i++)
		{
			if(edges[i].label == EdgeLabel.Entry)
			{
				return i;
			}
		}
		return -1;
	}

	public int GetExitEdgeIndex()
	{
		for(int i = 0; i< 3; i++)
		{
			if (edges[i].label == EdgeLabel.Exit)
			{
				return i;
			}
		}
		return -1;
	}


	public bool IsNeutralTriangle()
	{
		return edges[0].label == EdgeLabel.Neutral && edges[1].label == EdgeLabel.Neutral;
	}
	/// <summary>
	/// Sort Triangle's vertices.
	/// </summary>
	public void  Resort()
	{
		if (edges[0].label == EdgeLabel.Neutral && edges[1].label == EdgeLabel.Neutral)
		{
			return;
		}

		Node[] nodes = new Node[4];
		for(int i = 0; i< 3; i++)
		{
			if(edges[i].label == EdgeLabel.Entry)
			{
				nodes[0] = edges[i].StartNode;
				nodes[1] = edges[i].EndNode;
			}
			if (edges[i].label == EdgeLabel.Exit)
			{
				nodes[2] = edges[i].StartNode;
				nodes[3] = edges[i].EndNode;
			}
		}

		if (nodes[0].vertexIndex == nodes[2].vertexIndex )
		{
			//0->1->3
			edges[0]= new Edge(nodes[0], nodes[1] , EdgeLabel.Entry);
			edges[1] = new Edge(nodes[1], nodes[3], EdgeLabel.Neutral);
			edges[2] = new Edge(nodes[3], nodes[0], EdgeLabel.Exit);
		}
		else if(nodes[0].vertexIndex == nodes[3].vertexIndex)
		{
			//0->1->2
			edges[0] = new Edge(nodes[0], nodes[1], EdgeLabel.Entry);
			edges[1] = new Edge(nodes[1], nodes[2], EdgeLabel.Neutral);
			edges[2] = new Edge(nodes[2], nodes[0], EdgeLabel.Exit);
		}
		else if(nodes[1].vertexIndex == nodes[2].vertexIndex)
		{
			//1->0->3
			edges[0] = new Edge(nodes[1], nodes[0], EdgeLabel.Entry);
			edges[1] = new Edge(nodes[0], nodes[3], EdgeLabel.Neutral);
			edges[2] = new Edge(nodes[3], nodes[1], EdgeLabel.Exit);
		}
		if(nodes[1].vertexIndex == nodes[3].vertexIndex)
		{
			//1->0->2
			edges[0] = new Edge(nodes[1], nodes[0], EdgeLabel.Entry);
			edges[1] = new Edge(nodes[0], nodes[2], EdgeLabel.Neutral);
			edges[2] = new Edge(nodes[2], nodes[1], EdgeLabel.Exit);
		}
	}
}


细分一个三角形
// 	   /  \
// 	  /__0_\
// 	 / \3 / \
// 	/_1_\/_2_\

	SquigTriangle[] SubdivideSquigTriange(SquigTriangle triangle)
	{

		//When Come up with Neutral triangle(with three Neutral edge)
		if (triangle.edges[0].label == EdgeLabel.Neutral && triangle.edges[1].label == EdgeLabel.Neutral)
		{
			FinalTriangleList.Add(triangle);
			return new SquigTriangle[] { };
		}

		Node OriginalNode0 = triangle.edges[0].StartNode;
		Node OriginalNode1 = triangle.edges[0].EndNode;
		Node OriginalNode2 = triangle.edges[1].EndNode;

		//Splite three edges to 6 small edge.
		//OriginEdge0 -> edge0 + edge3
		//OriginEdge1 -> edge4 + edge7
		//OriginEdge2 -> edge2 + edge8
		Edge[] splitedEdges = new Edge[9];
		SpliteEdge(triangle.edges[0], out splitedEdges[0], out splitedEdges[3]);
		SpliteEdge(triangle.edges[1], out splitedEdges[4], out splitedEdges[7]);
		SpliteEdge(triangle.edges[2],  out splitedEdges[8],out splitedEdges[2]);

		SquigTriangle successor0 = new SquigTriangle();
		SquigTriangle successor1 = new SquigTriangle();
		SquigTriangle successor2 = new SquigTriangle();
		SquigTriangle successor3 = new SquigTriangle();


		//Construct remain edges. Four situation. Take Symmetry into consideration
		//Situation 1

		if (splitedEdges[0].label == EdgeLabel.Entry && splitedEdges[2].label == EdgeLabel.Exit )
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
										   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
										   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
													   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
		}
		else if(splitedEdges[2].label == EdgeLabel.Entry && splitedEdges[0].label == EdgeLabel.Exit)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
										   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
										   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
													   EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
		}

//Situation 2
		else if (splitedEdges[0].label == EdgeLabel.Entry && splitedEdges[8].label == EdgeLabel.Exit)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Entry, EdgeLabel.Exit, EdgeLabel.Neutral);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
											EdgeLabel.Exit, EdgeLabel.Entry, EdgeLabel.Neutral);
		}
		else if(splitedEdges[0].label == EdgeLabel.Exit && splitedEdges[8].label == EdgeLabel.Entry)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Neutral, EdgeLabel.Exit, EdgeLabel.Entry);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
											EdgeLabel.Neutral, EdgeLabel.Entry, EdgeLabel.Exit);
		}

	//Situation 3
		else if (splitedEdges[3].label == EdgeLabel.Entry && splitedEdges[2].label == EdgeLabel.Exit)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Neutral, EdgeLabel.Entry, EdgeLabel.Exit);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
														EdgeLabel.Neutral, EdgeLabel.Exit, EdgeLabel.Entry);
		}else if(splitedEdges[3].label == EdgeLabel.Exit && splitedEdges[2].label == EdgeLabel.Entry)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Exit, EdgeLabel.Entry, EdgeLabel.Neutral);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
														EdgeLabel.Entry, EdgeLabel.Exit, EdgeLabel.Neutral);
		}

	//Situation 4
		else if (splitedEdges[3].label == EdgeLabel.Entry && splitedEdges[8].label == EdgeLabel.Exit)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
														EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
		}
		else if (splitedEdges[3].label == EdgeLabel.Exit && splitedEdges[8].label == EdgeLabel.Entry)
		{
			successor0 = new SquigTriangle(OriginalNode0, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]),
											EdgeLabel.Neutral, EdgeLabel.Neutral, EdgeLabel.Neutral);
			successor1 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]), OriginalNode1, EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]),
											EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
			successor2 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), OriginalNode2,
											EdgeLabel.Exit, EdgeLabel.Neutral, EdgeLabel.Entry);
			successor3 = new SquigTriangle(EdgeMidHashTable.GetEdgeMidNode(triangle.edges[1]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[2]), EdgeMidHashTable.GetEdgeMidNode(triangle.edges[0]),
														EdgeLabel.Entry, EdgeLabel.Neutral, EdgeLabel.Exit);
		}

		Node midNode0 = new Node(triangle.edges[0].GetEdgeCenter());
		Node midNode1 = new Node(triangle.edges[1].GetEdgeCenter());
		Node midNode2 = new Node(triangle.edges[2].GetEdgeCenter());

		Edge edge0 = new Edge(OriginalNode0, midNode0);
		Edge edge1 = new Edge(midNode0, midNode2);
		Edge edge2 = new Edge(midNode2, OriginalNode0);

		Edge edge3 = new Edge(midNode0, OriginalNode1);
		Edge edge4 = new Edge(OriginalNode1, midNode1);
		Edge edge5 = new Edge(midNode1, midNode0);

		Edge edge6 = new Edge(midNode2, midNode1);
		Edge edge7 = new Edge(midNode1, OriginalNode2);
		Edge edge8 = new Edge(OriginalNode2, midNode2);

		successor0.Resort();
		successor1.Resort();
		successor2.Resort();
		successor3.Resort();
		if (!successor3.IsNeutralTriangle())
		{
			CrossedEdge.Add(successor3.edges[successor3.GetEnteryEdgeIndex()]);
			CrossedEdge.Add(successor3.edges[successor3.GetExitEdgeIndex()]);
		}

		return new SquigTriangle[] { successor0, successor1, successor2, successor3 };
	}



然后再进行一些迭代就可以了

下面是运行的一些结果。




SQUIG CURVES with Midpoint生成山脉河流


将上面两个结合在一起,就可以实现山脉里面有河流的效果。在每次细分过程中,河流的路径和山脉的走势就越来越精细,当一个三角形进入了第n层迭代,则这个midpoint的displacement是当前所有负的

Displacement的和,其他的点不受影响。



如上图所示,被标出来的点就是被影响的点。




主要看一下边的分割函数

void SpliteEdge(Edge originalEdge, float roughness, out Edge subEdge0, out Edge subEdge1)
	{
		//float RandomScale = Vector3.Distance(originalEdge.StartNode.position, originalEdge.EndNode.position);
		float randomResult = roughness * Random.Range(-0.5f, 0.5f);
		float randomLowerBound = roughness * -0.5f;
		float altn = 0.0f;
		for (int i = 0; i < LowerRandomLimits.Count; i++)
		{
			altn += LowerRandomLimits[i];
		}


		Node midNode = new Node(Vector3.zero);
		subEdge0 = new Edge();
		subEdge1 = new Edge();
		if (EdgeMidHashTable.IsContainsEdge(originalEdge))
		{
			midNode = EdgeMidHashTable.GetEdgeMidNode(originalEdge);
		}else
		{
			switch (originalEdge.label)
			{
				//Entry-> Entry + Neutral
				case EdgeLabel.Entry:
				case EdgeLabel.Exit:
					midNode = new Node(0.5f * new Vector3(originalEdge.StartNode.position.x + originalEdge.EndNode.position.x,
					originalEdge.StartNode.position.y + originalEdge.EndNode.position.y, altn + randomLowerBound));
						EdgeMidHashTable.Add(originalEdge, midNode);
						PushNode(midNode);
					break;

				case EdgeLabel.Neutral:
					midNode = new Node(0.5f * new Vector3(originalEdge.StartNode.position.x + originalEdge.EndNode.position.x,
					originalEdge.StartNode.position.y + originalEdge.EndNode.position.y,
					originalEdge.StartNode.position.z + originalEdge.EndNode.position.z /*+ altn*/ + randomResult));
						EdgeMidHashTable.Add(originalEdge, midNode);
					PushNode(midNode);
					break;
			}	
		}

		switch (originalEdge.label)
		{
			//Entry-> Entry + Neutral
			case EdgeLabel.Entry:
				//RiverCrossed from last triangle
				if (CrossedEdge.Contains(new Edge(originalEdge.StartNode, midNode)))
				{
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Entry);
					subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);
				}
				else if (CrossedEdge.Contains(new Edge(midNode, originalEdge.EndNode)))
				{
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);
					subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Entry);
				}
				//Source of River
				else
				{
					int rand = Random.Range(0, 10);
					if(rand >5)
					{
						subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Entry);
						subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);
						CrossedEdge.Add(subEdge0);
					}else
					{
						subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);
						subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Entry);
						CrossedEdge.Add(subEdge1);
					}
				}

				break;
			//Neutral-> Neutral + Neutral
			case EdgeLabel.Neutral:
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);
					subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);
				break;

			//Exit-> Exit + Neutral
			case EdgeLabel.Exit:
				if (CrossedEdge.Contains(new Edge(originalEdge.StartNode, midNode)))
				{
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Exit);
					subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);
				}
				else if (CrossedEdge.Contains(new Edge(midNode, originalEdge.EndNode)))
				{
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);
					subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Exit);
				}
				//End of River
				else
				{
					int rand = Random.Range(0, 10);
					if (rand > 5)
					{
						subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Exit);
						subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);
						CrossedEdge.Add(subEdge0);
					}
					else
					{
					subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);
						subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Exit);
						CrossedEdge.Add(subEdge1);
					}
				}
				break;
		}
	}




看一下结果



体素风格渲染


Marching cube




 

参考

A Fractal Model of Mountains with Rivers

Generating RandomFractal Terrain

Simple2d Terrain With Midpoint Displacement


  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Matlab的分形插值程序是一种使用分形几何原理进行图像插值的技术。它可以通过对原始图像进行分形变换,生成具有更高分辨率和更丰富细节的图像。 这个程序包括以下几个主要步骤: 1. 图像加载:首先,需要使用Matlab的图像处理工具箱加载原始图像,将其转换为灰度图像,以便后续的处理。 2. 分形变换:使用分形几何的理论,将原始图像进行分形变换。这个过程通常包括将原始图像分割成小块,然后对每个小块进行平移、旋转、缩放等操作,以生成更多的细节和结构。这些变换也可以根据特定的分形维度或规则进行调整,以控制生成图像的细节丰富程度。 3. 图像重建:根据分形变换过程生成分形图像数据,通过插值算法将其转换为具有更高分辨率的图像。常用的插值算法包括双线性插值、双三次插值等。插值过程可以通过计算每个像素的亮度值来进行,也可以通过计算颜色值来进行。 4. 结果显示:生成的插值图像可以使用Matlab的图像显示功能展示出来,以供用户观看和分析。用户也可以根据自己的需求对插值参数进行调整,以探索更多不同的结果。 基于Matlab的分形插值程序在图像处理、数字艺术等领域具有广泛的应用。它可以用于图像重建、纹理合成、艺术创作等方面。通过分形插值技术生成的图像通常具有独特的细节和纹理特征,可以用于增强原始图像的视觉效果。同时,这种程序也提供了一个创作平台,让用户可以通过调整分形参数和插值算法来实现个性化的效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值