基于分形的山脉河流生成

自相似

所谓自相似,是一种尺度变换下的不变性(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]);
        }
  }


结果


基于分形的山脉河流生成_第1张图片

iteration = 5 Roughness = 0.2

基于分形的山脉河流生成_第2张图片

iteration = 15 Roughness = 0.8



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



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

Midpoint in 3D

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

基于分形的山脉河流生成_第3张图片



需要注意的是,这每次细分都是上下文相关的
基于分形的山脉河流生成_第4张图片



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

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

public class EdgeMidHashTable
{
	List edges;
	List midNodes;

	public EdgeMidHashTable()
	{
		edges = new List();
		midNodes = new List();
	}

	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);
		}
	}




看下生成结果

基于分形的山脉河流生成_第5张图片
迭代4次


基于分形的山脉河流生成_第6张图片
迭代5次





生成Mesh看一下

public void GenerateMesh()
	{
		meshVertices = new List();
		meshTriangles = new List();

		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().mesh = mesh;

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


运行结果

基于分形的山脉河流生成_第7张图片





SQUIG CURVES算法生成河流


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

边的细分遵循下面的规则

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

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

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

重合的边要么是一条entry和一条exit,要么全是neutral。
基于分形的山脉河流生成_第8张图片




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



代码实现如下

首先看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);
	}

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

	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;
	}
	/// 
	/// Sort Triangle's vertices.
	/// 
	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 };
	}



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

下面是运行的一些结果。

基于分形的山脉河流生成_第9张图片

基于分形的山脉河流生成_第10张图片


SQUIG CURVES with Midpoint生成山脉河流


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

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

基于分形的山脉河流生成_第11张图片


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




主要看一下边的分割函数

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;
		}
	}




看一下结果

基于分形的山脉河流生成_第12张图片



体素风格渲染

基于分形的山脉河流生成_第13张图片


Marching cube


基于分形的山脉河流生成_第14张图片



 

参考

A Fractal Model of Mountains with Rivers

Generating RandomFractal Terrain

Simple2d Terrain With Midpoint Displacement


你可能感兴趣的:(基于分形的山脉河流生成)