xref: /aosp_15_r20/external/skia/modules/skottie/src/effects/SphereEffect.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
8 #include "include/core/SkBlendMode.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkM44.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkPicture.h"
15 #include "include/core/SkPictureRecorder.h"
16 #include "include/core/SkPoint.h"
17 #include "include/core/SkRect.h"
18 #include "include/core/SkRefCnt.h"
19 #include "include/core/SkSamplingOptions.h"
20 #include "include/core/SkScalar.h"
21 #include "include/core/SkShader.h"
22 #include "include/core/SkSize.h"
23 #include "include/core/SkString.h"
24 #include "include/core/SkTileMode.h"
25 #include "include/effects/SkRuntimeEffect.h"
26 #include "include/private/base/SkAssert.h"
27 #include "include/private/base/SkTPin.h"
28 #include "modules/skottie/src/Adapter.h"
29 #include "modules/skottie/src/SkottiePriv.h"
30 #include "modules/skottie/src/SkottieValue.h"
31 #include "modules/skottie/src/effects/Effects.h"
32 #include "modules/sksg/include/SkSGNode.h"
33 #include "modules/sksg/include/SkSGRenderNode.h"
34 
35 #include <array>
36 #include <cmath>
37 #include <cstdio>
38 #include <utility>
39 #include <vector>
40 
41 namespace skjson {
42 class ArrayValue;
43 }
44 namespace sksg {
45 class InvalidationController;
46 }
47 
48 namespace skottie::internal {
49 namespace {
50 
51 // This shader maps its child shader onto a sphere.  To simplify things, we set it up such that:
52 //
53 //   - the sphere is centered at origin and has r == 1
54 //   - the eye is positioned at (0,0,eye_z), where eye_z is chosen to visually match AE
55 //   - the POI for a given pixel is on the z = 0 plane (x,y,0)
56 //   - we're only rendering inside the projected circle, which guarantees a quadratic solution
57 //
58 // Effect stages:
59 //
60 //   1) ray-cast to find the sphere intersection (selectable front/back solution);
61 //      given the sphere geometry, this is also the normal
62 //   2) rotate the normal
63 //   3) UV-map the sphere
64 //   4) scale uv to source size and sample
65 //   5) apply lighting model
66 //
67 // Note: the current implementation uses two passes for two-side ("full") rendering, on the
68 //       assumption that in practice most textures are opaque and two-side mode is infrequent;
69 //       if this proves to be problematic, we could expand the implementation to blend both sides
70 //       in one pass.
71 //
72 static constexpr char gSphereSkSL[] =
73     "uniform shader child;"
74 
75     "uniform half3x3 rot_matrix;"
76     "uniform half2 child_scale;"
77     "uniform half side_select;"
78 
79     // apply_light()
80     "%s"
81 
82     "half3 to_sphere(half3 EYE) {"
83         "half eye_z2 = EYE.z*EYE.z;"
84 
85         "half a = dot(EYE, EYE),"
86              "b = -2*eye_z2,"
87              "c = eye_z2 - 1,"
88              "t = (-b + side_select*sqrt(b*b - 4*a*c))/(2*a);"
89 
90         "return half3(0, 0, -EYE.z) + EYE*t;"
91     "}"
92 
93     "half4 main(float2 xy) {"
94         "half3 EYE = half3(xy, -5.5),"
95                 "N = to_sphere(EYE),"
96                "RN = rot_matrix*N;"
97 
98         "half kRPI = 1/3.1415927;"
99 
100         "half2 UV = half2("
101             "0.5 + kRPI * 0.5 * atan(RN.x, RN.z),"
102             "0.5 + kRPI * asin(RN.y)"
103         ");"
104 
105         "return apply_light(EYE, N, child.eval(UV*child_scale));"
106     "}"
107 ;
108 
109 // CC Sphere uses a Phong-like lighting model:
110 //
111 //   - "ambient" controls the intensity of the texture color
112 //   - "diffuse" controls a multiplicative mix of texture and light color
113 //   - "specular" controls a light color specular component
114 //   - "roughness" is the specular exponent reciprocal
115 //   - "light intensity" modulates the diffuse and specular components (but not ambient)
116 //   - "light height" and "light direction" specify the light source position in spherical coords
117 //
118 // Implementation-wise, light intensity/height/direction are all combined into l_vec.
119 // For efficiency, we fall back to a stripped-down shader (ambient-only) when the diffuse & specular
120 // components are not used.
121 //
122 // TODO: "metal" and "reflective" parameters are ignored.
123 static constexpr char gBasicLightSkSL[] =
124     "uniform half l_coeff_ambient;"
125 
126     "half4 apply_light(half3 EYE, half3 N, half4 c) {"
127         "c.rgb *= l_coeff_ambient;"
128         "return c;"
129     "}"
130 ;
131 
132 static constexpr char gFancyLightSkSL[] =
133     "uniform half3 l_vec;"
134     "uniform half3 l_color;"
135     "uniform half l_coeff_ambient;"
136     "uniform half l_coeff_diffuse;"
137     "uniform half l_coeff_specular;"
138     "uniform half l_specular_exp;"
139 
140     "half4 apply_light(half3 EYE, half3 N, half4 c) {"
141         "half3 LR = reflect(-l_vec*side_select, N);"
142         "half s_base = max(dot(normalize(EYE), LR), 0),"
143 
144         "a = l_coeff_ambient,"
145         "d = l_coeff_diffuse * max(dot(l_vec, N), 0),"
146         "s = l_coeff_specular * saturate(pow(s_base, l_specular_exp));"
147 
148         "c.rgb = (a + d*l_color)*c.rgb + s*l_color*c.a;"
149 
150         "return c;"
151     "}"
152 ;
153 
sphere_fancylight_effect()154 static sk_sp<SkRuntimeEffect> sphere_fancylight_effect() {
155     static const SkRuntimeEffect* effect =
156             SkRuntimeEffect::MakeForShader(SkStringPrintf(gSphereSkSL, gFancyLightSkSL), {})
157                     .effect.release();
158     if (0 && !effect) {
159         printf("!!! %s\n",
160                SkRuntimeEffect::MakeForShader(SkStringPrintf(gSphereSkSL, gFancyLightSkSL), {})
161                        .errorText.c_str());
162     }
163     SkASSERT(effect);
164 
165     return sk_ref_sp(effect);
166 }
167 
sphere_basiclight_effect()168 static sk_sp<SkRuntimeEffect> sphere_basiclight_effect() {
169     static const SkRuntimeEffect* effect =
170             SkRuntimeEffect::MakeForShader(SkStringPrintf(gSphereSkSL, gBasicLightSkSL), {})
171                     .effect.release();
172     SkASSERT(effect);
173 
174     return sk_ref_sp(effect);
175 }
176 
177 class SphereNode final : public sksg::CustomRenderNode {
178 public:
SphereNode(sk_sp<RenderNode> child,const SkSize & child_size)179     SphereNode(sk_sp<RenderNode> child, const SkSize& child_size)
180         : INHERITED({std::move(child)})
181         , fChildSize(child_size) {}
182 
183     enum class RenderSide {
184         kFull,
185         kOutside,
186         kInside,
187     };
188 
189     SG_ATTRIBUTE(Center  , SkPoint   , fCenter)
190     SG_ATTRIBUTE(Radius  , float     , fRadius)
191     SG_ATTRIBUTE(Rotation, SkM44     , fRot   )
192     SG_ATTRIBUTE(Side    , RenderSide, fSide  )
193 
194     SG_ATTRIBUTE(LightVec     , SkV3 , fLightVec     )
195     SG_ATTRIBUTE(LightColor   , SkV3 , fLightColor   )
196     SG_ATTRIBUTE(AmbientLight , float, fAmbientLight )
197     SG_ATTRIBUTE(DiffuseLight , float, fDiffuseLight )
198     SG_ATTRIBUTE(SpecularLight, float, fSpecularLight)
199     SG_ATTRIBUTE(SpecularExp  , float, fSpecularExp  )
200 
201 private:
contentShader()202     sk_sp<SkShader> contentShader() {
203         if (!fContentShader || this->hasChildrenInval()) {
204             const auto& child = this->children()[0];
205             child->revalidate(nullptr, SkMatrix::I());
206 
207             SkPictureRecorder recorder;
208             child->render(recorder.beginRecording(SkRect::MakeSize(fChildSize)));
209 
210             fContentShader = recorder.finishRecordingAsPicture()
211                     ->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkFilterMode::kLinear,
212                                  nullptr, nullptr);
213         }
214 
215         return fContentShader;
216     }
217 
buildEffectShader(float selector)218     sk_sp<SkShader> buildEffectShader(float selector) {
219         const auto has_fancy_light =
220                 fLightVec.length() > 0 && (fDiffuseLight > 0 || fSpecularLight > 0);
221 
222         SkRuntimeShaderBuilder builder(has_fancy_light
223                                            ? sphere_fancylight_effect()
224                                            : sphere_basiclight_effect());
225 
226         builder.child  ("child")       = this->contentShader();
227         builder.uniform("child_scale") = fChildSize;
228         builder.uniform("side_select") = selector;
229         builder.uniform("rot_matrix")  = std::array<float,9>{
230             fRot.rc(0,0), fRot.rc(0,1), fRot.rc(0,2),
231             fRot.rc(1,0), fRot.rc(1,1), fRot.rc(1,2),
232             fRot.rc(2,0), fRot.rc(2,1), fRot.rc(2,2),
233         };
234 
235         builder.uniform("l_coeff_ambient")  = fAmbientLight;
236 
237         if (has_fancy_light) {
238             builder.uniform("l_vec")            = fLightVec * -selector;
239             builder.uniform("l_color")          = fLightColor;
240             builder.uniform("l_coeff_diffuse")  = fDiffuseLight;
241             builder.uniform("l_coeff_specular") = fSpecularLight;
242             builder.uniform("l_specular_exp")   = fSpecularExp;
243         }
244 
245         const auto lm = SkMatrix::Translate(fCenter.fX, fCenter.fY) *
246                         SkMatrix::Scale(fRadius, fRadius);
247 
248         return builder.makeShader(&lm);
249     }
250 
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)251     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
252         fSphereShader.reset();
253         if (fSide != RenderSide::kOutside) {
254             fSphereShader = this->buildEffectShader(1);
255         }
256         if (fSide != RenderSide::kInside) {
257             auto outside = this->buildEffectShader(-1);
258             fSphereShader = fSphereShader
259                     ? SkShaders::Blend(SkBlendMode::kSrcOver,
260                                        std::move(fSphereShader),
261                                        std::move(outside))
262                     : std::move(outside);
263         }
264         SkASSERT(fSphereShader);
265 
266         return SkRect::MakeLTRB(fCenter.fX - fRadius,
267                                 fCenter.fY - fRadius,
268                                 fCenter.fX + fRadius,
269                                 fCenter.fY + fRadius);
270     }
271 
onRender(SkCanvas * canvas,const RenderContext * ctx) const272     void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
273         if (fRadius <= 0) {
274             return;
275         }
276 
277         SkPaint sphere_paint;
278         sphere_paint.setAntiAlias(true);
279         sphere_paint.setShader(fSphereShader);
280 
281         canvas->drawCircle(fCenter, fRadius, sphere_paint);
282     }
283 
onNodeAt(const SkPoint &) const284     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
285 
286     const SkSize fChildSize;
287 
288     // Cached shaders
289     sk_sp<SkShader> fSphereShader;
290     sk_sp<SkShader> fContentShader;
291 
292     // Effect controls.
293     SkM44      fRot;
294     SkPoint    fCenter = {0,0};
295     float      fRadius = 0;
296     RenderSide fSide   = RenderSide::kFull;
297 
298     SkV3       fLightVec      = {0,0,1},
299                fLightColor    = {1,1,1};
300     float      fAmbientLight  = 1,
301                fDiffuseLight  = 0,
302                fSpecularLight = 0,
303                fSpecularExp   = 0;
304 
305     using INHERITED = sksg::CustomRenderNode;
306 };
307 
308 class SphereAdapter final : public DiscardableAdapterBase<SphereAdapter, SphereNode> {
309 public:
SphereAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,sk_sp<SphereNode> node)310     SphereAdapter(const skjson::ArrayValue& jprops,
311                   const AnimationBuilder* abuilder,
312                   sk_sp<SphereNode> node)
313         : INHERITED(std::move(node))
314     {
315         enum : size_t {
316             //      kRotGrp_Index =  0,
317                       kRotX_Index =  1,
318                       kRotY_Index =  2,
319                       kRotZ_Index =  3,
320                   kRotOrder_Index =  4,
321             // ???                =  5,
322                     kRadius_Index =  6,
323                     kOffset_Index =  7,
324                     kRender_Index =  8,
325 
326             //       kLight_Index =  9,
327             kLightIntensity_Index = 10,
328                 kLightColor_Index = 11,
329                kLightHeight_Index = 12,
330             kLightDirection_Index = 13,
331             // ???                = 14,
332             //     kShading_Index = 15,
333                    kAmbient_Index = 16,
334                    kDiffuse_Index = 17,
335                   kSpecular_Index = 18,
336                  kRoughness_Index = 19,
337         };
338 
339         EffectBinder(jprops, *abuilder, this)
340             .bind(  kOffset_Index, fOffset  )
341             .bind(  kRadius_Index, fRadius  )
342             .bind(    kRotX_Index, fRotX    )
343             .bind(    kRotY_Index, fRotY    )
344             .bind(    kRotZ_Index, fRotZ    )
345             .bind(kRotOrder_Index, fRotOrder)
346             .bind(  kRender_Index, fRender  )
347 
348             .bind(kLightIntensity_Index, fLightIntensity)
349             .bind(    kLightColor_Index, fLightColor    )
350             .bind(   kLightHeight_Index, fLightHeight   )
351             .bind(kLightDirection_Index, fLightDirection)
352             .bind(       kAmbient_Index, fAmbient       )
353             .bind(       kDiffuse_Index, fDiffuse       )
354             .bind(      kSpecular_Index, fSpecular      )
355             .bind(     kRoughness_Index, fRoughness     );
356     }
357 
358 private:
onSync()359     void onSync() override {
360         const auto side = [](ScalarValue s) {
361             switch (SkScalarRoundToInt(s)) {
362                 case 1:  return SphereNode::RenderSide::kFull;
363                 case 2:  return SphereNode::RenderSide::kOutside;
364                 case 3:
365                 default: return SphereNode::RenderSide::kInside;
366             }
367             SkUNREACHABLE;
368         };
369 
370         const auto rotation = [](ScalarValue order,
371                                  ScalarValue x, ScalarValue y, ScalarValue z) {
372             const SkM44 rx = SkM44::Rotate({1,0,0}, SkDegreesToRadians( x)),
373                         ry = SkM44::Rotate({0,1,0}, SkDegreesToRadians( y)),
374                         rz = SkM44::Rotate({0,0,1}, SkDegreesToRadians(-z));
375 
376             switch (SkScalarRoundToInt(order)) {
377                 case 1: return rx * ry * rz;
378                 case 2: return rx * rz * ry;
379                 case 3: return ry * rx * rz;
380                 case 4: return ry * rz * rx;
381                 case 5: return rz * rx * ry;
382                 case 6:
383                default: return rz * ry * rx;
384             }
385             SkUNREACHABLE;
386         };
387 
388         const auto light_vec = [](float height, float direction) {
389             float z = std::sin(height * SK_ScalarPI / 2),
390                   r = std::sqrt(1 - z*z),
391                   x = std::cos(direction) * r,
392                   y = std::sin(direction) * r;
393 
394             return SkV3{x,y,z};
395         };
396 
397         const auto& sph = this->node();
398 
399         sph->setCenter({fOffset.x, fOffset.y});
400         sph->setRadius(fRadius);
401         sph->setSide(side(fRender));
402         sph->setRotation(rotation(fRotOrder, fRotX, fRotY, fRotZ));
403 
404         sph->setAmbientLight (SkTPin(fAmbient * 0.01f, 0.0f, 2.0f));
405 
406         const auto intensity = SkTPin(fLightIntensity * 0.01f,  0.0f, 10.0f);
407         sph->setDiffuseLight (SkTPin(fDiffuse * 0.01f, 0.0f, 1.0f) * intensity);
408         sph->setSpecularLight(SkTPin(fSpecular* 0.01f, 0.0f, 1.0f) * intensity);
409 
410         sph->setLightVec(light_vec(
411             SkTPin(fLightHeight    * 0.01f, -1.0f,  1.0f),
412             SkDegreesToRadians(fLightDirection - 90)
413         ));
414 
415         const auto lc = static_cast<SkColor4f>(fLightColor);
416         sph->setLightColor({lc.fR, lc.fG, lc.fB});
417 
418         sph->setSpecularExp(1/SkTPin(fRoughness, 0.001f, 0.5f));
419     }
420 
421     Vec2Value   fOffset   = {0,0};
422     ScalarValue fRadius   = 0,
423                 fRotX     = 0,
424                 fRotY     = 0,
425                 fRotZ     = 0,
426                 fRotOrder = 1,
427                 fRender   = 1;
428 
429     ColorValue  fLightColor;
430     ScalarValue fLightIntensity =   0,
431                 fLightHeight    =   0,
432                 fLightDirection =   0,
433                 fAmbient        = 100,
434                 fDiffuse        =   0,
435                 fSpecular       =   0,
436                 fRoughness      =   0.5f;
437 
438     using INHERITED = DiscardableAdapterBase<SphereAdapter, SphereNode>;
439 };
440 
441 } // namespace
442 
attachSphereEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const443 sk_sp<sksg::RenderNode> EffectBuilder::attachSphereEffect(
444         const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
445     auto sphere = sk_make_sp<SphereNode>(std::move(layer), fLayerSize);
446 
447     return fBuilder->attachDiscardableAdapter<SphereAdapter>(jprops, fBuilder, std::move(sphere));
448 }
449 
450 } // namespace skottie::internal
451