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