2 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "particle-view.h"
19 #include "dali/public-api/animation/constraints.h"
21 //#define ENABLE_DEBUG_VOLUME
23 #define USE_GLSL_VERSION(version) "#version " #version "\n"
30 const uint32_t POPULATION_GRANULARITY = 128;
32 ///@brief Shader for billboarded particles, where the vertices of the particles
33 /// are supplied as vec3 position (particle position) + vec2 sub-position.
34 const char* const PARTICLES_VSH = USE_GLSL_VERSION(300 es)
37 uniform mat4 uModelView; // DALi
38 uniform mat4 uProjection; // DALi
39 uniform vec3 uSize; // DALi
40 uniform vec4 uColor; // DALi
42 uniform vec3 uSecondaryColor;
43 uniform vec2 uDepthRange; // x is zNear, y is 1.f / (zFar - zNear)
44 uniform float uTwinkleFrequency;
45 uniform float uTwinkleSizeScale;
46 uniform float uTwinkleOpacityWeight;
48 uniform float uFocalLength;
49 uniform float uAperture;
50 uniform float uPopulation;
59 const int SCATTER_VARS = 6; // Must match ParticleView::mScatterProps' size.
60 uniform Scatter uScatter[SCATTER_VARS];
62 const int POPULATION_GRANULARITY = 128;
63 uniform float uOrderLookUp[POPULATION_GRANULARITY];
71 flat out float vDepth;
72 flat out float vFocalDistance;
74 flat out float vOpacity;
75 flat out vec3 vColor; // ignore alpha
77 float bezier(vec3 control, float alpha)
79 return mix(mix(control.x, control.y, alpha), mix(control.y, control.z, alpha), alpha);
83 // Get random order from the look-up table, based on particle ID.
84 int particleId = gl_VertexID / 6;
85 float order = uOrderLookUp[particleId & (POPULATION_GRANULARITY - 1)];
88 float twinkle = sin(uTime * floor(uTwinkleFrequency * aSeed) + fract(aSeed * 1.17137));
91 float s = sin(uTime + aSeed) * .5f + .5f; // different phase for all
92 // NOTE: you'd think that taking the bezier() calls apart would save 4 mix() calls, since
93 // the mix()es (of xy / yz / zw / wx) are all calculated twice. It turns out that the MALI
94 // compiler is already doing this; leaving it as is for readability.
95 float bx0 = bezier(aPath.xyz, s);
96 float bx1 = bezier(aPath.zwx, s);
97 float by0 = bezier(aPath.yzw, s);
98 float by1 = bezier(aPath.wxy, s);
99 vec3 motion = vec3(mix(bx0, bx1, s), mix(by0, by1, s), 0.f);
101 // Model to view position
102 vec3 position3 = aPosition * uSize + motion;
104 vec4 position = uModelView * vec4(position3, 1.f);
106 // Add scatter - calculated in view space, using view ray
107 vec3 normalizedPos = position.xyz / uSize;
108 for (int i = 0; i < SCATTER_VARS; ++i)
110 vec2 scatterDist = (normalizedPos - uScatter[i].ray * dot(uScatter[i].ray, normalizedPos)).xy;
112 // NOTE: replacing the division with a multiplication (by inverse) oddly results in more instructions (MALI).
113 float scatter = max(0.f, uScatter[i].radiusSqr - dot(scatterDist, scatterDist)) *
114 uScatter[i].amount / aSize;
115 position.xy += scatter * normalize(scatterDist) * uSize.xy;
118 // Calculate normalised depth and distance from focal plane
119 float depth = (position.z - uDepthRange.x) * uDepthRange.y;
122 float focalDist = (uFocalLength - depth) * uAperture;
123 focalDist *= focalDist;
124 vFocalDistance = max(focalDist, 1e-6f); // NOTE: was clamp(..., 1.f); side effect: out of focus particles get squashed at higher aperture values.
126 // Calculate expiring scale - for size and opacity.
127 float expiringScale = smoothstep(order + 1.f, order, uPopulation);
129 // Calculate billboard position and size
130 vec2 subPosition = aSubPosition * aSize *
131 (1.f + twinkle * aSeed * uTwinkleSizeScale) *
134 // Insist on hacking the size? Do it here...
135 float sizeHack = depth + .5f;
136 // NOTE: sizeHack *= sizeHack looked slightly better.
137 subPosition *= sizeHack;
139 vec3 subPositionView = vec3(subPosition, 0.);
141 // Add billboards to view position.
142 position += vec4(subPositionView, 0.f);
144 // subPosition doubles as normalized (-1..1) UV.
145 vUvUnit = aSubPosition;
147 // Vary opacity (actor alpha) by time as well as expiring scale.
148 vOpacity = uColor.a * expiringScale *
149 (1.0f + aSeed + twinkle * uTwinkleOpacityWeight) / (2.0f + uTwinkleOpacityWeight);
151 // Randomize RGB using seed.
152 vec3 mixColor = vec3(fract(aSeed), fract(aSeed * 16.f), fract(aSeed * 256.f));
153 vColor = mix(uColor.rgb, uSecondaryColor, mixColor);
155 gl_Position = uProjection * position;
158 ///@brief Fragment shader for particles, which simulates depth of field
159 /// using a combination of procedural texturing, alpha testing and alpha
161 const char* const PARTICLES_FSH = USE_GLSL_VERSION(300 es)
163 precision lowp float;
164 uniform float uAlphaTestRefValue;
165 uniform vec2 uFadeRange; // near, far
167 flat in float vDepth;
168 flat in float vFocalDistance;
169 flat in float vOpacity;
173 const float REF_VALUE_THRESHOLD = 1. / 64.;
176 // Softened disc pattern from normalized UVs
177 float value = 1.f - dot(vUvUnit, vUvUnit);
179 // Decrease area of particles 'in-focus'.
180 float refValue = (1.f - vFocalDistance) * .5f;
181 float threshold = REF_VALUE_THRESHOLD * (1.f + vDepth);
182 float alpha = pow(value, vFocalDistance) * smoothstep(refValue - threshold, refValue + threshold, value);
183 if (alpha < uAlphaTestRefValue)
192 // Fade particles out as they get close to the near and far clipping planes
193 alpha *= smoothstep(.0f, uFadeRange.x, vDepth) * smoothstep(1.f, uFadeRange.y, vDepth);
195 oFragColor = vec4(vColor, alpha);
198 ///@brief Shader for simple textured geometry.
199 const char* const SIMPLE_VSH = USE_GLSL_VERSION(300 es)
201 precision mediump float;
202 uniform mat4 uMvpMatrix;//by DALi
203 uniform vec3 uSize; // by DALi
206 gl_Position = uMvpMatrix * vec4(aPosition * uSize, 1.f);
209 ///@brief Shader for an unlit, unfogged, textured mesh.
210 const char* const SIMPLE_FSH = USE_GLSL_VERSION(300 es)
212 precision mediump float;
221 uint32_t GetSkipValue(uint32_t count, uint32_t prime)
226 skip = (rand() % prime) * count * count + (rand() % prime) * count + (rand() % prime);
228 while (skip % prime == 0);
234 ParticleView::ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera,
235 Dali::Geometry particleGeom)
237 mParticleBoxSize(field.mBoxSize)
242 particleGeom = field.MakeGeometry();
246 Shader particleShader = Shader::New(PARTICLES_VSH, PARTICLES_FSH, Shader::Hint::MODIFIES_GEOMETRY);
248 float zNear = camera.GetNearClippingPlane();
249 float zFar = camera.GetFarClippingPlane();
250 const Vector2 depthRange(zNear, 1.f / (zFar - zNear));
251 particleShader.RegisterProperty("uDepthRange", depthRange);
253 particleShader.RegisterProperty("uTwinkleFrequency", field.mTwinkleFrequency);
254 particleShader.RegisterProperty("uTwinkleSizeScale", field.mTwinkleSizeScale);
255 particleShader.RegisterProperty("uTwinkleOpacityWeight", field.mTwinkleOpacityWeight);
257 mPropPopulation = particleShader.RegisterProperty("uPopulation", 1.f);
258 mPropFocalLength = particleShader.RegisterProperty("uFocalLength", .5f);
259 mPropAperture = particleShader.RegisterProperty("uAperture", 8.f);
260 mPropAlphaTestRefValue = particleShader.RegisterProperty("uAlphaTestRefValue", 0.f);
261 mPropFadeRange = particleShader.RegisterProperty("uFadeRange", Vector2(0.f, 1.f));
265 char* writep = nameBuffer + snprintf(nameBuffer, sizeof(nameBuffer), "uScatter[");
266 for (uint32_t i = 0; i < std::extent<decltype(mScatterProps)>::value; ++i)
268 char* writep2 = writep + snprintf(writep, sizeof(nameBuffer) - std::distance(nameBuffer, writep), "%d].", i);
270 snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "radiusSqr");
271 mScatterProps[i].mPropRadius = particleShader.RegisterProperty(nameBuffer, 0.f);
273 snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "amount");
274 mScatterProps[i].mPropAmount = particleShader.RegisterProperty(nameBuffer, 0.f);
276 snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "ray");
277 mScatterProps[i].mPropRay = particleShader.RegisterProperty(nameBuffer, Vector3::ZERO);
280 // Create a look-up table for pseudo-random traversal of particles.
281 // Our particle mesh is sorted in Z; changing the population should remove
282 // particles "randomly", not from one end.
283 // Algorithm described in Mike McShaffry & al: Game Coding Complete.
284 const uint32_t prime = 131; // next prime after POPULATION_GRANULARITY
285 const uint32_t skip = GetSkipValue(POPULATION_GRANULARITY, prime);
288 writep = nameBuffer + snprintf(nameBuffer, sizeof(nameBuffer), "uOrderLookUp[");
289 for (uint32_t i = 0; i < POPULATION_GRANULARITY; ++i)
295 while (next == 0 || next > POPULATION_GRANULARITY);
297 snprintf(writep, sizeof(nameBuffer) - std::distance(nameBuffer, writep), "%d]", i);
298 particleShader.RegisterProperty(nameBuffer, float(next - 1));
301 // create animation for time in shader
302 auto propTime = particleShader.RegisterProperty("uTime", 0.f);
304 Animation animTime = Animation::New(field.mMotionCycleLength);
305 animTime.AnimateTo(Property(particleShader, propTime), static_cast<float>(M_PI * 2.f));
306 animTime.SetLoopCount(0);
309 mParticleShader = particleShader;
311 auto renderer = CreateRenderer(TextureSet::New(), particleGeom, particleShader, OPTION_BLEND);
312 auto masterParticles = CreateActor();
313 masterParticles.SetProperty(Actor::Property::SIZE, field.mBoxSize);
314 masterParticles.SetProperty(Actor::Property::VISIBLE, true);
315 masterParticles.AddRenderer(renderer);
317 mPropSecondaryColor = masterParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
319 #ifdef ENABLE_DEBUG_VOLUME
320 Geometry cubeGeom = CreateCuboidWireframeGeometry();
321 renderer = CreateRenderer(renderer.GetTextures(), cubeGeom, Shader::New(SIMPLE_VSH, SIMPLE_FSH));
322 masterParticles.AddRenderer(renderer);
325 world.Add(masterParticles);
326 mMasterParticles = masterParticles;
329 ParticleView::~ParticleView()
331 UnparentAndReset(mMasterParticles);
332 UnparentAndReset(mSlaveParticles);
334 for (auto anim: { mAngularAnim, mLinearAnim })
343 for (auto& s: mScatterProps)
345 auto& anim = s.mAnim;
354 void ParticleView::SetColorRange(const ColorRange& range)
356 mMasterParticles.SetProperty(Actor::Property::COLOR_RED, range.rgb0.r);
357 mMasterParticles.SetProperty(Actor::Property::COLOR_GREEN, range.rgb0.g);
358 mMasterParticles.SetProperty(Actor::Property::COLOR_BLUE, range.rgb0.b);
360 mMasterParticles.SetProperty(mPropSecondaryColor, range.rgb1);
363 void ParticleView::SetPopulation(float percentage)
365 percentage = 1.f - std::min(1.f, std::max(0.f, percentage));
366 mParticleShader.SetProperty(mPropPopulation, POPULATION_GRANULARITY * percentage);
369 void ParticleView::SetFocalLength(float f)
371 mParticleShader.SetProperty(mPropFocalLength, f);
374 void ParticleView::SetAperture(float a)
376 mParticleShader.SetProperty(mPropAperture, a);
379 void ParticleView::SetAlphaTestRefValue(float rv)
381 mParticleShader.SetProperty(mPropAlphaTestRefValue, rv);
384 void ParticleView::SetFadeRange(float near, float far)
386 mParticleShader.SetProperty(mPropFadeRange, Vector2(near, far));
389 void ParticleView::SetAngularVelocity(float v)
394 mAngularAnim.Clear();
395 mAngularAnim.Reset();
400 float sign = Sign(v);
401 auto anim = Animation::New(std::abs(2. * M_PI / v));
402 anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
403 Quaternion(Radian(Degree(120. * sign)), Vector3::ZAXIS), TimePeriod(0., anim.GetDuration() / 3.));
404 anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
405 Quaternion(Radian(Degree(240. * sign)), Vector3::ZAXIS), TimePeriod(anim.GetDuration() / 3., anim.GetDuration() / 3.));
406 anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
407 Quaternion(Radian(Degree(360. * sign)), Vector3::ZAXIS), TimePeriod(2. * anim.GetDuration() / 3., anim.GetDuration() / 3.));
408 anim.SetLoopCount(0);
415 void ParticleView::SetLinearVelocity(float v)
423 UnparentAndReset(mSlaveParticles);
427 float sign = Sign(v);
428 float directedSize = sign * mParticleBoxSize.z;
430 Actor slaveParticles = CloneActor(mMasterParticles);
431 Vector3 position = mMasterParticles.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>();
432 slaveParticles.SetProperty(Actor::Property::POSITION, position + Vector3(0., 0., directedSize));
434 auto propSecondaryColor = slaveParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
436 Actor world = mWorld.GetHandle();
437 world.Add(slaveParticles);
439 if (sign < 0.) // fix draw order
441 world.Remove(mMasterParticles);
442 world.Add(mMasterParticles);
445 Constraint constraint = Constraint::New<Vector4>(slaveParticles, Actor::Property::COLOR,
446 EqualToConstraint());
447 constraint.AddSource(Source(mMasterParticles, Actor::Property::COLOR));
450 constraint = Constraint::New<Vector3>(slaveParticles, propSecondaryColor,
451 EqualToConstraint());
452 constraint.AddSource(Source(mMasterParticles, mPropSecondaryColor));
455 constraint = Constraint::New<Quaternion>(slaveParticles, Actor::Property::ORIENTATION,
456 EqualToConstraint());
457 constraint.AddSource(Source(mMasterParticles, Actor::Property::ORIENTATION));
460 auto anim = Animation::New(std::abs(directedSize / v));
461 anim.AnimateTo(Property(mMasterParticles, Actor::Property::POSITION_Z), position.z - directedSize);
462 anim.AnimateTo(Property(slaveParticles, Actor::Property::POSITION_Z), position.z);
463 anim.SetLoopCount(0);
467 mSlaveParticles = slaveParticles;
471 void ParticleView::Scatter(float radius, float amount, float durationOut, float durationIn)
473 mActiveScatter = (mActiveScatter + 1) % std::extent<decltype(mScatterProps)>::value;
475 auto& scatter = mScatterProps[mActiveScatter];
478 scatter.mAnim.Stop();
481 radius /= mParticleBoxSize.y;
483 mParticleShader.SetProperty(scatter.mPropRadius, radius);
485 Animation anim = Animation::New(durationOut + durationIn);
486 auto scatterAmount = Property(mParticleShader, scatter.mPropAmount);
487 anim.AnimateTo(scatterAmount, amount, AlphaFunction::EASE_OUT,
488 TimePeriod(0.f, durationOut));
489 anim.AnimateTo(scatterAmount, 0.f, AlphaFunction::EASE_IN_OUT_SINE,
490 TimePeriod(durationOut, durationIn));
493 scatter.mAnim = anim;
496 void ParticleView::SetScatterRay(Dali::Vector3 rayDir)
498 auto& scatter = mScatterProps[mActiveScatter];
499 mParticleShader.SetProperty(scatter.mPropRay, rayDir);;
502 void ParticleView::Fade(float duration, float target, AlphaFunction alphaFn,
503 std::function<void(Dali::Animation&)> onFinished)
510 Animation anim = Animation::New(duration);
511 anim.AnimateTo(Property(mMasterParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
514 anim.AnimateTo(Property(mSlaveParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
519 anim.FinishedSignal().Connect(this, onFinished);
526 void ParticleView::Fade(float duration, float target, float from, AlphaFunction alphaFn,
527 std::function<void(Dali::Animation&)> onFinished)
529 mMasterParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
532 mSlaveParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
535 Fade(duration, target, alphaFn, onFinished);