#include "Includes/Math.cginc" #include "Includes/Common.cginc" #include "Includes/Bounds.cginc" #include "Includes/GPUNoiseParams.cginc" #include "Includes/GPUNoise.cginc" #pragma kernel CSMain #define kMaxShapesPerRay (16) #define kAabbTreeStackSize (16) #define kCheckTestSize (16) int fractalType; float seed = 0.0f; int octaves = 8.0f; float persistence = 0.65f; float frequency = 0.05f; float lacunarity = 1.5f; float XOffset = 0.0f; float ZOffset = 0.0f; float YOffset = 0.0f; StructuredBuffer terrainBounds; int terrainBoundsCount; StructuredBuffer aSdfShape; int numSdfShapes; float4 rayMarchParams; // maxSteps, hitDist, maxDist, time float blendDist; int maxCountBudget; StructuredBuffer aabbTree; int aabbTreeRoot; RWStructuredBuffer successBuffer; // Spawner-Specific Values int spawnRangeShape; // 0 = Circle, 1 = Square float3 spawnOriginBoundsMin; float3 spawnOriginBoundsMax; float3 spawnOriginLocation; bool spawnOriginIsTerrain; float spawnRange; bool checkRange; float4 spawnOriginRotation; float4x4 rotationMatrix; // ---- Spawn Criteria ----- bool forceSpawn; // Check Collisions int collisionLayer; int virginCheckType; // 0 = None, 1 = Point, 2 = Bounds float boundsBorder; float rayExtents; // Check Slope int checkSlopeType; // 0 = None, 1 = Range, 2 = MinMax, 3 = Mixed float minSlope; float maxSlope; float minSpawnSlope; float maxSpawnSlope; // Check Height int checkHeightType; // 0 = None, 1 = Range, 2 = MinMax, 3 = Mixed float minHeight; float maxHeight; float minSpawnHeight; // = -20 float maxSpawnHeight; // = 5000 // Terrain Data float3 terrainPosition; float3 terrainSize; int alphamapResolution; // Check Textures //The splat maps int splatmapCount = 4; int splatmapDimensions = 1025; StructuredBuffer splatmaps; bool checkTextures; float textureStrength; // = 0 float minTextureStrength; // = 0 float maxTextureStrength; // = 0 float textureVariance; // = 0 int selectedTextureIdx; // Check Mask float2 maskResolution = 1024.0; StructuredBuffer maskImageData; StructuredBuffer maskAlphaData; bool checkMask; int checkMaskType; // 0 = Perlin, 1 = Billow, 2 = Ridged, 3 = Image float minMaskFractal; // = 0 float maxMaskFractal; // = 0 float midMaskFractal; // = .5 float maskFractalRange; // = .5 bool maskInvert; // = false float4 imageFilterColor; float imageFilterFuzzyMatch; bool constrainWithinMaskedBounds; bool invertMaskedAlpha; bool successOnMaskedAlpha; bool scaleOnMaskedAlpha; float minScaleOnMaskedAlpha; float maxScaleOnMaskedAlpha; /// /// Rotate the m_point around the pivot - used to handle parentRotation /// /// Point to move /// Pivot /// New location float3 RotatePointAroundPivot(float3 position = float3(0.0f, 0.0f, 0.0f), float3 pivot = float3(0.0f, 0.0f, 0.0f)) { position -= pivot; position = mul((float3x3)rotationMatrix, position); position += pivot; return position; } float inverseLerp(float a = 0.0f, float b = 0.0f, float v = 0.0f) { return (v - a) / (b - a); } float2 WorldToTerrainPosition(float3 worldPos = float3(0.0f, 0.0f, 0.0f)) { worldPos = worldPos - terrainPosition; // Calculate & Return normalized local pos return float2( inverseLerp(0.f, terrainSize.x, worldPos.x), inverseLerp(0.f, terrainSize.z, worldPos.z)); } uint2 WorldToTerrainCoordinates(float3 worldPos = float3(0.0f, 0.0f, 0.0f), int resolution = 512) { float2 terrainPos = WorldToTerrainPosition(worldPos); int terrainWidth = resolution - 1; int terrainHeight = resolution - 1; int locationX = (int)round(terrainPos.x * terrainWidth); int locationY = (int)round(terrainPos.y * terrainHeight); return uint2(locationX, locationY); } uint GetSplatAddress(uint2 address2d = uint2(0, 0), uint splatIdx = (0)) { return (address2d.y * splatmapDimensions + address2d.x) * splatmapCount + splatIdx; } bool IsOverAnyTerrain(float3 location) { for (int i = 0; i < terrainBoundsCount; i++) { Bounds bounds = terrainBounds[i]; if (Contains(bounds, location)) { return true; } } return false; } bool CheckRange(float3 location = float3(0.0f, 0.0f, 0.0f)) { bool success = true; if (checkRange) { bool positionInRange = true; if (spawnOriginIsTerrain) { positionInRange = IsOverAnyTerrain(location); } if (positionInRange) { float3 inverseLocation = RotatePointAroundPivot(location, spawnOriginLocation); if (spawnRangeShape == 0) { // Circle float xDistance = spawnOriginLocation.x - inverseLocation.x; float zDistance = spawnOriginLocation.z - inverseLocation.z; float spawnRadius = spawnRange * .5f; float sqrDistance = xDistance * xDistance + zDistance * zDistance; float sqrSpawnRadius = spawnRadius * spawnRadius; if (sqrDistance >= sqrSpawnRadius) success = false; // Failure! } else { float3 min = spawnOriginBoundsMin; float3 max = spawnOriginBoundsMax; // Square if (inverseLocation.x < min.x || inverseLocation.x > max.x || inverseLocation.z < min.z || inverseLocation.z > max.z) success = false; // Failure! } } else { success = false; } } // Success return success; } bool CheckSlope(float3 normal = float3(0.0f, 0.0f, 0.0f)) { bool success = true; // Check Slope? if (checkSlopeType != 0) { float3 up = float3(0.0f, 1.0f, 0.0f); float slope = angle(up, normal); if (checkSlopeType >= 3) // Mixed { if (slope < minSlope || slope > maxSlope) success = false; // Failure! } if (slope < minSpawnSlope || slope > maxSpawnSlope) success = false; // Failure! } return success; } bool CheckHeight(float3 position = float3(0.0f, 0.0f, 0.0f)) { bool success = true; if (checkHeightType != 0) { if (checkHeightType >= 3) // Mixed if (position.y < minHeight || position.y > maxHeight) success = false; // Failure! if (position.y < minSpawnHeight || position.y > maxSpawnHeight) success = false; // Failure! } return success; } bool CheckCollisions(float3 position = float3(0.0f, 0.0f, 0.0f)) { bool result = true; // Check Collisions? if (virginCheckType != 0) { // params int maxSteps = int(rayMarchParams.x); float hitDist = rayMarchParams.y; float radius = rayMarchParams.z; Aabb rayBounds; rayBounds.boundsMin = float4(position, 0.0); rayBounds.boundsMax = float4(position, 0.0); rayBounds = aabb_expand(rayBounds, radius); // gather shapes around ray by casting it against AABB tree int aiNearShape[kMaxShapesPerRay]; int numNearShapes = 0; aabb_tree_query(aabbTree, aabbTreeRoot, rayBounds, boundsBorder, kAabbTreeStackSize, numNearShapes = min(numNearShapes + 1, kMaxShapesPerRay); aiNearShape[numNearShapes - 1] = shapeIndex; ); for (int i_step = 0; i_step < maxSteps; ++i_step) { float3 p = position; float d; int layer = collisionLayer; SDF_NEAR_SHAPES(d, p, radius, aiNearShape, numNearShapes, layer); // hit shape? if (d < hitDist) { // Failure result = false; break; } } } // Success return result; } bool CheckTexture(float3 position = float3(0.0f, 0.0f, 0.0f)) { bool success = true; if (checkTextures && spawnOriginIsTerrain) { // float2 uv = WorldToTerrainCoordinates(position, alphamapResolution); uint2 uv = WorldToTerrainCoordinates(position, alphamapResolution); float color = splatmaps[GetSplatAddress(uv, selectedTextureIdx)]; //color = max(color.r, max(color.g, max(color.b, color.a))); if (color < minTextureStrength || color > maxTextureStrength) success = false; // Failure! } return success; } bool ApproximatelyEqual(float a = 0.0f, float b = 0.0f, float delta = 1.401298E-45f) { return a == b || abs(a - b) < delta; } float3 RGBtoXYZ(float4 c = float4(0.0f, 0.0f, 0.0f, 0.0f)) { // Based on http://www.easyrgb.com/index.php?X=MATH&H=02 float r = abs(c.r); float g = abs(c.g); float b = abs(c.b); if (r > 0.04045f) r = pow(((r + 0.055f) / 1.055f), 2.4f); else r /= 12.92f; if (g > 0.04045f) g = pow(((g + 0.055f) / 1.055f), 2.4f); else g /= 12.92f; if (b > 0.04045f) b = pow(((b + 0.055f) / 1.055f), 2.4f); else b /= 12.92f; r *= 100.0f; g *= 100.0f; b *= 100.0f; // Observer. = 2°, Illuminant = D65 float x = r * 0.4124f + g * 0.3576f + b * 0.1805f; float y = r * 0.2126f + g * 0.7152f + b * 0.0722f; float z = r * 0.0193f + g * 0.1192f + b * 0.9505f; return float3(x, y, z); } float3 XYZtoLAB(float3 c = float3(0.0f, 0.0f, 0.0f)) { // Based on http://www.easyrgb.com/index.php?X=MATH&H=07 float ref_y = 100.0f; float ref_z = 108.883f; float ref_x = 95.047f; // Observer= 2°, Illuminant= D65 float y = abs(c.y / ref_y); float z = abs(c.z / ref_z); float x = abs(c.x / ref_x); if (x > 0.008856f) x = pow(x, 1.0f / 3.0f); else x = (7.787f * x) + (16.0f / 116.0f); if (y > 0.008856f) y = pow(y, 1.0f / 3.0f); else y = (7.787f * y) + (16.0f / 116.0f); if (z > 0.008856f) z = pow(z, 1.0f / 3.0f); else z = (7.787f * z) + (16.0f / 116.0f); float L = (116.0f * y) - 16.0f; float a = 500.0f * (x - y); float b = 200.0f * (y - z); return float3(L, a, b); } float3 RGBtoLAB(float4 c = float4(0.0f, 0.0f, 0.0f, 0.0f)) { return XYZtoLAB(RGBtoXYZ(c)); } float RGBDifference(float4 c1 = float4(0.0f, 0.0f, 0.0f, 0.0f), float4 c2 = float4(0.0f, 0.0f, 0.0f, 0.0f)) { float result = 0.0f; bool allEqual = ApproximatelyEqual(c1.r, c2.r) && ApproximatelyEqual(c1.g, c2.g) && ApproximatelyEqual(c1.b, c2.b); if (!allEqual) { float3 l1 = RGBtoLAB(c1); float3 l2 = RGBtoLAB(c2); float sum = 0.0f; sum += pow(l1.x - l2.x, 2.0f); sum += pow(l1.y - l2.y, 2.0f); sum += pow(l1.z - l2.z, 2.0f); result = max(min(sqrt(sum), 100.0f), 0.0f); } return result; } bool CheckMask(float3 position = float3(0.0f, 0.0f, 0.0f)) { bool success = true; if (spawnOriginIsTerrain) { if (!checkMask) { success = true; } // Image Check else if (checkMaskType == 1) { // Need to rotate the image mask to remain consistent with overall spawm parentRotation //var newLocation = GeNaUtility.RotatePointAroundPivot(location, SpawnOriginLocation, new Vector3(0f, 180f - rotationY, 0f)); float xN = (spawnOriginLocation.x - position.x) / spawnRange; float zN = (spawnOriginLocation.z - position.z) / spawnRange; // Offset by half a unit xN += .5f; zN += .5f; // Drop out if out of bounds if (xN < 0.0f || xN >= 1.0f || zN < 0.0f || zN > 1.0f) success = false; // Failure! else { xN *= maskResolution.x; zN *= maskResolution.y; int xR = (int)(xN); if (xR == maskResolution.x) xR = maskResolution.x - 1; int zR = (int)(zN); if (zR == maskResolution.y) zR = maskResolution.y - 1; uint coordinate = Translate2DTo1D(xR, zR, maskResolution); float hitAlpha = maskAlphaData[coordinate]; float v = maskImageData[coordinate]; float4 c; c.a = 0.0f; c.b = mod(v, 1000.0f).x; v -= c.b; v /= 1000.0f; c.b /= 255.0f; c.g = mod(v, 1000.0f).x; v -= c.g; v /= 1000.0f; c.g /= 255.0f; c.r = v; c.r /= 255.0f; if (RGBDifference(c, imageFilterColor) < (1.0f - imageFilterFuzzyMatch) * 100.0f) { if (successOnMaskedAlpha) { if (invertMaskedAlpha) { if (ApproximatelyEqual(1.0f - hitAlpha, 0.0f)) success = false; // Failure! } else { if (ApproximatelyEqual(hitAlpha, 0.0f)) success = false; // Failure! } } } else success = false; // Failure! } } else { float3 inverseLocation = RotatePointAroundPivot(position, spawnOriginLocation); float distance = length(spawnOriginLocation.xz - position.xz) / spawnRange; float xDistance = spawnOriginLocation.x - inverseLocation.x; float zDistance = spawnOriginLocation.z - inverseLocation.z; float spawnRadius = spawnRange * .5f; float sqrDistance = xDistance * xDistance + zDistance * zDistance; float falloff = EvaluateFalloff(distance, _NoiseFalloff, _NoiseFalloffCount); float sqrSpawnRadius = (spawnRadius * spawnRadius) * falloff; if (sqrDistance >= sqrSpawnRadius) success = false; // Failure! else { float2 location = 100000.0f + position.xz; float value = GetNoise(location) * _NoisemapStrength; if (maskInvert) { if (value >= minMaskFractal && value <= maxMaskFractal) // Failure! success = false; } else { if (value < minMaskFractal || value > maxMaskFractal) // Failure! success = false; } } } } // Success! return success; } // 0 = Successful, 1 = Check Range, 2 = Force Spawn // 3 = Check Height, 4 = Check Slope, 5 = Check Collisions, 6 = Check Texture, 7 = Check Mask AabbTest CHECK_LOCATION_FOR_SPAWN(AabbTest test) { if (!CheckRange(test.position)) { test.message = 1; test.hit = -1.0f; } else if (forceSpawn) { test.message = 2; test.hit = 1.0f; } else if (!CheckHeight(test.position)) { test.message = 3; test.hit = -1.0f; } else if (!CheckSlope(test.normal)) { test.message = 4; test.hit = -1.0f; } else if (!CheckCollisions(test.position)) { test.message = 5; test.hit = -1.0f; } else if (!CheckTexture(test.position)) { test.message = 6; test.hit = -1.0f; } else if (!CheckMask(test.position)) { test.message = 7; test.hit = -1.0f; } else { test.message = 0; test.hit = 1.0f; } return test; } //----------------------------------------------------------------------------- // end: ray marching // kernels //----------------------------------------------------------------------------- [numthreads(kCheckTestSize, kCheckTestSize, 1)] void CSMain(int3 id : SV_DispatchThreadID) { uint index = id.x; AabbTest aabbTest = successBuffer[index]; successBuffer[index] = CHECK_LOCATION_FOR_SPAWN(aabbTest); } // // [numthreads(32,32,1)] // void ProcessMask(uint3 id : SV_DispatchThreadID) // { // uint address = Translate2DTo1D(id.x, id.y, _MaskResolution); // float4 color = _MaskImagePixels[address]; // _MaskImageData[address] = (color.r * 255000000.0f) + (color.g * 255000.0f) + (color.b * 255.0f); // _MaskAlphaData[address] = color.a; // } //----------------------------------------------------------------------------- // end: kernels