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