diff --git a/modules/skottie/src/effects/FractalNoiseEffect.cpp b/modules/skottie/src/effects/FractalNoiseEffect.cpp index 80792b9da1..2dc3d21090 100644 --- a/modules/skottie/src/effects/FractalNoiseEffect.cpp +++ b/modules/skottie/src/effects/FractalNoiseEffect.cpp @@ -57,7 +57,9 @@ static constexpr char gNoiseEffectSkSL[] = "uniform half u_octaves," // number of octaves (can be fractional) "u_persistence," // relative octave weight - "u_evolution;" // evolution/seed + "u_evo," // evolution + "u_evo_offset," // evolution offset (based on rnd seed) + "u_evo_cycle;" // evolution period // Hash based on hash13 (https://www.shadertoy.com/view/4djSRW). "half hash(half3 v) {" @@ -72,10 +74,12 @@ static constexpr char gNoiseEffectSkSL[] = "half sample_noise(vec2 xy) {" "xy = floor(xy);" - "half e_ = floor(u_evolution)," - "t = u_evolution - e_," - "n0 = hash(half3(xy, e_ + 0))," - "n1 = hash(half3(xy, e_ + 1));" + "half e_ = floor(u_evo)," + "t = u_evo - e_," + "np0 = mod(e_ + 0, u_evo_cycle) + u_evo_offset," + "np1 = mod(e_ + 1, u_evo_cycle) + u_evo_offset," + "n0 = hash(half3(xy, np0))," + "n1 = hash(half3(xy, np1));" // Note: Ideally we would use 4 samples (-1, 0, 1, 2) and cubic interpolation for // better results -- but that's significantly more expensive than lerp. @@ -235,14 +239,16 @@ class FractalNoiseNode final : public sksg::CustomRenderNode { public: explicit FractalNoiseNode(sk_sp child) : INHERITED({std::move(child)}) {} - SG_ATTRIBUTE(Matrix , SkMatrix , fMatrix ) - SG_ATTRIBUTE(SubMatrix , SkMatrix , fSubMatrix ) + SG_ATTRIBUTE(Matrix , SkMatrix , fMatrix ) + SG_ATTRIBUTE(SubMatrix , SkMatrix , fSubMatrix ) - SG_ATTRIBUTE(NoiseFilter , NoiseFilter , fFilter ) - SG_ATTRIBUTE(NoiseFractal, NoiseFractal, fFractal ) - SG_ATTRIBUTE(Octaves , float , fOctaves ) - SG_ATTRIBUTE(Persistence , float , fPersistence) - SG_ATTRIBUTE(Evolution , float , fEvolution ) + SG_ATTRIBUTE(NoiseFilter , NoiseFilter , fFilter ) + SG_ATTRIBUTE(NoiseFractal , NoiseFractal, fFractal ) + SG_ATTRIBUTE(Octaves , float , fOctaves ) + SG_ATTRIBUTE(Persistence , float , fPersistence ) + SG_ATTRIBUTE(Evolution , float , fEvolution ) + SG_ATTRIBUTE(EvolutionOffset, float , fEvolutionOffset) + SG_ATTRIBUTE(EvolutionCycle , float , fEvolutionCycle ) private: template @@ -285,10 +291,12 @@ private: sk_sp buildEffectShader() const { SkRuntimeShaderBuilder builder(this->getEffect()); - builder.uniform("u_octaves") = fOctaves; + builder.uniform("u_octaves" ) = fOctaves; builder.uniform("u_persistence") = fPersistence; - builder.uniform("u_evolution") = fEvolution; - builder.uniform("u_submatrix") = std::array{ + builder.uniform("u_evo" ) = fEvolution; + builder.uniform("u_evo_offset" ) = fEvolutionOffset; + builder.uniform("u_evo_cycle" ) = fEvolutionCycle; + builder.uniform("u_submatrix" ) = std::array{ fSubMatrix.rc(0,0), fSubMatrix.rc(1,0), fSubMatrix.rc(2,0), fSubMatrix.rc(0,1), fSubMatrix.rc(1,1), fSubMatrix.rc(2,1), fSubMatrix.rc(0,2), fSubMatrix.rc(1,2), fSubMatrix.rc(2,2), @@ -327,11 +335,13 @@ private: SkMatrix fMatrix, fSubMatrix; - NoiseFilter fFilter = NoiseFilter::kNearest; - NoiseFractal fFractal = NoiseFractal::kBasic; - float fOctaves = 1, - fPersistence = 1, - fEvolution = 0; + NoiseFilter fFilter = NoiseFilter::kNearest; + NoiseFractal fFractal = NoiseFractal::kBasic; + float fOctaves = 1, + fPersistence = 1, + fEvolution = 0, + fEvolutionOffset = 0, + fEvolutionCycle = SK_ScalarMax; using INHERITED = sksg::CustomRenderNode; }; @@ -370,8 +380,8 @@ public: // 22 -- sub settings end-group .bind(23, fEvolution ) // 24 -- evolution options begin-group - // 25 -- cycle evolution - // 26 -- cycle revolution + .bind(25, fCycleEvolution ) + .bind(26, fCycleRevolutions) .bind(27, fRandomSeed ) // 28 -- evolution options end-group .bind(29, fOpacity ); @@ -379,14 +389,33 @@ public: } private: - float evolution() const { + std::tuple evolution() const { // Constant chosen to visually match AE's evolution rate. - const auto evo = SkDegreesToRadians(fEvolution) * 0.25f; + static constexpr auto kEvolutionScale = 0.25f; - // The random seed determines an arbitrary start plane. - const auto base = SkRandom(static_cast(fRandomSeed)).nextRangeU(0, 100); + // Evolution inputs: + // + // * evolution - main evolution control (degrees) + // * cycle evolution - flag controlling whether evolution cycles + // * cycle revolutions - number of revolutions after which evolution cycles (period) + // * random seed - determines an arbitrary starting plane (evolution offset) + // + // The shader uses evolution floor/ceil to select two noise planes, and the fractional part + // to interpolate between the two -> in order to wrap around smoothly, the cycle/period + // must be integral. + const float + evo_rad = SkDegreesToRadians(fEvolution), + rev_rad = std::max(fCycleRevolutions, 1.0f)*SK_FloatPI*2, + cycle = fCycleEvolution + ? SkScalarRoundToScalar(rev_rad*kEvolutionScale) + : SK_ScalarMax, + // Adjust scale when cycling to ensure an integral period (post scaling). + scale = fCycleEvolution + ? cycle/rev_rad + : kEvolutionScale, + offset = SkRandom(static_cast(fRandomSeed)).nextRangeU(0, 100); - return evo + base; + return std::make_tuple(evo_rad*scale, offset, cycle); } SkMatrix shaderMatrix() const { @@ -433,9 +462,13 @@ private: void onSync() override { const auto& n = this->node(); + const auto [evo, evo_offset, evo_cycle] = this->evolution(); + n->setOctaves(SkTPin(fComplexity, 1.0f, 20.0f)); n->setPersistence(SkTPin(fSubInfluence * 0.01f, 0.0f, 100.0f)); - n->setEvolution(this->evolution()); + n->setEvolution(evo); + n->setEvolutionOffset(evo_offset); + n->setEvolutionCycle(evo_cycle); n->setNoiseFilter(this->noiseFilter()); n->setNoiseFractal(this->noiseFractal()); n->setMatrix(this->shaderMatrix()); @@ -460,6 +493,8 @@ private: fSubRotation = 0, fEvolution = 0, + fCycleEvolution = 0, + fCycleRevolutions = 0, fRandomSeed = 0, fOpacity = 100, // TODO diff --git a/resources/skottie/skottie-fractalnoise-cycle.json b/resources/skottie/skottie-fractalnoise-cycle.json new file mode 100644 index 0000000000..af2031b936 --- /dev/null +++ b/resources/skottie/skottie-fractalnoise-cycle.json @@ -0,0 +1 @@ +{"v":"5.7.14","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"fractal-cycle","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"Black Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Fractal Noise","np":33,"mn":"ADBE Fractal Noise","ix":1,"en":1,"ef":[{"ty":7,"nm":"Fractal Type","mn":"ADBE Fractal Noise-0001","ix":1,"v":{"a":0,"k":5,"ix":1}},{"ty":7,"nm":"Noise Type","mn":"ADBE Fractal Noise-0002","ix":2,"v":{"a":0,"k":3,"ix":2}},{"ty":7,"nm":"Invert","mn":"ADBE Fractal Noise-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Contrast","mn":"ADBE Fractal Noise-0004","ix":4,"v":{"a":0,"k":100,"ix":4}},{"ty":0,"nm":"Brightness","mn":"ADBE Fractal Noise-0005","ix":5,"v":{"a":0,"k":0,"ix":5}},{"ty":7,"nm":"Overflow","mn":"ADBE Fractal Noise-0006","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":6,"nm":"Transform","mn":"ADBE Fractal Noise-0007","ix":7,"v":0},{"ty":0,"nm":"Rotation","mn":"ADBE Fractal Noise-0008","ix":8,"v":{"a":0,"k":0,"ix":8}},{"ty":7,"nm":"Uniform Scaling","mn":"ADBE Fractal Noise-0009","ix":9,"v":{"a":0,"k":1,"ix":9}},{"ty":0,"nm":"Scale","mn":"ADBE Fractal Noise-0010","ix":10,"v":{"a":0,"k":100,"ix":10}},{"ty":0,"nm":"Scale Width","mn":"ADBE Fractal Noise-0011","ix":11,"v":{"a":0,"k":100,"ix":11}},{"ty":0,"nm":"Scale Height","mn":"ADBE Fractal Noise-0012","ix":12,"v":{"a":0,"k":100,"ix":12}},{"ty":3,"nm":"Offset Turbulence","mn":"ADBE Fractal Noise-0013","ix":13,"v":{"a":0,"k":[250,250],"ix":13}},{"ty":7,"nm":"Perspective Offset","mn":"ADBE Fractal Noise-0031","ix":14,"v":{"a":0,"k":0,"ix":14}},{"ty":6,"nm":"","mn":"ADBE Fractal Noise-0014","ix":15,"v":0},{"ty":0,"nm":"Complexity","mn":"ADBE Fractal Noise-0015","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":6,"nm":"Sub Settings","mn":"ADBE Fractal Noise-0016","ix":17,"v":0},{"ty":0,"nm":"Sub Influence (%)","mn":"ADBE Fractal Noise-0017","ix":18,"v":{"a":0,"k":70,"ix":18}},{"ty":0,"nm":"Sub Scaling","mn":"ADBE Fractal Noise-0018","ix":19,"v":{"a":0,"k":56,"ix":19}},{"ty":0,"nm":"Sub Rotation","mn":"ADBE Fractal Noise-0019","ix":20,"v":{"a":0,"k":0,"ix":20}},{"ty":3,"nm":"Sub Offset","mn":"ADBE Fractal Noise-0020","ix":21,"v":{"a":0,"k":[0,0],"ix":21}},{"ty":7,"nm":"Center Subscale","mn":"ADBE Fractal Noise-0021","ix":22,"v":{"a":0,"k":0,"ix":22}},{"ty":6,"nm":"","mn":"ADBE Fractal Noise-0022","ix":23,"v":0},{"ty":0,"nm":"Evolution","mn":"ADBE Fractal Noise-0023","ix":24,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-720]},{"t":299,"s":[720]}],"ix":24}},{"ty":6,"nm":"Evolution Options","mn":"ADBE Fractal Noise-0024","ix":25,"v":0},{"ty":7,"nm":"Cycle Evolution","mn":"ADBE Fractal Noise-0025","ix":26,"v":{"a":0,"k":1,"ix":26}},{"ty":0,"nm":"Cycle (in Revolutions)","mn":"ADBE Fractal Noise-0026","ix":27,"v":{"a":0,"k":2,"ix":27}},{"ty":0,"nm":"Random Seed","mn":"ADBE Fractal Noise-0027","ix":28,"v":{"a":0,"k":350,"ix":28}},{"ty":6,"nm":"Random Seed","mn":"ADBE Fractal Noise-0028","ix":29,"v":0},{"ty":0,"nm":"Opacity","mn":"ADBE Fractal Noise-0029","ix":30,"v":{"a":0,"k":100,"ix":30}},{"ty":7,"nm":"Blending Mode","mn":"ADBE Fractal Noise-0030","ix":31,"v":{"a":0,"k":2,"ix":31}}]}],"sw":500,"sh":500,"sc":"#000000","ip":0,"op":300,"st":0,"bm":0}],"markers":[]} \ No newline at end of file