Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FractalNoiseEffect.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
10#include "include/core/SkM44.h"
13#include "include/core/SkRect.h"
28#include "src/base/SkRandom.h"
29
30#include <algorithm>
31#include <array>
32#include <cmath>
33#include <cstddef>
34#include <cstdint>
35#include <tuple>
36#include <utility>
37#include <vector>
38
39struct SkPoint;
40
41namespace skjson {
42class ArrayValue;
43}
44namespace sksg {
45class InvalidationController;
46}
47
48namespace skottie::internal {
49namespace {
50
51// An implementation of the ADBE Fractal Noise effect:
52//
53// - multiple noise sublayers (octaves) are combined using a weighted average
54// - each layer is subject to a (cumulative) transform, filter and post-sampling options
55//
56// Parameters:
57//
58// * Noise Type -- controls noise layer post-sampling filtering
59// (Block, Linear, Soft Linear, Spline)
60// * Fractal Type -- determines a noise layer post-filtering transformation
61// (Basic, Turbulent Smooth, Turbulent Basic, etc)
62// * Transform -- offset/scale/rotate the noise effect (local matrix)
63//
64// * Complexity -- number of sublayers;
65// can be fractional, where the fractional part modulates the last layer
66// * Evolution -- controls noise topology in a gradual manner (can be animated for smooth
67// noise transitions)
68// * Sub Influence -- relative amplitude weight for sublayers (cumulative)
69//
70// * Sub Scaling/Rotation/Offset -- relative scale for sublayers (cumulative)
71//
72// * Invert -- invert noise values
73//
74// * Contrast -- apply a contrast to the noise result
75//
76// * Brightness -- apply a brightness effect to the noise result
77//
78//
79// TODO:
80// - Invert
81// - Contrast/Brightness
82
83static constexpr char gNoiseEffectSkSL[] =
84 "uniform float3x3 u_submatrix;" // sublayer transform
85
86 "uniform float2 u_noise_planes;" // noise planes computed based on evolution params
87 "uniform float u_noise_weight," // noise planes lerp weight
88 "u_octaves," // number of octaves (can be fractional)
89 "u_persistence;" // relative octave weight
90
91 // Hash based on hash13 (https://www.shadertoy.com/view/4djSRW).
92 "float hash(float3 v) {"
93 "v = fract(v*0.1031);"
94 "v += dot(v, v.zxy + 31.32);"
95 "return fract((v.x + v.y)*v.z);"
96 "}"
97
98 // The general idea is to compute a coherent hash for two planes in discretized (x,y,e) space,
99 // and interpolate between them. This yields gradual changes when animating |e| - which is the
100 // desired outcome.
101 "float sample_noise(float2 xy) {"
102 "xy = floor(xy);"
103
104 "float n0 = hash(float3(xy, u_noise_planes.x)),"
105 "n1 = hash(float3(xy, u_noise_planes.y));"
106
107 // Note: Ideally we would use 4 samples (-1, 0, 1, 2) and cubic interpolation for
108 // better results -- but that's significantly more expensive than lerp.
109
110 "return mix(n0, n1, u_noise_weight);"
111 "}"
112
113 // filter() placeholder
114 "%s"
115
116 // fractal() placeholder
117 "%s"
118
119 // Generate ceil(u_octaves) noise layers and combine based on persistentce and sublayer xform.
120 "float4 main(vec2 xy) {"
121 "float oct = u_octaves," // initial octave count (this is the effective loop counter)
122 "amp = 1," // initial layer amplitude
123 "wacc = 0," // weight accumulator
124 "n = 0;" // noise accumulator
125
126 // Constant loop counter chosen to be >= ceil(u_octaves).
127 // The logical counter is actually 'oct'.
128 "for (float i = 0; i < %u; ++i) {"
129 // effective layer weight
130 // -- for full octaves: layer amplitude
131 // -- for fractional octave: layer amplitude modulated by fractional part
132 "float w = amp*min(oct,1.0);"
133
134 "n += w*fractal(filter(xy));"
135 "wacc += w;"
136
137 "if (oct <= 1.0) { break; }"
138
139 "oct -= 1.0;"
140 "amp *= u_persistence;"
141 "xy = (u_submatrix*float3(xy,1)).xy;"
142 "}"
143
144 "n /= wacc;"
145
146 // TODO: fractal functions
147
148 "return float4(n,n,n,1);"
149 "}";
150
151static constexpr char gFilterNearestSkSL[] =
152 "float filter(float2 xy) {"
153 "return sample_noise(xy);"
154 "}";
155
156static constexpr char gFilterLinearSkSL[] =
157 "float filter(float2 xy) {"
158 "xy -= 0.5;"
159
160 "float n00 = sample_noise(xy + float2(0,0)),"
161 "n10 = sample_noise(xy + float2(1,0)),"
162 "n01 = sample_noise(xy + float2(0,1)),"
163 "n11 = sample_noise(xy + float2(1,1));"
164
165 "float2 t = fract(xy);"
166
167 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
168 "}";
169
170static constexpr char gFilterSoftLinearSkSL[] =
171 "float filter(float2 xy) {"
172 "xy -= 0.5;"
173
174 "float n00 = sample_noise(xy + float2(0,0)),"
175 "n10 = sample_noise(xy + float2(1,0)),"
176 "n01 = sample_noise(xy + float2(0,1)),"
177 "n11 = sample_noise(xy + float2(1,1));"
178
179 "float2 t = smoothstep(0, 1, fract(xy));"
180
181 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
182 "}";
183
184static constexpr char gFractalBasicSkSL[] =
185 "float fractal(float n) {"
186 "return n;"
187 "}";
188
189static constexpr char gFractalTurbulentBasicSkSL[] =
190 "float fractal(float n) {"
191 "return 2*abs(0.5 - n);"
192 "}";
193
194static constexpr char gFractalTurbulentSmoothSkSL[] =
195 "float fractal(float n) {"
196 "n = 2*abs(0.5 - n);"
197 "return n*n;"
198 "}";
199
200static constexpr char gFractalTurbulentSharpSkSL[] =
201 "float fractal(float n) {"
202 "return sqrt(2*abs(0.5 - n));"
203 "}";
204
205enum class NoiseFilter {
206 kNearest,
207 kLinear,
208 kSoftLinear,
209 // TODO: kSpline?
210};
211
212enum class NoiseFractal {
213 kBasic,
214 kTurbulentBasic,
215 kTurbulentSmooth,
216 kTurbulentSharp,
217};
218
219sk_sp<SkRuntimeEffect> make_noise_effect(unsigned loops, const char* filter, const char* fractal) {
221 SkStringPrintf(gNoiseEffectSkSL, filter, fractal, loops), {});
222
223 return std::move(result.effect);
224}
225
226sk_sp<SkRuntimeEffect> noise_effect(float octaves, NoiseFilter filter, NoiseFractal fractal) {
227 static constexpr char const* gFilters[] = {
228 gFilterNearestSkSL,
229 gFilterLinearSkSL,
230 gFilterSoftLinearSkSL
231 };
232
233 static constexpr char const* gFractals[] = {
234 gFractalBasicSkSL,
235 gFractalTurbulentBasicSkSL,
236 gFractalTurbulentSmoothSkSL,
237 gFractalTurbulentSharpSkSL
238 };
239
240 SkASSERT(static_cast<size_t>(filter) < std::size(gFilters));
241 SkASSERT(static_cast<size_t>(fractal) < std::size(gFractals));
242
243 // Bin the loop counter based on the number of octaves (range: [1..20]).
244 // Low complexities are common, so we maximize resolution for the low end.
245 struct BinInfo {
246 float threshold;
247 unsigned loops;
248 };
249 static constexpr BinInfo kLoopBins[] = {
250 { 8, 20 },
251 { 4, 8 },
252 { 3, 4 },
253 { 2, 3 },
254 { 1, 2 },
255 { 0, 1 }
256 };
257
258 auto bin_index = [](float octaves) {
259 SkASSERT(octaves > kLoopBins[std::size(kLoopBins) - 1].threshold);
260
261 for (size_t i = 0; i < std::size(kLoopBins); ++i) {
262 if (octaves > kLoopBins[i].threshold) {
263 return i;
264 }
265 }
267 };
268
269 static SkRuntimeEffect* kEffectCache[std::size(kLoopBins)]
270 [std::size(gFilters)]
271 [std::size(gFractals)];
272
273 const size_t bin = bin_index(octaves);
274
275 auto& effect = kEffectCache[bin]
276 [static_cast<size_t>(filter)]
277 [static_cast<size_t>(fractal)];
278 if (!effect) {
279 effect = make_noise_effect(kLoopBins[bin].loops,
280 gFilters[static_cast<size_t>(filter)],
281 gFractals[static_cast<size_t>(fractal)])
282 .release();
283 }
284
285 SkASSERT(effect);
286 return sk_ref_sp(effect);
287}
288
289class FractalNoiseNode final : public sksg::CustomRenderNode {
290public:
291 explicit FractalNoiseNode(sk_sp<RenderNode> child) : INHERITED({std::move(child)}) {}
292
293 SG_ATTRIBUTE(Matrix , SkMatrix , fMatrix )
294 SG_ATTRIBUTE(SubMatrix , SkMatrix , fSubMatrix )
295
296 SG_ATTRIBUTE(NoiseFilter , NoiseFilter , fFilter )
297 SG_ATTRIBUTE(NoiseFractal , NoiseFractal, fFractal )
298 SG_ATTRIBUTE(NoisePlanes , SkV2 , fNoisePlanes )
299 SG_ATTRIBUTE(NoiseWeight , float , fNoiseWeight )
300 SG_ATTRIBUTE(Octaves , float , fOctaves )
301 SG_ATTRIBUTE(Persistence , float , fPersistence )
302
303private:
304 sk_sp<SkRuntimeEffect> getEffect(NoiseFilter filter) const {
305 switch (fFractal) {
306 case NoiseFractal::kBasic:
307 return noise_effect(fOctaves, filter, NoiseFractal::kBasic);
308 case NoiseFractal::kTurbulentBasic:
309 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentBasic);
310 case NoiseFractal::kTurbulentSmooth:
311 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentSmooth);
312 case NoiseFractal::kTurbulentSharp:
313 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentSharp);
314 }
316 }
317
318 sk_sp<SkRuntimeEffect> getEffect() const {
319 switch (fFilter) {
320 case NoiseFilter::kNearest : return this->getEffect(NoiseFilter::kNearest);
321 case NoiseFilter::kLinear : return this->getEffect(NoiseFilter::kLinear);
322 case NoiseFilter::kSoftLinear: return this->getEffect(NoiseFilter::kSoftLinear);
323 }
325 }
326
327 sk_sp<SkShader> buildEffectShader() const {
328 SkRuntimeShaderBuilder builder(this->getEffect());
329
330 builder.uniform("u_noise_planes") = fNoisePlanes;
331 builder.uniform("u_noise_weight") = fNoiseWeight;
332 builder.uniform("u_octaves" ) = fOctaves;
333 builder.uniform("u_persistence" ) = fPersistence;
334 builder.uniform("u_submatrix" ) = std::array<float,9>{
335 fSubMatrix.rc(0,0), fSubMatrix.rc(1,0), fSubMatrix.rc(2,0),
336 fSubMatrix.rc(0,1), fSubMatrix.rc(1,1), fSubMatrix.rc(2,1),
337 fSubMatrix.rc(0,2), fSubMatrix.rc(1,2), fSubMatrix.rc(2,2),
338 };
339
340 return builder.makeShader(&fMatrix);
341 }
342
343 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
344 const auto& child = this->children()[0];
345 const auto bounds = child->revalidate(ic, ctm);
346
347 fEffectShader = this->buildEffectShader();
348
349 return bounds;
350 }
351
352 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
353 const auto& bounds = this->bounds();
354 const auto local_ctx = ScopedRenderContext(canvas, ctx)
355 .setIsolation(bounds, canvas->getTotalMatrix(), true);
356
357 canvas->saveLayer(&bounds, nullptr);
358 this->children()[0]->render(canvas, local_ctx);
359
360 SkPaint effect_paint;
361 effect_paint.setShader(fEffectShader);
362 effect_paint.setBlendMode(SkBlendMode::kSrcIn);
363
364 canvas->drawPaint(effect_paint);
365 }
366
367 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
368
369 sk_sp<SkShader> fEffectShader;
370
371 SkMatrix fMatrix,
372 fSubMatrix;
373 NoiseFilter fFilter = NoiseFilter::kNearest;
374 NoiseFractal fFractal = NoiseFractal::kBasic;
375 SkV2 fNoisePlanes = {0,0};
376 float fNoiseWeight = 0,
377 fOctaves = 1,
378 fPersistence = 1;
379
381};
382
383class FractalNoiseAdapter final : public DiscardableAdapterBase<FractalNoiseAdapter,
384 FractalNoiseNode> {
385public:
386 FractalNoiseAdapter(const skjson::ArrayValue& jprops,
387 const AnimationBuilder* abuilder,
389 : INHERITED(std::move(node))
390 {
391 EffectBinder(jprops, *abuilder, this)
392 .bind( 0, fFractalType )
393 .bind( 1, fNoiseType )
394 .bind( 2, fInvert )
395 .bind( 3, fContrast )
396 .bind( 4, fBrightness )
397 // 5 -- overflow
398 // 6 -- transform begin-group
399 .bind( 7, fRotation )
400 .bind( 8, fUniformScaling )
401 .bind( 9, fScale )
402 .bind(10, fScaleWidth )
403 .bind(11, fScaleHeight )
404 .bind(12, fOffset )
405 // 13 -- TODO: perspective offset
406 // 14 -- transform end-group
407 .bind(15, fComplexity )
408 // 16 -- sub settings begin-group
409 .bind(17, fSubInfluence )
410 .bind(18, fSubScale )
411 .bind(19, fSubRotation )
412 .bind(20, fSubOffset )
413 // 21 -- center subscale
414 // 22 -- sub settings end-group
415 .bind(23, fEvolution )
416 // 24 -- evolution options begin-group
417 .bind(25, fCycleEvolution )
418 .bind(26, fCycleRevolutions)
419 .bind(27, fRandomSeed )
420 // 28 -- evolution options end-group
421 .bind(29, fOpacity );
422 // 30 -- TODO: blending mode
423 }
424
425private:
426 std::tuple<SkV2, float> noise() const {
427 // Constant chosen to visually match AE's evolution rate.
428 static constexpr auto kEvolutionScale = 0.25f;
429
430 // Evolution inputs:
431 //
432 // * evolution - main evolution control (degrees)
433 // * cycle evolution - flag controlling whether evolution cycles
434 // * cycle revolutions - number of revolutions after which evolution cycles (period)
435 // * random seed - determines an arbitrary starting plane (evolution offset)
436 //
437 // The shader uses evolution floor/ceil to select two noise planes, and the fractional part
438 // to interpolate between the two -> in order to wrap around smoothly, the cycle/period
439 // must be integral.
440 const float
441 evo_rad = SkDegreesToRadians(fEvolution),
442 rev_rad = std::max(fCycleRevolutions, 1.0f)*SK_FloatPI*2,
443 cycle = fCycleEvolution
444 ? SkScalarRoundToScalar(rev_rad*kEvolutionScale)
445 : SK_ScalarMax,
446 // Adjust scale when cycling to ensure an integral period (post scaling).
447 scale = fCycleEvolution
448 ? cycle/rev_rad
449 : kEvolutionScale,
450 offset = SkRandom(static_cast<uint32_t>(fRandomSeed)).nextRangeU(0, 100),
451 evo = evo_rad*scale,
452 evo_ = std::floor(evo),
453 weight = evo - evo_;
454
455 // We want the GLSL mod() flavor.
456 auto glsl_mod = [](float x, float y) {
457 return x - y*std::floor(x/y);
458 };
459
460 const SkV2 noise_planes = {
461 glsl_mod(evo_ + 0, cycle) + offset,
462 glsl_mod(evo_ + 1, cycle) + offset,
463 };
464
465 return std::make_tuple(noise_planes, weight);
466 }
467
468 SkMatrix shaderMatrix() const {
469 static constexpr float kGridSize = 64;
470
471 const auto scale = (SkScalarRoundToInt(fUniformScaling) == 1)
472 ? SkV2{fScale, fScale}
473 : SkV2{fScaleWidth, fScaleHeight};
474
475 return SkMatrix::Translate(fOffset.x, fOffset.y)
476 * SkMatrix::Scale(SkTPin(scale.x, 1.0f, 10000.0f) * 0.01f,
477 SkTPin(scale.y, 1.0f, 10000.0f) * 0.01f)
478 * SkMatrix::RotateDeg(fRotation)
479 * SkMatrix::Scale(kGridSize, kGridSize);
480 }
481
482 SkMatrix subMatrix() const {
483 const auto scale = 100 / SkTPin(fSubScale, 10.0f, 10000.0f);
484
485 return SkMatrix::Translate(-fSubOffset.x * 0.01f, -fSubOffset.y * 0.01f)
486 * SkMatrix::RotateDeg(-fSubRotation)
488 }
489
490 NoiseFilter noiseFilter() const {
491 switch (SkScalarRoundToInt(fNoiseType)) {
492 case 1: return NoiseFilter::kNearest;
493 case 2: return NoiseFilter::kLinear;
494 default: return NoiseFilter::kSoftLinear;
495 }
497 }
498
499 NoiseFractal noiseFractal() const {
500 switch (SkScalarRoundToInt(fFractalType)) {
501 case 1: return NoiseFractal::kBasic;
502 case 3: return NoiseFractal::kTurbulentSmooth;
503 case 4: return NoiseFractal::kTurbulentBasic;
504 default: return NoiseFractal::kTurbulentSharp;
505 }
507 }
508
509 void onSync() override {
510 const auto& n = this->node();
511
512 const auto [noise_planes, noise_weight] = this->noise();
513
514 n->setOctaves(SkTPin(fComplexity, 1.0f, 20.0f));
515 n->setPersistence(SkTPin(fSubInfluence * 0.01f, 0.0f, 100.0f));
516 n->setNoisePlanes(noise_planes);
517 n->setNoiseWeight(noise_weight);
518 n->setNoiseFilter(this->noiseFilter());
519 n->setNoiseFractal(this->noiseFractal());
520 n->setMatrix(this->shaderMatrix());
521 n->setSubMatrix(this->subMatrix());
522 }
523
524 Vec2Value fOffset = {0,0},
525 fSubOffset = {0,0};
526
527 ScalarValue fFractalType = 0,
528 fNoiseType = 0,
529
530 fRotation = 0,
531 fUniformScaling = 0,
532 fScale = 100, // used when uniform scaling is selected
533 fScaleWidth = 100, // used when uniform scaling is not selected
534 fScaleHeight = 100, // ^
535
536 fComplexity = 1,
537 fSubInfluence = 100,
538 fSubScale = 50,
539 fSubRotation = 0,
540
541 fEvolution = 0,
542 fCycleEvolution = 0,
543 fCycleRevolutions = 0,
544 fRandomSeed = 0,
545
546 fOpacity = 100, // TODO
547 fInvert = 0, // TODO
548 fContrast = 100, // TODO
549 fBrightness = 0; // TODO
550
551 using INHERITED = DiscardableAdapterBase<FractalNoiseAdapter, FractalNoiseNode>;
552};
553
554} // namespace
555
556sk_sp<sksg::RenderNode> EffectBuilder::attachFractalNoiseEffect(
557 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
558 auto fractal_noise = sk_make_sp<FractalNoiseNode>(std::move(layer));
559
560 return fBuilder->attachDiscardableAdapter<FractalNoiseAdapter>(jprops, fBuilder,
561 std::move(fractal_noise));
562}
563
564} // namespace skottie::internal
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkASSERT(cond)
Definition SkAssert.h:116
@ kSrcIn
r = s * da
constexpr float SK_FloatPI
#define INHERITED(method,...)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
#define SG_ATTRIBUTE(attr_name, attr_type, attr_container)
Definition SkSGNode.h:100
#define SkDegreesToRadians(degrees)
Definition SkScalar.h:77
#define SK_ScalarMax
Definition SkScalar.h:24
#define SkScalarRoundToInt(x)
Definition SkScalar.h:37
#define SkScalarRoundToScalar(x)
Definition SkScalar.h:32
SK_API SkString static SkString SkStringPrintf()
Definition SkString.h:287
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition SkTPin.h:19
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition SkCanvas.cpp:500
void drawPaint(const SkPaint &paint)
SkMatrix getTotalMatrix() const
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
static SkMatrix RotateDeg(SkScalar deg)
Definition SkMatrix.h:104
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
SkScalar rc(int r, int c) const
Definition SkMatrix.h:404
void setShader(sk_sp< SkShader > shader)
void setBlendMode(SkBlendMode mode)
Definition SkPaint.cpp:151
static Result MakeForShader(SkString sksl, const Options &)
void attachDiscardableAdapter(sk_sp< T > adapter) const
GAsyncResult * result
double y
double x
Optional< SkRect > bounds
Definition SkRecords.h:189
SkScalar ScalarValue
SkV2 Vec2Value
Definition Skottie.h:32
SIN Vec< N, float > floor(const Vec< N, float > &x)
Definition SkVx.h:703
Definition ref_ptr.h:256
const Scalar scale
Point offset
Definition SkM44.h:19