From 2104dbc682feb808c4b294a10fd73bb4083f03d4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 31 Jul 2022 22:20:38 +0200 Subject: [PATCH] Add new **Noise** library for C++/C#/VisualScript/HLSL utilities --- .../MaterialTemplates/GPUParticles.shader | 63 +--- Source/Engine/Core/Math/Math.h | 5 + Source/Engine/Core/Math/Mathd.h | 5 + Source/Engine/Core/Math/Vector2.h | 2 +- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 65 +--- ...rticleEmitterGraph.GPU.ParticleModules.cpp | 3 +- Source/Engine/Utilities/Noise.cpp | 343 ++++++++++++++++++ Source/Engine/Utilities/Noise.h | 73 ++++ Source/Engine/Utilities/PerlinNoise.cs | 15 +- Source/Shaders/Noise.hlsl | 343 ++++++++++++++++++ 10 files changed, 786 insertions(+), 131 deletions(-) create mode 100644 Source/Engine/Utilities/Noise.cpp create mode 100644 Source/Engine/Utilities/Noise.h create mode 100644 Source/Shaders/Noise.hlsl diff --git a/Content/Editor/MaterialTemplates/GPUParticles.shader b/Content/Editor/MaterialTemplates/GPUParticles.shader index 0115b9e23..678eef099 100644 --- a/Content/Editor/MaterialTemplates/GPUParticles.shader +++ b/Content/Editor/MaterialTemplates/GPUParticles.shader @@ -5,6 +5,7 @@ #include "./Flax/Common.hlsl" #include "./Flax/GBufferCommon.hlsl" #include "./Flax/Matrix.hlsl" +#include "./Flax/Noise.hlsl" @7 // Primary constant buffer META_CB_BEGIN(0, Data) @@ -62,68 +63,6 @@ float Rand(inout uint seed) return asfloat((seed >> 9) | 0x3f800000) - 1.0f; } -float4 Mod289(float4 x) -{ - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -float4 Perm(float4 x) -{ - return Mod289(((x * 34.0) + 1.0) * x); -} - -float Noise(float3 p) -{ - float3 a = floor(p); - float3 d = p - a; - d = d * d * (3.0 - 2.0 * d); - - float4 b = a.xxyy + float4(0.0, 1.0, 0.0, 1.0); - float4 k1 = Perm(b.xyxy); - float4 k2 = Perm(k1.xyxy + b.zzww); - - float4 c = k2 + a.zzzz; - float4 k3 = Perm(c); - float4 k4 = Perm(c + 1.0); - - float4 o1 = frac(k3 * (1.0 / 41.0)); - float4 o2 = frac(k4 * (1.0 / 41.0)); - - float4 o3 = o2 * d.z + o1 * (1.0 - d.z); - float2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x); - - return o4.y * d.y + o4.x * (1.0 - d.y); -} - -float3 Noise3D(float3 p) -{ - float o = Noise(p); - float a = Noise(p + float3(0.0001f, 0.0f, 0.0f)); - float b = Noise(p + float3(0.0f, 0.0001f, 0.0f)); - float c = Noise(p + float3(0.0f, 0.0f, 0.0001f)); - - float3 grad = float3(o - a, o - b, o - c); - float3 other = abs(grad.zxy); - return normalize(cross(grad,other)); -} - -float3 Noise3D(float3 position, int octaves, float roughness) -{ - float weight = 0.0f; - float3 noise = float3(0.0, 0.0, 0.0); - float scale = 1.0f; - for (int i = 0; i < octaves; i++) - { - float curWeight = pow((1.0-((float)i / octaves)), lerp(2.0, 0.2, roughness)); - - noise += Noise3D(position * scale) * curWeight; - weight += curWeight; - - scale *= 1.72531; - } - return noise / weight; -} - // Reprojects the world space position from the given UV and raw device depth float3 ReprojectPosition(float2 uv, float rawDepth) { diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index bd498bb1a..784afa1b2 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -172,6 +172,11 @@ namespace Math return modff(a, b); } + static FORCE_INLINE float Frac(float value) + { + return value - Floor(value); + } + /// /// Returns signed fractional part of a float. /// diff --git a/Source/Engine/Core/Math/Mathd.h b/Source/Engine/Core/Math/Mathd.h index 8d8504a55..92b760682 100644 --- a/Source/Engine/Core/Math/Mathd.h +++ b/Source/Engine/Core/Math/Mathd.h @@ -130,6 +130,11 @@ namespace Math return modf(a, b); } + static FORCE_INLINE double Frac(double value) + { + return value - Floor(value); + } + /// /// Returns signed fractional part of a double. /// diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h index cdbeda8e4..f41865956 100644 --- a/Source/Engine/Core/Math/Vector2.h +++ b/Source/Engine/Core/Math/Vector2.h @@ -469,7 +469,7 @@ public: static Vector2Base Frac(const Vector2Base& v) { - return Vector3(v.X - (int32)v.X, v.Y - (int32)v.Y); + return Vector2Base(v.X - (int32)v.X, v.Y - (int32)v.Y); } static Vector2Base Round(const Vector2Base& v) diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 699805703..94ba3de87 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -2,6 +2,7 @@ #include "ParticleEmitterGraph.CPU.h" #include "Engine/Core/Random.h" +#include "Engine/Utilities/Noise.h" // ReSharper disable CppCStyleCast // ReSharper disable CppClangTidyClangDiagnosticCastAlign @@ -25,68 +26,6 @@ namespace { - FORCE_INLINE Float4 Mod289(Float4 x) - { - return x - Float4::Floor(x * (1.0f / 289.0f)) * 289.0f; - } - - FORCE_INLINE Float4 Perm(Float4 x) - { - return Mod289((x * 34.0f + 1.0f) * x); - } - - float Noise(Float3 p) - { - Float3 a = Float3::Floor(p); - Float3 d = p - a; - d = d * d * (3.0f - 2.0f * d); - - Float4 b(a.X, a.X + 1.0f, a.Y, a.Y + 1.0f); - Float4 k1 = Perm(Float4(b.X, b.Y, b.X, b.Y)); - Float4 k2 = Perm(Float4(k1.X + b.Z, k1.Y + b.Z, k1.X + b.W, k1.Y + b.W)); - - Float4 c = k2 + Float4(a.Z); - Float4 k3 = Perm(c); - Float4 k4 = Perm(c + 1.0f); - - Float4 o1 = Float4::Frac(k3 * (1.0f / 41.0f)); - Float4 o2 = Float4::Frac(k4 * (1.0f / 41.0f)); - - Float4 o3 = o2 * d.Z + o1 * (1.0f - d.Z); - Float2 o4 = Float2(o3.Y, o3.W) * d.X + Float2(o3.X, o3.Z) * (1.0f - d.X); - - return o4.Y * d.Y + o4.X * (1.0f - d.Y); - } - - Float3 Noise3D(Float3 p) - { - const float o = Noise(p); - const float a = Noise(p + Float3(0.0001f, 0.0f, 0.0f)); - const float b = Noise(p + Float3(0.0f, 0.0001f, 0.0f)); - const float c = Noise(p + Float3(0.0f, 0.0f, 0.0001f)); - - const Float3 grad(o - a, o - b, o - c); - const Float3 other = Float3::Abs(Float3(grad.Z, grad.X, grad.Y)); - return Float3::Normalize(Float3::Cross(grad, other)); - } - - Float3 Noise3D(Float3 position, int32 octaves, float roughness) - { - float weight = 0.0f; - Float3 noise = Float3::Zero; - float scale = 1.0f; - for (int32 i = 0; i < octaves; i++) - { - const float curWeight = Math::Pow(1.0f - ((float)i / (float)octaves), Math::Lerp(2.0f, 0.2f, roughness)); - - noise += Noise3D(position * scale) * curWeight; - weight += curWeight; - - scale *= 1.72531f; - } - return noise / Math::Max(weight, ZeroTolerance); - } - VariantType::Types GetVariantType(ParticleAttribute::ValueTypes type) { switch (type) @@ -570,7 +509,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* const int32 octavesCount = (int)GetValue(octavesCountBox, 7) #define LOGIC() \ Float3 vectorFieldUVW = Float3::Transform(*((Float3*)positionPtr), invFieldTransformMatrix); \ - Float3 force = Noise3D(vectorFieldUVW + 0.5f, octavesCount, roughness); \ + Float3 force = Noise::CustomNoise3D(vectorFieldUVW + 0.5f, octavesCount, roughness); \ force = Float3::Transform(force, fieldTransformMatrix) * intensity; \ *((Float3*)velocityPtr) += force * (context.DeltaTime / Math::Max(*(float*)massPtr, ZeroTolerance)); \ positionPtr += stride; \ diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index d02cf4c97..b75fb920d 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -207,6 +207,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) auto roughness = tryGetValue(node->GetBox(3), 5, Value::Zero).AsFloat(); auto intensity = tryGetValue(node->GetBox(4), 6, Value::Zero).AsFloat(); auto octavesCount = tryGetValue(node->GetBox(5), 7, Value::Zero).AsInt(); + _includes.Add(TEXT("./Flax/Noise.hlsl")); // TODO: build fieldTransformMatrix and invFieldTransformMatrix on CPU and pass in constant buffer - no need to support field transform per particle _writer.Write( TEXT( @@ -218,7 +219,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " fieldTransformMatrix = mul(fieldTransformMatrix, scaleMatrix);\n" " float4x4 invFieldTransformMatrix = Inverse(fieldTransformMatrix);\n" " float3 vectorFieldUVW = mul(invFieldTransformMatrix, float4({0}, 1.0f)).xyz;\n" - " float3 force = Noise3D(vectorFieldUVW + 0.5f, {8}, {6});\n" + " float3 force = CustomNoise3D(vectorFieldUVW + 0.5f, {8}, {6});\n" " force = mul(fieldTransformMatrix, float4(force, 0.0f)).xyz * {7};\n" " {1} += force * (DeltaTime / max({2}, PARTICLE_THRESHOLD));\n" " }}\n" diff --git a/Source/Engine/Utilities/Noise.cpp b/Source/Engine/Utilities/Noise.cpp new file mode 100644 index 000000000..5610b8df4 --- /dev/null +++ b/Source/Engine/Utilities/Noise.cpp @@ -0,0 +1,343 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Copyright (c) 2011 Stefan Gustavson. All rights reserved. +// Distributed under the MIT license. +// https://github.com/stegu/webgl-noise + +#include "Noise.h" +#include "Engine/Core/Math/Vector4.h" + +namespace +{ + FORCE_INLINE Float2 Mod289(const Float2& x) + { + return x - Float2::Floor(x * (1.0f / 289.0f)) * 289.0f; + } + + FORCE_INLINE Float3 Mod289(const Float3& x) + { + return x - Float3::Floor(x * (1.0f / 289.0f)) * 289.0f; + } + + FORCE_INLINE Float4 Mod289(const Float4& x) + { + return x - Float4::Floor(x * (1.0f / 289.0f)) * 289.0f; + } + + FORCE_INLINE Float3 Mod7(const Float3& x) + { + return x - Float3::Floor(x * (1.0f / 7.0f)) * 7.0f; + } + + FORCE_INLINE Float3 Permute(const Float3& x) + { + return Mod289((x * 34.0f + 1.0f) * x); + } + + FORCE_INLINE Float4 Permute(const Float4& x) + { + return Mod289((x * 34.0f + 1.0f) * x); + } + + FORCE_INLINE Float4 TaylorInvSqrt(const Float4& r) + { + return 1.79284291400159f - 0.85373472095314f * r; + } + + FORCE_INLINE Float2 PerlinNoiseFade(const Float2& t) + { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); + } + + float rand2dTo1d(const Float2& value, const Float2& dotDir) + { + // https://www.ronja-tutorials.com/post/024-white-noise/ + const Float2 smallValue(Math::Sin(value.X), Math::Sin(value.Y)); + const float random = Float2::Dot(smallValue, dotDir); + return Math::Frac(Math::Sin(random) * 143758.5453f); + } + + float rand2dTo1d(const Float2& value) + { + // https://www.ronja-tutorials.com/post/024-white-noise/ + return rand2dTo1d(value, Float2(12.9898f, 78.233f)); + } + + Float2 rand2dTo2d(const Float2& value) + { + // https://www.ronja-tutorials.com/post/024-white-noise/ + return Float2( + rand2dTo1d(value, Float2(12.989f, 78.233f)), + rand2dTo1d(value, Float2(39.346f, 11.135f)) + ); + } +} + +float Noise::PerlinNoise(const Float2& p) +{ + Float4 Pxy(p.X, p.Y, p.X, p.Y); + Float4 Pi = Float4::Floor(Pxy) + Float4(0.0, 0.0, 1.0, 1.0); + Float4 Pf = Float4::Frac(Pxy) - Float4(0.0, 0.0, 1.0, 1.0); + Pi = Mod289(Pi); + Float4 ix(Pi.X, Pi.Z, Pi.X, Pi.Z); + Float4 iy(Pi.Y, Pi.Y, Pi.W, Pi.W); + Float4 fx(Pf.X, Pf.Z, Pf.X, Pf.Z); + Float4 fy(Pf.Y, Pf.Y, Pf.W, Pf.W); + + Float4 i = Permute(Permute(ix) + iy); + + Float4 gx = Float4::Frac(i * (1.0 / 41.0)) * 2.0 - 1.0; + Float4 gy = Float4::Abs(gx) - 0.5; + Float4 tx = Float4::Floor(gx + 0.5); + gx = gx - tx; + + Float2 g00(gx.X, gy.X); + Float2 g10(gx.Y, gy.Y); + Float2 g01(gx.Z, gy.Z); + Float2 g11(gx.W, gy.W); + + Float4 norm = TaylorInvSqrt(Float4(Float2::Dot(g00, g00), Float2::Dot(g01, g01), Float2::Dot(g10, g10), Float2::Dot(g11, g11))); + g00 *= norm.X; + g01 *= norm.Y; + g10 *= norm.Z; + g11 *= norm.W; + + float n00 = Float2::Dot(g00, Float2(fx.X, fy.X)); + float n10 = Float2::Dot(g10, Float2(fx.Y, fy.Y)); + float n01 = Float2::Dot(g01, Float2(fx.Z, fy.Z)); + float n11 = Float2::Dot(g11, Float2(fx.W, fy.W)); + + Float2 fade_xy = PerlinNoiseFade(Float2(Pf)); + Float2 n_x = Float2::Lerp(Float2(n00, n01), Float2(n10, n11), fade_xy.X); + float n_xy = Math::Lerp(n_x.X, n_x.Y, fade_xy.Y); + return Math::Saturate(2.3f * n_xy); +} + +float Noise::PerlinNoise(const Float2& p, const Float2& rep) +{ + Float4 Pxy(p.X, p.Y, p.X, p.Y); + Float4 Repxy(rep.X, rep.Y, rep.X, rep.Y); + Float4 Pi = Float4::Floor(Pxy) + Float4(0.0, 0.0, 1.0, 1.0); + Float4 Pf = Float4::Frac(Pxy) - Float4(0.0, 0.0, 1.0, 1.0); + Pi = Float4::Mod(Pi, Repxy); + Pi = Mod289(Pi); + Float4 ix(Pi.X, Pi.Z, Pi.X, Pi.Z); + Float4 iy(Pi.Y, Pi.Y, Pi.W, Pi.W); + Float4 fx(Pf.X, Pf.Z, Pf.X, Pf.Z); + Float4 fy(Pf.Y, Pf.Y, Pf.W, Pf.W); + + Float4 i = Permute(Permute(ix) + iy); + + Float4 gx = Float4::Frac(i * (1.0 / 41.0)) * 2.0 - 1.0; + Float4 gy = Float4::Abs(gx) - 0.5; + Float4 tx = Float4::Floor(gx + 0.5); + gx = gx - tx; + + Float2 g00(gx.X, gy.X); + Float2 g10(gx.Y, gy.Y); + Float2 g01(gx.Z, gy.Z); + Float2 g11(gx.W, gy.W); + + Float4 norm = TaylorInvSqrt(Float4(Float2::Dot(g00, g00), Float2::Dot(g01, g01), Float2::Dot(g10, g10), Float2::Dot(g11, g11))); + g00 *= norm.X; + g01 *= norm.Y; + g10 *= norm.Z; + g11 *= norm.W; + + float n00 = Float2::Dot(g00, Float2(fx.X, fy.X)); + float n10 = Float2::Dot(g10, Float2(fx.Y, fy.Y)); + float n01 = Float2::Dot(g01, Float2(fx.Z, fy.Z)); + float n11 = Float2::Dot(g11, Float2(fx.W, fy.W)); + + Float2 fade_xy = PerlinNoiseFade(Float2(Pf)); + Float2 n_x = Float2::Lerp(Float2(n00, n01), Float2(n10, n11), fade_xy.X); + float n_xy = Math::Lerp(n_x.X, n_x.Y, fade_xy.Y); + return Math::Saturate(2.3f * n_xy); +} + +float Noise::SimplexNoise(const Float2& p) +{ + Float4 C(0.211324865405187f, // (3.0-math.sqrt(3.0))/6.0 + 0.366025403784439f, // 0.5*(math.sqrt(3.0)-1.0) + -0.577350269189626f, // -1.0 + 2.0 * C.x + 0.024390243902439f); // 1.0 / 41.0 + + // First corner + Float2 i = Float2::Floor(p + Float2::Dot(p, Float2(C.Y, C.Y))); + Float2 x0 = p - i + Float2::Dot(i, Float2(C.X, C.X)); + + // Other corners + Float2 i1 = x0.X > x0.Y ? Float2(1.0f, 0.0f) : Float2(0.0f, 1.0f); + Float4 x12 = Float4(x0.X, x0.Y, x0.X, x0.Y) + Float4(C.X, C.X, C.Z, C.Z); + x12.X -= i1.X; + x12.Y -= i1.Y; + + // Permutations + i = Mod289(i); + Float3 perm = Permute(Permute(i.Y + Float3(0.0f, i1.Y, 1.0f)) + i.X + Float3(0.0f, i1.X, 1.0f)); + Float2 x12xy(x12.X, x12.Y); + Float2 x12zw(x12.Z, x12.W); + Float3 m = Float3::Max(0.5f - Float3(Float2::Dot(x0, x0), Float2::Dot(x12xy, x12xy), Float2::Dot(x12zw, x12zw)), 0.0f); + m = m * m; + m = m * m; + + // Gradients: 41 points uniformly over a line, mapped onto a diamond. + // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + Float3 x = 2.0f * Float3::Frac(perm * C.W) - 1.0f; + Float3 h = Float3::Abs(x) - 0.5f; + Float3 ox = Float3::Floor(x + 0.5f); + Float3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversemath.sqrt( a0*a0 + h*h ); + m *= 1.79284291400159f - 0.85373472095314f * (a0 * a0 + h * h); + + // Compute final noise value at P + float gx = a0.X * x0.X + h.X * x0.Y; + Float2 gyz = Float2(a0.Y, a0.Z) * Float2(x12.X, x12.Z) + Float2(h.Y, h.Z) * Float2(x12.Y, x12.W); + Float3 g(gx, gyz.X, gyz.Y); + return Math::Saturate(130.0f * Float3::Dot(m, g)); +} + +Float2 Noise::WorleyNoise(const Float2& p) +{ + const float K = 0.142857142857f; // 1/7 + const float Ko = 0.428571428571f; // 3/7 + const float jitter = 1.0f; // Less gives more regular pattern + Float2 Pi = Mod289(Float2::Floor(p)); + Float2 Pf = Float2::Frac(p); + Float3 oi = Float3(-1.0f, 0.0f, 1.0f); + Float3 of = Float3(-0.5f, 0.5f, 1.5f); + Float3 px = Permute(Pi.X + oi); + Float3 pp = Permute(px.X + Pi.Y + oi); // p11, p12, p13 + Float3 ox = Float3::Frac(pp * K) - Ko; + Float3 oy = Mod7(Float3::Floor(pp * K)) * K - Ko; + Float3 dx = Pf.X + 0.5f + jitter * ox; + Float3 dy = Pf.Y - of + jitter * oy; + Float3 d1 = dx * dx + dy * dy; // d11, d12 and d13, squared + pp = Permute(px.Y + Pi.Y + oi); // p21, p22, p23 + ox = Float3::Frac(pp * K) - Ko; + oy = Mod7(Float3::Floor(pp * K)) * K - Ko; + dx = Pf.X - 0.5f + jitter * ox; + dy = Pf.Y - of + jitter * oy; + Float3 d2 = dx * dx + dy * dy; // d21, d22 and d23, squared + pp = Permute(px.Z + Pi.Y + oi); // p31, p32, p33 + ox = Float3::Frac(pp * K) - Ko; + oy = Mod7(Float3::Floor(pp * K)) * K - Ko; + dx = Pf.X - 1.5f + jitter * ox; + dy = Pf.Y - of + jitter * oy; + Float3 d3 = dx * dx + dy * dy; // d31, d32 and d33, squared + Float3 d1a = Float3::Min(d1, d2); // Sort out the two smallest distances (F1, F2) + d2 = Float3::Max(d1, d2); // Swap to keep candidates for F2 + d2 = Float3::Min(d2, d3); // neither F1 nor F2 are now in d3 + d1 = Float3::Min(d1a, d2); // F1 is now in d1 + d2 = Float3::Max(d1a, d2); // Swap to keep candidates for F2 + d1.X = d1.X < d1.Y ? d1.X : d1.Y; // Swap if smaller + d1.Y = d1.X < d1.Y ? d1.Y : d1.X; + d1.X = d1.X < d1.Z ? d1.X : d1.Z; // F1 is in d1.x + d1.Z = d1.X < d1.Z ? d1.Z : d1.X; + d1.Y = Math::Min(d1.Y, d2.Y); // F2 is now not in d2.yz + d1.Z = Math::Min(d1.Z, d2.Z); + d1.Y = Math::Min(d1.Y, d1.Z); // nor in d1.z + d1.Y = Math::Min(d1.Y, d2.X); // F2 is in d1.y, we're done. + return Float2(Math::Saturate(Math::Sqrt(d1.X)), Math::Saturate(Math::Sqrt(d1.Y))); +} + +Float3 Noise::VoronoiNoise(const Float2& p) +{ + // Reference: https://www.ronja-tutorials.com/post/028-voronoi-noise/ + const Float2 baseCell = Float2::Floor(p); + + // first pass to find the closest cell + float minDistToCell = 10.0; + Float2 toClosestCell; + Float2 closestCell; + for (int32 x1 = -1; x1 <= 1; x1++) + { + for (int32 y1 = -1; y1 <= 1; y1++) + { + Float2 cell = baseCell + Float2((float)x1, (float)y1); + Float2 cellPosition = cell + rand2dTo2d(cell); + Float2 toCell = cellPosition - p; + const float distToCell = toCell.Length(); + if (distToCell < minDistToCell) + { + minDistToCell = distToCell; + closestCell = cell; + toClosestCell = toCell; + } + } + } + + // second pass to find the distance to the closest edge + float minEdgeDistance = 10.0; + for (int32 x2 = -1; x2 <= 1; x2++) + { + for (int32 y2 = -1; y2 <= 1; y2++) + { + Float2 cell = baseCell + Float2((float)x2, (float)y2); + Float2 cellPosition = cell + rand2dTo2d(cell); + Float2 toCell = cellPosition - p; + const Float2 diffToClosestCell = Float2::Abs(closestCell - cell); + if (diffToClosestCell.X + diffToClosestCell.Y >= 0.1f) + { + Float2 toCenter = (toClosestCell + toCell) * 0.5; + Float2 cellDifference = Float2::Normalize(toCell - toClosestCell); + minEdgeDistance = Math::Min(minEdgeDistance, Float2::Dot(toCenter, cellDifference)); + } + } + } + + float random = rand2dTo1d(closestCell); + return Float3(Math::Saturate(minDistToCell), Math::Saturate(random), Math::Saturate(minEdgeDistance)); +} + +float Noise::CustomNoise(const Float3& p) +{ + Float3 a = Float3::Floor(p); + Float3 d = p - a; + d = d * d * (3.0f - 2.0f * d); + + Float4 b(a.X, a.X + 1.0f, a.Y, a.Y + 1.0f); + Float4 k1 = Permute(Float4(b.X, b.Y, b.X, b.Y)); + Float4 k2 = Permute(Float4(k1.X + b.Z, k1.Y + b.Z, k1.X + b.W, k1.Y + b.W)); + + Float4 c = k2 + Float4(a.Z); + Float4 k3 = Permute(c); + Float4 k4 = Permute(c + 1.0f); + + Float4 o1 = Float4::Frac(k3 * (1.0f / 41.0f)); + Float4 o2 = Float4::Frac(k4 * (1.0f / 41.0f)); + + Float4 o3 = o2 * d.Z + o1 * (1.0f - d.Z); + Float2 o4 = Float2(o3.Y, o3.W) * d.X + Float2(o3.X, o3.Z) * (1.0f - d.X); + + return o4.Y * d.Y + o4.X * (1.0f - d.Y); +} + +Float3 Noise::CustomNoise3D(const Float3& p) +{ + const float o = CustomNoise(p); + const float a = CustomNoise(p + Float3(0.0001f, 0.0f, 0.0f)); + const float b = CustomNoise(p + Float3(0.0f, 0.0001f, 0.0f)); + const float c = CustomNoise(p + Float3(0.0f, 0.0f, 0.0001f)); + + const Float3 grad(o - a, o - b, o - c); + const Float3 other = Float3::Abs(Float3(grad.Z, grad.X, grad.Y)); + return Float3::Normalize(Float3::Cross(grad, other)); +} + +Float3 Noise::CustomNoise3D(const Float3& p, int32 octaves, float roughness) +{ + float weight = 0.0f; + Float3 noise = Float3::Zero; + float scale = 1.0f; + for (int32 i = 0; i < octaves; i++) + { + const float curWeight = Math::Pow(1.0f - (float)i / (float)octaves, Math::Lerp(2.0f, 0.2f, roughness)); + noise += CustomNoise3D(p * scale) * curWeight; + weight += curWeight; + scale *= 1.72531f; + } + return noise / Math::Max(weight, ZeroTolerance); +} diff --git a/Source/Engine/Utilities/Noise.h b/Source/Engine/Utilities/Noise.h new file mode 100644 index 000000000..c8a12fd1a --- /dev/null +++ b/Source/Engine/Utilities/Noise.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" + +/// +/// Collection of various noise functions (eg. Perlin, Worley, Voronoi). +/// +API_CLASS(Static, Namespace="FlaxEngine.Utilities") class FLAXENGINE_API Noise +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(Noise); +public: + /// + /// Classic Perlin noise. + /// + /// Point on a 2D grid to sample noise at. + /// Noise value (range 0-1). + API_FUNCTION() static float PerlinNoise(const Float2& p); + + /// + /// Classic Perlin noise with periodic variant (tiling). + /// + /// Point on a 2D grid to sample noise at. + /// Periodic variant of the noise - period of the noise. + /// Noise value (range 0-1). + API_FUNCTION() static float PerlinNoise(const Float2& p, const Float2& rep); + + /// + /// Simplex noise. + /// + /// Point on a 2D grid to sample noise at. + /// Noise value (range 0-1). + API_FUNCTION() static float SimplexNoise(const Float2& p); + + /// + /// Worley noise (cellar noise with standard 3x3 search window for F1 and F2 values). + /// + /// Point on a 2D grid to sample noise at. + /// Noise value with: F1 and F2 feature points. + API_FUNCTION() static Float2 WorleyNoise(const Float2& p); + + /// + /// Voronoi noise. + /// + /// Point on a 2D grid to sample noise at. + /// Noise result with: X=minDistToCell, Y=randomColor, Z=minEdgeDistance. + API_FUNCTION() static Float3 VoronoiNoise(const Float2& p); + + /// + /// Custom noise function (3D -> 1D). + /// + /// Point on a 3D grid to sample noise at. + /// Noise result. + API_FUNCTION() static float CustomNoise(const Float3& p); + + /// + /// Custom noise function (3D -> 3D). + /// + /// Point on a 3D grid to sample noise at. + /// Noise result. + API_FUNCTION() static Float3 CustomNoise3D(const Float3& p); + + /// + /// Custom noise function for forces. + /// + /// Point on a 3D grid to sample noise at. + /// Noise octaves count. + /// Noise roughness (in range 0-1). + /// Noise result. + API_FUNCTION() static Float3 CustomNoise3D(const Float3& p, int32 octaves, float roughness); +}; diff --git a/Source/Engine/Utilities/PerlinNoise.cs b/Source/Engine/Utilities/PerlinNoise.cs index 6fe8037b1..bf28e9823 100644 --- a/Source/Engine/Utilities/PerlinNoise.cs +++ b/Source/Engine/Utilities/PerlinNoise.cs @@ -13,22 +13,29 @@ namespace FlaxEngine.Utilities /// /// The base value. /// - private float Base; + public float Base = 0.0f; /// /// The noise scale parameter. /// - public float NoiseScale; + public float NoiseScale = 1.0f; /// /// The noise amount parameter. /// - public float NoiseAmount; + public float NoiseAmount = 1.0f; /// /// The noise octaves count. /// - public int Octaves; + public int Octaves = 4; + + /// + /// Initializes a new instance of the class. + /// + public PerlinNoise() + { + } /// /// Initializes a new instance of the class. diff --git a/Source/Shaders/Noise.hlsl b/Source/Shaders/Noise.hlsl new file mode 100644 index 000000000..42e8d2e67 --- /dev/null +++ b/Source/Shaders/Noise.hlsl @@ -0,0 +1,343 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Copyright (c) 2011 Stefan Gustavson. All rights reserved. +// Distributed under the MIT license. +// https://github.com/stegu/webgl-noise + +#ifndef __NOISE__ +#define __NOISE__ + +#include "./Flax/Common.hlsl" + +float2 Mod289(float2 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +float3 Mod289(float3 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +float4 Mod289(float4 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +float3 Mod7(float3 x) +{ + return x - floor(x * (1.0f / 7.0f)) * 7.0f; +} + +float2 Permute(float2 x) +{ + return Mod289(((x * 34.0) + 1.0) * x); +} + +float3 Permute(float3 x) +{ + return Mod289(((x * 34.0) + 1.0) * x); +} + +float4 Permute(float4 x) +{ + return Mod289(((x * 34.0) + 1.0) * x); +} + +float4 TaylorInvSqrt(float4 r) +{ + return 1.79284291400159 - 0.85373472095314 * r; +} + +float2 PerlinNoiseFade(float2 t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +float rand2dTo1d(float2 value, float2 dotDir = float2(12.9898, 78.233)) +{ + // https://www.ronja-tutorials.com/post/024-white-noise/ + float2 smallValue = sin(value); + float random = dot(smallValue, dotDir); + return frac(sin(random) * 143758.5453); +} + +float2 rand2dTo2d(float2 value) +{ + // https://www.ronja-tutorials.com/post/024-white-noise/ + return float2( + rand2dTo1d(value, float2(12.989, 78.233)), + rand2dTo1d(value, float2(39.346, 11.135)) + ); +} + +// Classic Perlin noise +float PerlinNoise(float2 p) +{ + float4 Pi = floor(p.xyxy) + float4(0.0, 0.0, 1.0, 1.0); + float4 Pf = frac(p.xyxy) - float4(0.0, 0.0, 1.0, 1.0); + Pi = Mod289(Pi); + float4 ix = Pi.xzxz; + float4 iy = Pi.yyww; + float4 fx = Pf.xzxz; + float4 fy = Pf.yyww; + + float4 i = Permute(Permute(ix) + iy); + + float4 gx = frac(i * (1.0 / 41.0)) * 2.0 - 1.0; + float4 gy = abs(gx) - 0.5; + float4 tx = floor(gx + 0.5); + gx = gx - tx; + + float2 g00 = float2(gx.x, gy.x); + float2 g10 = float2(gx.y, gy.y); + float2 g01 = float2(gx.z, gy.z); + float2 g11 = float2(gx.w, gy.w); + + float4 norm = TaylorInvSqrt(float4(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, float2(fx.x, fy.x)); + float n10 = dot(g10, float2(fx.y, fy.y)); + float n01 = dot(g01, float2(fx.z, fy.z)); + float n11 = dot(g11, float2(fx.w, fy.w)); + + float2 fade_xy = PerlinNoiseFade(Pf.xy); + float2 n_x = lerp(float2(n00, n01), float2(n10, n11), fade_xy.x); + float n_xy = lerp(n_x.x, n_x.y, fade_xy.y); + return saturate(2.3 * n_xy); +} + +// Classic Perlin noise with periodic variant +float PerlinNoise(float2 p, float2 rep) +{ + float4 Pi = floor(p.xyxy) + float4(0.0, 0.0, 1.0, 1.0); + float4 Pf = frac(p.xyxy) - float4(0.0, 0.0, 1.0, 1.0); + Pi = fmod(Pi, rep.xyxy); + Pi = Mod289(Pi); + float4 ix = Pi.xzxz; + float4 iy = Pi.yyww; + float4 fx = Pf.xzxz; + float4 fy = Pf.yyww; + + float4 i = Permute(Permute(ix) + iy); + + float4 gx = frac(i * (1.0 / 41.0)) * 2.0 - 1.0; + float4 gy = abs(gx) - 0.5; + float4 tx = floor(gx + 0.5); + gx = gx - tx; + + float2 g00 = float2(gx.x, gy.x); + float2 g10 = float2(gx.y, gy.y); + float2 g01 = float2(gx.z, gy.z); + float2 g11 = float2(gx.w, gy.w); + + float4 norm = TaylorInvSqrt(float4(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, float2(fx.x, fy.x)); + float n10 = dot(g10, float2(fx.y, fy.y)); + float n01 = dot(g01, float2(fx.z, fy.z)); + float n11 = dot(g11, float2(fx.w, fy.w)); + + float2 fade_xy = PerlinNoiseFade(Pf.xy); + float2 n_x = lerp(float2(n00, n01), float2(n10, n11), fade_xy.x); + float n_xy = lerp(n_x.x, n_x.y, fade_xy.y); + return saturate(2.3 * n_xy); +} + +// Simplex noise +float SimplexNoise(float2 p) +{ + float4 C = float4(0.211324865405187f, // (3.0-math.sqrt(3.0))/6.0 + 0.366025403784439f, // 0.5*(math.sqrt(3.0)-1.0) + -0.577350269189626f, // -1.0 + 2.0 * C.x + 0.024390243902439f); // 1.0 / 41.0 + + // First corner + float2 i = floor(p + dot(p, C.yy)); + float2 x0 = p - i + dot(i, C.xx); + + // Other corners + float2 i1 = (x0.x > x0.y) ? float2(1.0f, 0.0f) : float2(0.0f, 1.0f); + float4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + // Permutations + i = Mod289(i); + float3 perm = Permute(Permute(i.y + float3(0.0f, i1.y, 1.0f)) + i.x + float3(0.0f, i1.x, 1.0f)); + float3 m = max(0.5f - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0f); + m = m * m; + m = m * m; + + // Gradients: 41 points uniformly over a line, mapped onto a diamond. + // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + float3 x = 2.0f * frac(perm * C.www) - 1.0f; + float3 h = abs(x) - 0.5f; + float3 ox = floor(x + 0.5f); + float3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversemath.sqrt( a0*a0 + h*h ); + m *= 1.79284291400159f - 0.85373472095314f * (a0 * a0 + h * h); + + // Compute final noise value at P + float gx = a0.x * x0.x + h.x * x0.y; + float2 gyz = a0.yz * x12.xz + h.yz * x12.yw; + float3 g = float3(gx, gyz); + return saturate(130.0f * dot(m, g)); +} + +// Worley noise (cellar noise with standard 3x3 search window for F1 and F2 values) +float2 WorleyNoise(float2 p) +{ + const float K = 0.142857142857f; // 1/7 + const float Ko = 0.428571428571f; // 3/7 + const float jitter = 1.0f; // Less gives more regular pattern + float2 Pi = Mod289(floor(p)); + float2 Pf = frac(p); + float3 oi = float3(-1.0f, 0.0f, 1.0f); + float3 of = float3(-0.5f, 0.5f, 1.5f); + float3 px = Permute(Pi.x + oi); + float3 pp = Permute(px.x + Pi.y + oi); // p11, p12, p13 + float3 ox = frac(pp * K) - Ko; + float3 oy = Mod7(floor(pp * K)) * K - Ko; + float3 dx = Pf.x + 0.5f + jitter * ox; + float3 dy = Pf.y - of + jitter * oy; + float3 d1 = dx * dx + dy * dy; // d11, d12 and d13, squared + pp = Permute(px.y + Pi.y + oi); // p21, p22, p23 + ox = frac(pp * K) - Ko; + oy = Mod7(floor(pp * K)) * K - Ko; + dx = Pf.x - 0.5f + jitter * ox; + dy = Pf.y - of + jitter * oy; + float3 d2 = dx * dx + dy * dy; // d21, d22 and d23, squared + pp = Permute(px.z + Pi.y + oi); // p31, p32, p33 + ox = frac(pp * K) - Ko; + oy = Mod7(floor(pp * K)) * K - Ko; + dx = Pf.x - 1.5f + jitter * ox; + dy = Pf.y - of + jitter * oy; + float3 d3 = dx * dx + dy * dy; // d31, d32 and d33, squared + float3 d1a = min(d1, d2); // Sort out the two smallest distances (F1, F2) + d2 = max(d1, d2); // Swap to keep candidates for F2 + d2 = min(d2, d3); // neither F1 nor F2 are now in d3 + d1 = min(d1a, d2); // F1 is now in d1 + d2 = max(d1a, d2); // Swap to keep candidates for F2 + d1.xy = (d1.x < d1.y) ? d1.xy : d1.yx; // Swap if smaller + d1.xz = (d1.x < d1.z) ? d1.xz : d1.zx; // F1 is in d1.x + d1.yz = min(d1.yz, d2.yz); // F2 is now not in d2.yz + d1.y = min(d1.y, d1.z); // nor in d1.z + d1.y = min(d1.y, d2.x); // F2 is in d1.y, we're done. + return saturate(sqrt(d1.xy)); +} + +// Voronoi noise (X=minDistToCell, Y=randomColor, Z=minEdgeDistance) +float3 VoronoiNoise(float2 p) +{ + // Reference: https://www.ronja-tutorials.com/post/028-voronoi-noise/ + float2 baseCell = floor(p); + + // first pass to find the closest cell + float minDistToCell = 10; + float2 toClosestCell; + float2 closestCell; + UNROLL + for (int x1 = -1; x1 <= 1; x1++) + { + UNROLL + for (int y1 = -1; y1 <= 1; y1++) + { + float2 cell = baseCell + float2(x1, y1); + float2 cellPosition = cell + rand2dTo2d(cell); + float2 toCell = cellPosition - p; + float distToCell = length(toCell); + if (distToCell < minDistToCell) + { + minDistToCell = distToCell; + closestCell = cell; + toClosestCell = toCell; + } + } + } + + // second pass to find the distance to the closest edge + float minEdgeDistance = 10; + UNROLL + for (int x2 = -1; x2 <= 1; x2++) + { + UNROLL + for (int y2 = -1; y2 <= 1; y2++) + { + float2 cell = baseCell + float2(x2, y2); + float2 cellPosition = cell + rand2dTo2d(cell); + float2 toCell = cellPosition - p; + float2 diffToClosestCell = abs(closestCell - cell); + if (diffToClosestCell.x + diffToClosestCell.y >= 0.1) + { + float2 toCenter = (toClosestCell + toCell) * 0.5; + float2 cellDifference = normalize(toCell - toClosestCell); + minEdgeDistance = min(minEdgeDistance, dot(toCenter, cellDifference)); + } + } + } + + float random = rand2dTo1d(closestCell); + return saturate(float3(minDistToCell, random, minEdgeDistance)); +} + +float CustomNoise(float3 p) +{ + float3 a = floor(p); + float3 d = p - a; + d = d * d * (3.0 - 2.0 * d); + + float4 b = a.xxyy + float4(0.0, 1.0, 0.0, 1.0); + float4 k1 = Permute(b.xyxy); + float4 k2 = Permute(k1.xyxy + b.zzww); + + float4 c = k2 + a.zzzz; + float4 k3 = Permute(c); + float4 k4 = Permute(c + 1.0); + + float4 o1 = frac(k3 * (1.0 / 41.0)); + float4 o2 = frac(k4 * (1.0 / 41.0)); + + float4 o3 = o2 * d.z + o1 * (1.0 - d.z); + float2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x); + + return o4.y * d.y + o4.x * (1.0 - d.y); +} + +float3 CustomNoise3D(float3 p) +{ + float o = CustomNoise(p); + float a = CustomNoise(p + float3(0.0001f, 0.0f, 0.0f)); + float b = CustomNoise(p + float3(0.0f, 0.0001f, 0.0f)); + float c = CustomNoise(p + float3(0.0f, 0.0f, 0.0001f)); + + float3 grad = float3(o - a, o - b, o - c); + float3 other = abs(grad.zxy); + return normalize(cross(grad,other)); +} + +float3 CustomNoise3D(float3 position, int octaves, float roughness) +{ + float weight = 0.0f; + float3 noise = float3(0.0, 0.0, 0.0); + float scale = 1.0f; + for (int i = 0; i < octaves; i++) + { + float curWeight = pow((1.0 - ((float)i / octaves)), lerp(2.0, 0.2, roughness)); + noise += CustomNoise3D(position * scale) * curWeight; + weight += curWeight; + scale *= 1.72531; + } + return noise / weight; +} + +#endif