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