// Initialization creates a System.Random object and precomputes indices and weights of erosion brush static void Initialize(int mapSize, bool resetSeed, ErossionSettings erossionSettings, int seed) { if (resetSeed || prng == null || currentSeed != seed) { prng = new System.Random(seed); currentSeed = seed; } if (erosionBrushIndices == null || currentErosionRadius != erossionSettings.erosionBrushRadius || currentMapSize != mapSize) { InitializeBrushIndices(mapSize, erossionSettings.erosionBrushRadius); currentErosionRadius = erossionSettings.erosionBrushRadius; currentMapSize = mapSize; } }
//----GPU-------------------------- public static float[] ErodeGPU(float[] map, int mapSize, ErossionSettings erossionSettings, ComputeShader erosion) { int mapSizeWithBorder = mapSize + erossionSettings.erosionBrushRadius * 2; int numThreads = erossionSettings.numErosionIterations / 1024; // Create brush List <int> brushIndexOffsets = new List <int>(); List <float> brushWeights = new List <float>(); float weightSum = 0; for (int brushY = -erossionSettings.erosionBrushRadius; brushY <= erossionSettings.erosionBrushRadius; brushY++) { for (int brushX = -erossionSettings.erosionBrushRadius; brushX <= erossionSettings.erosionBrushRadius; brushX++) { float sqrDst = brushX * brushX + brushY * brushY; if (sqrDst < erossionSettings.erosionBrushRadius * erossionSettings.erosionBrushRadius) { brushIndexOffsets.Add(brushY * mapSize + brushX); float brushWeight = 1 - Mathf.Sqrt(sqrDst) / erossionSettings.erosionBrushRadius; weightSum += brushWeight; brushWeights.Add(brushWeight); } } } for (int i = 0; i < brushWeights.Count; i++) { brushWeights[i] /= weightSum; } // Send brush data to compute shader ComputeBuffer brushIndexBuffer = new ComputeBuffer(brushIndexOffsets.Count, sizeof(int)); ComputeBuffer brushWeightBuffer = new ComputeBuffer(brushWeights.Count, sizeof(int)); brushIndexBuffer.SetData(brushIndexOffsets); brushWeightBuffer.SetData(brushWeights); erosion.SetBuffer(0, "brushIndices", brushIndexBuffer); erosion.SetBuffer(0, "brushWeights", brushWeightBuffer); // Generate random indices for droplet placement int[] randomIndices = new int[erossionSettings.numErosionIterations]; for (int i = 0; i < erossionSettings.numErosionIterations; i++) { int randomX = Random.Range(erossionSettings.erosionBrushRadius, mapSize + erossionSettings.erosionBrushRadius); int randomY = Random.Range(erossionSettings.erosionBrushRadius, mapSize + erossionSettings.erosionBrushRadius); randomIndices[i] = randomY * mapSize + randomX; } // Send random indices to compute shader ComputeBuffer randomIndexBuffer = new ComputeBuffer(randomIndices.Length, sizeof(int)); randomIndexBuffer.SetData(randomIndices); erosion.SetBuffer(0, "randomIndices", randomIndexBuffer); // Heightmap buffer ComputeBuffer mapBuffer = new ComputeBuffer(map.Length, sizeof(float)); mapBuffer.SetData(map); erosion.SetBuffer(0, "map", mapBuffer); // Settings erosion.SetInt("borderSize", erossionSettings.erosionBrushRadius); erosion.SetInt("mapSize", mapSizeWithBorder); erosion.SetInt("brushLength", brushIndexOffsets.Count); erosion.SetInt("maxLifetime", erossionSettings.maxLifetime); erosion.SetFloat("inertia", erossionSettings.inertia); erosion.SetFloat("sedimentCapacityFactor", erossionSettings.sedimentCapacityFactor); erosion.SetFloat("minSedimentCapacity", erossionSettings.minSedimentCapacity); erosion.SetFloat("depositSpeed", erossionSettings.depositSpeed); erosion.SetFloat("erodeSpeed", erossionSettings.erodeSpeed); erosion.SetFloat("evaporateSpeed", erossionSettings.evaporateSpeed); erosion.SetFloat("gravity", erossionSettings.gravity); erosion.SetFloat("startSpeed", erossionSettings.startSpeed); erosion.SetFloat("startWater", erossionSettings.startWater); // Run compute shader erosion.Dispatch(0, numThreads, 1, 1); mapBuffer.GetData(map); // Release buffers mapBuffer.Release(); randomIndexBuffer.Release(); brushIndexBuffer.Release(); brushWeightBuffer.Release(); return(map); }
public static float[] ErodeCPU(float[] map, int mapSize, ErossionSettings erossionSettings, int seed) { bool resetSeed = false; Initialize(mapSize, resetSeed, erossionSettings, seed); for (int iteration = 0; iteration < erossionSettings.numErosionIterations; iteration++) { // Create water droplet at random point on map float posX = prng.Next(0, mapSize - 1); float posY = prng.Next(0, mapSize - 1); float dirX = 0; float dirY = 0; float speed = erossionSettings.startSpeed; float water = erossionSettings.startWater; float sediment = 0; for (int lifetime = 0; lifetime < erossionSettings.maxLifetime; lifetime++) { int nodeX = (int)posX; int nodeY = (int)posY; int dropletIndex = nodeY * mapSize + nodeX; // Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node float cellOffsetX = posX - nodeX; float cellOffsetY = posY - nodeY; // Calculate droplet's height and direction of flow with bilinear interpolation of surrounding heights HeightAndGradient heightAndGradient = CalculateHeightAndGradient(map, mapSize, posX, posY); // Update the droplet's direction and position (move position 1 unit regardless of speed) dirX = (dirX * erossionSettings.inertia - heightAndGradient.gradientX * (1 - erossionSettings.inertia)); dirY = (dirY * erossionSettings.inertia - heightAndGradient.gradientY * (1 - erossionSettings.inertia)); // Normalize direction float len = Mathf.Sqrt(dirX * dirX + dirY * dirY); if (len != 0) { dirX /= len; dirY /= len; } posX += dirX; posY += dirY; // Stop simulating droplet if it's not moving or has flowed over edge of map if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= mapSize - 1 || posY < 0 || posY >= mapSize - 1) { break; } // Find the droplet's new height and calculate the deltaHeight float newHeight = CalculateHeightAndGradient(map, mapSize, posX, posY).height; float deltaHeight = newHeight - heightAndGradient.height; // Calculate the droplet's sediment capacity (higher when moving fast down a slope and contains lots of water) float sedimentCapacity = Mathf.Max(-deltaHeight * speed * water * erossionSettings.sedimentCapacityFactor, erossionSettings.minSedimentCapacity); // If carrying more sediment than capacity, or if flowing uphill: if (sediment > sedimentCapacity || deltaHeight > 0) { // If moving uphill (deltaHeight > 0) try fill up to the current height, otherwise deposit a fraction of the excess sediment float amountToDeposit = (deltaHeight > 0) ? Mathf.Min(deltaHeight, sediment) : (sediment - sedimentCapacity) * erossionSettings.depositSpeed; sediment -= amountToDeposit; // Add the sediment to the four nodes of the current cell using bilinear interpolation // Deposition is not distributed over a radius (like erosion) so that it can fill small pits map[dropletIndex] += amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY); map[dropletIndex + 1] += amountToDeposit * cellOffsetX * (1 - cellOffsetY); map[dropletIndex + mapSize] += amountToDeposit * (1 - cellOffsetX) * cellOffsetY; map[dropletIndex + mapSize + 1] += amountToDeposit * cellOffsetX * cellOffsetY; } else { // Erode a fraction of the droplet's current carry capacity. // Clamp the erosion to the change in height so that it doesn't dig a hole in the terrain behind the droplet float amountToErode = Mathf.Min((sedimentCapacity - sediment) * erossionSettings.erodeSpeed, -deltaHeight); // Use erosion brush to erode from all nodes inside the droplet's erosion radius for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++) { int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex]; float weighedErodeAmount = amountToErode * erosionBrushWeights[dropletIndex][brushPointIndex]; float deltaSediment = (map[nodeIndex] < weighedErodeAmount) ? map[nodeIndex] : weighedErodeAmount; map[nodeIndex] -= deltaSediment; sediment += deltaSediment; } } // Update droplet's speed and water content speed = Mathf.Sqrt(speed * speed + deltaHeight * erossionSettings.gravity); water *= (1 - erossionSettings.evaporateSpeed); } } return(map); }