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.
20 #include "dali/dali.h"
21 #include "dali/public-api/actors/actor.h"
22 #include "dali/public-api/rendering/renderer.h"
28 //=============================================================================
29 // Demonstrates deferred shading with multiple render targets (for color,
30 // position, and normal), a Phong lighting model and 32 point lights.
32 // Invoked with the --show-lights it will render a mesh at each light position.
33 //=============================================================================
35 #define QUOTE(x) DALI_COMPOSE_SHADER(x)
39 #define DEFINE_MAX_LIGHTS "const int kMaxLights = " QUOTE(MAX_LIGHTS) ";"
41 #define DEFINE(x) "#define " DALI_COMPOSE_SHADER(x) DALI_COMPOSE_SHADER(\n)
45 //=============================================================================
47 //=============================================================================
48 const char* const PREPASS_VSH = DALI_COMPOSE_SHADER(#version 300 es\n
49 precision mediump float;)
53 uniform mat4 uMvpMatrix;
54 uniform mat3 uNormalMatrix;
57 uniform vec3 uDepth_InvDepth_Near;\n)
58 DEFINE(DEPTH uDepth_InvDepth_Near.x)
59 DEFINE(INV_DEPTH uDepth_InvDepth_Near.y)
60 DEFINE(NEAR uDepth_InvDepth_Near.z)
69 vec4 Map(vec4 v) // projection space -> texture
71 return vec4(v.xyz / (2.f * v.w) + vec3(.5f), (v.w - NEAR) * INV_DEPTH);
76 vec4 position = uMvpMatrix * vec4(aPosition * uSize, 1.f);
77 vPosition = Map(position);
78 gl_Position = position;
80 vNormal = normalize(uNormalMatrix * aNormal);
83 //=============================================================================
84 const char* const PREPASS_FSH = DALI_COMPOSE_SHADER(#version 300 es\n
85 precision mediump float;
93 // These are our outputs.
94 layout(location = 0) out vec3 oNormal;
95 layout(location = 1) out vec4 oPosition;
96 layout(location = 2) out vec3 oColor;
101 oPosition = vPosition;
102 oNormal = normalize(vNormal) * .5f + .5f;
105 //=============================================================================
106 // MAIN (LIGHTING) PASS
107 //=============================================================================
108 const char* const MAINPASS_VSH = DALI_COMPOSE_SHADER(#version 300 es\n
109 precision mediump float;
112 uniform mat4 uMvpMatrix;
122 vec4 position = uMvpMatrix * vec4(aPosition * uSize, 1.f);
125 gl_Position = position;
128 //=============================================================================
129 const char* const MAINPASS_FSH = DALI_COMPOSE_SHADER(#version 300 es\n
130 precision mediump float;\n)
134 const float kAttenuationConst = .05f;
135 const float kAttenuationLinear = .1f;
136 const float kAttenuationQuadratic = .15f;
139 uniform sampler2D uTextureNormal;
140 uniform sampler2D uTexturePosition;
141 uniform sampler2D uTextureColor;
143 uniform mat4 uInvProjection;
145 uniform vec3 uDepth_InvDepth_Near;\n)
146 DEFINE(DEPTH uDepth_InvDepth_Near.x)
147 DEFINE(INV_DEPTH uDepth_InvDepth_Near.y)
148 DEFINE(NEAR uDepth_InvDepth_Near.z)
151 // Light source uniforms
154 vec3 position; // view space
159 uniform Light uLights[kMaxLights];
165 vec4 Unmap(vec4 m) // texture -> projection
167 m.w = m.w * DEPTH + NEAR;
168 m.xyz = (m.xyz - vec3(.5)) * (2.f * m.w);
172 vec3 CalculateLighting(vec3 pos, vec3 normal)
174 vec3 viewDir = normalize(pos);
175 vec3 viewDirRefl = -reflect(viewDir, normal);
177 vec3 light = vec3(0.04f); // fake ambient term
178 for (int i = 0; i < kMaxLights; ++i)
180 vec3 rel = pos - uLights[i].position;
181 float distance = length(rel);
184 float a = uLights[i].radius / (kAttenuationConst + kAttenuationLinear * distance +
185 kAttenuationQuadratic * distance * distance); // attenuation
187 float l = max(0.f, dot(normal, rel)); // lambertian
188 float s = pow(max(0.f, dot(viewDirRefl, rel)), 256.f); // specular
190 light += (uLights[i].color * (l + s)) * a;
198 vec3 normSample = texture(uTextureNormal, vUv).xyz;
199 if (dot(normSample, normSample) == 0.f)
201 discard; // if we didn't write this texel, don't bother lighting it.
204 vec3 normal = normalize(normSample - .5f);
206 vec4 posSample = texture(uTexturePosition, vUv);
207 vec3 pos = (uInvProjection * Unmap(posSample)).xyz;
209 vec3 color = texture(uTextureColor, vUv).rgb;
210 vec3 finalColor = color * CalculateLighting(pos, normal);
212 oColor = vec4(finalColor, 1.f);
216 //=============================================================================
220 std::random_device mDevice;
221 std::mt19937 mMersenneTwister;
222 std::uniform_real_distribution<float> mDistribution;
225 : mMersenneTwister(mDevice()),
226 mDistribution(0., 1.)
232 return mDistribution(mMersenneTwister);
236 //=============================================================================
237 float FastFloor(float x)
239 return static_cast<int>(x) - static_cast<int>(x < 0);
242 //=============================================================================
243 Vector3 FromHueSaturationLightness(Vector3 hsl)
246 if(hsl.y * hsl.y > 0.f)
254 int i = FastFloor(hsl.x);
255 float ff = hsl.x - i;
256 float p = hsl.z * (1.0 - hsl.y);
257 float q = hsl.z * (1.0 - (hsl.y * ff));
258 float t = hsl.z * (1.0 - (hsl.y * (1.f - ff)));
302 rgb = Vector3::ONE * hsl.z;
308 //=============================================================================
309 Geometry CreateTexturedQuadGeometry(bool flipV)
311 // Create geometry -- unit square with whole of the texture mapped to it.
318 Vertex vertexData[] = {
319 {Vector3(-.5f, .5f, .0f), Vector2(.0f, 1.0f)},
320 {Vector3(.5f, .5f, .0f), Vector2(1.0f, 1.0f)},
321 {Vector3(-.5f, -.5f, .0f), Vector2(.0f, .0f)},
322 {Vector3(.5f, -.5f, .0f), Vector2(1.0f, .0f)},
327 std::swap(vertexData[0].aTexCoord, vertexData[2].aTexCoord);
328 std::swap(vertexData[1].aTexCoord, vertexData[3].aTexCoord);
331 VertexBuffer vertexBuffer = VertexBuffer::New(Property::Map()
332 .Add("aPosition", Property::VECTOR3)
333 .Add("aTexCoord", Property::VECTOR2));
334 vertexBuffer.SetData(vertexData, std::extent<decltype(vertexData)>::value);
336 Geometry geometry = Geometry::New();
337 geometry.AddVertexBuffer(vertexBuffer);
338 geometry.SetType(Geometry::TRIANGLE_STRIP);
342 //=============================================================================
343 Geometry CreateOctahedron(bool invertNormals)
345 Vector3 positions[] = {
346 Vector3{-1.f, 0.f, 0.f},
347 Vector3{1.f, 0.f, 0.f},
348 Vector3{0.f, -1.f, 0.f},
349 Vector3{0.f, 1.f, 0.f},
350 Vector3{0.f, 0.f, -1.f},
351 Vector3{0.f, 0.f, 1.f},
359 Vertex vertexData[] = {
394 for(uint32_t i = 0; i < std::extent<decltype(vertexData)>::value / 3; ++i)
396 uint32_t idx = i * 3;
398 Vector3 normal = (vertexData[idx + 2].position - vertexData[idx].position).Cross(vertexData[idx + 1].position - vertexData[idx].position);
400 normal *= invertNormals * 2.f - 1.f;
402 vertexData[idx++].normal = normal;
403 vertexData[idx++].normal = normal;
404 vertexData[idx].normal = normal;
407 // Configure property buffers and create geometry.
408 VertexBuffer vertexBuffer = VertexBuffer::New(Property::Map()
409 .Add("aPosition", Property::VECTOR3)
410 .Add("aNormal", Property::VECTOR3));
411 vertexBuffer.SetData(vertexData, std::extent<decltype(vertexData)>::value);
413 Geometry geometry = Geometry::New();
414 geometry.AddVertexBuffer(vertexBuffer);
415 geometry.SetType(Geometry::TRIANGLES);
419 //=============================================================================
424 OPTION_DEPTH_TEST = 0x02,
425 OPTION_DEPTH_WRITE = 0x04
428 Renderer CreateRenderer(TextureSet textures, Geometry geometry, Shader shader, uint32_t options = OPTION_NONE)
430 Renderer renderer = Renderer::New(geometry, shader);
431 renderer.SetProperty(Renderer::Property::BLEND_MODE,
432 (options & OPTION_BLEND) ? BlendMode::ON : BlendMode::OFF);
433 renderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE,
434 (options & OPTION_DEPTH_TEST) ? DepthTestMode::ON : DepthTestMode::OFF);
435 renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE,
436 (options & OPTION_DEPTH_WRITE) ? DepthWriteMode::ON : DepthWriteMode::OFF);
437 renderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK);
441 textures = TextureSet::New();
444 renderer.SetTextures(textures);
448 //=============================================================================
449 void CenterActor(Actor actor)
451 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
452 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
455 //=============================================================================
456 void RegisterDepthProperties(float depth, float near, Handle& h)
458 h.RegisterProperty("uDepth_InvDepth_Near", Vector3(depth, 1.f / depth, near));
463 //=============================================================================
464 /// Create a String whose size can be evaluated at compile time
465 struct ConstantString
467 const char* const string;
470 template<uint16_t inputSize>
471 constexpr ConstantString(const char (&input)[inputSize])
478 constexpr ConstantString POSITION_STRING("position");
479 constexpr ConstantString RADIUS_STRING("radius");
480 constexpr ConstantString COLOR_STRING("color");
481 constexpr uint16_t LIGHT_SOURCE_BUFFER_SIZE(128u);
483 //=============================================================================
484 class DeferredShadingExample : public ConnectionTracker
496 DeferredShadingExample(Application& app, uint32_t options = Options::NONE)
500 app.InitSignal().Connect(this, &DeferredShadingExample::Create);
501 app.TerminateSignal().Connect(this, &DeferredShadingExample::Destroy);
505 void Create(Application& app)
507 // Grab window, configure layer
508 Window window = app.GetWindow();
509 auto rootLayer = window.GetRootLayer();
510 rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
512 Vector2 windowSize = window.GetSize();
514 float unit = windowSize.y / 24.f;
516 // Get camera - we'll be re-using the same old camera in the two passes.
517 RenderTaskList tasks = window.GetRenderTaskList();
518 CameraActor camera = tasks.GetTask(0).GetCameraActor();
520 auto zCameraPos = camera.GetProperty(Actor::Property::POSITION_Z).Get<float>();
521 camera.SetFarClippingPlane(zCameraPos + windowSize.y * .5f);
522 camera.SetNearClippingPlane(zCameraPos - windowSize.y * .5f);
524 const float zNear = camera.GetNearClippingPlane();
525 const float zFar = camera.GetFarClippingPlane();
526 const float depth = zFar - zNear;
528 // Create root of scene that shall be rendered off-screen.
529 auto sceneRoot = Actor::New();
530 CenterActor(sceneRoot);
532 mSceneRoot = sceneRoot;
533 window.Add(sceneRoot);
535 // Create an axis to spin our actors around.
536 auto axis = Actor::New();
541 // Create an octahedral mesh for our main actors and to visualise the light sources.
542 Geometry mesh = CreateOctahedron(false);
544 // Create main actors
545 Shader preShader = Shader::New(PREPASS_VSH, PREPASS_FSH);
546 TextureSet noTexturesThanks = TextureSet::New();
547 Renderer meshRenderer = CreateRenderer(noTexturesThanks, mesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
548 meshRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK);
549 RegisterDepthProperties(depth, zNear, meshRenderer);
561 Vector3{0.f, -c, -c},
566 Vector3{-c, 0.f, -c},
571 Vector3{-c, -c, 0.f},
577 Actor a = Actor::New();
580 Vector3 position{v * unit * 5.f};
581 a.SetProperty(Actor::Property::POSITION, position);
583 float scale = (c + ((v.x + v.y + v.z) + c * 3.f) * .5f) / (c * 4.f);
584 Vector3 size{Vector3::ONE * scale * unit * 2.f};
585 a.SetProperty(Actor::Property::SIZE, size);
587 a.SetProperty(Actor::Property::COLOR, Color::WHITE * .25f + (Color::RED * (v.x + c) / (c * 2.f) + Color::GREEN * (v.y + c) / (c * 2.f) + Color::BLUE * (v.z + c) / (c * 2.f)) * .015625f);
588 a.AddRenderer(meshRenderer);
593 // Create off-screen textures, fbo and render task.
594 uint32_t width = static_cast<uint32_t>(windowSize.x);
595 uint32_t height = static_cast<uint32_t>(windowSize.y);
597 Texture rttNormal = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height);
598 Texture rttPosition = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGBA8888, width, height);
599 Texture rttColor = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height);
600 FrameBuffer fbo = FrameBuffer::New(width, height, FrameBuffer::Attachment::DEPTH);
601 fbo.AttachColorTexture(rttNormal);
602 fbo.AttachColorTexture(rttPosition);
603 fbo.AttachColorTexture(rttColor);
605 RenderTask sceneRender = tasks.CreateTask();
606 sceneRender.SetViewportSize(windowSize);
607 sceneRender.SetFrameBuffer(fbo);
608 sceneRender.SetCameraActor(camera);
609 sceneRender.SetSourceActor(sceneRoot);
610 sceneRender.SetInputEnabled(false);
611 sceneRender.SetCullMode(false);
612 sceneRender.SetClearEnabled(true);
613 sceneRender.SetClearColor(Color::BLACK);
614 sceneRender.SetExclusive(true);
616 mSceneRender = sceneRender;
618 // Create final image for deferred shading
619 auto finalImage = Actor::New();
620 CenterActor(finalImage);
621 finalImage.SetProperty(Actor::Property::SIZE, windowSize);
623 TextureSet finalImageTextures = TextureSet::New();
624 finalImageTextures.SetTexture(0, rttNormal);
625 finalImageTextures.SetTexture(1, rttPosition);
626 finalImageTextures.SetTexture(2, rttColor);
628 Sampler sampler = Sampler::New();
629 sampler.SetFilterMode(FilterMode::NEAREST, FilterMode::NEAREST);
630 finalImageTextures.SetSampler(0, sampler);
631 finalImageTextures.SetSampler(1, sampler);
632 finalImageTextures.SetSampler(2, sampler);
634 Shader shdMain = Shader::New(MAINPASS_VSH, MAINPASS_FSH);
635 Geometry finalImageGeom = CreateTexturedQuadGeometry(true);
636 Renderer finalImageRenderer = CreateRenderer(finalImageTextures, finalImageGeom, shdMain);
637 RegisterDepthProperties(depth, zNear, finalImageRenderer);
639 auto propInvProjection = finalImageRenderer.RegisterProperty("uInvProjection", Matrix::IDENTITY);
640 Constraint cnstrInvProjection = Constraint::New<Matrix>(finalImageRenderer, propInvProjection, [zCameraPos, zNear, depth](Matrix& output, const PropertyInputContainer& input) {
641 output = input[0]->GetMatrix();
642 DALI_ASSERT_ALWAYS(output.Invert() && "Failed to invert projection matrix.");
644 cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::PROJECTION_MATRIX));
645 cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX));
646 cnstrInvProjection.Apply();
648 finalImage.AddRenderer(finalImageRenderer);
650 mFinalImage = finalImage;
651 window.Add(finalImage);
653 // Create a node for our lights
654 auto lights = Actor::New();
656 sceneRoot.Add(lights);
659 const bool showLights = mOptions & Options::SHOW_LIGHTS;
660 Renderer lightRenderer;
663 Geometry lightMesh = CreateOctahedron(true);
664 lightRenderer = CreateRenderer(noTexturesThanks, lightMesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
665 lightRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::FRONT);
668 Vector3 lightPos{unit * 12.f, 0.f, 0.f};
669 float theta = M_PI * 2.f / MAX_LIGHTS;
670 float cosTheta = std::cos(theta);
671 float sinTheta = std::sin(theta);
672 for(int i = 0; i < MAX_LIGHTS; ++i)
674 Vector3 color = FromHueSaturationLightness(Vector3((360.f * i) / MAX_LIGHTS, .5f, 1.f));
676 Actor light = CreateLight(lightPos * (1 + (i % 8)) / 8.f, unit * 16.f, color, camera, finalImageRenderer);
678 float z = (((i & 1) << 1) - 1) * unit * 8.f;
679 lightPos = Vector3(cosTheta * lightPos.x - sinTheta * lightPos.y, sinTheta * lightPos.x + cosTheta * lightPos.y, z);
683 light.SetProperty(Actor::Property::SIZE, Vector3::ONE * unit / 8.f);
684 light.AddRenderer(lightRenderer);
690 // Take them for a spin.
691 Animation animLights = Animation::New(40.f);
692 animLights.SetLooping(true);
693 animLights.AnimateBy(Property(lights, Actor::Property::ORIENTATION), Quaternion(Radian(M_PI * 2.f), Vector3::YAXIS));
697 window.KeyEventSignal().Connect(this, &DeferredShadingExample::OnKeyEvent);
699 mPanDetector = PanGestureDetector::New();
700 mPanDetector.DetectedSignal().Connect(this, &DeferredShadingExample::OnPan);
701 mPanDetector.Attach(window.GetRootLayer());
704 void Destroy(Application& app)
706 app.GetWindow().GetRenderTaskList().RemoveTask(mSceneRender);
707 mSceneRender.Reset();
709 UnparentAndReset(mSceneRoot);
710 UnparentAndReset(mFinalImage);
713 Actor CreateLight(Vector3 position, float radius, Vector3 color, CameraActor camera, Renderer renderer)
715 Actor light = Actor::New();
717 light.SetProperty(Actor::Property::COLOR, Color::WHITE);
718 light.SetProperty(Actor::Property::POSITION, position);
720 auto iPropRadius = light.RegisterProperty("radius", radius);
721 auto iPropLightColor = light.RegisterProperty("lightcolor", color);
723 // Create light source uniforms on lighting shader.
724 char buffer[LIGHT_SOURCE_BUFFER_SIZE];
725 char* writep = buffer + snprintf(buffer, LIGHT_SOURCE_BUFFER_SIZE, "uLights[%d].", mNumLights);
728 strncpy(writep, POSITION_STRING.string, POSITION_STRING.size);
729 auto oPropLightPos = renderer.RegisterProperty(buffer, position);
731 strncpy(writep, RADIUS_STRING.string, RADIUS_STRING.size);
732 auto oPropLightRadius = renderer.RegisterProperty(buffer, radius);
734 strncpy(writep, COLOR_STRING.string, COLOR_STRING.size);
735 auto oPropLightColor = renderer.RegisterProperty(buffer, color);
737 // Constrain the light position, radius and color to lighting shader uniforms.
738 // Convert light position to view space;
739 Constraint cLightPos = Constraint::New<Vector3>(renderer, oPropLightPos, [](Vector3& output, const PropertyInputContainer& input) {
740 Vector4 worldPos(input[0]->GetVector3());
743 worldPos = input[1]->GetMatrix() * worldPos;
744 output = Vector3(worldPos);
746 cLightPos.AddSource(Source(light, Actor::Property::WORLD_POSITION));
747 cLightPos.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX));
750 Constraint cLightRadius = Constraint::New<float>(renderer, oPropLightRadius, EqualToConstraint());
751 cLightRadius.AddSource(Source(light, iPropRadius));
752 cLightRadius.Apply();
754 Constraint cLightColor = Constraint::New<Vector3>(renderer, oPropLightColor, EqualToConstraint());
755 cLightColor.AddSource(Source(light, iPropLightColor));
761 void OnPan(Actor, PanGesture const& gesture)
763 Quaternion q = mAxis.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
764 const Vector2& displacement = gesture.GetScreenDisplacement();
765 Quaternion qx(Radian(Degree(displacement.y) * -.5f), Vector3::XAXIS);
766 Quaternion qy(Radian(Degree(displacement.x) * .5f), Vector3::YAXIS);
767 mAxis.SetProperty(Actor::Property::ORIENTATION, qy * qx * q);
770 void OnKeyEvent(const KeyEvent& event)
772 if(event.GetState() == KeyEvent::DOWN)
774 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
787 RenderTask mSceneRender;
792 PanGestureDetector mPanDetector;
795 int DALI_EXPORT_API main(int argc, char** argv)
797 const bool showLights = [](int argc, char** argv) {
798 auto endArgs = argv + argc;
799 return std::find_if(argv, endArgs, [](const char* arg) {
800 return strcmp(arg, "--show-lights") == 0;
804 Application app = Application::New(&argc, &argv);
805 DeferredShadingExample example(app, (showLights ? DeferredShadingExample::Options::SHOW_LIGHTS : 0));