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