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 <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"
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/
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";
50 const char* SPHERE_URL = DEMO_MODEL_DIR "sphere.obj";
51 const char* TEAPOT_URL = DEMO_MODEL_DIR "teapot.obj";
53 const char* VERTEX_SHADER_URL = DEMO_SHADER_DIR "pbr_shader.vsh";
54 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "pbr_shader.fsh";
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);
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);
69 * This example shows a Physically Based Rendering illumination model.
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
78 class BasicPbrController : public ConnectionTracker
81 BasicPbrController(Application& application)
82 : mApplication(application),
93 // Connect to the Application's Init signal
94 mApplication.InitSignal().Connect(this, &BasicPbrController::Create);
99 // Nothing to do here;
102 // The Init signal is received once (only) during the Application lifetime
103 void Create(Application& application)
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
120 // Step 2. Create texture
123 // Step 3. Initialise Main Actor
126 // Respond to a click anywhere on the window
127 window.GetRootLayer().TouchedSignal().Connect(this, &BasicPbrController::OnTouch);
129 // Respond to key events
130 window.KeyEventSignal().Connect(this, &BasicPbrController::OnKeyEvent);
132 mDoubleTapTime = Timer::New(150);
133 mDoubleTapTime.TickSignal().Connect(this, &BasicPbrController::OnDoubleTapTime);
136 bool OnDoubleTapTime()
143 * This function will change the material Roughness, Metalness or the model orientation when touched
145 bool OnTouch(Actor actor, const TouchEvent& touch)
147 const PointState::Type state = touch.GetState(0);
151 case PointState::DOWN:
155 mTeapotView = !mTeapotView;
156 mModel[0].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, !mTeapotView);
157 mModel[1].GetActor().SetProperty(Dali::Actor::Property::VISIBLE, mTeapotView);
159 mDoubleTapTime.Stop();
160 mStartTouch = touch.GetScreenPosition(0);
161 mPointZ = mStartTouch;
163 mLabel.SetProperty(Actor::Property::COLOR_ALPHA, 1.0f);
166 case PointState::MOTION:
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)))
176 mRoughness += (mStartTouch.y - point.y) / (scaleY * 0.9f);
178 //Clamp Roughness to 0.0 to 1.0
179 mRoughness = std::max(0.f, std::min(1.f, mRoughness));
181 mShader.SetProperty(mShader.GetPropertyIndex("uRoughness"), mRoughness);
182 std::ostringstream oss;
184 oss << " R:" << mRoughness << ","
185 << " M:" << mMetalness;
186 mLabel.SetProperty(TextLabel::Property::TEXT, oss.str());
190 if((mStartTouch.x > (scaleX * 0.7f)) && (point.x > (scaleX * 0.7f)))
192 mMetalness += (mStartTouch.y - point.y) / (scaleY * 0.9f);
194 //Clamp Metalness to 0.0 to 1.0
195 mMetalness = std::max(0.f, std::min(1.f, mMetalness));
197 mShader.SetProperty(mShader.GetPropertyIndex("uMetallic"), mMetalness);
198 std::ostringstream oss;
200 oss << " R:" << mRoughness << ","
201 << " M:" << mMetalness;
202 mLabel.SetProperty(TextLabel::Property::TEXT, oss.str());
206 //If the touch is not processed above, then change the model orientation
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();
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));
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);
231 mDoubleTapTime.Start();
233 mAnimation.AnimateTo(Property(mLabel, Actor::Property::COLOR_ALPHA), 0.0f, TimePeriod(0.5f, 1.0f));
247 * @brief Called when any key event is received
249 * Will use this to quit the application if Back or the Escape key is received
250 * @param[in] event The key event information
252 void OnKeyEvent(const KeyEvent& event)
254 if(event.GetState() == KeyEvent::DOWN)
256 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
264 * Creates new main actor
268 Window window = mApplication.GetWindow();
269 Vector2 windowSize = window.GetSize();
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);
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);
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);
286 RenderTask rendertask = window.GetRenderTaskList().CreateTask();
287 rendertask.SetCameraActor(cameraUi);
288 rendertask.SetSourceActor(mUiRoot);
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()));
294 m3dRoot.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
295 m3dRoot.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
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)));
306 window.Add(cameraUi);
310 m3dRoot.Add(mSkybox.GetActor());
311 m3dRoot.Add(mModel[0].GetActor());
312 m3dRoot.Add(mModel[1].GetActor());
314 if((windowSize.x > 360.0f) && (windowSize.y > 360.0f))
321 * Creates a shader using file path
323 void CreateModelShader()
325 const std::string mCurrentVShaderFile(VERTEX_SHADER_URL);
326 const std::string mCurrentFShaderFile(FRAGMENT_SHADER_URL);
328 mShader = LoadShaders(mCurrentVShaderFile, mCurrentFShaderFile);
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);
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());
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());
350 // This texture should have 6 faces and only one mipmap
351 PbrDemo::CubeData diffuse;
352 PbrDemo::LoadCubeMapFromKtxFile(CUBEMAP_DIFFUSE_TEXTURE_URL, diffuse);
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)
357 for(unsigned int i = 0; i < diffuse.img.size(); ++i)
359 diffuseTexture.Upload(diffuse.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, diffuse.img[i][midmapLevel].GetWidth(), diffuse.img[i][midmapLevel].GetHeight());
363 // This texture should have 6 faces and 6 mipmaps
364 PbrDemo::CubeData specular;
365 PbrDemo::LoadCubeMapFromKtxFile(CUBEMAP_SPECULAR_TEXTURE_URL, specular);
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)
370 for(unsigned int i = 0; i < specular.img.size(); ++i)
372 specularTexture.Upload(specular.img[i][midmapLevel], CubeMapLayer::POSITIVE_X + i, midmapLevel, 0, 0, specular.img[i][midmapLevel].GetWidth(), specular.img[i][midmapLevel].GetHeight());
376 mModel[0].InitTexture(textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture);
377 mModel[1].InitTexture(textureAlbedoMetal, textureNormalRough, diffuseTexture, specularTexture);
378 mSkybox.InitTexture(specularTexture);
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
387 bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
389 Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
390 FILE* file = fileStream.GetFile();
396 bool retValue = false;
397 if(!fseek(file, 0, SEEK_END))
399 long int size = ftell(file);
402 (!fseek(file, 0, SEEK_SET)))
404 output.resize(size + 1);
405 std::fill(output.begin(), output.end(), 0);
406 ssize_t result = fread(output.data(), size, 1, file);
408 retValue = (result >= 0);
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
421 Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
424 std::vector<char> bufV, bufF;
426 if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
428 if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
430 shader = Shader::New(bufV.data(), bufF.data());
437 Application& mApplication;
442 Animation mAnimation;
443 Timer mDoubleTapTime;
451 Quaternion mModelOrientation;
458 int DALI_EXPORT_API main(int argc, char** argv)
460 Application application = Application::New(&argc, &argv);
461 BasicPbrController test(application);
462 application.MainLoop();