/* * Copyright (c) 2020 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "dali/dali.h" #include "dali/public-api/actors/actor.h" #include "dali/public-api/rendering/renderer.h" #include #include #include using namespace Dali; namespace { //============================================================================= // Demonstrates deferred shading with multiple render targets (for color, // position, and normal), a Phong lighting model and 32 point lights. // // Invoked with the --show-lights it will render a mesh at each light position. //============================================================================= #define QUOTE(x) DALI_COMPOSE_SHADER(x) #define MAX_LIGHTS 32 #define DEFINE_MAX_LIGHTS "const int kMaxLights = " QUOTE(MAX_LIGHTS) ";" #define DEFINE(x) "#define " DALI_COMPOSE_SHADER(x) DALI_COMPOSE_SHADER(\n) //============================================================================= // PRE-PASS //============================================================================= const char* const PREPASS_VSH = DALI_COMPOSE_SHADER(#version 300 es\n precision mediump float;) DALI_COMPOSE_SHADER( // DALI uniforms uniform mat4 uMvpMatrix; uniform mat3 uNormalMatrix; uniform vec3 uSize; uniform vec3 uDepth_InvDepth_Near;\n) DEFINE(DEPTH uDepth_InvDepth_Near.x) DEFINE(INV_DEPTH uDepth_InvDepth_Near.y) DEFINE(NEAR uDepth_InvDepth_Near.z) DALI_COMPOSE_SHADER( in vec3 aPosition; in vec3 aNormal; out vec4 vPosition; out vec3 vNormal; vec4 Map(vec4 v) // projection space -> texture { return vec4(v.xyz / (2.f * v.w) + vec3(.5f), (v.w - NEAR) * INV_DEPTH); } void main() { vec4 position = uMvpMatrix * vec4(aPosition * uSize, 1.f); vPosition = Map(position); gl_Position = position; vNormal = normalize(uNormalMatrix * aNormal); }); //============================================================================= const char* const PREPASS_FSH = DALI_COMPOSE_SHADER(#version 300 es\n precision mediump float; // DALI uniform uniform vec4 uColor; in vec4 vPosition; in vec3 vNormal; // These are our outputs. layout(location = 0) out vec3 oNormal; layout(location = 1) out vec4 oPosition; layout(location = 2) out vec3 oColor; void main() { oColor = uColor.rgb; oPosition = vPosition; oNormal = normalize(vNormal) * .5f + .5f; }); //============================================================================= // MAIN (LIGHTING) PASS //============================================================================= const char* const MAINPASS_VSH = DALI_COMPOSE_SHADER(#version 300 es\n precision mediump float; // DALI uniforms uniform mat4 uMvpMatrix; uniform vec3 uSize; in vec3 aPosition; in vec2 aTexCoord; out vec2 vUv; void main() { vec4 position = uMvpMatrix * vec4(aPosition * uSize, 1.f); vUv = aTexCoord; gl_Position = position; }); //============================================================================= const char* const MAINPASS_FSH = DALI_COMPOSE_SHADER(#version 300 es\n precision mediump float;\n) DEFINE_MAX_LIGHTS DALI_COMPOSE_SHADER( const float kAttenuationConst = .05f; const float kAttenuationLinear = .1f; const float kAttenuationQuadratic = .15f; // G-buffer uniform sampler2D uTextureNormal; uniform sampler2D uTexturePosition; uniform sampler2D uTextureColor; uniform mat4 uInvProjection; uniform vec3 uDepth_InvDepth_Near;\n) DEFINE(DEPTH uDepth_InvDepth_Near.x) DEFINE(INV_DEPTH uDepth_InvDepth_Near.y) DEFINE(NEAR uDepth_InvDepth_Near.z) DALI_COMPOSE_SHADER( // Light source uniforms struct Light { vec3 position; // view space float radius; vec3 color; }; uniform Light uLights[kMaxLights]; in vec2 vUv; out vec4 oColor; vec4 Unmap(vec4 m) // texture -> projection { m.w = m.w * DEPTH + NEAR; m.xyz = (m.xyz - vec3(.5)) * (2.f * m.w); return m; } vec3 CalculateLighting(vec3 pos, vec3 normal) { vec3 viewDir = normalize(pos); vec3 viewDirRefl = -reflect(viewDir, normal); vec3 light = vec3(0.04f); // fake ambient term for (int i = 0; i < kMaxLights; ++i) { vec3 rel = pos - uLights[i].position; float distance = length(rel); rel /= distance; float a = uLights[i].radius / (kAttenuationConst + kAttenuationLinear * distance + kAttenuationQuadratic * distance * distance); // attenuation float l = max(0.f, dot(normal, rel)); // lambertian float s = pow(max(0.f, dot(viewDirRefl, rel)), 256.f); // specular light += (uLights[i].color * (l + s)) * a; } return light; } void main() { vec3 normSample = texture(uTextureNormal, vUv).xyz; if (dot(normSample, normSample) == 0.f) { discard; // if we didn't write this texel, don't bother lighting it. } vec3 normal = normalize(normSample - .5f); vec4 posSample = texture(uTexturePosition, vUv); vec3 pos = (uInvProjection * Unmap(posSample)).xyz; vec3 color = texture(uTextureColor, vUv).rgb; vec3 finalColor = color * CalculateLighting(pos, normal); oColor = vec4(finalColor, 1.f); }); //============================================================================= // PRNG for floats. struct FloatRand { std::random_device mDevice; std::mt19937 mMersenneTwister; std::uniform_real_distribution mDistribution; FloatRand() : mMersenneTwister(mDevice()), mDistribution(0., 1.) {} float operator()() { return mDistribution(mMersenneTwister); } }; //============================================================================= float FastFloor(float x) { return static_cast(x) - static_cast(x < 0); } //============================================================================= Vector3 FromHueSaturationLightness(Vector3 hsl) { Vector3 rgb; if (hsl.y * hsl.y > 0.f) { if(hsl.x >= 360.f) { hsl.x -= 360.f; } hsl.x /= 60.f; int i = FastFloor(hsl.x); float ff = hsl.x - i; float p = hsl.z * (1.0 - hsl.y); float q = hsl.z * (1.0 - (hsl.y * ff)); float t = hsl.z * (1.0 - (hsl.y * (1.f - ff))); switch (i) { case 0: rgb.r = hsl.z; rgb.g = t; rgb.b = p; break; case 1: rgb.r = q; rgb.g = hsl.z; rgb.b = p; break; case 2: rgb.r = p; rgb.g = hsl.z; rgb.b = t; break; case 3: rgb.r = p; rgb.g = q; rgb.b = hsl.z; break; case 4: rgb.r = t; rgb.g = p; rgb.b = hsl.z; break; case 5: default: rgb.r = hsl.z; rgb.g = p; rgb.b = q; break; } } else { rgb = Vector3::ONE * hsl.z; } return rgb; } //============================================================================= Geometry CreateTexturedQuadGeometry(bool flipV) { // Create geometry -- unit square with whole of the texture mapped to it. struct Vertex { Vector3 aPosition; Vector2 aTexCoord; }; Vertex vertexData[] = { { Vector3(-.5f, .5f, .0f), Vector2(.0f, 1.0f) }, { Vector3(.5f, .5f, .0f), Vector2(1.0f, 1.0f) }, { Vector3(-.5f, -.5f, .0f), Vector2(.0f, .0f) }, { Vector3(.5f, -.5f, .0f), Vector2(1.0f, .0f) }, }; if (flipV) { std::swap(vertexData[0].aTexCoord, vertexData[2].aTexCoord); std::swap(vertexData[1].aTexCoord, vertexData[3].aTexCoord); } PropertyBuffer vertexBuffer = PropertyBuffer::New( Property::Map() .Add( "aPosition", Property::VECTOR3 ) .Add( "aTexCoord", Property::VECTOR2 ) ); vertexBuffer.SetData( vertexData, std::extent::value ); Geometry geometry = Geometry::New(); geometry.AddVertexBuffer( vertexBuffer ); geometry.SetType( Geometry::TRIANGLE_STRIP ); return geometry; } //============================================================================= Geometry CreateOctahedron(bool invertNormals) { Vector3 positions[] = { Vector3{ -1.f, 0.f, 0.f }, Vector3{ 1.f, 0.f, 0.f }, Vector3{ 0.f, -1.f, 0.f }, Vector3{ 0.f, 1.f, 0.f }, Vector3{ 0.f, 0.f, -1.f }, Vector3{ 0.f, 0.f, 1.f }, }; struct Vertex { Vector3 position; Vector3 normal; }; Vertex vertexData[] = { { positions[0] }, { positions[3] }, { positions[5] }, { positions[5] }, { positions[3] }, { positions[1] }, { positions[1] }, { positions[3] }, { positions[4] }, { positions[4] }, { positions[3] }, { positions[0] }, { positions[0] }, { positions[5] }, { positions[2] }, { positions[5] }, { positions[1] }, { positions[2] }, { positions[1] }, { positions[4] }, { positions[2] }, { positions[4] }, { positions[0] }, { positions[2] }, }; // Calculate normals for (uint32_t i = 0; i < std::extent::value / 3; ++i) { uint32_t idx = i * 3; Vector3 normal = (vertexData[idx + 2].position - vertexData[idx].position). Cross(vertexData[idx + 1].position - vertexData[idx].position); normal.Normalize(); normal *= invertNormals * 2.f - 1.f; vertexData[idx++].normal = normal; vertexData[idx++].normal = normal; vertexData[idx].normal = normal; } // Configure property buffers and create geometry. PropertyBuffer vertexBuffer = PropertyBuffer::New(Property::Map() .Add("aPosition", Property::VECTOR3) .Add("aNormal", Property::VECTOR3)); vertexBuffer.SetData(vertexData, std::extent::value); Geometry geometry = Geometry::New(); geometry.AddVertexBuffer( vertexBuffer ); geometry.SetType( Geometry::TRIANGLES ); return geometry; } //============================================================================= enum RendererOptions { OPTION_NONE = 0x0, OPTION_BLEND = 0x01, OPTION_DEPTH_TEST = 0x02, OPTION_DEPTH_WRITE = 0x04 }; Renderer CreateRenderer(TextureSet textures, Geometry geometry, Shader shader, uint32_t options = OPTION_NONE) { Renderer renderer = Renderer::New(geometry, shader); renderer.SetProperty(Renderer::Property::BLEND_MODE, (options & OPTION_BLEND) ? BlendMode::ON : BlendMode::OFF); renderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, (options & OPTION_DEPTH_TEST) ? DepthTestMode::ON : DepthTestMode::OFF); renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE, (options & OPTION_DEPTH_WRITE) ? DepthWriteMode::ON : DepthWriteMode::OFF); renderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK); if (!textures) { textures = TextureSet::New(); } renderer.SetTextures(textures); return renderer; } //============================================================================= void CenterActor(Actor actor) { actor.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER ); actor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER ); } //============================================================================= void RegisterDepthProperties(float depth, float near, Handle& h) { h.RegisterProperty("uDepth_InvDepth_Near", Vector3(depth, 1.f / depth, near)); } } //============================================================================= /// Create a String whose size can be evaluated at compile time struct ConstantString { const char * const string; const uint16_t size; template constexpr ConstantString(const char (&input)[inputSize]) : string(input), size(inputSize) { } }; constexpr ConstantString POSITION_STRING("position"); constexpr ConstantString RADIUS_STRING("radius"); constexpr ConstantString COLOR_STRING("color"); constexpr uint16_t LIGHT_SOURCE_BUFFER_SIZE(128u); //============================================================================= class DeferredShadingExample : public ConnectionTracker { public: struct Options { enum { NONE = 0x0, SHOW_LIGHTS = 0x1, }; }; DeferredShadingExample(Application& app, uint32_t options = Options::NONE) : mApp(app), mOptions(options) { app.InitSignal().Connect( this, &DeferredShadingExample::Create ); app.TerminateSignal().Connect( this, &DeferredShadingExample::Destroy ); } private: void Create(Application& app) { // Grab stage, configure layer Stage stage = Stage::GetCurrent(); auto rootLayer = stage.GetRootLayer(); rootLayer.SetBehavior(Layer::LAYER_3D); auto stageSize = stage.GetSize(); auto stageHalfSize = stageSize * .5f; auto invStageHalfSize = Vector2::ONE / stageHalfSize; float unit = stageSize.y / 24.f; // Get camera - we'll be re-using the same old camera in the two passes. RenderTaskList tasks = stage.GetRenderTaskList(); CameraActor camera = tasks.GetTask(0).GetCameraActor(); auto zCameraPos = camera.GetProperty(Actor::Property::POSITION_Z).Get(); camera.SetFarClippingPlane(zCameraPos + stageSize.y * .5f); camera.SetNearClippingPlane(zCameraPos - stageSize.y * .5f); const float zNear = camera.GetNearClippingPlane(); const float zFar = camera.GetFarClippingPlane(); const float depth = zFar - zNear; // Create root of scene that shall be rendered off-screen. auto sceneRoot = Actor::New(); CenterActor(sceneRoot); mSceneRoot = sceneRoot; stage.Add(sceneRoot); // Create an axis to spin our actors around. auto axis = Actor::New(); CenterActor(axis); sceneRoot.Add(axis); mAxis = axis; // Create an octahedral mesh for our main actors and to visualise the light sources. Geometry mesh = CreateOctahedron(false); // Create main actors Shader preShader = Shader::New(PREPASS_VSH, PREPASS_FSH); TextureSet noTexturesThanks = TextureSet::New(); Renderer meshRenderer = CreateRenderer(noTexturesThanks, mesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE); meshRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK); meshRenderer.RegisterProperty("uInvStageHalfSize", invStageHalfSize); RegisterDepthProperties(depth, zNear, meshRenderer); float c = 1.f; for (auto v: { Vector3{ -c, -c, -c }, Vector3{ c, -c, -c }, Vector3{ -c, c, -c }, Vector3{ c, c, -c }, Vector3{ -c, -c, c }, Vector3{ c, -c, c }, Vector3{ -c, c, c }, Vector3{ c, c, c }, Vector3{ 0.f, -c, -c }, Vector3{ 0.f, c, -c }, Vector3{ 0.f, -c, c }, Vector3{ 0.f, c, c }, Vector3{ -c, 0.f, -c }, Vector3{ c, 0.f, -c }, Vector3{ -c, 0.f, c }, Vector3{ c, 0.f, c }, Vector3{ -c, -c, 0.f }, Vector3{ c, -c, 0.f }, Vector3{ -c, c, 0.f }, Vector3{ c, c, 0.f }, }) { Actor a = Actor::New(); CenterActor(a); Vector3 position{ v * unit * 5.f }; a.SetPosition(position); float scale = (c + ((v.x + v.y + v.z) + c * 3.f) * .5f) / (c * 4.f); Vector3 size{ Vector3::ONE * scale * unit * 2.f }; a.SetSize(size); 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); a.AddRenderer(meshRenderer); axis.Add(a); } // Create off-screen textures, fbo and render task. uint32_t width = static_cast(stageSize.x); uint32_t height = static_cast(stageSize.y); Texture rttNormal = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height); Texture rttPosition = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGBA8888, width, height); Texture rttColor = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height); FrameBuffer fbo = FrameBuffer::New(width, height, FrameBuffer::Attachment::DEPTH); fbo.AttachColorTexture(rttNormal); fbo.AttachColorTexture(rttPosition); fbo.AttachColorTexture(rttColor); RenderTask sceneRender = tasks.CreateTask(); sceneRender.SetViewportSize(stageSize); sceneRender.SetFrameBuffer(fbo); sceneRender.SetCameraActor(camera); sceneRender.SetSourceActor(sceneRoot); sceneRender.SetInputEnabled(false); sceneRender.SetCullMode(false); sceneRender.SetClearEnabled(true); sceneRender.SetClearColor(Color::BLACK); sceneRender.SetExclusive(true); mSceneRender = sceneRender; // Create final image for deferred shading auto finalImage = Actor::New(); CenterActor(finalImage); finalImage.SetSize(stageSize); TextureSet finalImageTextures = TextureSet::New(); finalImageTextures.SetTexture(0, rttNormal); finalImageTextures.SetTexture(1, rttPosition); finalImageTextures.SetTexture(2, rttColor); Sampler sampler = Sampler::New(); sampler.SetFilterMode(FilterMode::NEAREST, FilterMode::NEAREST); finalImageTextures.SetSampler(0, sampler); finalImageTextures.SetSampler(1, sampler); finalImageTextures.SetSampler(2, sampler); Shader shdMain = Shader::New(MAINPASS_VSH, MAINPASS_FSH); Geometry finalImageGeom = CreateTexturedQuadGeometry(true); Renderer finalImageRenderer = CreateRenderer(finalImageTextures, finalImageGeom, shdMain); finalImageRenderer.RegisterProperty("uStageHalfSize", stageHalfSize); RegisterDepthProperties(depth, zNear, finalImageRenderer); auto propInvProjection = finalImageRenderer.RegisterProperty("uInvProjection", Matrix::IDENTITY); Constraint cnstrInvProjection = Constraint::New(finalImageRenderer, propInvProjection, [zCameraPos, zNear, depth](Matrix& output, const PropertyInputContainer& input) { output = input[0]->GetMatrix(); DALI_ASSERT_ALWAYS(output.Invert() && "Failed to invert projection matrix."); }); cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::PROJECTION_MATRIX)); cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX)); cnstrInvProjection.Apply(); finalImage.AddRenderer(finalImageRenderer); mFinalImage = finalImage; stage.Add(finalImage); // Create a node for our lights auto lights = Actor::New(); CenterActor(lights); sceneRoot.Add(lights); // Create Lights const bool showLights = mOptions & Options::SHOW_LIGHTS; Renderer lightRenderer; if (showLights) { Geometry lightMesh = CreateOctahedron(true); lightRenderer = CreateRenderer(noTexturesThanks, lightMesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE); lightRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::FRONT); } Vector3 lightPos{ unit * 12.f, 0.f, 0.f }; float theta = M_PI * 2.f / MAX_LIGHTS; float cosTheta = std::cos(theta); float sinTheta = std::sin(theta); for (int i = 0; i < MAX_LIGHTS; ++i) { Vector3 color = FromHueSaturationLightness(Vector3((360.f * i) / MAX_LIGHTS, .5f, 1.f)); Actor light = CreateLight(lightPos * (1 + (i % 8)) / 8.f, unit * 16.f, color, camera, finalImageRenderer); float z = (((i & 1) << 1) - 1) * unit * 8.f; lightPos = Vector3(cosTheta * lightPos.x - sinTheta * lightPos.y, sinTheta * lightPos.x + cosTheta * lightPos.y, z); if (showLights) { light.SetProperty(Actor::Property::SIZE, Vector3::ONE * unit / 8.f); light.AddRenderer(lightRenderer); } lights.Add(light); } // Take them for a spin. Animation animLights = Animation::New(40.f); animLights.SetLooping(true); animLights.AnimateBy(Property(lights, Actor::Property::ORIENTATION), Quaternion(Radian(M_PI * 2.f), Vector3::YAXIS)); animLights.Play(); // Event handling stage.KeyEventSignal().Connect(this, &DeferredShadingExample::OnKeyEvent); mPanDetector = PanGestureDetector::New(); mPanDetector.DetectedSignal().Connect(this, &DeferredShadingExample::OnPan); mPanDetector.Attach(stage.GetRootLayer()); } void Destroy(Application& app) { Stage::GetCurrent().GetRenderTaskList().RemoveTask(mSceneRender); mSceneRender.Reset(); UnparentAndReset(mSceneRoot); UnparentAndReset(mFinalImage); } Actor CreateLight(Vector3 position, float radius, Vector3 color, CameraActor camera, Renderer renderer) { Actor light = Actor::New(); CenterActor(light); light.SetProperty(Actor::Property::COLOR, Color::WHITE); light.SetProperty(Actor::Property::POSITION, position); auto iPropRadius = light.RegisterProperty("radius", radius); auto iPropLightColor = light.RegisterProperty("lightcolor", color); // Create light source uniforms on lighting shader. char buffer[LIGHT_SOURCE_BUFFER_SIZE]; char* writep = buffer + snprintf(buffer, LIGHT_SOURCE_BUFFER_SIZE, "uLights[%d].", mNumLights); ++mNumLights; strncpy(writep, POSITION_STRING.string, POSITION_STRING.size); auto oPropLightPos = renderer.RegisterProperty(buffer, position); strncpy(writep, RADIUS_STRING.string, RADIUS_STRING.size); auto oPropLightRadius = renderer.RegisterProperty(buffer, radius); strncpy(writep, COLOR_STRING.string, COLOR_STRING.size); auto oPropLightColor = renderer.RegisterProperty(buffer, color); // Constrain the light position, radius and color to lighting shader uniforms. // Convert light position to view space; Constraint cLightPos = Constraint::New(renderer, oPropLightPos, [](Vector3& output, const PropertyInputContainer& input) { Vector4 worldPos(input[0]->GetVector3()); worldPos.w = 1.f; worldPos = input[1]->GetMatrix() * worldPos; output = Vector3(worldPos); }); cLightPos.AddSource(Source(light, Actor::Property::WORLD_POSITION)); cLightPos.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX)); cLightPos.Apply(); Constraint cLightRadius = Constraint::New(renderer, oPropLightRadius, EqualToConstraint()); cLightRadius.AddSource(Source(light, iPropRadius)); cLightRadius.Apply(); Constraint cLightColor = Constraint::New(renderer, oPropLightColor, EqualToConstraint()); cLightColor.AddSource(Source(light, iPropLightColor)); cLightColor.Apply(); return light; } void OnPan(Actor, PanGesture const& gesture) { Quaternion q = mAxis.GetProperty(Actor::Property::ORIENTATION).Get(); Quaternion qx(Radian(Degree(gesture.screenDisplacement.y) * -.5f), Vector3::XAXIS); Quaternion qy(Radian(Degree(gesture.screenDisplacement.x) * .5f), Vector3::YAXIS); mAxis.SetProperty(Actor::Property::ORIENTATION, qy * qx * q); } void OnKeyEvent(const KeyEvent& event) { if(event.state == KeyEvent::Down) { if( IsKey( event, Dali::DALI_KEY_ESCAPE) || IsKey( event, Dali::DALI_KEY_BACK) ) { mApp.Quit(); } } } Application& mApp; uint32_t mOptions; Actor mSceneRoot; Actor mAxis; RenderTask mSceneRender; Actor mFinalImage; int mNumLights = 0; PanGestureDetector mPanDetector; }; int main(int argc, char** argv) { const bool showLights = [](int argc, char** argv) { auto endArgs = argv + argc; return std::find_if(argv, endArgs, [](const char* arg) { return strcmp(arg, "--show-lights") == 0; }) != endArgs; }(argc, argv); Application app = Application::New(&argc, &argv); DeferredShadingExample example(app, (showLights ? DeferredShadingExample::Options::SHOW_LIGHTS : 0)); app.MainLoop(); return 0; }