自相似

所谓自相似,是一种尺度变换下的不变性(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->3edges[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->2edges[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->3edges[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->2edges[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 + edge8Edge[] 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 1if (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 2else 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 3else 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 4else 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 + Neutralcase 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 + Neutralcase EdgeLabel.Entry://RiverCrossed from last triangleif (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 Riverelse{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 + Neutralcase EdgeLabel.Neutral:subEdge0 = new Edge(originalEdge.StartNode, midNode, EdgeLabel.Neutral);subEdge1 = new Edge(midNode, originalEdge.EndNode, EdgeLabel.Neutral);break;//Exit-> Exit + Neutralcase 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 Riverelse{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

基于分形的山脉河流生成相关推荐

  1. RDKit | 基于多片段的分子生成(骨架A+骨架B+骨架C)

    通过BRICS算法产生片段库 通过结合三个片段(A,B,C)生成ABC型分子. 环境 Win10 RDKit2020.09.1 Python=3.7.9 基于多片段的分子生成 导入库 import n ...

  2. pandas使用replace函数移除dataframe数值数据中的逗号并基于处理后的数据生成新的整型数据列(remove comma from column values in Pandas)

    pandas使用replace函数移除dataframe数值数据中的逗号并基于处理后的数据生成新的整型数据列(remove comma from column values in Pandas Dat ...

  3. 近期活动盘点:高级机器学习训练营、基于神经网络的代码自动生成” “开放学术图谱”、西山金融科技产业创新论坛...

    想知道近期有什么最新活动?大数点为你整理的近期活动信息在此: 清华唐杰教授授课 高级机器学习训练营 我们都知道随着数据复杂度的不断提高,经典机器学习算法已经很难满足实际需求,当前,针对复杂数据对象.复 ...

  4. 科技人员在计算机前的肖像,基于计算机视觉的人脸肖像画生成研究

    摘要: 通过计算机处理进行人脸肖像画生成是计算机视觉的研究方向之一,该技术在科普展览领域有了广泛使用,并且其中使用的计算机视觉相关的研究技术在工业生产和日常生活中也都有广泛应用.本文介绍了一种基于计算 ...

  5. 非自回归也能预训练:基于插入的硬约束生成模型预训练方法

    论文标题: POINTER: Constrained Text Generation via Insertion-based Generative Pre-training 论文作者: Yizhe Z ...

  6. 直播预告:基于动态词表的对话生成研究 | PhD Talk #21

    「PhD Talk」是 PaperWeekly 的学术直播间,旨在帮助更多的青年学者宣传其最新科研成果.我们一直认为,单向地输出知识并不是一个最好的方式,而有效地反馈和交流可能会让知识的传播更加有意义 ...

  7. 直播预告:基于动态词表的对话生成研究 | PaperWeekly x 微软亚洲研究院

    「PhD Talk」是 PaperWeekly 的学术直播间,旨在帮助更多的青年学者宣传其最新科研成果.我们一直认为,单向地输出知识并不是一个最好的方式,而有效地反馈和交流可能会让知识的传播更加有意义 ...

  8. 论文浅尝 | 基于知识库的神经网络问题生成方法

    论文笔记整理:谭亦鸣,东南大学博士生,研究方向为跨语言知识图谱问答. 来源:NLPCC2018 链接:http://tcci.ccf.org.cn/conference/2018/papers/EV7 ...

  9. 基于 Tracing 数据的拓扑关系生成原理

    背景 随着互联网架构的流行,越来越多的系统开始走向分布式化.微服务化.如何快速发现和定位分布式系统下的各类性能瓶颈成为了摆在开发者面前的难题.借助分布式追踪系统的调用链路还原能力,开发者可以完整地了解 ...

最新文章

  1. 2020年2月全国程序员工资统计,平均工资13716元,你被平均了吗?
  2. linux查看网卡速率
  3. java 判断二叉树是否平衡_剑指Offer - 判断二叉树是否是平衡二叉树
  4. background-attachment:fixed应用
  5. windows配置Python多版本共存
  6. 字符串匹配算法——KMP算法学习
  7. SQL SERVER LEFT JOIN, INNER JOIN, RIGHT JOIN
  8. 【转】解决IE8无法加载webplayer流媒体播放器的方法
  9. 创建脚本的步骤整理(转发)
  10. 罗技无法使用计算机上的配置文件,Win10专业版罗技无线鼠标无法使用咋办?
  11. Matplotlab可视化学习笔记(二):如何绘制柱状图
  12. matlab geodetic2ecef,卫星轨道问题
  13. 山东腾飞科尔沁 国稻种芯·中国水稻节:内蒙沙漠万亩水稻
  14. Taro微信小程序使用getUserProfile获取微信用户头像昵称等信息
  15. cisco交换机配置记录(一)
  16. 抽奖随机滚动_原来抽奖不是凭运气!两个技巧,让你在抽奖环节独占鳌头
  17. 【贪心】Bin Packing
  18. 让程序员跳槽的非钱原因
  19. 科达录播服务器修改ip,科达视讯平台API使用说明
  20. 理性讨论:国产沙盒游戏为何都比不上《方舟:生存进化》跟《MC》?

热门文章

  1. RTOS设备如何快速实现OTA升级--快速接入OTA平台
  2. 【程序员必修数学课】-基础思想篇-数学归纳法-如何用数学归纳提高代码效率
  3. 2022宜宾市南溪区总工会招聘工会工作者仿真试题及答案(多选题)
  4. JAVA char转int
  5. FLOPS(每秒浮点运算次数), TFLOP,Statistical vs. Computational Efficiency
  6. Java-查询数据库
  7. 避免软件延时被编译器优化
  8. yEd Graph Editor 操作指南
  9. 三种常见的 Mac 安装 git 工具的方法
  10. Java课程设计——房屋出租信息管理系统