Updated demos to use DALi clang-format
[platform/core/uifw/dali-demo.git] / examples / rendering-basic-pbr / rendering-basic-pbr-example.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // EXTERNAL INCLUDES
19 #include <dali-toolkit/dali-toolkit.h>
20
21 #include <stdio.h>
22 #include <sstream>
23
24 // INTERNAL INCLUDES
25 #include <dali/devel-api/adaptor-framework/file-stream.h>
26 #include <dali/integration-api/debug.h>
27 #include "ktx-loader.h"
28 #include "model-pbr.h"
29 #include "model-skybox.h"
30
31 using namespace Dali;
32 using namespace Toolkit;
33
34 /*
35  *
36  * "papermill_pmrem.ktx" and "papermill_E_diffuse-64.ktx are image files generated from
37  * image file downloaded from "http://www.hdrlabs.com/sibl/archive.html" with title
38  * "Papermill Ruins E" by Blochi and is licensed under Creative Commons License
39  * http://creativecommons.org/licenses/by-nc-sa/3.0/us/
40  *
41 */
42
43 namespace
44 {
45 const char* NORMAL_ROUGH_TEXTURE_URL     = DEMO_IMAGE_DIR "Test_100_normal_roughness.png";
46 const char* ALBEDO_METAL_TEXTURE_URL     = DEMO_IMAGE_DIR "Test_wblue_100_albedo_metal.png";
47 const char* CUBEMAP_SPECULAR_TEXTURE_URL = DEMO_IMAGE_DIR "papermill_pmrem.ktx";
48 const char* CUBEMAP_DIFFUSE_TEXTURE_URL  = DEMO_IMAGE_DIR "papermill_E_diffuse-64.ktx";
49
50 const char* SPHERE_URL = DEMO_MODEL_DIR "sphere.obj";
51 const char* TEAPOT_URL = DEMO_MODEL_DIR "teapot.obj";
52
53 const char* VERTEX_SHADER_URL   = DEMO_SHADER_DIR "pbr_shader.vsh";
54 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "pbr_shader.fsh";
55
56 const Vector3 SKYBOX_SCALE(1.0f, 1.0f, 1.0f);
57 const Vector3 SPHERE_SCALE(1.5f, 1.5f, 1.5f);
58 const Vector3 TEAPOT_SCALE(2.0f, 2.0f, 2.0f);
59
60 const float   CAMERA_DEFAULT_FOV(60.0f);
61 const float   CAMERA_DEFAULT_NEAR(0.1f);
62 const float   CAMERA_DEFAULT_FAR(1000.0f);
63 const Vector3 CAMERA_DEFAULT_POSITION(0.0f, 0.0f, 3.5f);
64
65 } // namespace
66
67 /*
68  *
69  * This example shows a Physically Based Rendering illumination model.
70  *
71  * - Double-tap to toggle between 3D models (teapot & cube)
72  * - Pan up/down on left side of screen to change roughness
73  * - Pan up/down on right side of screen to change metalness
74  * - Pan anywhere else to rotate scene
75  *
76 */
77
78 class BasicPbrController : public ConnectionTracker
79 {
80 public:
81   BasicPbrController(Application& application)
82   : mApplication(application),
83     mLabel(),
84     m3dRoot(),
85     mUiRoot(),
86     mDoubleTapTime(),
87     mModelOrientation(),
88     mRoughness(1.f),
89     mMetalness(0.f),
90     mDoubleTap(false),
91     mTeapotView(true)
92   {
93     // Connect to the Application's Init signal
94     mApplication.InitSignal().Connect(this, &BasicPbrController::Create);
95   }
96
97   ~BasicPbrController()
98   {
99     // Nothing to do here;
100   }
101
102   // The Init signal is received once (only) during the Application lifetime
103   void Create(Application& application)
104   {
105     // Get a handle to the window
106     Window window = application.GetWindow();
107     window.SetBackgroundColor(Color::BLACK);
108     mAnimation = Animation::New(1.0f);
109     mLabel     = TextLabel::New("R:1 M:0");
110     mLabel.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_CENTER);
111     mLabel.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_CENTER);
112     mLabel.SetProperty(Actor::Property::SIZE, Vector2(window.GetSize().GetWidth() * 0.5f, window.GetSize().GetHeight() * 0.083f));
113     mLabel.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER");
114     mLabel.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER");
115     mLabel.SetProperty(TextLabel::Property::TEXT_COLOR, Color::WHITE);
116     mLabel.SetProperty(Actor::Property::COLOR_ALPHA, 0.0f);
117     // Step 1. Create shader
118     CreateModelShader();
119
120     // Step 2. Create texture
121     CreateTexture();
122
123     // Step 3. Initialise Main Actor
124     InitActors();
125
126     // Respond to a click anywhere on the window
127     window.GetRootLayer().TouchedSignal().Connect(this, &BasicPbrController::OnTouch);
128
129     // Respond to key events
130     window.KeyEventSignal().Connect(this, &BasicPbrController::OnKeyEvent);
131
132     mDoubleTapTime = Timer::New(150);
133     mDoubleTapTime.TickSignal().Connect(this, &BasicPbrController::OnDoubleTapTime);
134   }
135
136   bool OnDoubleTapTime()
137   {
138     mDoubleTap = false;
139     return true;
140   }
141
142   /**
143    * This function will change the material Roughness, Metalness or the model orientation when touched
144    */
145   bool OnTouch(Actor actor, const TouchEvent& touch)
146   {
147     const PointState::Type state = touch.GetState(0);
148
149     switch(state)
150     {
151       case PointState::DOWN:
152       {
153         if(mDoubleTap)
154         {
155           mTeapotView = !mTeapotView;
156           mModel[0].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, !mTeapotView);
157           mModel[1].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, mTeapotView);
158         }
159         mDoubleTapTime.Stop();
160         mStartTouch = touch.GetScreenPosition(0);
161         mPointZ     = mStartTouch;
162         mAnimation.Stop();
163         mLabel.SetProperty(Actor::Property::COLOR_ALPHA, 1.0f);
164         break;
165       }
166       case PointState::MOTION:
167       {
168         const Window  window  = mApplication.GetWindow();
169         const Size    size    = window.GetSize();
170         const float   scaleX  = size.width;
171         const float   scaleY  = size.height;
172         const Vector2 point   = touch.GetScreenPosition(0);
173         bool          process = false;
174         if((mStartTouch.x < (scaleX * 0.3f)) && (point.x < (scaleX * 0.3f)))
175         {
176           mRoughness += (mStartTouch.y - point.y) / (scaleY * 0.9f);
177
178           //Clamp Roughness to 0.0 to 1.0
179           mRoughness = std::max(0.f, std::min(1.f, mRoughness));
180
181           mShader.SetProperty(mShader.GetPropertyIndex("uRoughness"), mRoughness);
182           std::ostringstream oss;
183           oss.precision(2);
184           oss << " R:" << mRoughness << ","
185               << " M:" << mMetalness;
186           mLabel.SetProperty(TextLabel::Property::TEXT, oss.str());
187           mStartTouch = point;
188           process     = true;
189         }
190         if((mStartTouch.x > (scaleX * 0.7f)) && (point.x > (scaleX * 0.7f)))
191         {
192           mMetalness += (mStartTouch.y - point.y) / (scaleY * 0.9f);
193
194           //Clamp Metalness to 0.0 to 1.0
195           mMetalness = std::max(0.f, std::min(1.f, mMetalness));
196
197           mShader.SetProperty(mShader.GetPropertyIndex("uMetallic"), mMetalness);
198           std::ostringstream oss;
199           oss.precision(2);
200           oss << " R:" << mRoughness << ","
201               << " M:" << mMetalness;
202           mLabel.SetProperty(TextLabel::Property::TEXT, oss.str());
203           mStartTouch = point;
204           process     = true;
205         }
206         //If the touch is not processed above, then change the model orientation
207         if(!process)
208         {
209           process            = true;
210           const float angle1 = ((mPointZ.y - point.y) / scaleY);
211           const float angle2 = ((mPointZ.x - point.x) / scaleX);
212           Actor       actor1 = mModel[0].GetActor();
213           Actor       actor2 = mModel[1].GetActor();
214
215           Quaternion modelOrientation = mModelOrientation;
216           modelOrientation.Conjugate();
217           const Quaternion pitchRot(Radian(Degree(angle1 * -200.0f)), modelOrientation.Rotate(Vector3::XAXIS));
218           const Quaternion yawRot(Radian(Degree(angle2 * -200.0f)), modelOrientation.Rotate(Vector3::YAXIS));
219
220           mModelOrientation = mModelOrientation * yawRot * pitchRot;
221           mSkybox.GetActor().SetProperty(Actor::Property::ORIENTATION, mModelOrientation);
222           actor1.SetProperty(Actor::Property::ORIENTATION, mModelOrientation);
223           actor2.SetProperty(Actor::Property::ORIENTATION, mModelOrientation);
224
225           mPointZ = point;
226         }
227         break;
228       }
229       case PointState::UP:
230       {
231         mDoubleTapTime.Start();
232         mDoubleTap = true;
233         mAnimation.AnimateTo(Property(mLabel, Actor::Property::COLOR_ALPHA), 0.0f, TimePeriod(0.5f, 1.0f));
234         mAnimation.Play();
235         break;
236       }
237
238       default:
239       {
240         break;
241       }
242     }
243     return true;
244   }
245
246   /**
247    * @brief Called when any key event is received
248    *
249    * Will use this to quit the application if Back or the Escape key is received
250    * @param[in] event The key event information
251    */
252   void OnKeyEvent(const KeyEvent& event)
253   {
254     if(event.GetState() == KeyEvent::DOWN)
255     {
256       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
257       {
258         mApplication.Quit();
259       }
260     }
261   }
262
263   /**
264    * Creates new main actor
265    */
266   void InitActors()
267   {
268     Window  window     = mApplication.GetWindow();
269     Vector2 windowSize = window.GetSize();
270
271     mSkybox.Init(SKYBOX_SCALE);
272     mModel[0].Init(mShader, SPHERE_URL, Vector3::ZERO, SPHERE_SCALE);
273     mModel[1].Init(mShader, TEAPOT_URL, Vector3::ZERO, TEAPOT_SCALE);
274
275     // Hide the model according with mTeapotView variable
276     mModel[0].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, !mTeapotView);
277     mModel[1].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, mTeapotView);
278
279     // Creating root and camera actor for rendertask for 3D Scene rendering
280     mUiRoot              = Actor::New();
281     m3dRoot              = Actor::New();
282     CameraActor cameraUi = CameraActor::New(window.GetSize());
283     cameraUi.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
284     cameraUi.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
285
286     RenderTask rendertask = window.GetRenderTaskList().CreateTask();
287     rendertask.SetCameraActor(cameraUi);
288     rendertask.SetSourceActor(mUiRoot);
289
290     mUiRoot.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
291     mUiRoot.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
292     mUiRoot.SetProperty(Actor::Property::SIZE, Vector2(window.GetSize()));
293
294     m3dRoot.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
295     m3dRoot.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
296
297     // Setting camera parameters for 3D Scene
298     mSkybox.GetActor().SetProperty(Actor::Property::POSITION, CAMERA_DEFAULT_POSITION);
299     CameraActor camera3d = window.GetRenderTaskList().GetTask(0).GetCameraActor();
300     camera3d.SetInvertYAxis(true);
301     camera3d.SetProperty(Actor::Property::POSITION, CAMERA_DEFAULT_POSITION);
302     camera3d.SetNearClippingPlane(CAMERA_DEFAULT_NEAR);
303     camera3d.SetFarClippingPlane(CAMERA_DEFAULT_FAR);
304     camera3d.SetFieldOfView(Radian(Degree(CAMERA_DEFAULT_FOV)));
305
306     window.Add(cameraUi);
307     window.Add(mUiRoot);
308     window.Add(m3dRoot);
309
310     m3dRoot.Add(mSkybox.GetActor());
311     m3dRoot.Add(mModel[0].GetActor());
312     m3dRoot.Add(mModel[1].GetActor());
313
314     if((windowSize.x > 360.0f) && (windowSize.y > 360.0f))
315     {
316       mUiRoot.Add(mLabel);
317     }
318   }
319
320   /**
321    * Creates a shader using file path
322    */
323   void CreateModelShader()
324   {
325     const std::string mCurrentVShaderFile(VERTEX_SHADER_URL);
326     const std::string mCurrentFShaderFile(FRAGMENT_SHADER_URL);
327
328     mShader = LoadShaders(mCurrentVShaderFile, mCurrentFShaderFile);
329
330     // Initialise shader uniforms
331     // Level 8 because the environment texture has 6 levels plus 2 are missing (2x2 and 1x1)
332     mShader.RegisterProperty("uMaxLOD", 8.0f);
333     mShader.RegisterProperty("uRoughness", 1.0f);
334     mShader.RegisterProperty("uMetallic", 0.0f);
335   }
336
337   /**
338    * Create Textures
339    */
340   void CreateTexture()
341   {
342     PixelData albeldoPixelData   = SyncImageLoader::Load(ALBEDO_METAL_TEXTURE_URL);
343     Texture   textureAlbedoMetal = Texture::New(TextureType::TEXTURE_2D, albeldoPixelData.GetPixelFormat(), albeldoPixelData.GetWidth(), albeldoPixelData.GetHeight());
344     textureAlbedoMetal.Upload(albeldoPixelData, 0, 0, 0, 0, albeldoPixelData.GetWidth(), albeldoPixelData.GetHeight());
345
346     PixelData normalPixelData    = SyncImageLoader::Load(NORMAL_ROUGH_TEXTURE_URL);
347     Texture   textureNormalRough = Texture::New(TextureType::TEXTURE_2D, normalPixelData.GetPixelFormat(), normalPixelData.GetWidth(), normalPixelData.GetHeight());
348     textureNormalRough.Upload(normalPixelData, 0, 0, 0, 0, normalPixelData.GetWidth(), normalPixelData.GetHeight());
349
350     // This texture should have 6 faces and only one mipmap
351     PbrDemo::CubeData diffuse;
352     PbrDemo::LoadCubeMapFromKtxFile(CUBEMAP_DIFFUSE_TEXTURE_URL, diffuse);
353
354     Texture diffuseTexture = Texture::New(TextureType::TEXTURE_CUBE, diffuse.img[0][0].GetPixelFormat(), diffuse.img[0][0].GetWidth(), diffuse.img[0][0].GetHeight());
355     for(unsigned int midmapLevel = 0; midmapLevel < diffuse.img[0].size(); ++midmapLevel)
356     {
357       for(unsigned int i = 0; i < diffuse.img.size(); ++i)
358       {
359         diffuseTexture.Upload(diffuse.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, diffuse.img[i][midmapLevel].GetWidth(), diffuse.img[i][midmapLevel].GetHeight());
360       }
361     }
362
363     // This texture should have 6 faces and 6 mipmaps
364     PbrDemo::CubeData specular;
365     PbrDemo::LoadCubeMapFromKtxFile(CUBEMAP_SPECULAR_TEXTURE_URL, specular);
366
367     Texture specularTexture = Texture::New(TextureType::TEXTURE_CUBE, specular.img[0][0].GetPixelFormat(), specular.img[0][0].GetWidth(), specular.img[0][0].GetHeight());
368     for(unsigned int midmapLevel = 0; midmapLevel < specular.img[0].size(); ++midmapLevel)
369     {
370       for(unsigned int i = 0; i < specular.img.size(); ++i)
371       {
372         specularTexture.Upload(specular.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, specular.img[i][midmapLevel].GetWidth(), specular.img[i][midmapLevel].GetHeight());
373       }
374     }
375
376     mModel[0].InitTexture(textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture);
377     mModel[1].InitTexture(textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture);
378     mSkybox.InitTexture(specularTexture);
379   }
380
381   /**
382   * @brief Load a shader source file
383   * @param[in] The path of the source file
384   * @param[out] The contents of file
385   * @return True if the source was read successfully
386   */
387   bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
388   {
389     Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
390     FILE*            file = fileStream.GetFile();
391     if(NULL == file)
392     {
393       return false;
394     }
395
396     bool retValue = false;
397     if(!fseek(file, 0, SEEK_END))
398     {
399       long int size = ftell(file);
400
401       if((size != -1L) &&
402          (!fseek(file, 0, SEEK_SET)))
403       {
404         output.resize(size + 1);
405         std::fill(output.begin(), output.end(), 0);
406         ssize_t result = fread(output.data(), size, 1, file);
407
408         retValue = (result >= 0);
409       }
410     }
411
412     return retValue;
413   }
414
415   /**
416   * @brief Load vertex and fragment shader source
417   * @param[in] shaderVertexFileName is the filepath of Vertex shader
418   * @param[in] shaderFragFileName is the filepath of Fragment shader
419   * @return the Dali::Shader object
420   */
421   Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
422   {
423     Shader            shader;
424     std::vector<char> bufV, bufF;
425
426     if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
427     {
428       if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
429       {
430         shader = Shader::New(bufV.data(), bufF.data());
431       }
432     }
433     return shader;
434   }
435
436 private:
437   Application& mApplication;
438   TextLabel    mLabel;
439   Actor        m3dRoot;
440   Actor        mUiRoot;
441   Shader       mShader;
442   Animation    mAnimation;
443   Timer        mDoubleTapTime;
444
445   ModelSkybox mSkybox;
446   ModelPbr    mModel[2];
447
448   Vector2 mPointZ;
449   Vector2 mStartTouch;
450
451   Quaternion mModelOrientation;
452   float      mRoughness;
453   float      mMetalness;
454   bool       mDoubleTap;
455   bool       mTeapotView;
456 };
457
458 int DALI_EXPORT_API main(int argc, char** argv)
459 {
460   Application        application = Application::New(&argc, &argv);
461   BasicPbrController test(application);
462   application.MainLoop();
463   return 0;
464 }