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