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