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.
19 #include <dali-toolkit/dali-toolkit.h>
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-stream.h>
32 using namespace Toolkit;
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/
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";
51 const char* SPHERE_URL = DEMO_MODEL_DIR "sphere.obj";
52 const char* TEAPOT_URL = DEMO_MODEL_DIR "teapot.obj";
54 const char* VERTEX_SHADER_URL = DEMO_SHADER_DIR "pbr_shader.vsh";
55 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "pbr_shader.fsh";
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 );
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 );
70 * This example shows a Physically Based Rendering illumination model.
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
79 class BasicPbrController : public ConnectionTracker
83 BasicPbrController( Application& application )
84 : mApplication( application ),
95 // Connect to the Application's Init signal
96 mApplication.InitSignal().Connect( this, &BasicPbrController::Create );
101 // Nothing to do here;
104 // The Init signal is received once (only) during the Application lifetime
105 void Create( Application& application )
107 // Get a handle to the window
108 Window window = application.GetWindow();
109 window.SetBackgroundColor( Color::BLACK );
110 mAnimation = Animation::New( 1.0f );
111 mLabel = TextLabel::New( "R:1 M:0" );
112 mLabel.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_CENTER );
113 mLabel.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_CENTER );
114 mLabel.SetProperty( Actor::Property::SIZE, Vector2( window.GetSize().GetWidth() * 0.5f, window.GetSize().GetHeight() * 0.083f ) );
115 mLabel.SetProperty( TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" );
116 mLabel.SetProperty( TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" );
117 mLabel.SetProperty( TextLabel::Property::TEXT_COLOR, Color::WHITE );
118 mLabel.SetProperty( Actor::Property::COLOR_ALPHA, 0.0f );
119 // Step 1. Create shader
122 // Step 2. Create texture
125 // Step 3. Initialise Main Actor
128 // Respond to a click anywhere on the window
129 window.GetRootLayer().TouchSignal().Connect( this, &BasicPbrController::OnTouch );
131 // Respond to key events
132 window.KeyEventSignal().Connect( this, &BasicPbrController::OnKeyEvent );
134 mDoubleTapTime = Timer::New(150);
135 mDoubleTapTime.TickSignal().Connect( this, &BasicPbrController::OnDoubleTapTime );
139 bool OnDoubleTapTime()
146 * This function will change the material Roughness, Metalness or the model orientation when touched
148 bool OnTouch( Actor actor, const TouchEvent& touch )
150 const PointState::Type state = touch.GetState( 0 );
154 case PointState::DOWN:
158 mTeapotView = !mTeapotView;
159 mModel[0].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, !mTeapotView);
160 mModel[1].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, mTeapotView);
162 mDoubleTapTime.Stop();
163 mStartTouch = touch.GetScreenPosition(0);
164 mPointZ = mStartTouch;
166 mLabel.SetProperty( Actor::Property::COLOR_ALPHA, 1.0f );
169 case PointState::MOTION:
171 const Window window = mApplication.GetWindow();
172 const Size size = window.GetSize();
173 const float scaleX = size.width;
174 const float scaleY = size.height;
175 const Vector2 point = touch.GetScreenPosition(0);
176 bool process = false;
177 if( ( mStartTouch.x < ( scaleX * 0.3f ) ) && ( point.x < ( scaleX * 0.3f ) ) )
179 mRoughness += ( mStartTouch.y - point.y ) / ( scaleY * 0.9f );
181 //Clamp Roughness to 0.0 to 1.0
182 mRoughness = std::max( 0.f, std::min( 1.f, mRoughness ) );
184 mShader.SetProperty( mShader.GetPropertyIndex( "uRoughness" ), mRoughness );
185 std::ostringstream oss;
187 oss << " R:" << mRoughness<< "," << " M:" << mMetalness;
188 mLabel.SetProperty( TextLabel::Property::TEXT, oss.str() );
192 if( ( mStartTouch.x > ( scaleX * 0.7f ) ) && ( point.x > ( scaleX * 0.7f ) ) )
194 mMetalness += ( mStartTouch.y - point.y ) / ( scaleY * 0.9f );
196 //Clamp Metalness to 0.0 to 1.0
197 mMetalness = std::max( 0.f, std::min( 1.f, mMetalness ) );
199 mShader.SetProperty( mShader.GetPropertyIndex( "uMetallic" ), mMetalness );
200 std::ostringstream oss;
202 oss << " R:" << mRoughness<< "," << " M:" << mMetalness;
203 mLabel.SetProperty( TextLabel::Property::TEXT, oss.str() );
207 //If the touch is not processed above, then change the model orientation
211 const float angle1 = ( ( mPointZ.y - point.y ) / scaleY );
212 const float angle2 = ( ( mPointZ.x - point.x ) / scaleX );
213 Actor actor1 = mModel[0].GetActor();
214 Actor actor2 = mModel[1].GetActor();
216 Quaternion modelOrientation = mModelOrientation;
217 modelOrientation.Conjugate();
218 const Quaternion pitchRot(Radian(Degree(angle1 * -200.0f)), modelOrientation.Rotate( Vector3::XAXIS ) );
219 const Quaternion yawRot(Radian(Degree(angle2 * -200.0f)), modelOrientation.Rotate( Vector3::YAXIS ) );
221 mModelOrientation = mModelOrientation * yawRot * pitchRot ;
222 mSkybox.GetActor().SetProperty( Actor::Property::ORIENTATION, mModelOrientation );
223 actor1.SetProperty( Actor::Property::ORIENTATION, mModelOrientation );
224 actor2.SetProperty( Actor::Property::ORIENTATION, mModelOrientation );
232 mDoubleTapTime.Start();
234 mAnimation.AnimateTo( Property( mLabel, Actor::Property::COLOR_ALPHA ), 0.0f, TimePeriod( 0.5f, 1.0f ) );
248 * @brief Called when any key event is received
250 * Will use this to quit the application if Back or the Escape key is received
251 * @param[in] event The key event information
253 void OnKeyEvent( const KeyEvent& event )
255 if( event.GetState() == KeyEvent::DOWN )
257 if( IsKey( event, Dali::DALI_KEY_ESCAPE ) || IsKey( event, Dali::DALI_KEY_BACK ) )
265 * Creates new main actor
269 Window window = mApplication.GetWindow();
270 Vector2 windowSize = window.GetSize();
272 mSkybox.Init( SKYBOX_SCALE );
273 mModel[0].Init( mShader, SPHERE_URL, Vector3::ZERO, SPHERE_SCALE );
274 mModel[1].Init( mShader, TEAPOT_URL, Vector3::ZERO, TEAPOT_SCALE );
276 // Hide the model according with mTeapotView variable
277 mModel[0].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, !mTeapotView);
278 mModel[1].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, mTeapotView);
280 // Creating root and camera actor for rendertask for 3D Scene rendering
281 mUiRoot = Actor::New();
282 m3dRoot = Actor::New();
283 CameraActor cameraUi = CameraActor::New(window.GetSize());
284 cameraUi.SetProperty( Actor::Property::ANCHOR_POINT,AnchorPoint::CENTER);
285 cameraUi.SetProperty( Actor::Property::PARENT_ORIGIN,ParentOrigin::CENTER);
287 RenderTask rendertask = window.GetRenderTaskList().CreateTask();
288 rendertask.SetCameraActor( cameraUi );
289 rendertask.SetSourceActor( mUiRoot );
291 mUiRoot.SetProperty(Actor::Property::ANCHOR_POINT,AnchorPoint::TOP_LEFT);
292 mUiRoot.SetProperty(Actor::Property::PARENT_ORIGIN,ParentOrigin::TOP_LEFT);
293 mUiRoot.SetProperty(Actor::Property::SIZE, Vector2(window.GetSize()) );
295 m3dRoot.SetProperty(Actor::Property::ANCHOR_POINT,AnchorPoint::CENTER);
296 m3dRoot.SetProperty(Actor::Property::PARENT_ORIGIN,ParentOrigin::CENTER);
298 // Setting camera parameters for 3D Scene
299 mSkybox.GetActor().SetProperty( Actor::Property::POSITION, CAMERA_DEFAULT_POSITION );
300 CameraActor camera3d = window.GetRenderTaskList().GetTask(0).GetCameraActor();
301 camera3d.SetInvertYAxis( true );
302 camera3d.SetProperty( Actor::Property::POSITION, CAMERA_DEFAULT_POSITION );
303 camera3d.SetNearClippingPlane( CAMERA_DEFAULT_NEAR );
304 camera3d.SetFarClippingPlane( CAMERA_DEFAULT_FAR );
305 camera3d.SetFieldOfView( Radian( Degree( CAMERA_DEFAULT_FOV ) ) );
307 window.Add( cameraUi );
308 window.Add( mUiRoot );
309 window.Add( m3dRoot );
311 m3dRoot.Add( mSkybox.GetActor() );
312 m3dRoot.Add( mModel[0].GetActor() );
313 m3dRoot.Add( mModel[1].GetActor() );
316 if( (windowSize.x > 360.0f) && (windowSize.y > 360.0f) )
318 mUiRoot.Add( mLabel );
324 * Creates a shader using file path
326 void CreateModelShader()
328 const std::string mCurrentVShaderFile( VERTEX_SHADER_URL );
329 const std::string mCurrentFShaderFile( FRAGMENT_SHADER_URL );
331 mShader = LoadShaders( mCurrentVShaderFile, mCurrentFShaderFile );
333 // Initialise shader uniforms
334 // Level 8 because the environment texture has 6 levels plus 2 are missing (2x2 and 1x1)
335 mShader.RegisterProperty( "uMaxLOD", 8.0f );
336 mShader.RegisterProperty( "uRoughness", 1.0f );
337 mShader.RegisterProperty( "uMetallic" , 0.0f );
345 PixelData albeldoPixelData = SyncImageLoader::Load( ALBEDO_METAL_TEXTURE_URL );
346 Texture textureAlbedoMetal = Texture::New( TextureType::TEXTURE_2D, albeldoPixelData.GetPixelFormat(), albeldoPixelData.GetWidth(), albeldoPixelData.GetHeight() );
347 textureAlbedoMetal.Upload( albeldoPixelData, 0, 0, 0, 0, albeldoPixelData.GetWidth(), albeldoPixelData.GetHeight() );
349 PixelData normalPixelData = SyncImageLoader::Load( NORMAL_ROUGH_TEXTURE_URL );
350 Texture textureNormalRough = Texture::New( TextureType::TEXTURE_2D, normalPixelData.GetPixelFormat(), normalPixelData.GetWidth(), normalPixelData.GetHeight() );
351 textureNormalRough.Upload( normalPixelData, 0, 0, 0, 0, normalPixelData.GetWidth(), normalPixelData.GetHeight() );
353 // This texture should have 6 faces and only one mipmap
354 PbrDemo::CubeData diffuse;
355 PbrDemo::LoadCubeMapFromKtxFile( CUBEMAP_DIFFUSE_TEXTURE_URL, diffuse );
357 Texture diffuseTexture = Texture::New( TextureType::TEXTURE_CUBE, diffuse.img[0][0].GetPixelFormat(), diffuse.img[0][0].GetWidth(), diffuse.img[0][0].GetHeight() );
358 for( unsigned int midmapLevel = 0; midmapLevel < diffuse.img[0].size(); ++midmapLevel )
360 for( unsigned int i = 0; i < diffuse.img.size(); ++i )
362 diffuseTexture.Upload( diffuse.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, diffuse.img[i][midmapLevel].GetWidth(), diffuse.img[i][midmapLevel].GetHeight() );
366 // This texture should have 6 faces and 6 mipmaps
367 PbrDemo::CubeData specular;
368 PbrDemo::LoadCubeMapFromKtxFile( CUBEMAP_SPECULAR_TEXTURE_URL, specular);
370 Texture specularTexture = Texture::New( TextureType::TEXTURE_CUBE, specular.img[0][0].GetPixelFormat(), specular.img[0][0].GetWidth(), specular.img[0][0].GetHeight() );
371 for( unsigned int midmapLevel = 0; midmapLevel < specular.img[0].size(); ++midmapLevel )
373 for( unsigned int i = 0; i < specular.img.size(); ++i )
375 specularTexture.Upload( specular.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, specular.img[i][midmapLevel].GetWidth(), specular.img[i][midmapLevel].GetHeight() );
379 mModel[0].InitTexture( textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture );
380 mModel[1].InitTexture( textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture );
381 mSkybox.InitTexture( specularTexture );
385 * @brief Load a shader source file
386 * @param[in] The path of the source file
387 * @param[out] The contents of file
388 * @return True if the source was read successfully
390 bool LoadShaderCode( const std::string& fullpath, std::vector<char>& output )
392 Dali::FileStream fileStream( fullpath, FileStream::READ | FileStream::BINARY );
393 FILE* file = fileStream.GetFile();
399 bool retValue = false;
400 if( ! fseek( file, 0, SEEK_END ) )
402 long int size = ftell( file );
404 if( ( size != -1L ) &&
405 ( ! fseek( file, 0, SEEK_SET ) ) )
407 output.resize( size + 1 );
408 std::fill( output.begin(), output.end(), 0 );
409 ssize_t result = fread( output.data(), size, 1, file );
411 retValue = ( result >= 0 );
419 * @brief Load vertex and fragment shader source
420 * @param[in] shaderVertexFileName is the filepath of Vertex shader
421 * @param[in] shaderFragFileName is the filepath of Fragment shader
422 * @return the Dali::Shader object
424 Shader LoadShaders( const std::string& shaderVertexFileName, const std::string& shaderFragFileName )
427 std::vector<char> bufV, bufF;
429 if( LoadShaderCode( shaderVertexFileName.c_str(), bufV ) )
431 if( LoadShaderCode( shaderFragFileName.c_str(), bufF ) )
433 shader = Shader::New( bufV.data() , bufF.data() );
440 Application& mApplication;
445 Animation mAnimation;
446 Timer mDoubleTapTime;
454 Quaternion mModelOrientation;
462 int DALI_EXPORT_API main( int argc, char **argv )
464 Application application = Application::New( &argc, &argv);
465 BasicPbrController test( application );
466 application.MainLoop();