diff --git a/README.md b/README.md
index 15fe2baf506de8173963919e7821a794b1efde05..6389f33100b6a90fd99819b70a3f6fdcb27d3ad4 100644
--- a/README.md
+++ b/README.md
@@ -1,145 +1,27 @@
-<a href="https://git.io/liblava"><img align="left" src="https://github.com/liblava.png" width="200" style="margin:0px 40px 0px 0px"></a>
-
-šŸŒ‹ **[liblava](https://git.io/liblava) &nbsp; A modern and easy-to-use library for the <a href="https://www.khronos.org/vulkan/" target="_blank">Vulkan</a>Ā® API**
-
-**lava** is a lean framework that provides **essentials** for **low-level graphics**<br />and is specially well suited for **prototyping**, **tooling** and **education**
-
-<br />
-
-**C++20** &nbsp; + &nbsp; **Modular** &nbsp; / &nbsp; **Windows** &nbsp; + &nbsp; **Linux** &nbsp; / &nbsp; **[Demos](#demos)** &nbsp; + &nbsp; **[Projects](#projects)**
-
-[![Version](https://img.shields.io/badge/Version-0.6.2-blue)](https://git.io/liblava) [![License](https://img.shields.io/github/license/liblava/liblava)](LICENSE) [![CodeFactor](https://www.codefactor.io/repository/github/liblava/liblava/badge)](https://www.codefactor.io/repository/github/liblava/liblava) [![Discord](https://img.shields.io/discord/439508141722435595)](https://discord.lava-block.com) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/liblava) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow)](https://twitter.com/liblava)
-
-<br />
-
-### [Features](doc/Features.md)
-
-* written in **modern C++** with latest **Vulkan** support
-* **run loop** abstraction for **window** and **input** handling
-* plain **renderer** and **command buffer** model
-* **texture** and **mesh** loading from virtual **file system**
-* **GUI** + **camera** + **logging** + **utils** and much more
-
-<br />
-
-##### āžœ Download latest **<a href="https://github.com/liblava/liblava/releases/latest">demo</a>** (December 20, 2020)
-
-<br />
-
-<a href="https://www.khronos.org/vulkan/" target="_blank"><img align="right" hspace="20" src="res/Vulkan_170px_Dec16.png" width="300"></a>
-
-[Requirements](#requirements) &nbsp; [Build](#build) &nbsp; [Template](#template)
-
-<br />
-
-### Docs
-
- **[Tutorial](doc/Tutorial.md)** &nbsp; [Guide](doc/Guide.md) &nbsp; [Reference](doc/Reference.md) &nbsp; [Tests](doc/Tests.md) &nbsp; [Third-Party](doc/Third-Party.md) &nbsp; [Install](doc/Install.md)
-
-<br />
-
-### [Modules](doc/Modules.md)
-
-[![core](https://img.shields.io/badge/lava-core-blue.svg)](liblava/core) [![util](https://img.shields.io/badge/lava-util-blue.svg)](liblava/util) [![file](https://img.shields.io/badge/lava-file-blue.svg)](liblava/file) &nbsp; [![base](https://img.shields.io/badge/lava-base-orange.svg)](liblava/base) [![resource](https://img.shields.io/badge/lava-resource-orange.svg)](liblava/resource) [![asset](https://img.shields.io/badge/lava-asset-orange.svg)](liblava/asset) &nbsp; [![frame](https://img.shields.io/badge/lava-frame-red.svg)](liblava/frame) [![block](https://img.shields.io/badge/lava-block-red.svg)](liblava/block) &nbsp; [![app](https://img.shields.io/badge/lava-app-brightgreen.svg)](liblava/app) [![demo](https://img.shields.io/badge/lava-demo-brightgreen.svg)](liblava-demo)
-
-<br />
-
-### Demos
-
-##### [lava spawn](liblava-demo/spawn.cpp) āžœ uniform buffer camera
-
-<a href="liblava-demo/spawn.cpp">![spawn](res/spawn/screenshot.png)</a>
-
-<br />
-
-##### [lava lamp](liblava-demo/lamp.cpp) āžœ push constants to shader
-
-<a href="liblava-demo/lamp.cpp">![lamp](res/lamp/screenshot.png)</a>
-
-<br />
-
-##### [lava triangle](liblava-demo/triangle.cpp) āžœ classic colored mesh
-
-<a href="liblava-demo/triangle.cpp">![triangle](res/triangle/screenshot.png)</a>
-
-<br />
-
-### Projects
-
-##### [raytracing cubes](https://github.com/pezcode/lava-rt/blob/main/demo/cubes.cpp) āžœ raytraced reflecting cubes ([pezcode/lava-rt](https://github.com/pezcode/lava-rt))
-
-<a href="https://github.com/pezcode/lava-rt/blob/main/demo/cubes.cpp">![cubes](https://raw.githubusercontent.com/pezcode/lava-rt/main/demo/res/cubes/screenshot.png)</a>
-
-<br />
-
-## Requirements
-
-* **C++20** compatible compiler
-* CMake **3.20+**
-* [Vulkan SDK](https://vulkan.lunarg.com)
-
-<br />
-
-## Build
-
-[![Build status](https://ci.appveyor.com/api/projects/status/gxvjpo73qf637hy3?svg=true)](https://ci.appveyor.com/project/TheLavaBlock/liblava) [![Build Status](https://travis-ci.com/liblava/liblava.svg?branch=master)](https://travis-ci.com/liblava/liblava)
-
-```bash
-git clone https://github.com/liblava/liblava.git
-cd liblava
-
-git submodule update --init --recursive
-
-mkdir build
-cd build
-
-cmake ..
-make
-```
-
-<br />
-
-## Template
-
-You can start coding with the **template** project. If you like you can rename it in [CMakeLists](CMakeLists.txt)
-
-āžœ Just put your code in the [src](src) folder. Everything you need is in [main.cpp](src/main.cpp)
-
-<br />
-
-## Collaborate
-
-Use the [issue tracker](https://github.com/liblava/liblava/issues) to report any bug or compatibility issue
-
-:heart: Thanks to all **[contributors](https://github.com/liblava/liblava/graphs/contributors)** making **liblava** flow...
-
-<br />
-
-### Support
-
-If you want to contribute, we suggest the following:
-
-1. Fork the [official repository](https://github.com/liblava/liblava/fork)
-2. Apply your changes to your fork
-3. Submit a [pull request](https://github.com/liblava/liblava/pulls) describing the changes you have made
-
-<br />
-
-## License
-
-<a href="https://opensource.org" target="_blank"><img align="right" width="90" src="http://opensource.org/trademarks/opensource/OSI-Approved-License-100x137.png" style="margin:0px 0px 0px 80px"></a>
-
-**liblava** is licensed under [MIT License](LICENSE.md) which allows you to use the software for any purpose you might like, including commercial and for-profit use!
-
-<br />
-
-However, this library includes several [Third-Party](doc/Third-Party.md) libraries, which are licensed under their own respective **Open Source** licenses āžœ They all allow static linking with closed source software
-
-**All copies of liblava must include a copy of the MIT License terms and the copyright notice**
-
-##### Vulkan and the Vulkan logo are trademarks of the <a href="http://www.khronos.org" target="_blank">Khronos Group Inc.</a>
-##### Copyright (c) 2018-present, <a href="https://lava-block.com">Lava Block OÜ</a> and [contributors](https://github.com/liblava/liblava/graphs/contributors)
-
-<br />
-
-<a href="https://git.io/liblava"><img src="https://github.com/liblava.png" width="50"></a>
+# Virtual Reality Rendering Framework
+This framework is based on [liblava](https://github.com/liblava/liblava) and was extended to include features necessary for developing and evaluating new stereo rendering strategies.
+A special focus of the framework lies on remote rendering for standalone consumer HMDs over WiFi.
+
+In order to support multiple APIs as well as local and remote rendering a [Headset interface](src/headset/headset.hpp) was introduced with implementations for
+* [OpenXR Headset](src/headset/openxr_headset.hpp), which uses the [OpenXR standard](https://www.khronos.org/openxr/) developed by Khronos to communicate with an HMD.
+* [OpenVR Headset (legacy)](src/headset/openvr_headset.hpp), which uses the [OpenVR API](https://github.com/ValveSoftware/openvr) developed by Valve to communicate with HMDs via the SteamVR platform.
+* [Remote Headset](src/headset/remote_headset.hpp), which uses custom protocol to communicate with a standalone HMD running a [custom application](https://git-ce.rwth-aachen.de/vr-vis/VR-Group/hmd-streaming). 
+* [Emulated Headset](src/headset/emulated_headset.hpp), which is a fallback solution to test out the rendering techniques without the need to having an actual HMD attached.
+
+## Rendering
+This framework is intended to investiage different stereo rendering strategies. 
+Thus, a [stereo strategy interface](src/strategy/stereo_strategy.hpp) was created to easiliy switch between different strategies and compare them.
+Currently, there exist the following stereo strategies:
+* [Naive Stereo Forward](src/strategy/native_stereo_forward.hpp): renders the image for one eye at a time using forward shading.
+* [Naive Stereo Deferred](src/strategy/native_stereo_deferred.hpp): renders the image for one eye at a time using deferred shading.
+* [Multi View Stereo](src/strategy/native_stereo_deferred.hpp): renders both images simulatenously using multi-view.
+* [Depth Peeling Reprojection](src/strategy/depth_peeling_reprojection.hpp): a custom rendering technique desribed below.
+
+In general, the framework supports [shadow mapping](src/utility/shadow_cache.hpp) and approximate global illumination using [light propagation volumes](src/utility/indirect_cache.hpp).
+For evaluation of the performance and quality of the different rendering techniques the framework provides utility functions for [measuring gpu times](src/utility/pass_timer.hpp) and [capturing images](src/utility/frame_capture.hpp) for an external comparison to a ground truth.
+
+## Depth Peeling Reprojection
+Depth Peeling Reprojection is a rendering technique that aims to reduce the duplicate shading that occurs when rendering images for the left and right eye in virtual reality applications.
+Instead of rendering the scene from two perspectives, it will render the first two layers from a single perspective similar to [Mara et. al, Deep G-Buffers for Stable Global Illumination Approximation](https://research.nvidia.com/publication/2016-06_deep-g-buffers-stable-global-illumination-approximation).
+The goal of this approach is to have more information available when reprojecting the resulting images and, thus, having less artifacts due to disoccluded regions.
+Especially when considering streaming the result wirelessly to remote clients it is critical to have reprojection strategies that can handle lost or delayed frames nicely.
diff --git a/ext/assimp b/ext/assimp
index 67eae8ee5afa149b11267de8ec87de1538fa80b6..5b7ff294b83335557f2786ffe70642318851ae17 160000
--- a/ext/assimp
+++ b/ext/assimp
@@ -1 +1 @@
-Subproject commit 67eae8ee5afa149b11267de8ec87de1538fa80b6
+Subproject commit 5b7ff294b83335557f2786ffe70642318851ae17
diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp
index 99f7d1aa3c0c27336a330f40247cb321a296534f..43b29bf0cc2718a214b31be348c46d121db9f3a6 100644
--- a/src/encoder/encoder.cpp
+++ b/src/encoder/encoder.cpp
@@ -95,8 +95,10 @@ bool shutdown_encoder(EncoderType encoder_type)
     {
     case ENCODER_TYPE_VULKAN:
         shutdown_vulkan_encoder();
+        break;
     case ENCODER_TYPE_NVIDIA:
         shutdown_nvidia_encoder();
+        break;
     default:
         lava::log()->error("Unkown encoder type!");
         return false;
diff --git a/src/encoder/nvidia_encoder.cpp b/src/encoder/nvidia_encoder.cpp
index 65adb4292e813cbf0c9aba405666abe8eac146f8..db1656d39fca9f70ad3e30ac779900131bced9a9 100644
--- a/src/encoder/nvidia_encoder.cpp
+++ b/src/encoder/nvidia_encoder.cpp
@@ -1158,7 +1158,8 @@ bool NvidiaEncoder::create_semaphore(NvidiaEncoderFrame::Ptr frame, lava::device
     #error "Not implemented for this platform!"
 #endif
     semaphore_description.flags = 0;
- 
+    memset(semaphore_description.reserved, 0, sizeof(semaphore_description.reserved));
+
     if (cuImportExternalSemaphore(&frame->cuda_external_semaphore, &semaphore_description) != CUDA_SUCCESS)
     {
         lava::log()->error("Nvidia Encoder: Can't import semaphore!");
diff --git a/src/headset/emulated_headset.cpp b/src/headset/emulated_headset.cpp
index 30925cbf152ed40e6706f496040b483d1c401be1..35a8b299b30934189320e95bc71cffa4b073161f 100644
--- a/src/headset/emulated_headset.cpp
+++ b/src/headset/emulated_headset.cpp
@@ -93,7 +93,7 @@ void EmulatedHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout
         image_barriers.push_back(frame_barrier);
     }
 
-    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
 
     VkImageSubresourceLayers subresource_layers;
     subresource_layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp
index 373f3e66d5967c8a06ce8a4e803f537911e969b1..aa5702dd9402ab541ccb16fcc2b11b175b024100 100644
--- a/src/headset/remote_headset.cpp
+++ b/src/headset/remote_headset.cpp
@@ -38,12 +38,20 @@ bool RemoteHeadset::on_setup_device(lava::device::create_param& parameters)
 
 bool RemoteHeadset::on_create()
 {
+    const CommandParser& command_parser = this->get_application()->get_command_parser();
+    this->encoder_mode = command_parser.get_encoder_mode().value_or((EncoderMode)this->encoder_mode);
+    this->encoder_input_rate = command_parser.get_encoder_input_rate().value_or(this->encoder_input_rate);
+    this->encoder_key_rate = command_parser.get_encoder_key_rate().value_or(this->encoder_key_rate);
+    this->encoder_frame_rate = command_parser.get_encoder_frame_rate().value_or(this->encoder_frame_rate);
+    this->encoder_quality = command_parser.get_encoder_quality().value_or(this->encoder_quality);
+    this->encoder_bit_rate = command_parser.get_encoder_bitrate().value_or(this->encoder_bit_rate);
+
     StatisticLog::Ptr statistic_log = this->get_application()->get_statistic_log();
 
     this->statistic_send_bitrate = make_statistic("host_send_bitrate", UNIT_MBITS_PER_SECOND);
-    this->statistic_send_queue_size = make_statistic("host_send_queue", UNIT_KBITS);
+    this->statistic_send_queue_size = make_statistic("host_send_queue", UNIT_KBYTES);
     this->statistic_receive_bitrate = make_statistic("host_receive_bitrate", UNIT_MBITS_PER_SECOND);
-    this->statistic_receive_queue_size = make_statistic("host_receive_queue", UNIT_KBITS);
+    this->statistic_receive_queue_size = make_statistic("host_receive_queue", UNIT_KBYTES);
     
     statistic_log->add_statistic(this->statistic_send_bitrate);
     statistic_log->add_statistic(this->statistic_send_queue_size);
@@ -116,7 +124,7 @@ bool RemoteHeadset::on_interface()
         return false;
     }
 
-    ImGui::DragFloat("Movement Speed", &this->movement_speed, 1.0f, 0.0f, 1000.0);
+    ImGui::DragFloat("Movement Speed", &this->movement_speed, 1.0f, 0.0f, 1000.0f);
 
     ImGui::Separator();
 
@@ -137,11 +145,11 @@ bool RemoteHeadset::on_interface()
         }
     }
 
-    ImGui::SliderInt("Input-Rate (Fps)", (int32_t*) &this->encoder_input_rate, 1, 180);
+    ImGui::SliderInt("Input-Rate [Frames/s]", (int32_t*) &this->encoder_input_rate, 1, 180);
 
     if (encoder->is_supported(ENCODER_SETTING_KEY_RATE))
     {
-        if (ImGui::SliderInt("Key-Rate (Frames)", (int32_t*) &this->encoder_key_rate, 1, 180))
+        if (ImGui::SliderInt("Key-Rate [Frames]", (int32_t*) &this->encoder_key_rate, 1, 180))
         {
             for (Encoder::Ptr encoder : this->encoders)
             {
@@ -154,7 +162,7 @@ bool RemoteHeadset::on_interface()
     {
         if (encoder->is_supported(ENCODER_SETTING_BITRATE))
         {
-            if (ImGui::SliderFloat("Bit-Rate (Mbps)", &this->encoder_bit_rate, 0.1, 10.0))
+            if (ImGui::SliderFloat("Bit-Rate [Mbit/s]", &this->encoder_bit_rate, 0.1f, 100.0f))
             {
                 for (Encoder::Ptr encoder : this->encoders)
                 {
@@ -165,7 +173,7 @@ bool RemoteHeadset::on_interface()
       
         if (encoder->is_supported(ENCODER_SETTING_FRAME_RATE))
         {
-            if (ImGui::SliderInt("Frame-Rate (Fps)", (int32_t*) &this->encoder_frame_rate, 1, 180))
+            if (ImGui::SliderInt("Frame-Rate [Frames/s]", (int32_t*) &this->encoder_frame_rate, 1, 180))
             {
                 for (Encoder::Ptr encoder : this->encoders)
                 {
@@ -179,7 +187,7 @@ bool RemoteHeadset::on_interface()
     {
         if (encoder->is_supported(ENCODER_SETTING_QUALITY))
         {
-            if (ImGui::SliderFloat("Quality", &this->encoder_quality, 0.0, 1.0))
+            if (ImGui::SliderFloat("Quality", &this->encoder_quality, 0.0f, 1.0f))
             {
                 for (Encoder::Ptr encoder : this->encoders)
                 {
@@ -196,11 +204,6 @@ bool RemoteHeadset::on_interface()
         return false;
     }
 
-    if (ImGui::SliderInt("Send-Queue Limit (KBytes)", (int32_t*)&this->send_queue_size_limit, 1, 1000))
-    {
-        this->transport->set_max_send_queue_size(this->send_queue_size_limit * 1000);
-    }
-
     ImGui::Separator();
 
     if(ImGui::Checkbox("Overlay", &this->overlay_enable))
@@ -417,8 +420,6 @@ bool RemoteHeadset::create_transport()
         this->on_transport_error();
     });
 
-    this->transport->set_max_send_queue_size(this->send_queue_size_limit * 1000);
-
     if (!this->transport->create(this->server_port))
     {
         return false;
@@ -493,7 +494,7 @@ bool RemoteHeadset::create_encoders()
         encoder->set_key_rate(this->encoder_key_rate);
         encoder->set_frame_rate(this->encoder_frame_rate);
 
-        EncoderCodec codec = this->get_application()->get_command_parser().get_codec();
+        EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec();
         lava::device_ptr device = this->get_application()->get_device();
         lava::renderer& renderer = this->get_application()->get_renderer();
         
@@ -512,7 +513,7 @@ bool RemoteHeadset::create_encoders()
             std::filesystem::create_directory(this->video_directory);
         }
 
-        EncoderCodec codec = this->get_application()->get_command_parser().get_codec();
+        EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec();
         std::string file_extension;
 
         switch(codec)
@@ -675,7 +676,7 @@ void RemoteHeadset::update_overlay()
 
 void RemoteHeadset::on_setup(const glm::u32vec2& resolution)
 {
-    EncoderCodec codec = this->get_application()->get_command_parser().get_codec();
+    EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec();
 
     this->transport->send_setup_complete(this->remote_strategy, codec, this->frame_id_count, this->near_plane, this->far_plane);
 
diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp
index 02d97c42b110b0ee58fde8040cadbd19e96ced23..c5acbde69340af0972487ee7d0b29c07fcf9b966 100644
--- a/src/headset/remote_headset.hpp
+++ b/src/headset/remote_headset.hpp
@@ -113,17 +113,15 @@ private:
 
     std::vector<Encoder::Ptr> encoders;
     std::vector<std::fstream> encoder_files;
-    uint32_t encoder_mode = ENCODER_MODE_CONSTANT_QUALITY;
+    uint32_t encoder_mode = ENCODER_MODE_CONSTANT_BITRATE;
     uint32_t encoder_input_rate = 90;
     uint32_t encoder_key_rate = 90;
     uint32_t encoder_frame_rate = 90;
     float encoder_quality = 0.0;
-    float encoder_bit_rate = 8.0;               //NOTE: Bitrate in Mbit per seconds
+    float encoder_bit_rate = 64.0;              //NOTE: Bitrate in Mbit per seconds
     float encoder_last_submit = 0.0f;
     bool encoder_enable_submit = true;
 
-    uint32_t send_queue_size_limit = 100;       //NOTE: Size in KBytes
-
     bool overlay_enable = false;
     bool overlay_enable_pressed = false;
     uint32_t overlay_graph = 0;
diff --git a/src/scene.cpp b/src/scene.cpp
index c757da3ec5a3d11b3aaf4534e40a36c5709cbb44..11404f1237266739cdde19c509f05fd5f5777743 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -58,7 +58,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, uint32_t
         //NOTE: Need to compute scene bounds for sky-sphere
         this->reset_transforms();
 
-        if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, device))
+        if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, config.sky_sphere_intensity, device))
         {
             return false;
         }
@@ -94,6 +94,9 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, uint32_t
         return false;
     }
 
+    this->exposure = config.scene_exposure;
+    this->exposure_changed = true;
+
     //NOTE: Recompute all transfromations for final scene bounds and matrices
     this->reset_transforms();
     
@@ -233,7 +236,7 @@ bool Scene::update(lava::delta delta_time, Headset::Ptr headset)
     if (this->animation_active && !this->animations.empty())
     {
         const SceneAnimation& active_animation = this->animations[this->animation_index];
-        this->animation_time += delta_time * active_animation.ticks_per_second * this->animation_playback_speed;
+        this->animation_time += delta_time * this->animation_playback_speed;
 
         if (this->animation_loop)
         {
@@ -978,11 +981,11 @@ bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneO
     return true;
 }
 
-bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device)
+bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, float intensity, lava::device_ptr device)
 {
     lava::log()->info("Loading sky-sphere file '{}'", file_name.c_str());
 
-    if (!this->load_texture(file_name, device, true, false, this->sky_emissive_texture))
+    if (!this->load_texture(file_name, device, true, false, false, this->sky_emissive_texture))
     {
         lava::log()->error("Faild to load sky-sphere '{}'", file_name.c_str());
 
@@ -999,7 +1002,7 @@ bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, u
     glsl::MaterialData& sky_material_data = sky_material.material_data;
     sky_material_data.base_color = glm::vec3(1.0f);
     sky_material_data.opacity = 1.0f;
-    sky_material_data.emissive = glm::vec3(1.0f);
+    sky_material_data.emissive = glm::vec3(intensity);
     sky_material_data.roughness = 1.0f;
     sky_material_data.padding1 = glm::uvec3(0);
     sky_material_data.metallic = 1.0f;
@@ -1171,8 +1174,7 @@ bool Scene::load_animations(const aiScene* scene, float scale, const std::map<st
         SceneAnimation& scene_animation = this->animations.emplace_back();
 
         scene_animation.name = animation->mName.C_Str();
-        scene_animation.duration = animation->mDuration;
-        scene_animation.ticks_per_second = animation->mTicksPerSecond;
+        scene_animation.duration = animation->mDuration / animation->mTicksPerSecond;
         scene_animation.channels.resize(animation->mNumChannels);
 
         for (uint32_t channel_index = 0; channel_index < animation->mNumChannels; channel_index++)
@@ -1195,19 +1197,19 @@ bool Scene::load_animations(const aiScene* scene, float scale, const std::map<st
             for (uint32_t frame_index = 0; frame_index < channel->mNumPositionKeys; frame_index++)
             {
                 const aiVectorKey key = channel->mPositionKeys[frame_index];
-                scene_channel.position_frames[frame_index] = std::make_pair((float)key.mTime, glm::make_vec3(&key.mValue.x) * scale);
+                scene_channel.position_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, glm::make_vec3(&key.mValue.x) * scale);
             }
 
             for (uint32_t frame_index = 0; frame_index < channel->mNumRotationKeys; frame_index++)
             {
                 const aiQuatKey key = channel->mRotationKeys[frame_index];
-                scene_channel.rotation_frames[frame_index] = std::make_pair((float)key.mTime, key.mValue);
+                scene_channel.rotation_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, key.mValue);
             }
 
             for (uint32_t frame_index = 0; frame_index < channel->mNumScalingKeys; frame_index++)
             {
                 const aiVectorKey key = channel->mScalingKeys[frame_index];
-                scene_channel.scaling_frames[frame_index] = std::make_pair((float)key.mTime, glm::make_vec3(&key.mValue.x));
+                scene_channel.scaling_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, glm::make_vec3(&key.mValue.x));
             }
         }
     }
@@ -1228,15 +1230,6 @@ bool Scene::load_lights(const aiScene* scene, float scale, const std::map<std::s
             return false;
         }
 
-        float light_scale = 1.0;
-
-        if (light->mType == aiLightSource_DIRECTIONAL)
-        {
-            //NOTE: If the scene is in centimeters assume that directional light intensity is given as watt / cm^2.
-            //      In this case the intensity has to be converted into watt / meter^2
-            light_scale = glm::pow(scale, 1); //It should be a 2 instead of a 1
-        }
-
         glm::vec3 color = glm::make_vec3(&light->mColorDiffuse.r);
         glm::vec3 position = glm::make_vec3(&light->mPosition.x);
         glm::vec3 direction = glm::make_vec3(&light->mDirection.x);
@@ -1254,7 +1247,7 @@ bool Scene::load_lights(const aiScene* scene, float scale, const std::map<std::s
 
         glsl::LightData& light_data = scene_light.data;
         light_data.position = scene_light.initial_position;
-        light_data.color = color * light_scale;
+        light_data.color = color;
         light_data.outer_angle = light->mAngleOuterCone;
         light_data.direction = scene_light.initial_direction;
         light_data.inner_angle = light->mAngleInnerCone;
@@ -1357,7 +1350,6 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
     {
         const aiMaterial* material = scene->mMaterials[index];
         SceneMaterial& scene_material = this->materials.emplace_back();
-        scene_material.emissive = this->dummy_emissive_texture;
 
         aiColor3D diffuse_color = aiColor3D(0.0f, 0.0f, 0.0f);
         material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse_color);
@@ -1377,16 +1369,16 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
         glsl::MaterialData& material_data = scene_material.material_data;
         material_data.base_color = glm::vec3(1.0f);
         material_data.opacity = 1.0f;
-        material_data.emissive = glm::make_vec3(&emissive_color.r);
+        material_data.emissive = glm::vec3(1.0f);
         material_data.roughness = 1.0f;
         material_data.padding1 = glm::uvec3(0);
-        material_data.metallic = 0.0f;
+        material_data.metallic = 1.0f;
         material_data.padding2 = glm::uvec4(0);
         material_data.padding3 = glm::mat4(0);
         material_data.padding4 = glm::mat4(0);
         material_data.padding5 = glm::mat4(0);
 
-        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, device, scene_material.diffuse))
+        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, true, device, scene_material.diffuse))
         {
             scene_material.diffuse = this->dummy_diffuse_texture;
 
@@ -1394,7 +1386,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.opacity = glm::clamp(opacity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, device, scene_material.specular))
+        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, false, device, scene_material.specular))
         {
             scene_material.specular = this->dummy_specular_texture;
 
@@ -1402,16 +1394,23 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.metallic = glm::clamp(reflectivity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, device, scene_material.normal))
+        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, false, device, scene_material.normal))
         {
             scene_material.normal = this->dummy_normal_texture;
         }
+
+        if (!this->load_texture(material, base_name, aiTextureType_EMISSIVE, false, device, scene_material.emissive))
+        {
+            scene_material.emissive = this->dummy_emissive_texture;
+
+            material_data.emissive = glm::make_vec3(&emissive_color.r);
+        }
     }
 
     return true;
 }
 
-bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture)
+bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, bool use_srgb, lava::device_ptr device, lava::texture::ptr& texture)
 {
     aiString texture_path;
 
@@ -1433,7 +1432,7 @@ bool Scene::load_texture(const aiMaterial* material, const std::string& base_nam
     std::string file_name = (base_path / texture_path.C_Str()).string();
     std::replace(file_name.begin(), file_name.end(), '\\', '/');
     
-    if (!this->load_texture(file_name, device, false, true, texture))
+    if (!this->load_texture(file_name, device, false, true, use_srgb, texture))
     {
         lava::log()->warn("Faild to load texture file '{}'. Using dummy texture instead.", file_name.c_str());
 
@@ -1443,7 +1442,7 @@ bool Scene::load_texture(const aiMaterial* material, const std::string& base_nam
     return true;
 }
 
-bool Scene::load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture)
+bool Scene::load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, bool use_srgb, lava::texture::ptr& texture)
 {
     for (const SceneTexture& scene_texture : this->textures)
     {
@@ -1506,11 +1505,29 @@ bool Scene::load_texture(const std::string& file_name, lava::device_ptr device,
 
         switch (texture.format())
         {
+        case gli::FORMAT_RGBA_DXT1_SRGB_BLOCK8:
         case gli::FORMAT_RGBA_DXT1_UNORM_BLOCK8:
-            format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_BC1_RGBA_SRGB_BLOCK;
+            }
+
+            else
+            {
+                format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+            }
             break;
+        case gli::FORMAT_RGBA_DXT5_SRGB_BLOCK16:
         case gli::FORMAT_RGBA_DXT5_UNORM_BLOCK16:
-            format = VK_FORMAT_BC3_UNORM_BLOCK;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_BC3_SRGB_BLOCK;
+            }
+
+            else
+            {
+                format = VK_FORMAT_BC3_UNORM_BLOCK;
+            }
             break;
         case gli::FORMAT_RG_ATI2N_UNORM_BLOCK16:
             format = VK_FORMAT_BC5_UNORM_BLOCK;
@@ -1559,7 +1576,15 @@ bool Scene::load_texture(const std::string& file_name, lava::device_ptr device,
 
         else
         {
-            format = VK_FORMAT_R8G8B8A8_SRGB;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_R8G8B8A8_SRGB;
+            }
+
+            else
+            {
+                format = VK_FORMAT_R8G8B8A8_UNORM;
+            }
 
             data_pointer = (uint8_t*) stbi_load_from_memory((const stbi_uc*) image_data.ptr, image_data.size, &width, &height, nullptr, STBI_rgb_alpha);
             data_bitdepth = 4 * sizeof(uint8_t);
@@ -2114,7 +2139,7 @@ void Scene::apply_transforms()
         const SceneNode& node = this->nodes[camera.node_index];
 
         glm::vec3 global_location = node.current_global_transform * glm::vec4(camera.local_position, 1.0f);
-        glm::vec3 global_look_at = node.current_global_transform * glm::vec4(camera.local_look_at, 0.0f);
+        glm::vec3 global_look_at = node.current_global_transform * glm::vec4(camera.local_look_at, 1.0f);
         glm::vec3 global_up = node.current_global_transform * glm::vec4(camera.local_up, 0.0f);
 
         camera.view_matrix = glm::lookAt(global_location, global_look_at, global_up);
diff --git a/src/scene.hpp b/src/scene.hpp
index 6c6dd0e2660a7b318e30bd664a58971bc135a35d..32ea34cc6f6798e190862277c2c7b03420e53894 100644
--- a/src/scene.hpp
+++ b/src/scene.hpp
@@ -43,9 +43,12 @@ struct SceneConfig
     std::string controller_left_file;
     std::string controller_right_file;
 
+    float scene_exposure = 1.0f;
+
     std::string sky_sphere_file;                //NOTE: The given file has to be a .hdr file.
     uint32_t sky_sphere_rings = 16;             //NOTE: Defines the number of rings used in the mesh of the sky-sphere. Needs to be at least 3.
     uint32_t sky_sphere_segments = 32;          //NOTE: Defines the number of segments used in the mesh of the sky-sphere. Needs to be at least 3.
+    float sky_sphere_intensity = 1.0f;
 
     SceneUnit scene_unit = SCENE_UNIT_METERS;
     SceneUnit controller_left_unit = SCENE_UNIT_METERS;
@@ -142,7 +145,6 @@ struct SceneAnimation
 {
     std::string name;
     float duration = 0.0f;
-    float ticks_per_second = 0.0f;
     std::vector<SceneAnimationChannel> channels;
 };
 
@@ -205,7 +207,7 @@ private:
 
     bool load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, SceneUVs uvs, lava::device_ptr device, std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, SceneUVs uvs, lava::device_ptr device, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices);
-    bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device);
+    bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, float intensity, lava::device_ptr device);
 
     bool load_nodes(const aiNode* node, float scale, uint32_t base_mesh, SceneNodeIndex parent_index, std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_cameras(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
@@ -213,8 +215,8 @@ private:
     bool load_lights(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_meshes(const aiScene* scene, float scale, uint32_t base_material, bool scene_bounds, lava::device_ptr device);
     bool load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device);
-    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture);
-    bool load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture);
+    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, bool use_srgb, lava::device_ptr device, lava::texture::ptr& texture);
+    bool load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, bool use_srgb, lava::texture::ptr& texture);
 
     bool stage_textures(lava::device_ptr device);
 
diff --git a/src/transport/transport.hpp b/src/transport/transport.hpp
index 580ed5d670b4494bf7e4291ad9790235e2421125..cafde57896524ad5659d7031cec5e3e6e9155b56 100644
--- a/src/transport/transport.hpp
+++ b/src/transport/transport.hpp
@@ -181,7 +181,8 @@ public:
     virtual void set_on_transport_error(OnTransportError function) = 0;
 
     // The following functions should be thread safe
-    virtual void set_max_send_queue_size(uint32_t size) = 0; // The size is given in Bytes
+    virtual void set_max_send_queue_enabled(bool enabled) = 0; // Default: disabled
+    virtual void set_max_send_queue_size(uint32_t size) = 0;   // The size is given in Bytes. Default: 128 * DATAGRAM_SIZE
 
     // The following function should be thread safe
     virtual TransportState get_state() = 0;
@@ -190,6 +191,7 @@ public:
     virtual uint32_t get_max_send_queue_size() = 0; // The size is given in Bytes
     virtual uint32_t get_send_queue_size() = 0;     // In Bytes
     virtual uint32_t get_receive_queue_size() = 0;  // In Bytes
+    virtual bool is_max_send_queue_enabled() = 0;
 };
 
 Transport::Ptr make_transport(TransportType transport_type);
\ No newline at end of file
diff --git a/src/transport/udp_transport.cpp b/src/transport/udp_transport.cpp
index e10ac8139a191215173d8e6a19e3abde0a04551e..9ef30b3e3f8bfadc82cc325143d7636072fcb3ce 100644
--- a/src/transport/udp_transport.cpp
+++ b/src/transport/udp_transport.cpp
@@ -391,6 +391,12 @@ void UDPTransport::set_on_transport_error(OnTransportError function)
     this->on_transport_error = std::move(function);
 }
 
+void UDPTransport::set_max_send_queue_enabled(bool enabled)
+{
+    std::unique_lock<std::mutex> lock(this->worker_mutex);
+    this->max_send_queue_enabled = enabled;
+}
+
 void UDPTransport::set_max_send_queue_size(uint32_t size)
 {
     std::unique_lock<std::mutex> lock(this->worker_mutex);
@@ -463,6 +469,15 @@ uint32_t UDPTransport::get_receive_queue_size()
     return bytes;
 }
 
+bool UDPTransport::is_max_send_queue_enabled()
+{
+    std::unique_lock<std::mutex> lock(this->worker_mutex);
+    bool value = this->max_send_queue_enabled;
+    lock.unlock();
+
+    return value;
+}
+
 void UDPTransport::set_state(TransportState state)
 {
     std::unique_lock<std::mutex> lock(this->worker_mutex);
@@ -529,23 +544,26 @@ void UDPTransport::send_datagram(const Datagram& datagram)
 {
     std::unique_lock<std::mutex> lock(this->worker_mutex);
 
-    uint32_t bytes = 0;
-
-    for (uint32_t index = 1; index < this->send_queue.size(); index++)
+    if (this->max_send_queue_enabled)
     {
-        const Datagram& datagram = this->send_queue[index];
-        const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer;
-        bytes += header->size;
-    }
+        uint32_t bytes = 0;
 
-    while (bytes > this->max_send_queue_size && this->send_queue.size() > 1)
-    {
-        const Datagram& datagram = this->send_queue[1];
-        const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer;
-        bytes -= header->size;
+        for (uint32_t index = 1; index < this->send_queue.size(); index++)
+        {
+            const Datagram& datagram = this->send_queue[index];
+            const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer;
+            bytes += header->size;
+        }
 
-        this->datagram_pool.release_datagram(datagram);
-        this->send_queue.erase(this->send_queue.begin() + 1);
+        while (bytes > this->max_send_queue_size && this->send_queue.size() > 1)
+        {
+            const Datagram& datagram = this->send_queue[1];
+            const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer;
+            bytes -= header->size;
+
+            this->datagram_pool.release_datagram(datagram);
+            this->send_queue.erase(this->send_queue.begin() + 1);
+        }
     }
 
     this->send_queue.push_back(datagram);
diff --git a/src/transport/udp_transport.hpp b/src/transport/udp_transport.hpp
index 5628fe137fc5157ec60eaf483a5a899eae950007..909918cd8e8fff774e45696edbb4d7499e969c1d 100644
--- a/src/transport/udp_transport.hpp
+++ b/src/transport/udp_transport.hpp
@@ -42,6 +42,7 @@ public:
     void set_on_performance_sample(OnPerformanceSample function) override;
     void set_on_transport_error(OnTransportError function) override;
 
+    void set_max_send_queue_enabled(bool enabled) override;
     void set_max_send_queue_size(uint32_t size) override;
 
     TransportState get_state() override;
@@ -50,6 +51,7 @@ public:
     uint32_t get_max_send_queue_size() override;
     uint32_t get_send_queue_size() override;
     uint32_t get_receive_queue_size() override;
+    bool is_max_send_queue_enabled() override;
 
 private:
     void set_state(TransportState state);
@@ -92,6 +94,7 @@ private:
     uint32_t bits_received = 0;                           //NOTE: Owned by worker_thread
     double bitrate_send = 0.0;                            //NOTE: Protected by worker_mutex
     double bitrate_receive = 0.0;                         //NOTE: Protected by worker_mutex
+    bool max_send_queue_enabled = false;                  //NOTE: Protected by worker_mutex
     uint32_t max_send_queue_size = DATAGRAM_SIZE * 128;   //NOTE: Protected by worker_mutex
     bool send_active = false;                             //NOTE: Protected by worker_mutex
     double inactive_time = 0.0;                           //NOTE: Owned by worker_thread
diff --git a/src/utility/command_parser.cpp b/src/utility/command_parser.cpp
index 2f370c8582a21574ded9c2652ccd1747565db536..3bf9d9943fc6db18f5efd4629bbe906239949810 100644
--- a/src/utility/command_parser.cpp
+++ b/src/utility/command_parser.cpp
@@ -76,6 +76,16 @@ bool CommandParser::parse_command(const argh::parser& cmd_line)
             this->sky_sphere_path = path;
         }
 
+        else if (parameter.first == "sky-sphere-intensity")
+        {
+            this->sky_sphere_intensity = std::atoi(parameter.second.c_str());
+        }
+
+        else if (parameter.first == "scene-exposure")
+        {
+            this->scene_exposure = std::atof(parameter.second.c_str());
+        }
+
         else if (parameter.first == "scene-unit")
         {
             if (!this->set_scene_unit(parameter.second))
@@ -132,14 +142,47 @@ bool CommandParser::parse_command(const argh::parser& cmd_line)
             }
         }
 
-        else if (parameter.first == "codec")
+        else if (parameter.first == "encoder-codec")
         {
-            if(!this->set_codec(parameter.second))
+            if(!this->set_encoder_codec(parameter.second))
             {
                 return false;
             }
         }
 
+        else if (parameter.first == "encoder-mode")
+        {
+            if (!this->set_encoder_mode(parameter.second))
+            {
+                return false;
+            }
+        }
+
+        else if (parameter.first == "encoder-input-rate")
+        {
+            this->encoder_input_rate = std::atoi(parameter.second.c_str());
+        }
+
+        else if (parameter.first == "encoder-key-rate")
+        {
+            this->encoder_key_rate = std::atoi(parameter.second.c_str());
+        }
+
+        else if (parameter.first == "encoder-frame-rate")
+        {
+            this->encoder_frame_rate = std::atoi(parameter.second.c_str());
+        }
+
+        else if (parameter.first == "encoder-quality")
+        {
+            this->encoder_quality = (float)std::atof(parameter.second.c_str());
+        }
+
+        else if (parameter.first == "encoder-bitrate")
+        {
+            this->encoder_bitrate = (float)std::atof(parameter.second.c_str());
+        }
+
         else if (parameter.first == "animation")
         {
             this->animation_name = parameter.second;
@@ -266,9 +309,9 @@ bool CommandParser::parse_command(const argh::parser& cmd_line)
         }
     }
 
-    if (this->encoder == ENCODER_TYPE_VULKAN && this->codec == ENCODER_CODEC_H265)
+    if (this->encoder == ENCODER_TYPE_VULKAN && this->encoder_codec == ENCODER_CODEC_H265)
     {
-        std::cout << "Configuration 'encoder = vulkan' and 'codec = h265' not supported. Use option --help to get more information" << std::endl;
+        std::cout << "Configuration 'encoder = vulkan' and 'encoder-codec = h265' not supported. Use option --help to get more information" << std::endl;
 
         return false;
     }
@@ -296,9 +339,9 @@ EncoderType CommandParser::get_encoder() const
     return this->encoder;
 }
 
-EncoderCodec CommandParser::get_codec() const
+EncoderCodec CommandParser::get_encoder_codec() const
 {
-    return this->codec;
+    return this->encoder_codec;
 }
 
 SceneUnit CommandParser::get_scene_unit() const
@@ -326,6 +369,16 @@ std::optional<std::string> CommandParser::get_sky_sphere_path() const
     return this->sky_sphere_path;
 }
 
+float CommandParser::get_scene_exposure() const
+{
+    return this->scene_exposure;
+}
+
+float CommandParser::get_sky_sphere_intensity() const
+{
+    return this->sky_sphere_intensity;
+}
+
 std::optional<std::string> CommandParser::get_animation_name() const
 {
     return this->animation_name;
@@ -351,6 +404,36 @@ std::optional<float> CommandParser::get_update_rate() const
     return this->update_rate;
 }
 
+std::optional<EncoderMode> CommandParser::get_encoder_mode() const
+{
+    return this->encoder_mode;
+}
+
+std::optional<uint32_t> CommandParser::get_encoder_input_rate() const
+{
+    return this->encoder_input_rate;
+}
+
+std::optional<uint32_t> CommandParser::get_encoder_key_rate() const
+{
+    return this->encoder_key_rate;
+}
+
+std::optional<uint32_t> CommandParser::get_encoder_frame_rate() const
+{
+    return this->encoder_frame_rate;
+}
+
+std::optional<float> CommandParser::get_encoder_quality() const
+{
+    return this->encoder_quality;
+}
+
+std::optional<float> CommandParser::get_encoder_bitrate() const
+{
+    return this->encoder_bitrate;
+}
+
 bool CommandParser::should_benchmark() const
 {
     return this->benchmark;
@@ -389,6 +472,10 @@ void CommandParser::show_help()
     std::cout << "Options:" << std::endl;
     std::cout << "   --sky-sphere={file}                  The sky sphere that should be shown. The given file has to be a .hdr file." << std::endl;
     std::cout << "                                        If no file is specified, sky stays black." << std::endl;
+    std::cout << "   --sky-sphere-intensity={intensity}   The value with which the sky sphere is multiplied." << std::endl;
+    std::cout << "                                        The default value is 1.0." << std::endl;
+    std::cout << "   --scene-exposure={exposure}          The exposure value that should be used during rendering." << std::endl;
+    std::cout << "                                        The default value is 1.0." << std::endl;
     std::cout << "   --scene-unit={unit}                  The unit in which the geometry of the scene is defined." << std::endl;
     std::cout << "                                        Options: meters (default), centimeters" << std::endl;
     std::cout << "   --scene-orientation={orientation}    The coordinate system in which the geometry of the scene is defined." << std::endl;
@@ -405,10 +492,24 @@ void CommandParser::show_help()
     std::cout << "   --encoder={encoder}                  The encoder that should be used when a remote headset is used." << std::endl;
     std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
     std::cout << "                                        Options: vulkan (default), nvidia" << std::endl;
-    std::cout << "   --codec={codec}                      The codec that should be used by the encoder." << std::endl;
+    std::cout << "   --encoder-codec={codec}              The codec that should be used by the encoder." << std::endl;
     std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
     std::cout << "                                        The vulkan encoder only supports h264 encoding." << std::endl;
     std::cout << "                                        Options: h264 (default), h265" << std::endl;
+    std::cout << "   --encoder-mode={mode}                The mode which determins the quality of the encoded images." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
+    std::cout << "                                        Options: constant-bitrate (default), constant-quality" << std::endl;
+    std::cout << "   --encoder-input-rate={rate}          The rate with which images are submitted to the encoders. The default value is 90 hz." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
+    std::cout << "   --encoder-key-rate={rate}            The rate with which the endoder inserts key-frames. The default value is 90 frames." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
+    std::cout << "   --encoder-frame-rate={rate}          The frame rate which is used to determine the quality when constant bitrate is used. The default value is 90 hz." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
+    std::cout << "   --encoder-quality={quailty}          The quality that is used in constant quality mode." << std::endl;
+    std::cout << "                                        A value of 0.0 specifies the lowest quality and a value of 1.0 specifies the highest quaility. The default value is 0.0." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
+    std::cout << "   --encoder-bitrate={bitrate}          The bitrate which should be used in constant bitrate mode. The default value is 25.0 Mbit/s." << std::endl;
+    std::cout << "                                        This parameter should only be used when the parameter headset is set to remote." << std::endl;
     std::cout << "   --benchmark                          Play animation once and close program after completion." << std::endl;
     std::cout << "                                        If not set, the application runs indefinitely and the interface is enabled." << std::endl;
     std::cout << "   --animation={animation_name}         The name of the animation that should be played." << std::endl;
@@ -535,21 +636,43 @@ bool CommandParser::set_encoder(const std::string& name)
     return true;
 }
 
-bool CommandParser::set_codec(const std::string& name)
+bool CommandParser::set_encoder_codec(const std::string& name)
 {
     if (name == "h264")
     {
-        this->codec = ENCODER_CODEC_H264;
+        this->encoder_codec = ENCODER_CODEC_H264;
     }
 
     else if(name == "h265")
     {
-        this->codec = ENCODER_CODEC_H265;
+        this->encoder_codec = ENCODER_CODEC_H265;
+    }
+
+    else
+    {
+        std::cout << "Invalid option set of parameter 'encoder-codec'. Use option --help to get more information." << std::endl;
+
+        return false;
+    }
+
+    return true;
+}
+
+bool CommandParser::set_encoder_mode(const std::string& name)
+{
+    if (name == "constant-quality")
+    {
+        this->encoder_mode = ENCODER_MODE_CONSTANT_QUALITY;
+    }
+
+    else if (name == "constant-bitrate")
+    {
+        this->encoder_mode = ENCODER_MODE_CONSTANT_BITRATE;
     }
 
     else
     {
-        std::cout << "Invalid option set of parameter 'codec'. Use option --help to get more information." << std::endl;
+        std::cout << "Invalid option set of parameter 'encoder-mode'. Use option --help to get more information." << std::endl;
 
         return false;
     }
diff --git a/src/utility/command_parser.hpp b/src/utility/command_parser.hpp
index c451afa89d4df3c278bbb21b979ea13cd1cfdfa3..6a123b5e7c7a22b9e68f42f203a1a09c188fb223 100644
--- a/src/utility/command_parser.hpp
+++ b/src/utility/command_parser.hpp
@@ -25,13 +25,15 @@ public:
     StereoStrategyType get_stereo_strategy() const;
     TransportType get_transport() const;
     EncoderType get_encoder() const;
-    EncoderCodec get_codec() const;
+    EncoderCodec get_encoder_codec() const;
     SceneUnit get_scene_unit() const;
     SceneOrientation get_scene_orientation() const;
     SceneUVs get_scene_uvs() const;
 
     const std::string& get_scene_path() const;
     std::optional<std::string> get_sky_sphere_path() const;
+    float get_scene_exposure() const;
+    float get_sky_sphere_intensity() const;
 
     std::optional<std::string> get_animation_name() const;
     std::optional<uint32_t> get_animation_index() const;
@@ -41,6 +43,13 @@ public:
 
     std::optional<float> get_update_rate() const;
 
+    std::optional<EncoderMode> get_encoder_mode() const;
+    std::optional<uint32_t> get_encoder_input_rate() const;
+    std::optional<uint32_t> get_encoder_key_rate() const;
+    std::optional<uint32_t> get_encoder_frame_rate() const;
+    std::optional<float> get_encoder_quality() const;
+    std::optional<float> get_encoder_bitrate() const;
+
     bool should_benchmark() const;
     bool should_show_companion_window() const;
     bool should_write_frames() const;
@@ -55,7 +64,8 @@ private:
     bool set_stero_strategy(const std::string& name);
     bool set_transport(const std::string& name);
     bool set_encoder(const std::string& name);
-    bool set_codec(const std::string& name);
+    bool set_encoder_codec(const std::string& name);
+    bool set_encoder_mode(const std::string& name);
     bool set_scene_unit(const std::string& name);
     bool set_scene_orientation(const std::string& name);
     bool set_scene_uvs(const std::string& name);
@@ -65,13 +75,15 @@ private:
     StereoStrategyType stereo_strategy = STEREO_STRATEGY_TYPE_NATIVE_FORWARD;
     TransportType transport = TRANSPORT_TYPE_UDP;
     EncoderType encoder = ENCODER_TYPE_VULKAN;
-    EncoderCodec codec = ENCODER_CODEC_H264;
+    EncoderCodec encoder_codec = ENCODER_CODEC_H264;
     SceneUnit scene_unit = SCENE_UNIT_METERS;
     SceneOrientation scene_orientation = SCENE_ORIENTATION_RIGHT_HANDED;
     SceneUVs scene_uvs = SCENE_UVS_Y_FLIP;
 
     std::string scene_path = "";
     std::optional<std::string> sky_sphere_path;
+    float scene_exposure = 1.0f;
+    float sky_sphere_intensity = 1.0f;
 
     std::optional<std::string> animation_name;
     std::optional<uint32_t> animation_index;
@@ -81,6 +93,13 @@ private:
 
     std::optional<float> update_rate;
 
+    std::optional<EncoderMode> encoder_mode;
+    std::optional<uint32_t> encoder_input_rate;
+    std::optional<uint32_t> encoder_key_rate;
+    std::optional<uint32_t> encoder_frame_rate;
+    std::optional<float> encoder_quality;
+    std::optional<float> encoder_bitrate;
+
     bool benchmark = false;
     bool show_companion_window = false;
     bool write_frames = false;
diff --git a/src/utility/datagram.hpp b/src/utility/datagram.hpp
index 7a03502fb452aa463c78da987c8f1a193ce954b7..2f9d9755a6c1b28fffd8d549cad9af172e539dff 100644
--- a/src/utility/datagram.hpp
+++ b/src/utility/datagram.hpp
@@ -2,7 +2,7 @@
 #include <cstdint>
 #include <vector>
 
-#define DATAGRAM_SIZE 512
+#define DATAGRAM_SIZE 1024
 
 struct Datagram
 {
diff --git a/src/utility/frame_capture.cpp b/src/utility/frame_capture.cpp
index 5c8680b53d5229de33baf8bb6c51bb8c69468748..2ff93093529d27401bffc356a6fc8ccc78a26590 100644
--- a/src/utility/frame_capture.cpp
+++ b/src/utility/frame_capture.cpp
@@ -211,13 +211,28 @@ bool FrameCapture::write_to_file(const ImageCapture& image_capture, const uint8_
         std::filesystem::create_directory(directory_path);
     }
 
-    std::vector<uint8_t> converted_content = this->convert_content(image_content, image_capture.size, image_capture.format);
+    if (image_capture.format == VK_FORMAT_R8G8B8_UNORM || image_capture.format == VK_FORMAT_R8G8B8_SRGB || image_capture.format == VK_FORMAT_R8G8B8A8_UNORM || image_capture.format == VK_FORMAT_R8G8B8A8_SRGB)
+    {
+        uint32_t component_count = this->get_component_count(image_capture.format);
+
+        if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, component_count, image_content, image_capture.size.x * component_count * sizeof(uint8_t)) == 0)
+        {
+            lava::log()->error("Can't store image '" + image_capture.name + "'");
+
+            return false;
+        }
+    }
 
-    if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, 4, converted_content.data(), image_capture.size.x * 4 * sizeof(uint8_t)) == 0)
+    else
     {
-        lava::log()->error("can't store image '" + image_capture.name + "'");
+        std::vector<uint8_t> converted_content = this->convert_content(image_content, image_capture.size, image_capture.format);
 
-        return false;
+        if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, 4, converted_content.data(), image_capture.size.x * 4 * sizeof(uint8_t)) == 0)
+        {
+            lava::log()->error("can't store image '" + image_capture.name + "'");
+
+            return false;
+        }
     }
 
     return true;
diff --git a/src/utility/statistic.cpp b/src/utility/statistic.cpp
index 045edc984971f3bf471b6c52ae987fd84bb19f9e..6b0da71c4609ca3253d4b625168697625bbd8de4 100644
--- a/src/utility/statistic.cpp
+++ b/src/utility/statistic.cpp
@@ -60,6 +60,7 @@ void Statistic::add_sample(float value)
 {
     StatisticSample sample;
     sample.value = value;
+    sample.time_point = std::chrono::high_resolution_clock::now();
 
     this->samples.push_back(sample);
 }
@@ -258,6 +259,11 @@ bool Statistic::is_log_only() const
     return this->log_only;
 }
 
+StatisticLog::StatisticLog()
+{
+    this->time_origin = std::chrono::high_resolution_clock::now();
+}
+
 bool StatisticLog::write(const std::string& directory)
 {
     if (!std::filesystem::exists(directory))
@@ -287,18 +293,22 @@ bool StatisticLog::write(const std::string& directory)
     {
         for (const StatisticSample& sample : statistic->get_samples())
         {
-            std::string label;
+            std::string label_value;
+            std::string label_time;
 
             if (sample.frame_id.has_value())
             {
-                label = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + " [" + statistic->get_unit_name() + "]";
+                label_value = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + " [" + statistic->get_unit_name() + "]";
+                label_time = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + "_time [ms]";
             }
 
             else
             {
-                label = statistic->get_name() + " [" + statistic->get_unit_name() + "]";
+                label_value = statistic->get_name() + " [" + statistic->get_unit_name() + "]";
+                label_time = statistic->get_name() + "_time [ms]";
             }
 
+            double sample_time = std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(sample.time_point - this->time_origin).count();
             bool ordered = sample.frame_number.has_value() || sample.transform_id.has_value();
             
             if (ordered)
@@ -312,7 +322,7 @@ bool StatisticLog::write(const std::string& directory)
 
                 for (uint32_t index = 0; index < ordered_sample_labels.size(); index++)
                 {
-                    if (ordered_sample_labels[index] == label)
+                    if (ordered_sample_labels[index] == label_value)
                     {
                         column_index = index;
                         column_found = true;
@@ -324,17 +334,19 @@ bool StatisticLog::write(const std::string& directory)
                 if (!column_found)
                 {
                     column_index = ordered_sample_labels.size();
-                    ordered_sample_labels.push_back(label);
+                    ordered_sample_labels.push_back(label_value);
+                    ordered_sample_labels.push_back(label_time);
                 }
 
                 std::vector<std::optional<double>>& row = ordered_samples[key];
 
                 if (row.size() <= column_index)
                 {
-                    row.resize(column_index + 1);
+                    row.resize(column_index + 2);
                 }
 
                 row[column_index] = sample.value;
+                row[column_index + 1] = sample_time;
             }
 
             else
@@ -344,7 +356,7 @@ bool StatisticLog::write(const std::string& directory)
 
                 for (uint32_t index = 0; index < unordered_sample_labels.size(); index++)
                 {
-                    if (unordered_sample_labels[index] == label)
+                    if (unordered_sample_labels[index] == label_value)
                     {
                         column_index = index;
                         column_found = true;
@@ -356,15 +368,17 @@ bool StatisticLog::write(const std::string& directory)
                 if (!column_found)
                 {
                     column_index = unordered_sample_labels.size();
-                    unordered_sample_labels.push_back(label);
+                    unordered_sample_labels.push_back(label_value);
+                    unordered_sample_labels.push_back(label_time);
                 }
 
                 if (unordered_samples.size() <= column_index)
                 {
-                    unordered_samples.resize(column_index + 1);
+                    unordered_samples.resize(column_index + 2);
                 }
 
                 unordered_samples[column_index].push_back(sample.value);
+                unordered_samples[column_index + 1].push_back(sample_time);
             }
         }
     }
diff --git a/src/utility/statistic.hpp b/src/utility/statistic.hpp
index f8883be7e2d7875235c17dd3effbdf145bda6e5e..84c73ac779e7fc0300e50390bee5e6e5924ab56f 100644
--- a/src/utility/statistic.hpp
+++ b/src/utility/statistic.hpp
@@ -14,6 +14,7 @@
 */
 
 #pragma once
+#include <chrono>
 #include <memory>
 #include <string>
 #include <vector>
@@ -25,6 +26,7 @@
 struct StatisticSample
 {
     double value;
+    std::chrono::high_resolution_clock::time_point time_point;
 
     std::optional<FrameNumber> frame_number;
     std::optional<FrameId> frame_id;
@@ -89,7 +91,7 @@ public:
     typedef std::shared_ptr<StatisticLog> Ptr;
 
 public:
-    StatisticLog() = default;
+    StatisticLog();
 
     bool write(const std::string& directory);
 
@@ -100,6 +102,7 @@ private:
 
 private:
     std::vector<Statistic::Ptr> statistic_list;
+    std::chrono::high_resolution_clock::time_point time_origin;
 };
 
 Statistic::Ptr make_statistic(const std::string& name, Unit unit, bool log_only = false);
diff --git a/src/vr_application.cpp b/src/vr_application.cpp
index 307c70e886c5738955496ccf21b3617807df516f..8ddde89a56a04368e21d287d43a335d1bd100304 100644
--- a/src/vr_application.cpp
+++ b/src/vr_application.cpp
@@ -321,18 +321,23 @@ bool VRApplication::on_create()
     config.scene_unit = this->command_parser.get_scene_unit();
     config.scene_orientation = this->command_parser.get_scene_orientation();
     config.scene_uvs = this->command_parser.get_scene_uvs();
+    config.scene_exposure = this->command_parser.get_scene_exposure();
 
     if (this->command_parser.get_sky_sphere_path().has_value())
     {
         config.sky_sphere_file = this->command_parser.get_sky_sphere_path().value();
+        config.sky_sphere_intensity = this->command_parser.get_sky_sphere_intensity();
     }
 
     if (!this->command_parser.should_benchmark())
     {
-        config.controller_left_file = std::string(RESOURCE_FOLDER) + "/dpr-controller/ObjModelViveFocus3ControllerLeft.fbx";
+        std::string left_controller_file = "/dpr-controller/ObjModelViveFocus3ControllerLeft.fbx";
+        std::string right_controller_file = "/dpr-controller/ObjModelViveFocus3ControllerRight.fbx";
+
+        config.controller_left_file = lava::file_system::get_real_dir(right_controller_file.c_str()) + left_controller_file;
         config.controller_left_unit = SCENE_UNIT_CENTIMETERS;
         
-        config.controller_right_file = std::string(RESOURCE_FOLDER) + "/dpr-controller/ObjModelViveFocus3ControllerRight.fbx";
+        config.controller_right_file = lava::file_system::get_real_dir(right_controller_file.c_str()) + right_controller_file;
         config.controller_right_unit = SCENE_UNIT_CENTIMETERS;
     }
 
@@ -526,9 +531,12 @@ void VRApplication::on_destroy()
         this->scene->destroy();
     }
 
-    if (!this->statistic_log->write("statistics"))
+    if (this->statistic_log != nullptr)
     {
-        lava::log()->error("Can't write statistic!");
+        if (!this->statistic_log->write("statistics"))
+        {
+            lava::log()->error("Can't write statistic!");
+        }
     }
 }
 
@@ -560,6 +568,8 @@ bool VRApplication::on_interface()
 
     if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
     {
+        ImGui::PushID("SceneInterface");
+
         if (!this->scene->interface())
         {
             lava::log()->error("Error during scene interface!");
@@ -567,10 +577,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Headset", ImGuiTreeNodeFlags_DefaultOpen))
     {        
+        ImGui::PushID("HeadsetInterface");
+
         if (!this->headset->on_interface())
         {
             lava::log()->error("Error during headset interface!");
@@ -578,10 +592,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Strategy", ImGuiTreeNodeFlags_DefaultOpen))
     {
+        ImGui::PushID("StrategyInterface");
+
         std::vector<const char*> strategy_names;
 
         for (StereoStrategy::Ptr strategy : this->strategies)
@@ -603,10 +621,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Debug"))
     {
+        ImGui::PushID("DebugInterface");
+
         bool companion_enabled = this->companion_window->is_enabled();
         ImGui::Checkbox("Companion Window", &companion_enabled);
         this->companion_window->set_enabled(companion_enabled);
@@ -619,6 +641,8 @@ bool VRApplication::on_interface()
         this->pass_timer->interface();
 
         this->app->draw_about(true);
+
+        ImGui::PopID();
     }
 
     ImGui::End();