Flutter Engine
The Flutter Engine
SphereEffect.cpp
Go to the documentation of this file.
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
11#include "include/core/SkM44.h"
16#include "include/core/SkRect.h"
21#include "include/core/SkSize.h"
34
35#include <array>
36#include <cmath>
37#include <cstdio>
38#include <utility>
39#include <vector>
40
41namespace skjson {
42class ArrayValue;
43}
44namespace sksg {
45class InvalidationController;
46}
47
48namespace skottie::internal {
49namespace {
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//
72static 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.
123static 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
132static 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
154static 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
168static 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
177class SphereNode final : public sksg::CustomRenderNode {
178public:
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
201private:
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()
212 nullptr, nullptr);
213 }
214
215 return fContentShader;
216 }
217
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
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
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
272 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
284 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
306};
307
308class SphereAdapter final : public DiscardableAdapterBase<SphereAdapter, SphereNode> {
309public:
310 SphereAdapter(const skjson::ArrayValue& jprops,
311 const AnimationBuilder* abuilder,
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
358private:
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 }
368 };
369
370 const auto rotation = [](ScalarValue order,
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 }
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
443sk_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
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kSrcOver
r = s + (1-sa)*d
static int side(double x)
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
#define SG_ATTRIBUTE(attr_name, attr_type, attr_container)
Definition: SkSGNode.h:100
#define SkDegreesToRadians(degrees)
Definition: SkScalar.h:77
#define SkScalarRoundToInt(x)
Definition: SkScalar.h:37
#define SK_ScalarPI
Definition: SkScalar.h:21
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint &paint)
Definition: SkCanvas.cpp:2707
Definition: SkM44.h:150
SkScalar rc(int r, int c) const
Definition: SkM44.h:261
static SkM44 Rotate(SkV3 axis, SkScalar radians)
Definition: SkM44.h:239
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition: SkMatrix.h:75
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition: SkMatrix.h:91
static const SkMatrix & I()
Definition: SkMatrix.cpp:1544
void setAntiAlias(bool aa)
Definition: SkPaint.h:170
void setShader(sk_sp< SkShader > shader)
SkCanvas * beginRecording(const SkRect &bounds, sk_sp< SkBBoxHierarchy > bbh)
sk_sp< SkPicture > finishRecordingAsPicture()
sk_sp< SkShader > makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode mode, const SkMatrix *localMatrix, const SkRect *tileRect) const
static Result MakeForShader(SkString sksl, const Options &)
void reset(T *ptr=nullptr)
Definition: SkRefCnt.h:310
void attachDiscardableAdapter(sk_sp< T > adapter) const
Definition: SkottiePriv.h:139
struct MyStruct s
double y
double x
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
SK_API sk_sp< SkShader > Blend(SkBlendMode mode, sk_sp< SkShader > dst, sk_sp< SkShader > src)
SkScalar ScalarValue
Definition: SkottieValue.h:22
SkV2 Vec2Value
Definition: SkottieValue.h:23
Definition: Skottie.h:32
SIN Vec< N, float > sqrt(const Vec< N, float > &x)
Definition: SkVx.h:706
Definition: ref_ptr.h:256
int32_t height
float fX
x-axis value
Definition: SkPoint_impl.h:164
float fY
y-axis value
Definition: SkPoint_impl.h:165
static constexpr SkRect MakeSize(const SkSize &size)
Definition: SkRect.h:633
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition: SkRect.h:646
Definition: SkSize.h:52
Definition: SkM44.h:56
SkScalar length() const
Definition: SkM44.h:88