OK alors je me lance dans une petite explication.
Si vous voulez comparer, il y a une explication de la première version en page 1 du sujet.
Alors, pour commencer, il n'y a rien, juste un gameobject empty avec un script. ^^
en structure j'ai :
Code : Tout sélectionner
struct POINT
{
public Vector3 pos; // position du vertex
public int[] PointNext; // array des vertex directement connectés par un edge
public int PNC; // nombre de vertexconnecté, pour se passer des listes moins rapides
public int Color; // couleur du vertex
}
struct EDGE
{
public int P1; // premier vertexx de l'edge
public int P2; // deuxieme vertex de l'edge
public int Enew; // lors d'une subdivision, le nouvel edge créé
}
struct TRI
{
public int P1; // vertex 1 du triangle
public int P2; // vertex 2 du triangle
public int P3; // vertex 3 du triangle
public int E1; // edge 1 du triangle
public int E2; // edge 2 du triangle
public int E3; // edge 3 du triangle
}
Avec ça je peut stocker toutes les donnée dont j'ai besoin.
Je commence par créer 8 vertex, et les edge et triangle correspondant:
Ensuite, pour chacune des edges, je rajoute un point au milieu, je multiplie donc par 4 le nombre de triangle. c'est en fait une simple subdivision genre tesselation ou turboSmooth de 3ds max par exemple. je réalise l'action 5 fois , en ajoutant un peu de smooth et repousse les bords sur un cercle. La subdivision se passe en multithread :
Je cré la forme basique de l'ile en choisissant quelques points que je met en rouge, d'autres en bleu et je propage les couleurs aux voisins :
Cette fois je procède a la subdivision de edge uniquement pour ceux qui ont deux points rouge, et en trouvant les points rouge qui ont un voisin bleu, je détermine une ligne de cote, que j'adouci :
Je réalise encore une fois l'action, en deplaçant la cote vers la terre pour avoir une transition douce entre la densité des point bleu et les rouges :
Encore une fois la même étape mais cette fois je rajoute un calcul des hauteurs, basé sur la distance entre un point et son point de la cote le plus proche ( calcul GPU, voir après) :
Et je subdivise encore une fois, mais cette fois au lieu d'une élévation par rapport a la cote, je calcul un mix de noise via GPU, une petite tambouille qui mix 15 itération de noise avec l'altitude de base pour tenter d'avoir un relief cohérent :
Enfin une dernière itération de subdivision, pour arriver au nombre final de vertex et triangles :
et sans le debug qui affiche les edge :
Pour ce qui est du multithreading des subdivisions, faut éviter les modification de valeurs commune, l'ajout de plusieurs threads à une même liste (d'ailleurs les listes sont trop lente, préférer les array) ou l'incrémentation d'une même valeurs par plusieurs thread. Il me faut donc savoir , pour chaque triangles, le nombre max futur de triangle, idem pour les edge et les points pour redimensionner les array. J'ai donc :
- l'ajout d'un point par edge donc :
- pour chaque Edge, je les coupe en deux donc 2x plus, et dans chaque triangle, 3 nouveaux edge qui relient les trois noux point de chaque edge, et donc :
- et pour chaque triangle, j'en ai 4x plus (un pour chaque sommets + un central) :
Ensuite, encore dans le thread principal, j'ajoute les points au milieu de chaque edge en suivant une condition de couleur ( tous aux premières étapes, puis juste ceux aux point rouge), et pour chaque edge j'assigne l'indice du nouvel edge créé:
Code : Tout sélectionner
for (int e = 0; e < EdgeCount; e++) {
p1 = Edge [e].P1;
p2 = Edge [e].P2;
if (p1 != p2) {
Moyenne = Point [p1].Color + Point [p2].Color;
if (Moyenne > 1) {
Point [PointCount + e].pos = (Point [p1].pos + Point [p2].pos) * 0.5f;
Point [PointCount + e].Color = Mathf.Max (Point [p1].Color, Point [p2].Color);
Point [PointCount + e].PointNext = new int[7];
Edge [EdgeCount + e].P1 = p2;
Edge [EdgeCount + e].P2 = PointCount + e;
Edge [e].P2 = PointCount + e;
Edge [e].Enew = EdgeCount + e;
} else {
Edge [e].Enew = -1;
}
}
}
C'est ensuite qu’intervient le multi thread vu le nombre de triangles. chaque thread (8) calcul une partie des triangles (1/8). faut donc assigner les thread :
Code : Tout sélectionner
for (int k = 0; k < core; k++) { // core = nombre de cœurs dispo du processeur, ici 8
int K = k;
tache [K] = new Thread (ThreadTri);
tache [K].Start (K);
}
for (int i = 0; i < core; i++) {
tache [i].Join ();
}
Pour chaque thread lancé, je calcul le premier et le dernier triangle dont il doit s'occuper :
Code : Tout sélectionner
int y = Convert.ToInt32 (K);
int count = (TriCount / core); // TriCount = Tri.length
int start = y * count;
int end = (y + 1) * count;
if (y == (core - 1)) {
end = TriCount;
}
et pour chaque triangle, trois cas possible :
- tout ses points bleu ou un seul point rouge, pas de subdivision
- deux points rouge, une division qui cré deux triangles a la place d'un seul
- trois points rouge , une division qui fait 4 triangle à partir d'un seul.
Code : Tout sélectionner
for (int t = start; t < end; t++) { // on s'occupe juste des triangles utilisé par le thread
if ((Tri [t].P1 + Tri [t].P2) != 0) {
offsetE = EdgeCount * 2 + t * 3; // pour chaque edge on a deja crée un autre en ajoutnt les points,
//ensuite pour chaque triangle au mieux on ajout trois edge.
// OffsetE sers pour chaque triangle à n'assigner des nouveaux edges unique
// sans que plusieurs triangles assignent deux fois le même edge.
// on liste les edge du triangles et les edge nouveau de chaque edge s'il y en a
triE [0] = Tri [t].E1;
triE [1] = Edge [triE [0]].Enew;
triE [2] = Tri [t].E2;
triE [3] = Edge [triE [2]].Enew;
triE [4] = Tri [t].E3;
triE [5] = Edge [triE [4]].Enew;
// on stock les trois points du triangle
triP [0] = Tri [t].P1;
triP [1] = Tri [t].P2;
triP [2] = Tri [t].P3;
// calcul de la couleur moyenne des points du triangle
Moyenne = Point [triP [0]].Color + Point [triP [1]].Color + Point [triP [2]].Color;
// si tout les points du triangle sont rouge (ou blanc pour les premières étapes) :
if (Moyenne > 2) {
// pour chacun des points du triangles, il va y avoir un nouveau triangle
for (int p = 0; p < triP.Length; p++) {
pointCount = 0;
edgeCount = 0;
for (int e = 0; e < triE.Length; e++) {
Te = triE [e];
if (Edge [Te].P1 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P2;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
if (Edge [Te].P2 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P1;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
}
// idem que pour OffsetE, offsetT pour que chaque tri ne modifie jamais deux fois le meme triangle
offsetT = TriCount + t * 3 + p;
Edge [offsetE + p].P1 = point [0];
Edge [offsetE + p].P2 = point [1];
Tri [offsetT] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE + p);
}
Tri [t] = new TRI (Edge [triE [0]].P2, Edge [triE [2]].P2, Edge [triE [4]].P2, offsetE, offsetE + 1, offsetE + 2);
// si deux points blancs (les points des cotes on été passé a blanc):
} else if (Moyenne == 2) {
int C = 0;
for (int p = 0; p < triP.Length; p++) {
// pour chacun des points, on va rajouter juste un triangle pour les somet blanc
if (Point [triP [p]].Color == White) {
pointCount = 0;
edgeCount = 0;
for (int e = 0; e < triE.Length; e++) {
Te = triE [e];
if (Te != -1) {
if (Edge [Te].P1 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P2;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
} else if (Edge [Te].P2 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P1;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
}
}
C += 1;
if (C == 1) {
offsetT = TriCount + t * 3 + p;
Tri [offsetT] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE);
} else {
Tri [t] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE);
}
}
}
Edge [offsetE].P1 = point [0];
Edge [offsetE].P2 = point [1];
}
}
}
A ce moment je me retrouve avec des arrays plus long que le nombre reel d'éléments utilisé (vu j'ai compté le nombre max possible), 'ai donc une petite fonction qui, en thread principal, qui retrouve que les éléments utilisé, réarange les array pour supprimer les éléments inutilisé :
Code : Tout sélectionner
void reduce ()
{
int[] PointTemp = new int[Point.Length];
int[] EdgeTemp = new int[Edge.Length];
int Pcount = 0;
int Ecount = 0;
int Tcount = 0;
for (int i = 0; i < Point.Length; i++) {
if (Point [i].PNC != 0) {
PointTemp [i] = Pcount;
Point [Pcount] = Point [i];
Pcount += 1;
}
}
for (int i = 0; i < CoteCount; i++) {
COTE [i] = PointTemp [COTE [i]];
}
for (int i = 0; i < Edge.Length; i++) {
if (Edge [i].P1 != Edge [i].P2) {
EdgeTemp [i] = Ecount;
Edge [Ecount].P1 = PointTemp [Edge [i].P1];
Edge [Ecount].P2 = PointTemp [Edge [i].P2];
if (Edge [i].Enew != -1) {
Edge [Ecount].Enew = EdgeTemp [Edge [i].Enew];
}
Ecount += 1;
}
}
for (int i = 0; i < Tri.Length; i++) {
if ((Tri [i].P1 + Tri [i].P2) != 0) {
Tri [Tcount].P1 = PointTemp [Tri [i].P1];
Tri [Tcount].P2 = PointTemp [Tri [i].P2];
Tri [Tcount].P3 = PointTemp [Tri [i].P3];
Tri [Tcount].E1 = EdgeTemp [Tri [i].E1];
Tri [Tcount].E2 = EdgeTemp [Tri [i].E2];
Tri [Tcount].E3 = EdgeTemp [Tri [i].E3];
Tcount += 1;
}
}
Array.Resize (ref Point, Pcount);
Array.Resize (ref Edge, Ecount);
Array.Resize (ref Tri, Tcount);
}
Voilà dans les grandes lignes pour le multiThread, maintenant passons au compute shader, ^^ :
Donc c'est sensiblement les même genre de fonction qu'en C#, mais executé par le GPU. L'avantage c'est que du coup on a acces a 1024 unité de calcul (768 pour le shader 4.0) contre 8 pour le multithread.
pour l'utiliser, il faut transmettre les données au GPU via des buffer.
ça peut etre de simple array de int, float ou autre, ou des struct personalisée. en cas de struct personalisée, il faut qu'ils soit déclaré à l'identique dans le compute shader, ordre, nom..
Par exemple dans le script en C# :
Code : Tout sélectionner
private struct POINT1
{
public Vector3 pos;
public int Color;
public POINT1 (Vector3 pos, int Color)
{
this.pos = pos;
this.Color = Color;
}
}
faut retrouver dans le compute shader :
Il faut eviter d'envoyer trop de donnée, car c'est le plus long par rapport au temps de calcul, d'ou ce struct avec juste la position et le couleur des vertex (pour éviter d'envoyer des infos qui ne me seront pas utiles comme les points voisins)
Je déclare donc en C# mon nouveau struct et ses valeurs pour le calcul de l'élévation:
Code : Tout sélectionner
POINT1[] P = new POINT1[PointCount];
for (int i = 0; i < PointCount; i++) {
P [i].pos = Point [i].pos;
P [i].Color = Point [i].Color;
}
et faut créer un buffer qui va stocker, envoyer au GPU et aussi pouvoir les récupérer plus tard :
Code : Tout sélectionner
// PointCount est le nombre d'éléments que va contenir l'array, ou plutôt le buffer
// 16 est le nombre de bit, avec 4 bit par donnée, donc 4x3 pour le vector3 et 4 pour le int
ComputeBuffer VertexBuffer = new ComputeBuffer (PointCount, 16);
VertexBuffer.SetData (P);
ensuite il faut savoir combien de donné va traiter chaque thread du GPU :
et on assigne au GPU la donnée :
Code : Tout sélectionner
MyCompute.SetBuffer (IndexKernel, "Point", VertexBuffer);
// avec Mycompute le compute shader déclaré en C# par "public ComputeShader MyCompute;"
// "Point" le nom de la variable dans le compute shader ou assigner les valeurs
// VertexBuffer a assigner a "Point" du compute shader
avec forcément la déclaration de "Point" dans le compute shader :
ensuite il faut retrouver dans le compute shader le "Kernel" qui est en fait l'index de la fonction que l'ont va utiliser. on peut donc avoir un seul compute shader, avec plusieurs fonction donc de kernel, il suffit de trouver le bon :
On déclare en C# :
qui va retrouver dans le compute shader :
Code : Tout sélectionner
#pragma kernel Elevation // déclaration du Kernel
[numthreads(1024,1,1)] //
void Elevation (uint3 id : SV_DispatchThreadID)// la fonction correspondant au kernel "Elevation"
{
// ici tout le code qui devra être utilisé
}
ensuite en C# il suffit de lancer l'execution du GPU :
et enfin récupérer les données modifiée par le GPU, et les libérer :
Code : Tout sélectionner
VertexBuffer.GetData (P); // on recupere le données du buffer "VertexBuffer" qu'on defini à la place de "P"
VertexBuffer.Release ();
le code complet donnerai :
En C#:
Code : Tout sélectionner
public class Map : MonoBehaviour
{
// déclaration d'un struct
private struct POINT1
{
public Vector3 pos;
public int Color;
public POINT1 (Vector3 pos, int Color)
{
this.pos = pos;
this.Color = Color;
}
}
déclaration du compute shader, qu'il faudra indiquer dans l'inspecteur
public ComputeShader MyCompute;
// une fonction
void Elevation ()
{
// on assigne des valeurs
POINT1[] P = new POINT1[PointCount];
for (int i = 0; i < PointCount; i++) {
P [i].pos = Point [i].pos;
P [i].Color = Point [i].Color;
}
// on defini quel kernel du compute shader on va utiliser
int IndexKernel = MyCompute.FindKernel ("Elevation");
// on crée un buffer avec un nombre d'élément et de bit
ComputeBuffer VertexBuffer = new ComputeBuffer (PointCount, 16);
// on assigne une donnée au buffer
VertexBuffer.SetData (P);
// on calcul le nombre d'élément que va taiter chaque thread du GPU
int NbThreadX = PointCount / 1024 + 1;
// on assigne au GPU le buffer
MyCompute.SetBuffer (IndexKernel, "Point", VertexBuffer);
// on dispatch les données au kernel, en fonction du nombre de thread a utiliser
MyCompute.Dispatch (IndexKernel, NbThreadX, 1, 1);
// on recupère les donnée du GPU pour les retrouver dans le CPU
VertexBuffer.GetData (P);
// on libere les données alouées au GPU
VertexBuffer.Release ();
}
}
et dans le compute shader :
Code : Tout sélectionner
// déclaration du kernel
#pragma kernel Elevation
// declaration du struct
struct POINT1
{
half3 pos;
uint Color;
};
// déclaration de la variable
RWStructuredBuffer<POINT1> Point;
// definition du kernel et le nombre de thread utilisée
[numthreads(1024,1,1)]
void Elevation (uint3 id : SV_DispatchThreadID)
{
// exemple : on redefini la position de chaque point par un vecteur3 zero
// id.x est l'index du point
Point[id.x].pos = half3(0,0,0)
}
bon voila, c'est peut etre pas tres clair, j'en ai surement oublié. Je me suis basé sur
cette page qui explique certainement bien mieux que moi ^^
Apres si vous avezz des questions, je peux tenter d'y répondre
en attendant je laisse un petit lien
vers le standalone si vous voulez tester.
Et le script complet et le compute shader complet :
► Afficher le texte
Script C#
Code : Tout sélectionner
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class Map : MonoBehaviour
{
private struct POINT
{
public Vector3 pos;
public int[] PointNext;
public int PNC;
public int Color;
public POINT (Vector3 pos, int[] PointNext, int PNC, int Color)
{
this.pos = pos;
this.PointNext = PointNext;
this.PNC = PNC;
this.Color = Color;
}
}
private struct POINT1
{
public Vector3 pos;
public int Color;
public POINT1 (Vector3 pos, int Color)
{
this.pos = pos;
this.Color = Color;
}
}
private struct EDGE
{
public int P1;
public int P2;
public int Enew;
public EDGE (int P1, int P2, int Enew)
{
this.P1 = P1;
this.P2 = P2;
this.Enew = Enew;
}
}
private struct TRI
{
public int P1;
public int P2;
public int P3;
public int E1;
public int E2;
public int E3;
public TRI (int P1, int P2, int P3, int E1, int E2, int E3)
{
this.P1 = P1;
this.P2 = P2;
this.P3 = P3;
this.E1 = E1;
this.E2 = E2;
this.E3 = E3;
}
}
private struct ZONE
{
public int[] tri;
public int triCount;
public ZONE (int[] tri, int triCount)
{
this.tri = tri;
this.triCount = triCount;
}
}
private POINT[] Point;
private int PointCount;
private EDGE[] Edge;
private int EdgeCount;
private TRI[] Tri;
private int TriCount;
private ZONE[] Zone;
private int[] COTE = new int[6000];
private int CoteCount = 0;
private float Rayon = 20000.0f;
private int White = 1;
private int Blue = 0;
private int Red = 2;
private Color[] Couleur;
public Material mat;
public ComputeShader MyCompute;
int core = 8;
Thread[] tache;
float tp;
void Start ()
{
gameObject.AddComponent<MeshFilter> ();
gameObject.AddComponent<MeshRenderer> ();
gameObject.GetComponent<Renderer> ().material = mat;
Init ();
}
void Init ()
{
tp = Time.realtimeSinceStartup;
tache = new Thread[core];
CreateBase ();
Couleur = new Color[3] {
new Color (0.0f, 0.0f, 1.0f, 0.4f),
new Color (1.0f, 1.0f, 1.0f, 0.4f),
new Color (1.0f, 0.0f, 0.0f, 0.4f),
};
for (int i = 0; i < 5; i++) {
ThreadSubdivise ();
spherify (i);
Smooth (White);
}
AddColor ();
Cote ();
ThreadSubdivise ();
ExtendCote ();
reduce ();
ThreadSubdivise ();
ExtendCote ();
PropageCote ();
Smooth (Red);
reduce ();
ThreadSubdivise ();
ExtendCote ();
PropageCote ();
Elevation ();
Smooth (Red);
reduce ();
ThreadSubdivise ();
ExtendCote ();
Elevation1 ();
reduce ();
ThreadSubdivise ();
TriCount = 0;
for (int i = 0; i < Tri.Length; i++) {
if (Tri [i].P1 != Tri [i].P2) {
TriCount += 1;
}
}
PointCount = 0;
for (int i = 0; i < Point.Length; i++) {
if (Point [i].PNC > 0) {
PointCount += 1;
}
}
CreateMeshGPU ();
tp = Time.realtimeSinceStartup - tp;
Debug.LogError ("temps : " + tp + "s , PointCount : " + Point.Length + " , triCount : " + TriCount);
Debug.LogError ("ratio : " + Mathf.Round (TriCount / tp / 10000.0f) / 100.0f + " MPoly/s");
}
void OnGUI() {
GUI.Box (new Rect (Screen.width/2 - 270,Screen.height-65,540,60), "");
GUI.Box(new Rect(Screen.width/2 - 138,Screen.height -60 ,380,25),"Temps : " + tp + "s , Vertex : " + Point.Length + " , Triangles : " + TriCount);
GUI.Box(new Rect(Screen.width/2 - 138,Screen.height-35,380,25),"Ratio : " + Mathf.Round (TriCount / tp / 10000.0f) / 100.0f + " MPoly/s");
if(GUI.Button(new Rect(Screen.width/2-242,Screen.height-60,100,50), "GENERATE"))
{
Init ();
}
}
void Update(){
//for (int i = 0; i < Edge.Length; i += 4) {
// int P1 = Edge [i].P1;
// int P2 = Edge [i].P2;
// if (Point [P1].Color == Red && Point [P2].Color == Red) {
// Debug.DrawLine (Point [P1].pos, Point [P2].pos, Couleur [2]);
// } else {
// Debug.DrawLine (Point [P1].pos, Point [P2].pos, Couleur [0]);
// }
//}
}
void CreateBase ()
{
Point = new POINT[8];
Edge = new EDGE[14];
Tri = new TRI[7];
PointCount = 0;
EdgeCount = 0;
TriCount = 0;
Point [PointCount].pos = Vector3.zero;
Point [PointCount].PointNext = new int[7];
Point [PointCount].Color = White;
PointCount += 1;
float espace = 2 * Mathf.PI / 7;
for (int i = 0; i < 7; i++) {
Point [PointCount].pos.x = Mathf.Sin (i * espace) * Rayon * 1.3f;
Point [PointCount].pos.z = Mathf.Cos (i * espace) * Rayon * 1.3f;
Point [PointCount].PointNext = new int[7];
Point [PointCount].Color = White;
PointCount += 1;
}
Edge [0] = new EDGE (0, 1, 0);
Edge [1] = new EDGE (0, 2, 0);
Edge [2] = new EDGE (1, 2, 0);
Edge [3] = new EDGE (0, 3, 0);
Edge [4] = new EDGE (2, 3, 0);
Edge [5] = new EDGE (0, 4, 0);
Edge [6] = new EDGE (3, 4, 0);
Edge [7] = new EDGE (0, 5, 0);
Edge [8] = new EDGE (4, 5, 0);
Edge [9] = new EDGE (0, 6, 0);
Edge [10] = new EDGE (5, 6, 0);
Edge [11] = new EDGE (0, 7, 0);
Edge [12] = new EDGE (6, 7, 0);
Edge [13] = new EDGE (7, 1, 0);
EdgeCount += 14;
Tri [0] = new TRI (0, 1, 2, 0, 1, 2);
Tri [1] = new TRI (0, 2, 3, 1, 3, 4);
Tri [2] = new TRI (0, 3, 4, 3, 5, 6);
Tri [3] = new TRI (0, 4, 5, 5, 7, 8);
Tri [4] = new TRI (0, 5, 6, 7, 9, 10);
Tri [5] = new TRI (0, 6, 7, 9, 11, 12);
Tri [6] = new TRI (0, 7, 1, 11, 0, 13);
TriCount += 7;
}
void Next ()
{
for (int p = 0; p < Point.Length; p++) {
Point [p].PNC = 0;
}
int p1;
int p2;
for (int e = 0; e < Edge.Length; e++) {
if (Edge [e].P1 != Edge [e].P2) {
p1 = Edge [e].P1;
p2 = Edge [e].P2;
Point [p1].PointNext [Point [p1].PNC] = p2;
Point [p1].PNC += 1;
if (Point [p1].PNC == Point [p1].PointNext.Length) {
Array.Resize (ref Point [p1].PointNext, Point [p1].PointNext.Length + 9);
}
Point [p2].PointNext [Point [p2].PNC] = p1;
Point [p2].PNC += 1;
if (Point [p2].PNC == Point [p2].PointNext.Length) {
Array.Resize (ref Point [p2].PointNext, Point [p2].PointNext.Length + 9);
}
}
}
}
void AddColor ()
{
for (int i = 0; i < 30; i++) {
int RD = UnityEngine.Random.Range (7, Point.Length);
float P = (new Vector2 (Point [RD].pos.x, Point [RD].pos.z)).magnitude;
if (P > Rayon * 1.1f) {
i -= 1;
} else {
Point [RD].Color = Red;
}
}
for (int i = 0; i < 20; i++) {
int RD = UnityEngine.Random.Range (7, Point.Length);
float P = (new Vector2 (Point [RD].pos.x, Point [RD].pos.z)).magnitude;
if (P < Rayon * 1.0f) {
i -= 1;
} else {
Point [RD].Color = Blue;
}
}
PropageColor ();
for (int i = 0; i < Point.Length; i++) {
if (Point [i].pos.magnitude > Rayon * 1.1f) {
Point [i].Color = Blue;
}
}
}
void PropageColor ()
{
for (int i = 0; i < Point.Length; i++) {
int[] next = Point [i].PointNext;
if (Point [i].Color == Red || Point [i].Color == Blue) {
for (int j = 0; j < Point [i].PNC; j++) {
if (Point [next [j]].Color == White) {
Point [next [j]].Color = Point [i].Color;
}
}
}
}
for (int k = 0; k < Point.Length; k++) {
if (Point [k].Color == White) {
PropageColor ();
return;
}
}
}
void spherify (float I)
{
for (int i = 0; i < Point.Length; i++) {
if (Point [i].PNC < 5) {
Point [i].pos *= (Rayon / Point [i].pos.magnitude) * (1.3f + I / 50.0f);
}
}
}
void Smooth (int C)
{
for (int l = 0; l < Point.Length; l++) {
if (Point [l].Color == C || Point [l].Color == Blue) {
POINT P = Point [l];
if (P.PNC > 4) {
Vector3 P1 = Vector3.zero;
for (int j = 0; j < P.PNC; j++) {
P1 += Point [P.PointNext [j]].pos;
}
P1 /= P.PNC;
Point [l].pos = P1;
}
}
}
}
void Cote ()
{
CoteCount = 0;
for (int i = 0; i < Point.Length; i++) {
if (Point [i].Color == Red) {
for (int k = 0; k < Point [i].PNC; k++) {
if (Point [Point [i].PointNext [k]].Color == Blue) {
COTE [CoteCount] = i;
Point [i].Color = White;
CoteCount += 1;
break;
}
}
}
}
VerifCote ();
}
void PropageCote ()
{
int[] CoteTemp = new int[COTE.Length];
int tempCount = CoteCount;
CoteCount = 0;
for (int i = 0; i < tempCount; i++) {
int I = COTE [i];
for (int j = 0; j < Point [I].PNC; j++) {
if (Point [Point [I].PointNext [j]].Color == Red) {
Point [Point [I].PointNext [j]].Color = White;
CoteTemp [CoteCount] = Point [I].PointNext [j];
CoteCount += 1;
}
}
Point [I].Color = Blue;
}
COTE = CoteTemp;
SmoothCote ();
}
void ExtendCote ()
{
int tempCount = CoteCount;
for (int i = 0; i < tempCount; i++) {
int I = COTE [i];
for (int j = 0; j < Point [I].PNC; j++) {
if (Point [Point [I].PointNext [j]].Color == White) {
Point [Point [I].PointNext [j]].Color = Red;
COTE [CoteCount] = Point [I].PointNext [j];
CoteCount += 1;
}
}
}
for (int i = 0; i < CoteCount; i++) {
Point [COTE [i]].Color = White;
}
SmoothCote ();
}
void VerifCote ()
{
int[] CoteTemp = COTE;
int tempCount = CoteCount;
CoteCount = 0;
for (int i = 0; i < tempCount; i++) {
int C = 0;
int I = CoteTemp [i];
for (int j = 0; j < Point [I].PNC; j++) {
if (Point [Point [I].PointNext [j]].Color == Red) {
C += 1;
COTE [CoteCount] = I;
CoteCount += 1;
break;
}
}
if (C == 0) {
Point [I].Color = Blue;
}
}
SmoothCote ();
}
void SmoothCote ()
{
POINT P;
Vector3 P1;
int[] Next;
for (int i = 0; i < CoteCount; i++) {
P = Point [COTE [i]];
P1 = P.pos * 2.0f;
Next = P.PointNext;
float divide = 2.0f;
for (int j = 0; j < P.PNC; j++) {
int J = Next [j];
if (Point [J].Color == White) {
P1 += Point [J].pos;
divide += 1.0f;
}
}
P1 /= divide;
Point [COTE [i]].pos = P1;
}
}
void reduce ()
{
int[] PointTemp = new int[Point.Length];
int[] EdgeTemp = new int[Edge.Length];
int Pcount = 0;
int Ecount = 0;
int Tcount = 0;
for (int i = 0; i < Point.Length; i++) {
if (Point [i].PNC != 0) {
PointTemp [i] = Pcount;
Point [Pcount] = Point [i];
Pcount += 1;
}
}
for (int i = 0; i < CoteCount; i++) {
COTE [i] = PointTemp [COTE [i]];
}
for (int i = 0; i < Edge.Length; i++) {
if (Edge [i].P1 != Edge [i].P2) {
EdgeTemp [i] = Ecount;
Edge [Ecount].P1 = PointTemp [Edge [i].P1];
Edge [Ecount].P2 = PointTemp [Edge [i].P2];
if (Edge [i].Enew != -1) {
Edge [Ecount].Enew = EdgeTemp [Edge [i].Enew];
}
Ecount += 1;
}
}
for (int i = 0; i < Tri.Length; i++) {
if ((Tri [i].P1 + Tri [i].P2) != 0) {
Tri [Tcount].P1 = PointTemp [Tri [i].P1];
Tri [Tcount].P2 = PointTemp [Tri [i].P2];
Tri [Tcount].P3 = PointTemp [Tri [i].P3];
Tri [Tcount].E1 = EdgeTemp [Tri [i].E1];
Tri [Tcount].E2 = EdgeTemp [Tri [i].E2];
Tri [Tcount].E3 = EdgeTemp [Tri [i].E3];
Tcount += 1;
}
}
Array.Resize (ref Point, Pcount);
Array.Resize (ref Edge, Ecount);
Array.Resize (ref Tri, Tcount);
}
void ThreadSubdivise ()
{
EdgeCount = Edge.Length;
TriCount = Tri.Length;
PointCount = Point.Length;
Array.Resize (ref Point, Point.Length + Edge.Length);
Array.Resize (ref Edge, Edge.Length * 2 + Tri.Length * 3);
Array.Resize (ref Tri, Tri.Length * 4);
int p1;
int p2;
int Moyenne;
for (int e = 0; e < EdgeCount; e++) {
p1 = Edge [e].P1;
p2 = Edge [e].P2;
if (p1 != p2) {
Moyenne = Point [p1].Color + Point [p2].Color;
if (Moyenne > 1) {
Point [PointCount + e].pos = (Point [p1].pos + Point [p2].pos) * 0.5f;
Point [PointCount + e].Color = Mathf.Max (Point [p1].Color, Point [p2].Color);
Point [PointCount + e].PointNext = new int[7];
Edge [EdgeCount + e].P1 = p2;
Edge [EdgeCount + e].P2 = PointCount + e;
Edge [e].P2 = PointCount + e;
Edge [e].Enew = EdgeCount + e;
} else {
Edge [e].Enew = -1;
}
}
}
TriDivide ();
Next ();
}
void TriDivide ()
{
for (int k = 0; k < core; k++) {
int K = k;
tache [K] = new Thread (ThreadTri);
tache [K].Start (K);
}
for (int i = 0; i < core; i++) {
tache [i].Join ();
}
}
void ThreadTri (object K)
{
int y = Convert.ToInt32 (K);
int count = (TriCount / core);
int start = y * count;
int end = (y + 1) * count;
if (y == (core - 1)) {
end = TriCount;
}
int[] point = new int[3];
int[] edge = new int[2];
int[] triE = new int[6];
int[] triP = new int[3];
int pointCount;
int edgeCount;
int Te;
int offsetE;
int offsetT;
int Moyenne;
for (int t = start; t < end; t++) {
if ((Tri [t].P1 + Tri [t].P2) != 0) {
offsetE = EdgeCount * 2 + t * 3;
triE [0] = Tri [t].E1;
triE [1] = Edge [triE [0]].Enew;
triE [2] = Tri [t].E2;
triE [3] = Edge [triE [2]].Enew;
triE [4] = Tri [t].E3;
triE [5] = Edge [triE [4]].Enew;
triP [0] = Tri [t].P1;
triP [1] = Tri [t].P2;
triP [2] = Tri [t].P3;
Moyenne = Point [triP [0]].Color + Point [triP [1]].Color + Point [triP [2]].Color;
if (Moyenne > 2) {
for (int p = 0; p < triP.Length; p++) {
pointCount = 0;
edgeCount = 0;
for (int e = 0; e < triE.Length; e++) {
Te = triE [e];
if (Edge [Te].P1 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P2;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
if (Edge [Te].P2 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P1;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
}
offsetT = TriCount + t * 3 + p;
Edge [offsetE + p].P1 = point [0];
Edge [offsetE + p].P2 = point [1];
Tri [offsetT] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE + p);
}
Tri [t] = new TRI (Edge [triE [0]].P2, Edge [triE [2]].P2, Edge [triE [4]].P2, offsetE, offsetE + 1, offsetE + 2);
} else if (Moyenne == 2) {
int C = 0;
for (int p = 0; p < triP.Length; p++) {
if (Point [triP [p]].Color == White) {
pointCount = 0;
edgeCount = 0;
for (int e = 0; e < triE.Length; e++) {
Te = triE [e];
if (Te != -1) {
if (Edge [Te].P1 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P2;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
} else if (Edge [Te].P2 == triP [p] && pointCount < 2) {
point [pointCount] = Edge [Te].P1;
edge [edgeCount] = Te;
pointCount += 1;
edgeCount += 1;
}
}
}
C += 1;
if (C == 1) {
offsetT = TriCount + t * 3 + p;
Tri [offsetT] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE);
} else {
Tri [t] = new TRI (triP [p], point [0], point [1], edge [0], edge [1], offsetE);
}
}
}
Edge [offsetE].P1 = point [0];
Edge [offsetE].P2 = point [1];
}
}
}
}
void Elevation ()
{
PointCount = Point.Length;
float Rand = UnityEngine.Random.Range (1.0f, 3.0f);
POINT1[] P = new POINT1[PointCount];
for (int i = 0; i < PointCount; i++) {
P [i].pos = Point [i].pos;
P [i].Color = Point [i].Color;
}
int IndexKernel = MyCompute.FindKernel ("Elevation");
ComputeBuffer VertexBuffer = new ComputeBuffer (PointCount, 16);
VertexBuffer.SetData (P);
ComputeBuffer CoteBuffer = new ComputeBuffer (CoteCount, 4);
CoteBuffer.SetData (COTE);
int NbThreadX = PointCount / 1024 + 1;
MyCompute.SetBuffer (IndexKernel, "Point", VertexBuffer);
MyCompute.SetBuffer (IndexKernel, "PointCote", CoteBuffer);
MyCompute.SetFloat ("Rand", Rand);
MyCompute.Dispatch (IndexKernel, NbThreadX, 1, 1);
VertexBuffer.GetData (P);
VertexBuffer.Release ();
CoteBuffer.Release ();
for (int i = 0; i < PointCount; i++) {
Point [i].pos = P [i].pos;
}
}
void Elevation1 ()
{
PointCount = Point.Length;
float Rand = UnityEngine.Random.Range (2.0f, 1000.0f);
Vector3[] P = new Vector3[PointCount];
for (int i = 0; i < PointCount; i++) {
P [i] = Point [i].pos;
}
int IndexKernel = MyCompute.FindKernel ("Elevation1");
ComputeBuffer VertexBuffer = new ComputeBuffer (PointCount, 12);
VertexBuffer.SetData (P);
int NbThreadX = PointCount / 1024 + 1;
MyCompute.SetBuffer (IndexKernel, "Poin", VertexBuffer);
MyCompute.SetFloat ("Rand", Rand);
MyCompute.Dispatch (IndexKernel, NbThreadX, 1, 1);
VertexBuffer.GetData (P);
VertexBuffer.Release ();
for (int i = 0; i < PointCount; i++) {
Point [i].pos = P [i];
}
}
void CreateMeshGPU ()
{
Vector3[] vertices = new Vector3[Point.Length];
for (int i = 0; i < Point.Length; i++) {
vertices [i] = Point [i].pos;
}
int[] triangle = new int[Tri.Length * 3];
for (int i = 0; i < Tri.Length; i++) {
triangle [i * 3] = Tri [i].P1;
triangle [i * 3 + 1] = Tri [i].P2;
triangle [i * 3 + 2] = Tri [i].P3;
}
ComputeBuffer VertexBuffer = new ComputeBuffer (vertices.Length, 12);
VertexBuffer.SetData (vertices);
ComputeBuffer TriBuffer = new ComputeBuffer (triangle.Length, 4);
TriBuffer.SetData (triangle);
int IndexKernel = MyCompute.FindKernel ("OrganiseTri");
int NbThreadX = (triangle.Length / 3) / 1024 + 1;
MyCompute.SetBuffer (IndexKernel, "Poin", VertexBuffer);
MyCompute.SetBuffer (IndexKernel, "Tri", TriBuffer);
MyCompute.Dispatch (IndexKernel, NbThreadX, 1, 1);
TriBuffer.GetData (triangle);
VertexBuffer.Release ();
TriBuffer.Release ();
Mesh mesh = gameObject.GetComponent<MeshFilter> ().mesh;
mesh.Clear ();
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.vertices = vertices;
mesh.triangles = triangle;
mesh.RecalculateNormals ();
}
}
et le compute shader qui gere l'elevation par rapport a la cote, leleation par rapport a un noise perso, et l'organisation des tri pour etre clockwise:
Code : Tout sélectionner
#pragma kernel Elevation
#pragma kernel Elevation1
#pragma kernel OrganiseTri
struct POINT1
{
half3 pos;
uint Color;
};
half Rand;
RWStructuredBuffer<POINT1> Point;
RWStructuredBuffer<int> PointCote;
RWStructuredBuffer<half3> Poin;
RWStructuredBuffer<int> Tri;
half4 mod289(half4 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
half4 permute(half4 x)
{
return mod289(((x*34.0)+1.0)*x);
}
half4 taylorInvSqrt(half4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
half2 fade(half2 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float cnoise(half2 P)
{
half4 Pi = floor(P.xyxy) + half4(0.0, 0.0, 1.0, 1.0);
half4 Pf = frac(P.xyxy) - half4(0.0, 0.0, 1.0, 1.0);
Pi = mod289(Pi); // To avoid truncation effects in permutation
half4 ix = Pi.xzxz;
half4 iy = Pi.yyww;
half4 fx = Pf.xzxz;
half4 fy = Pf.yyww;
half4 i = permute(permute(ix) + iy);
half4 gx = frac(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
half4 gy = abs(gx) - 0.5 ;
half4 tx = floor(gx + 0.5);
gx = gx - tx;
half2 g00 = half2(gx.x,gy.x);
half2 g10 = half2(gx.y,gy.y);
half2 g01 = half2(gx.z,gy.z);
half2 g11 = half2(gx.w,gy.w);
half4 norm = taylorInvSqrt(half4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
g00 *= norm.x;
g01 *= norm.y;
g10 *= norm.z;
g11 *= norm.w;
float n00 = dot(g00, half2(fx.x, fy.x));
float n10 = dot(g10, half2(fx.y, fy.y));
float n01 = dot(g01, half2(fx.z, fy.z));
float n11 = dot(g11, half2(fx.w, fy.w));
half2 fade_xy = fade(Pf.xy);
half2 n_x = lerp(half2(n00, n01), half2(n10, n11), fade_xy.x);
float n_xy = lerp(n_x.x, n_x.y, fade_xy.y);
return (2.3 * n_xy);
}
[numthreads(1024,1,1)]
void Elevation (uint3 id : SV_DispatchThreadID)
{
half Min = 20000.0f;
for (uint c = 0; c < PointCote.Length; c ++) {
half dist = length( Point[id.x].pos.xz - Point[PointCote[c]].pos.xz);
if (dist < Min && length(Point[PointCote[c]].pos) > 0 ) {
Min = dist;
}
}
if(Point[id.x].Color == 2){
Point[id.x].pos.y = Min;
}
else {
if ( Min > 0){
Point[id.x].pos.y = - Min*0.10f-100.0f;
}
}
}
[numthreads(1024,1,1)]
void Elevation1 (uint3 id : SV_DispatchThreadID)
{
half Min = Poin[id.x].y;
if(Min > 0 ){
half Perlin = 1.0;
half X = (Poin[id.x].x+20000.0f)/40000.0f+Rand;
half Z = (Poin[id.x].z+20000.0f)/40000.0f+Rand;
half2 uv = half2(X,Z)*2.5;
half Mul = 1.0;
half prev = 1.0;
half inv = 1.0;
for ( uint i = 0 ; i < 8 ; i++){
Perlin += inv * Mul *(cnoise (uv)*0.5+0.5)*1.0;
Perlin += inv * Perlin * Mul * abs(cnoise (uv))*(2.5-inv*0.2)* (cnoise (uv)*0.5+0.5);
uv = 2.1f * uv;
Mul *= 0.4;
inv = - inv;
}
Min *=0.000055f;
Perlin *= 0.40;
if( Min< 0.155f){
Poin[id.x].y = Perlin* Min*Min*5.0001f*2500.00f + Min*2500.00f*Perlin ;
}
else{
Poin[id.x].y = Perlin* (sqrt(Min*1.5f)-0.36205f) *2500.00f + Min*2500.00f*Perlin;
}
}
}
[numthreads(1024,1,1)]
void OrganiseTri (uint3 id : SV_DispatchThreadID)
{
uint i = id.x;
uint a = Tri [i*3];
uint b = Tri [i*3+1];
uint c = Tri [i*3+2];
uint3 trian = uint3(c, b, a);
if (Poin [a].x > Poin [b].x && Poin [a].x > Poin [c].x) {
trian = uint3 (a, b, c);
} else if (Poin [b].x > Poin [a].x && Poin [b].x > Poin [c].x) {
trian = uint3 (b, a, c);
}
half2 A = half2(Poin [trian.x].x, Poin [trian.x].z);
half2 B = half2(Poin [trian.y].x, Poin [trian.y].z);
half2 C = half2(Poin [trian.z].x, Poin [trian.z].z);
half2 dirY = (B - A);
half2 dirZ = (C - A);
half angleY = acos(dot(half2(0.0,1.0),dirY)/length(dirY));
half angleZ = acos(dot(half2(0.0,1.0),dirZ)/length(dirZ));
if (angleY > angleZ) {
a = trian.x;
b = trian.y;
c = trian.z;
} else {
a = trian.x;
b = trian.z;
c = trian.y;
}
Tri [i * 3] = a;
Tri [i * 3 + 1] = b;
Tri [i * 3 + 2] = c;
}