Generalize scene description for automated testing (#9627)
- Extended Settings to include properties for Camera, Animation, Lights, and Render options. - Moved camera options from Viewer options to Camera options - Implemented generic JSON parsing for these new settings in Settings.cpp. - Updated AutomationEngine to apply these settings, including dynamic creation of lights. - Fixed a JSON parsing bug in AutomationSpec that failed on nested objects. - Updated gltf_viewer to use the new settings and correctly initialize AutomationEngine context. - Add test for new json changes - Add README to libs/viewer - Link libs/viewer/README.md to official doc - Remove unused libs/viewer/schemas - Updated remote web assets (because the viewer/settings json needs to match)
This commit is contained in:
@@ -159,37 +159,50 @@ extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv* env, jclass,
|
||||
jlong nativeObject, jobject result) {
|
||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
||||
auto options = automation->getViewerOptions();
|
||||
const auto& settings = automation->getSettings();
|
||||
const auto& options = settings.viewer;
|
||||
|
||||
const jclass klass = env->GetObjectClass(result);
|
||||
|
||||
const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F");
|
||||
const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F");
|
||||
const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F");
|
||||
const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F");
|
||||
const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F");
|
||||
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
|
||||
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
|
||||
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
|
||||
const jfieldID cameraFocalLength = env->GetFieldID(klass, "cameraFocalLength", "F");
|
||||
const jfieldID cameraFocusDistance = env->GetFieldID(klass, "cameraFocusDistance", "F");
|
||||
const jfieldID autoScaleEnabled = env->GetFieldID(klass, "autoScaleEnabled", "Z");
|
||||
const jfieldID autoInstancingEnabled = env->GetFieldID(klass, "autoInstancingEnabled", "Z");
|
||||
|
||||
env->SetFloatField(result, cameraAperture, options.cameraAperture);
|
||||
env->SetFloatField(result, cameraSpeed, options.cameraSpeed);
|
||||
env->SetFloatField(result, cameraISO, options.cameraISO);
|
||||
env->SetFloatField(result, cameraNear, options.cameraNear);
|
||||
env->SetFloatField(result, cameraFar, options.cameraFar);
|
||||
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
|
||||
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
|
||||
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
|
||||
env->SetFloatField(result, cameraFocalLength, options.cameraFocalLength);
|
||||
env->SetFloatField(result, cameraFocusDistance, options.cameraFocusDistance);
|
||||
env->SetBooleanField(result, autoScaleEnabled, options.autoScaleEnabled);
|
||||
env->SetBooleanField(result, autoInstancingEnabled, options.autoInstancingEnabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetCameraSettings(JNIEnv* env, jclass,
|
||||
jlong nativeObject, jobject result) {
|
||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
||||
const auto& settings = automation->getSettings();
|
||||
const auto& camera = settings.camera;
|
||||
|
||||
const jclass klass = env->GetObjectClass(result);
|
||||
|
||||
const jfieldID aperture = env->GetFieldID(klass, "aperture", "F");
|
||||
const jfieldID shutterSpeed = env->GetFieldID(klass, "shutterSpeed", "F");
|
||||
const jfieldID sensitivity = env->GetFieldID(klass, "sensitivity", "F");
|
||||
const jfieldID near = env->GetFieldID(klass, "near", "F");
|
||||
const jfieldID far = env->GetFieldID(klass, "far", "F");
|
||||
const jfieldID focalLength = env->GetFieldID(klass, "focalLength", "F");
|
||||
const jfieldID focusDistance = env->GetFieldID(klass, "focusDistance", "F");
|
||||
|
||||
env->SetFloatField(result, aperture, camera.aperture);
|
||||
env->SetFloatField(result, shutterSpeed, camera.shutterSpeed);
|
||||
env->SetFloatField(result, sensitivity, camera.sensitivity);
|
||||
env->SetFloatField(result, near, camera.near);
|
||||
env->SetFloatField(result, far, camera.far);
|
||||
env->SetFloatField(result, focalLength, camera.focalLength);
|
||||
env->SetFloatField(result, focusDistance, camera.focusDistance);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetColorGrading(JNIEnv*, jclass,
|
||||
jlong nativeObject, jlong nativeEngine) {
|
||||
|
||||
@@ -94,20 +94,23 @@ public class AutomationEngine {
|
||||
* Allows remote control for the viewer.
|
||||
*/
|
||||
public static class ViewerOptions {
|
||||
public float cameraAperture = 16.0f;
|
||||
public float cameraSpeed = 125.0f;
|
||||
public float cameraISO = 100.0f;
|
||||
public float cameraNear = 0.1f;
|
||||
public float cameraFar = 100.0f;
|
||||
public float groundShadowStrength = 0.75f;
|
||||
public boolean groundPlaneEnabled = false;
|
||||
public boolean skyboxEnabled = true;
|
||||
public float cameraFocalLength = 28.0f;
|
||||
public float cameraFocusDistance = 0.0f;
|
||||
public boolean autoScaleEnabled = true;
|
||||
public boolean autoInstancingEnabled = false;
|
||||
}
|
||||
|
||||
public static class CameraSettings {
|
||||
public float aperture = 16.0f;
|
||||
public float shutterSpeed = 125.0f;
|
||||
public float sensitivity = 100.0f;
|
||||
public float near = 0.1f;
|
||||
public float far = 100.0f;
|
||||
public float focalLength = 28.0f;
|
||||
public float focusDistance = 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an automation engine from a JSON specification.
|
||||
*
|
||||
@@ -229,6 +232,13 @@ public class AutomationEngine {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public CameraSettings getCameraSettings() {
|
||||
CameraSettings result = new CameraSettings();
|
||||
nGetCameraSettings(mNativeObject, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a color grading object that corresponds to the latest settings.
|
||||
*
|
||||
@@ -280,6 +290,7 @@ public class AutomationEngine {
|
||||
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
||||
long scene, long renderer);
|
||||
private static native void nGetViewerOptions(long nativeObject, Object result);
|
||||
private static native void nGetCameraSettings(long nativeObject, Object result);
|
||||
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
|
||||
private static native void nSignalBatchMode(long nativeObject);
|
||||
private static native void nStopRunning(long nativeObject);
|
||||
|
||||
@@ -389,9 +389,9 @@ class MainActivity : Activity() {
|
||||
viewerContent.assetLights = modelViewer.asset?.lightEntities
|
||||
automation.applySettings(modelViewer.engine, json, viewerContent)
|
||||
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
|
||||
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
|
||||
modelViewer.cameraNear = automation.viewerOptions.cameraNear
|
||||
modelViewer.cameraFar = automation.viewerOptions.cameraFar
|
||||
modelViewer.cameraFocalLength = automation.cameraSettings.focalLength
|
||||
modelViewer.cameraNear = automation.cameraSettings.near
|
||||
modelViewer.cameraFar = automation.cameraSettings.far
|
||||
updateRootTransform()
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
"libs/fgviewer/README.md": {
|
||||
"dest": "dup/fgviewer.md"
|
||||
},
|
||||
"libs/viewer/README.md": {
|
||||
"dest": "dup/viewer.md"
|
||||
},
|
||||
"ios/CocoaPods/README.md": {
|
||||
"dest": "release/cocoapods.md"
|
||||
},
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
- [gltfio](./dup/gltfio.md)
|
||||
- [iblprefilter](./dup/iblprefilter.md)
|
||||
- [matdbg](./dup/matdbg.md)
|
||||
- [viewer](./dup/viewer.md)
|
||||
- [uberz](./dup/uberz.md)
|
||||
- [Tools](./notes/tools.md)
|
||||
- [beamsplitter](./dup/beamsplitter.md)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -274,7 +274,7 @@ const float kToastDelayDuration = 2.0f;
|
||||
self.modelView.engine, message->buffer, message->bufferByteCount, content);
|
||||
ColorGrading* const colorGrading = _automation->getColorGrading(self.modelView.engine);
|
||||
self.modelView.view->setColorGrading(colorGrading);
|
||||
self.modelView.cameraFocalLength = _automation->getViewerOptions().cameraFocalLength;
|
||||
self.modelView.cameraFocalLength = _automation->getSettings().camera.focalLength;
|
||||
}
|
||||
|
||||
- (void)loadGlb:(viewer::ReceivedMessage const*)message {
|
||||
|
||||
147
libs/viewer/README.md
Normal file
147
libs/viewer/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Viewer Library
|
||||
|
||||
The **Viewer Library** (`libs/viewer`) provides a high-level abstraction for configuring and rendering Filament scenes. It is used by tools like `gltf_viewer` to load assets, manage settings, and drive the rendering loop.
|
||||
|
||||
## Features
|
||||
|
||||
- **Settings Management**: Centralized configuration for View, Camera, Lights, and Materials via the `Settings` struct.
|
||||
- **JSON Serialization**: Full support for loading and saving settings via JSON.
|
||||
- **Automation**: `AutomationEngine` allows scripting the viewer with a sequence of JSON-based test cases (batch mode).
|
||||
- **GUI Integration**: Built-in support for `imgui` via `ViewerGui` and `Settings` binding.
|
||||
|
||||
## JSON Settings Schema
|
||||
|
||||
The viewer settings can be configured using a JSON object. This is used for `gltf_viewer --settings` or in automation specs.
|
||||
|
||||
### Root Object
|
||||
|
||||
The root object contains the following categories:
|
||||
|
||||
| Key | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `view` | Object | Post-processing and rendering quality settings. |
|
||||
| `camera` | Object | **[NEW]** Explicit camera control (pose, projection, exposure). |
|
||||
| `lighting` | Object | **[NEW]** Environment and dynamic light settings. |
|
||||
| `viewer` | Object | Global viewer options (skybox, background, scaling). |
|
||||
| `animation` | Object | **[NEW]** Animation playback control. |
|
||||
| `material` | Object | Material overrides. |
|
||||
|
||||
---
|
||||
|
||||
### Camera Settings (`camera`)
|
||||
|
||||
Allows explicit control over the camera. If `enabled` is false, the viewer uses its default orbit camera logic (auto-scaling/centering).
|
||||
|
||||
```json
|
||||
"camera": {
|
||||
"enabled": true, // Must be true to use these explicit settings
|
||||
"projection": "PERSPECTIVE", // "PERSPECTIVE" or "ORTHO"
|
||||
"center": [0, 0, 0], // World-space look-at point
|
||||
"lookAt": [0, 0, -1], // World-space eye position (confusingly named 'lookAt' in internal legacy, often 'eye')
|
||||
"up": [0, 1, 0], // Up vector
|
||||
"near": 0.1, // Near plane
|
||||
"far": 100.0, // Far plane
|
||||
"focalLength": 28.0, // Focal length in mm (Perspective only)
|
||||
"fov": 0.0, // Field of view in degrees (overrides focalLength if > 0)
|
||||
"aperture": 16.0, // f-stop
|
||||
"shutterSpeed": 125.0, // 1/seconds
|
||||
"sensitivity": 100.0, // ISO
|
||||
"focusDistance": 10.0, // Focus distance in world units
|
||||
"scaling": [1.0, 1.0], // Custom projection matrix scaling (mostly for Ortho)
|
||||
"shift": [0.0, 0.0] // Custom projection matrix shift
|
||||
}
|
||||
```
|
||||
|
||||
### Lighting Settings (`lighting`)
|
||||
|
||||
Controls the Image Based Lighting (IBL), the Sun, and additional dynamic lights.
|
||||
|
||||
```json
|
||||
"lighting": {
|
||||
"iblIntensity": 30000.0,
|
||||
"iblRotation": 0.0, // Rotation in degrees
|
||||
"enableSunlight": true,
|
||||
"enableShadows": true,
|
||||
"sunlight": { // **[NEW]** Nested sunlight properties
|
||||
"intensity": 100000.0,
|
||||
"color": [0.98, 0.92, 0.89],
|
||||
"direction": [0.6, -1.0, -0.8],
|
||||
"sunHaloSize": 10.0,
|
||||
"sunHaloFalloff": 80.0,
|
||||
"sunAngularRadius": 1.9,
|
||||
"castShadows": true,
|
||||
"shadowOptions": { // Per-light shadow options
|
||||
"mapSize": 1024,
|
||||
"shadowCascades": 1,
|
||||
"stable": false
|
||||
}
|
||||
},
|
||||
"lights": [ // **[NEW]** Array of custom lights
|
||||
{
|
||||
"type": "POINT", // "POINT", "SPOT", "FOCUSED_SPOT", "DIRECTIONAL", "SUN"
|
||||
"position": [0, 2, 0],
|
||||
"color": [1, 0, 0],
|
||||
"intensity": 5000.0,
|
||||
"falloff": 10.0,
|
||||
"castShadows": true,
|
||||
"shadowOptions": { "mapSize": 512 }
|
||||
},
|
||||
{
|
||||
"type": "SPOT",
|
||||
"position": [2, 5, 2],
|
||||
"direction": [0, -1, 0],
|
||||
"spotInner": 0.5, // Inner cone angle (radians)
|
||||
"spotOuter": 0.8 // Outer cone angle (radians)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### View Settings (`view`)
|
||||
|
||||
Standard Filament view settings.
|
||||
|
||||
```json
|
||||
"view": {
|
||||
"postProcessingEnabled": true,
|
||||
"antiAliasing": "FXAA", // "NONE", "FXAA"
|
||||
"msaa": {
|
||||
"enabled": true,
|
||||
"sampleCount": 4
|
||||
},
|
||||
"ssao": { "enabled": true, ... },
|
||||
"bloom": { "enabled": true, ... },
|
||||
"dof": { "enabled": false, ... },
|
||||
"vignette": { "enabled": false, ... },
|
||||
"colorGrading": {
|
||||
"toneMapping": "ACES_LEGACY", // "LINEAR", "ACES", "FILMIC", "PBR_NEUTRAL", etc.
|
||||
"exposure": 0.0,
|
||||
"gamma": [1.0, 1.0, 1.0]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Viewer Options (`viewer`)
|
||||
|
||||
General app-level settings.
|
||||
|
||||
```json
|
||||
"viewer": {
|
||||
"skyboxEnabled": true,
|
||||
"backgroundColor": [0, 0, 0], // Used if skybox is disabled
|
||||
"autoScaleEnabled": true, // Fit model to unit cube
|
||||
"groundPlaneEnabled": false
|
||||
}
|
||||
```
|
||||
|
||||
### Animation Settings (`animation`)
|
||||
|
||||
Control glTF animation playback.
|
||||
|
||||
```json
|
||||
"animation": {
|
||||
"enabled": true,
|
||||
"speed": 1.0,
|
||||
"time": -1.0 // If >= 0, forces animation to this specific time (seconds)
|
||||
}
|
||||
```
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
Scene* scene;
|
||||
IndirectLight* indirectLight;
|
||||
utils::Entity sunlight;
|
||||
utils::Entity* assetLights;
|
||||
const utils::Entity* assetLights;
|
||||
size_t assetLightCount;
|
||||
};
|
||||
|
||||
@@ -211,6 +211,11 @@ public:
|
||||
*/
|
||||
ViewerOptions getViewerOptions() const;
|
||||
|
||||
/**
|
||||
* Gets the current full settings object.
|
||||
*/
|
||||
const Settings& getSettings() const { return *mSettings; }
|
||||
|
||||
/**
|
||||
* Signals that batch mode can begin. Call this after all meshes and textures finish loading.
|
||||
*/
|
||||
@@ -264,6 +269,10 @@ private:
|
||||
Engine* mColorGradingEngine = nullptr;
|
||||
ColorGrading* mColorGrading = nullptr;
|
||||
ColorGradingSettings mColorGradingSettings = {};
|
||||
std::vector<utils::Entity> mCustomLights;
|
||||
|
||||
void updateCustomLights(Engine* engine, const std::vector<LightDefinition>& lights,
|
||||
Scene* scene);
|
||||
|
||||
size_t mCurrentTest;
|
||||
float mElapsedTime;
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
#ifndef VIEWER_SETTINGS_H
|
||||
#define VIEWER_SETTINGS_H
|
||||
|
||||
#include <filament/Camera.h>
|
||||
#include <filament/ColorGrading.h>
|
||||
#include <filament/ColorSpace.h>
|
||||
#include <filament/IndirectLight.h>
|
||||
#include <filament/LightManager.h>
|
||||
#include <filament/MaterialInstance.h>
|
||||
#include <filament/Renderer.h>
|
||||
#include <filament/Scene.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
@@ -44,6 +46,11 @@ class Renderer;
|
||||
|
||||
namespace viewer {
|
||||
|
||||
struct ColorGradingSettings;
|
||||
struct DynamicLightingSettings;
|
||||
struct MaterialSettings;
|
||||
struct Settings;
|
||||
struct ViewSettings;
|
||||
struct ColorGradingSettings;
|
||||
struct DynamicLightingSettings;
|
||||
struct MaterialSettings;
|
||||
@@ -52,6 +59,10 @@ struct ViewSettings;
|
||||
struct LightSettings;
|
||||
struct ViewerOptions;
|
||||
struct DebugOptions;
|
||||
struct CameraSettings;
|
||||
struct AnimationSettings;
|
||||
struct RenderSettings;
|
||||
struct LightDefinition;
|
||||
|
||||
enum class ToneMapping : uint8_t {
|
||||
LINEAR = 0,
|
||||
@@ -82,6 +93,8 @@ using VsmShadowOptions = filament::View::VsmShadowOptions;
|
||||
using GuardBandOptions = filament::View::GuardBandOptions;
|
||||
using StereoscopicOptions = filament::View::StereoscopicOptions;
|
||||
using LightManager = filament::LightManager;
|
||||
using CameraProjection = filament::Camera::Projection;
|
||||
using BlendMode = filament::BlendMode;
|
||||
|
||||
// These functions push all editable property values to their respective Filament objects.
|
||||
void applySettings(Engine* engine, const ViewSettings& settings, View* dest);
|
||||
@@ -92,6 +105,8 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera
|
||||
Renderer* renderer);
|
||||
void applySettings(Engine* engine, const DebugOptions& settings,
|
||||
Renderer* renderer);
|
||||
void applySettings(Engine* engine, const CameraSettings& settings, Camera* camera,
|
||||
double aspectRatio = 0.0);
|
||||
|
||||
// Creates a new ColorGrading object based on the given settings.
|
||||
UTILS_PUBLIC
|
||||
@@ -204,6 +219,9 @@ struct ViewSettings {
|
||||
ColorGradingSettings colorGrading;
|
||||
DynamicLightingSettings dynamicLighting;
|
||||
FogSettings fogSettings;
|
||||
BlendMode blendMode = BlendMode::OPAQUE;
|
||||
bool stencilBufferEnabled = false;
|
||||
uint8_t visibleLayers = 0x01;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -217,35 +235,68 @@ struct MaterialSettings {
|
||||
MaterialProperty<math::float4> float4[MAX_COUNT];
|
||||
};
|
||||
|
||||
struct LightSettings {
|
||||
bool enableShadows = true;
|
||||
bool enableSunlight = true;
|
||||
struct LightDefinition {
|
||||
LightManager::Type type = LightManager::Type::POINT;
|
||||
math::float3 position = { 0.0f, 0.0f, 0.0f };
|
||||
math::float3 direction = { 0.0f, -1.0f, 0.0f };
|
||||
math::float3 color = { 1.0f, 1.0f, 1.0f };
|
||||
float intensity = 0.0f;
|
||||
float falloff = 0.0f;
|
||||
float spotInner = 0.0f;
|
||||
float spotOuter = 0.0f;
|
||||
float sunHaloSize = 10.0f;
|
||||
float sunHaloFalloff = 80.0f;
|
||||
float sunAngularRadius = 1.9f;
|
||||
bool castShadows = false;
|
||||
LightManager::ShadowOptions shadowOptions;
|
||||
};
|
||||
|
||||
struct LightSettings {
|
||||
bool enableShadows = true; // Global toggle to enabling/disabling shadows
|
||||
bool enableSunlight = true;
|
||||
SoftShadowOptions softShadowOptions;
|
||||
float sunlightIntensity = 100000.0f;
|
||||
float sunlightHaloSize = 10.0f;
|
||||
float sunlightHaloFalloff = 80.0f;
|
||||
float sunlightAngularRadius = 1.9f;
|
||||
math::float3 sunlightDirection = {0.6, -1.0, -0.8};
|
||||
math::float3 sunlightColor = filament::Color::toLinear<filament::ACCURATE>({ 0.98, 0.92, 0.89});
|
||||
float iblIntensity = 30000.0f;
|
||||
float iblRotation = 0.0f;
|
||||
LightDefinition sunlight;
|
||||
std::vector<LightDefinition> lights;
|
||||
};
|
||||
|
||||
struct CameraSettings {
|
||||
math::float3 center = { 0.0f, 0.0f, 0.0f };
|
||||
math::float3 lookAt = { 0.0f, 0.0f, -1.0f };
|
||||
math::float3 up = { 0.0f, 1.0f, 0.0f };
|
||||
float horizontalFov = 0.0f; // degrees, if 0 use focal length
|
||||
float near = 0.1f;
|
||||
float far = 100.0f;
|
||||
float focalLength = 28.0f; // mm, if 0 use fov
|
||||
float aperture = 16.0f;
|
||||
float shutterSpeed = 125.0f;
|
||||
float sensitivity = 100.0f; // ISO
|
||||
float focusDistance = 10.0f;
|
||||
float eyeOcularDistance = 0.0f;
|
||||
float eyeToeIn = 0.0f;
|
||||
CameraProjection projection = CameraProjection::PERSPECTIVE;
|
||||
bool enabled = false;
|
||||
math::float2 scaling = { 1.0, 1.0 };
|
||||
math::float2 shift = { 0.0, 0.0 };
|
||||
};
|
||||
|
||||
struct AnimationSettings {
|
||||
bool enabled = false;
|
||||
float time = -1.0f;
|
||||
float speed = 1.0f;
|
||||
};
|
||||
|
||||
struct RenderSettings {
|
||||
Renderer::ClearOptions clearOptions = {};
|
||||
Renderer::FrameRateOptions frameRateOptions = {};
|
||||
};
|
||||
|
||||
struct ViewerOptions {
|
||||
float cameraAperture = 16.0f;
|
||||
float cameraSpeed = 125.0f;
|
||||
float cameraISO = 100.0f;
|
||||
float cameraNear = 0.1f;
|
||||
float cameraFar = 100.0f;
|
||||
float cameraEyeOcularDistance = 0.0f;
|
||||
float cameraEyeToeIn = 0.0f;
|
||||
float groundShadowStrength = 0.75f;
|
||||
bool groundPlaneEnabled = false;
|
||||
bool skyboxEnabled = true;
|
||||
sRGBColor backgroundColor = { 0.0f };
|
||||
float cameraFocalLength = 28.0f;
|
||||
float cameraFocusDistance = 10.0f;
|
||||
bool autoScaleEnabled = true;
|
||||
bool autoInstancingEnabled = false;
|
||||
};
|
||||
@@ -259,6 +310,9 @@ struct Settings {
|
||||
MaterialSettings material;
|
||||
LightSettings lighting;
|
||||
ViewerOptions viewer;
|
||||
CameraSettings camera;
|
||||
AnimationSettings animation;
|
||||
RenderSettings render;
|
||||
DebugOptions debug;
|
||||
};
|
||||
|
||||
|
||||
@@ -231,6 +231,9 @@ public:
|
||||
|
||||
int getCurrentCamera() const { return mCurrentCamera; }
|
||||
|
||||
utils::Entity getSunlight() const { return mSunlight; }
|
||||
IndirectLight* getIndirectLight() const { return mIndirectLight; }
|
||||
|
||||
private:
|
||||
using SceneMask = gltfio::NodeManager::SceneMask;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
The schema in this folder can be used to enable autocomplete and help features when authoring
|
||||
automation specs in your favorite text editor. For example, for Visual Studio Code you can add the
|
||||
following to `.vscode/settings`:
|
||||
|
||||
```
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"libs/viewer/tests/basic.json"
|
||||
],
|
||||
"url": "./libs/viewer/schemas/automation.json"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/google/filament/automation.json",
|
||||
"title": "Spec",
|
||||
"description": "A specification that generates a set of Settings objects",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Prefix used for screenshots and exported settings"
|
||||
},
|
||||
"base": {
|
||||
"type": "object",
|
||||
"description": "The base configuration to which permutations are applied",
|
||||
"patternProperties": {
|
||||
"[A-Z0-9_\\.]+": { "type": ["number", "boolean", "string" ] }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"permute": {
|
||||
"type": "object",
|
||||
"description": "Specifies a cross-product of property values",
|
||||
"patternProperties": {
|
||||
"[A-Z0-9_\\.]+": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"oneOf": [
|
||||
{ "items": { "type": "number" } },
|
||||
{ "items": { "type": "string" } },
|
||||
{ "items": { "type": "boolean" } }
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
@@ -20,9 +20,14 @@
|
||||
|
||||
#include <filament/Camera.h>
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/LightManager.h>
|
||||
#include <filament/Renderer.h>
|
||||
#include <filament/Scene.h>
|
||||
#include <filament/TransformManager.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <utils/EntityManager.h>
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
@@ -198,10 +203,21 @@ void AutomationEngine::applySettings(Engine* engine, const char* json, size_t js
|
||||
}
|
||||
viewer::applySettings(engine, mSettings->lighting, content.indirectLight, content.sunlight,
|
||||
content.assetLights, content.assetLightCount, content.lightManager, content.scene, content.view);
|
||||
updateCustomLights(engine, mSettings->lighting.lights, content.scene);
|
||||
|
||||
Camera* camera = &content.view->getCamera();
|
||||
Skybox* skybox = content.scene->getSkybox();
|
||||
viewer::applySettings(engine, mSettings->viewer, camera, skybox, content.renderer);
|
||||
viewer::applySettings(engine, mSettings->debug, content.renderer);
|
||||
|
||||
// Apply CameraSettings
|
||||
double const aspect = (double) content.view->getViewport().width /
|
||||
(double) content.view->getViewport().height;
|
||||
viewer::applySettings(engine, mSettings->camera, camera, aspect);
|
||||
|
||||
// Apply RenderSettings
|
||||
content.renderer->setClearOptions(mSettings->render.clearOptions);
|
||||
content.renderer->setFrameRateOptions(mSettings->render.frameRateOptions);
|
||||
}
|
||||
|
||||
ColorGrading* AutomationEngine::getColorGrading(Engine* engine) {
|
||||
@@ -217,14 +233,6 @@ ColorGrading* AutomationEngine::getColorGrading(Engine* engine) {
|
||||
}
|
||||
|
||||
ViewerOptions AutomationEngine::getViewerOptions() const {
|
||||
ViewerOptions options = mSettings->viewer;
|
||||
const auto dofOptions = mSettings->view.dof;
|
||||
if (dofOptions.enabled) {
|
||||
options.cameraFocalLength = Camera::computeEffectiveFocalLength(
|
||||
options.cameraFocalLength / 1000.0,
|
||||
std::max(0.1f, options.cameraFocusDistance)) * 1000.0;
|
||||
|
||||
}
|
||||
return mSettings->viewer;
|
||||
}
|
||||
|
||||
@@ -237,6 +245,20 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
|
||||
for (size_t i = 0; i < content.materialCount; i++) {
|
||||
viewer::applySettings(engine, mSettings->material, content.materials[i]);
|
||||
}
|
||||
viewer::applySettings(engine, mSettings->lighting, content.indirectLight, content.sunlight,
|
||||
content.assetLights, content.assetLightCount, content.lightManager, content.scene,
|
||||
content.view);
|
||||
updateCustomLights(engine, mSettings->lighting.lights, content.scene);
|
||||
|
||||
// Apply CameraSettings
|
||||
double const aspect = (double) content.view->getViewport().width /
|
||||
(double) content.view->getViewport().height;
|
||||
viewer::applySettings(engine, mSettings->camera, &content.view->getCamera(), aspect);
|
||||
|
||||
// Apply RenderSettings
|
||||
content.renderer->setClearOptions(mSettings->render.clearOptions);
|
||||
content.renderer->setFrameRateOptions(mSettings->render.frameRateOptions);
|
||||
|
||||
if (mOptions.verbose) {
|
||||
utils::slog.i << "Running test " << mCurrentTest << utils::io::endl;
|
||||
}
|
||||
@@ -263,7 +285,7 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
|
||||
|
||||
const bool isLastTest = mCurrentTest == mSpec->size() - 1;
|
||||
|
||||
const int digits = (int) log10 ((double) mSpec->size()) + 1;
|
||||
int const digits = (int) log10((double) mSpec->size()) + 1;
|
||||
std::ostringstream stringStream;
|
||||
stringStream << mSpec->getName(mCurrentTest)
|
||||
<< std::setfill('0') << std::setw(digits) << mCurrentTest;
|
||||
@@ -296,5 +318,51 @@ const char* AutomationEngine::getStatusMessage() const {
|
||||
return gStatus.c_str();
|
||||
}
|
||||
|
||||
void AutomationEngine::updateCustomLights(Engine* engine,
|
||||
const std::vector<LightDefinition>& lights, Scene* scene) {
|
||||
auto& em = utils::EntityManager::get();
|
||||
LightManager* lm = &engine->getLightManager();
|
||||
|
||||
// Destroy old lights
|
||||
for (auto entity: mCustomLights) {
|
||||
lm->destroy(entity);
|
||||
scene->remove(entity);
|
||||
em.destroy(entity);
|
||||
}
|
||||
mCustomLights.clear();
|
||||
|
||||
if (lights.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new lights
|
||||
mCustomLights.resize(lights.size());
|
||||
em.create(mCustomLights.size(), mCustomLights.data());
|
||||
|
||||
for (size_t i = 0; i < lights.size(); ++i) {
|
||||
const auto& def = lights[i];
|
||||
LightManager::Builder builder(def.type);
|
||||
builder.color(def.color)
|
||||
.intensity(def.intensity)
|
||||
.position(def.position)
|
||||
.direction(def.direction)
|
||||
.falloff(def.falloff)
|
||||
.spotLightCone(def.spotInner, def.spotOuter)
|
||||
.castShadows(def.castShadows)
|
||||
.sunHaloSize(def.sunHaloSize)
|
||||
.sunHaloFalloff(def.sunHaloFalloff)
|
||||
.sunAngularRadius(def.sunAngularRadius)
|
||||
.build(*engine, mCustomLights[i]);
|
||||
|
||||
// Shadow options must be set on the instance after creation
|
||||
auto instance = lm->getInstance(mCustomLights[i]);
|
||||
if (instance) {
|
||||
lm->setShadowOptions(instance, def.shadowOptions);
|
||||
}
|
||||
|
||||
scene->addEntity(mCustomLights[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace viewer
|
||||
} // namespace filament
|
||||
|
||||
@@ -47,7 +47,7 @@ static const char* DEFAULT_AUTOMATION = R"TXT([
|
||||
{
|
||||
"name": "vieweropts",
|
||||
"base": {
|
||||
"viewer.cameraFocusDistance": 0.1
|
||||
"camera.focusDistance": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,7 +59,7 @@ static const char* DEFAULT_AUTOMATION = R"TXT([
|
||||
"view.taa.enabled": [false, true],
|
||||
"view.antiAliasing": ["NONE", "FXAA"],
|
||||
"view.ssao.enabled": [false, true],
|
||||
"view.screenSpaceReflections.enabled": [false, true]
|
||||
"view.screenSpaceReflections.enabled": [false, true],
|
||||
"view.bloom.enabled": [false, true],
|
||||
"view.dof.enabled": [false, true],
|
||||
"view.guardBand.enabled": [false, true]
|
||||
@@ -89,10 +89,20 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, std::str
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static int skip(jsmntok_t const* tokens, int i) {
|
||||
int size = tokens[i].size;
|
||||
i++;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
i = skip(tokens, i);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j, i += 2) {
|
||||
for (int j = 0; j < size; ++j) {
|
||||
// Current token is Key
|
||||
std::stringstream dk(STR(tokens[i], jsonChunk));
|
||||
std::string token;
|
||||
std::string prefix;
|
||||
@@ -103,6 +113,8 @@ static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChu
|
||||
prefix += "{ \"" + token + "\": ";
|
||||
depth++;
|
||||
}
|
||||
|
||||
// Next token is Value
|
||||
std::string json = prefix + STR(tokens[i + 1], jsonChunk);
|
||||
for (int d = 0; d < depth; d++) { json += " } "; }
|
||||
if (VERBOSE) {
|
||||
@@ -112,6 +124,11 @@ static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChu
|
||||
// Now that we have a complete JSON string, apply this property change.
|
||||
JsonSerializer serializer;
|
||||
serializer.readJson(json.c_str(), json.size(), out);
|
||||
|
||||
// Advance past Key
|
||||
i++;
|
||||
// Advance past Value (recursively)
|
||||
i = skip(tokens, i);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::viewer {
|
||||
|
||||
std::string_view to_string(color::ColorSpace const& colorspace) noexcept {
|
||||
using namespace color;
|
||||
if (colorspace == Rec709-Linear-D65) {
|
||||
@@ -67,6 +68,7 @@ int parse(jsmntok_t const* tokens, int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
using CGQL = filament::ColorGrading::QualityLevel;
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CGQL* out) {
|
||||
@@ -106,6 +108,35 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, color::C
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CameraProjection* out) {
|
||||
if (0 == compare(tokens[i], jsonChunk, "PERSPECTIVE")) {
|
||||
*out = CameraProjection::PERSPECTIVE;
|
||||
} else if (0 == compare(tokens[i], jsonChunk, "ORTHO")) {
|
||||
*out = CameraProjection::ORTHO;
|
||||
} else {
|
||||
slog.w << "Invalid CameraProjection: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightManager::Type* out) {
|
||||
if (0 == compare(tokens[i], jsonChunk, "SUN")) {
|
||||
*out = LightManager::Type::SUN;
|
||||
} else if (0 == compare(tokens[i], jsonChunk, "DIRECTIONAL")) {
|
||||
*out = LightManager::Type::DIRECTIONAL;
|
||||
} else if (0 == compare(tokens[i], jsonChunk, "POINT")) {
|
||||
*out = LightManager::Type::POINT;
|
||||
} else if (0 == compare(tokens[i], jsonChunk, "FOCUSED_SPOT")) {
|
||||
*out = LightManager::Type::FOCUSED_SPOT;
|
||||
} else if (0 == compare(tokens[i], jsonChunk, "SPOT")) {
|
||||
*out = LightManager::Type::SPOT;
|
||||
} else {
|
||||
slog.w << "Invalid Light Type: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, GenericToneMapperSettings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
@@ -305,6 +336,12 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewSett
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->postProcessingEnabled);
|
||||
} else if (compare(tok, jsonChunk, "stereoscopicOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->stereoscopicOptions);
|
||||
} else if (compare(tok, jsonChunk, "blendMode") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->blendMode);
|
||||
} else if (compare(tok, jsonChunk, "stencilBufferEnabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->stencilBufferEnabled);
|
||||
} else if (compare(tok, jsonChunk, "visibleLayers") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->visibleLayers);
|
||||
} else {
|
||||
slog.w << "Invalid view setting key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
@@ -454,9 +491,220 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSettings* out) {
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightDefinition* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "type") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->type);
|
||||
} else if (compare(tok, jsonChunk, "position") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->position);
|
||||
} else if (compare(tok, jsonChunk, "direction") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->direction);
|
||||
} else if (compare(tok, jsonChunk, "color") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->color);
|
||||
} else if (compare(tok, jsonChunk, "intensity") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->intensity);
|
||||
} else if (compare(tok, jsonChunk, "falloff") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->falloff);
|
||||
} else if (compare(tok, jsonChunk, "spotInner") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->spotInner);
|
||||
} else if (compare(tok, jsonChunk, "spotOuter") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->spotOuter);
|
||||
} else if (compare(tok, jsonChunk, "spotOuter") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->spotOuter);
|
||||
} else if (compare(tok, jsonChunk, "sunHaloSize") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunHaloSize);
|
||||
} else if (compare(tok, jsonChunk, "sunHaloFalloff") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunHaloFalloff);
|
||||
} else if (compare(tok, jsonChunk, "sunAngularRadius") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunAngularRadius);
|
||||
} else if (compare(tok, jsonChunk, "castShadows") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->castShadows);
|
||||
} else if (compare(tok, jsonChunk, "shadowOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shadowOptions);
|
||||
} else {
|
||||
slog.w << "Invalid light definition key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid light definition value: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
std::vector<LightDefinition>* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);
|
||||
int size = tokens[i++].size;
|
||||
out->resize(size);
|
||||
for (int j = 0; j < size; ++j) {
|
||||
i = parse(tokens, i, jsonChunk, &(*out)[j]);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CameraSettings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "center") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->center);
|
||||
} else if (compare(tok, jsonChunk, "lookAt") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lookAt);
|
||||
} else if (compare(tok, jsonChunk, "up") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->up);
|
||||
} else if (compare(tok, jsonChunk, "horizontalFov") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->horizontalFov);
|
||||
} else if (compare(tok, jsonChunk, "near") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->near);
|
||||
} else if (compare(tok, jsonChunk, "far") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->far);
|
||||
} else if (compare(tok, jsonChunk, "focalLength") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->focalLength);
|
||||
} else if (compare(tok, jsonChunk, "projection") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->projection);
|
||||
} else if (compare(tok, jsonChunk, "enabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
|
||||
} else if (compare(tok, jsonChunk, "scaling") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->scaling);
|
||||
} else if (compare(tok, jsonChunk, "shift") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shift);
|
||||
} else if (compare(tok, jsonChunk, "aperture") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->aperture);
|
||||
} else if (compare(tok, jsonChunk, "shutterSpeed") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shutterSpeed);
|
||||
} else if (compare(tok, jsonChunk, "sensitivity") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sensitivity);
|
||||
} else if (compare(tok, jsonChunk, "focusDistance") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->focusDistance);
|
||||
} else if (compare(tok, jsonChunk, "eyeOcularDistance") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->eyeOcularDistance);
|
||||
} else if (compare(tok, jsonChunk, "eyeToeIn") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->eyeToeIn);
|
||||
} else {
|
||||
slog.w << "Invalid camera options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid camera options value: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AnimationSettings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "enabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
|
||||
} else if (compare(tok, jsonChunk, "time") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->time);
|
||||
} else if (compare(tok, jsonChunk, "speed") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->speed);
|
||||
} else {
|
||||
slog.w << "Invalid animation options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid animation options value: '" << STR(tok, jsonChunk) << "'"
|
||||
<< io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
Renderer::ClearOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "clearColor") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->clearColor);
|
||||
} else if (compare(tok, jsonChunk, "clear") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->clear);
|
||||
} else if (compare(tok, jsonChunk, "discard") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->discard);
|
||||
} else if (compare(tok, jsonChunk, "clearStencil") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->clearStencil);
|
||||
} else {
|
||||
slog.w << "Invalid clear options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid clear options value: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
Renderer::FrameRateOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "headRoomRatio") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->headRoomRatio);
|
||||
} else if (compare(tok, jsonChunk, "history") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->history);
|
||||
} else if (compare(tok, jsonChunk, "scaleRate") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->scaleRate);
|
||||
} else {
|
||||
slog.w << "Invalid frame rate options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid frame rate options value: '" << STR(tok, jsonChunk) << "'"
|
||||
<< io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, RenderSettings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "clearOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->clearOptions);
|
||||
} else if (compare(tok, jsonChunk, "frameRateOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->frameRateOptions);
|
||||
} else {
|
||||
slog.w << "Invalid render settings key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
}
|
||||
if (i < 0) {
|
||||
slog.e << "Invalid render settings value: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSettings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
@@ -464,26 +712,16 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSet
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enableShadows);
|
||||
} else if (compare(tok, jsonChunk, "enableSunlight") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enableSunlight);
|
||||
} else if (compare(tok, jsonChunk, "shadowOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shadowOptions);
|
||||
} else if (compare(tok, jsonChunk, "softShadowOptions") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->softShadowOptions);
|
||||
} else if (compare(tok, jsonChunk, "sunlightIntensity") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightIntensity);
|
||||
} else if (compare(tok, jsonChunk, "sunlightHaloSize") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightHaloSize);
|
||||
} else if (compare(tok, jsonChunk, "sunlightHaloFalloff") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightHaloFalloff);
|
||||
} else if (compare(tok, jsonChunk, "sunlightAngularRadius") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightAngularRadius);
|
||||
} else if (compare(tok, jsonChunk, "sunlightDirection") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightDirection);
|
||||
} else if (compare(tok, jsonChunk, "sunlightColor") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlightColor);
|
||||
} else if (compare(tok, jsonChunk, "sunlight") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->sunlight);
|
||||
} else if (compare(tok, jsonChunk, "iblIntensity") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->iblIntensity);
|
||||
} else if (compare(tok, jsonChunk, "iblRotation") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->iblRotation);
|
||||
} else if (compare(tok, jsonChunk, "lights") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lights);
|
||||
} else {
|
||||
slog.w << "Invalid light setting key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
@@ -498,40 +736,22 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSet
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "cameraAperture") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraAperture);
|
||||
} else if (compare(tok, jsonChunk, "cameraSpeed") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraSpeed);
|
||||
} else if (compare(tok, jsonChunk, "cameraISO") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraISO);
|
||||
} else if (compare(tok, jsonChunk, "cameraNear") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraNear);
|
||||
} else if (compare(tok, jsonChunk, "cameraFar") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraFar);
|
||||
} else if (compare(tok, jsonChunk, "cameraEyeOcularDistance") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeOcularDistance);
|
||||
} else if (compare(tok, jsonChunk, "cameraEyeToeIn") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeToeIn);
|
||||
} else if (compare(tok, jsonChunk, "groundShadowStrength") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength);
|
||||
if (compare(tok, jsonChunk, "groundShadowStrength") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength);
|
||||
} else if (compare(tok, jsonChunk, "groundPlaneEnabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->groundPlaneEnabled);
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->groundPlaneEnabled);
|
||||
} else if (compare(tok, jsonChunk, "skyboxEnabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->skyboxEnabled);
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->skyboxEnabled);
|
||||
} else if (compare(tok, jsonChunk, "backgroundColor") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->backgroundColor);
|
||||
} else if (compare(tok, jsonChunk, "cameraFocalLength") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraFocalLength);
|
||||
} else if (compare(tok, jsonChunk, "cameraFocusDistance") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraFocusDistance);
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->backgroundColor);
|
||||
} else if (compare(tok, jsonChunk, "autoInstancingEnabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->autoInstancingEnabled);
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->autoInstancingEnabled);
|
||||
} else if (compare(tok, jsonChunk, "autoScaleEnabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->autoScaleEnabled);
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->autoScaleEnabled);
|
||||
} else {
|
||||
slog.w << "Invalid viewer options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
@@ -546,7 +766,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOp
|
||||
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DebugOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
@@ -566,7 +786,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DebugOpt
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
int const size = tokens[i++].size;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
@@ -578,6 +798,12 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out)
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lighting);
|
||||
} else if (compare(tok, jsonChunk, "viewer") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->viewer);
|
||||
} else if (compare(tok, jsonChunk, "camera") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->camera);
|
||||
} else if (compare(tok, jsonChunk, "animation") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->animation);
|
||||
} else if (compare(tok, jsonChunk, "render") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->render);
|
||||
} else if (compare(tok, jsonChunk, "debug") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->debug);
|
||||
} else {
|
||||
@@ -612,6 +838,9 @@ void applySettings(Engine* engine, const ViewSettings& settings, View* dest) {
|
||||
dest->setGuardBandOptions(settings.guardBand);
|
||||
dest->setStereoscopicOptions(settings.stereoscopicOptions);
|
||||
dest->setPostProcessingEnabled(settings.postProcessingEnabled);
|
||||
dest->setBlendMode(static_cast<View::BlendMode>(settings.blendMode));
|
||||
dest->setStencilBufferEnabled(settings.stencilBufferEnabled);
|
||||
dest->setVisibleLayers(settings.visibleLayers, settings.visibleLayers);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -636,14 +865,14 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
|
||||
} else {
|
||||
scene->remove(sunlight);
|
||||
}
|
||||
lm->setIntensity(light, settings.sunlightIntensity);
|
||||
lm->setSunHaloSize(light, settings.sunlightHaloSize);
|
||||
lm->setSunHaloFalloff(light, settings.sunlightHaloFalloff);
|
||||
lm->setSunAngularRadius(light, settings.sunlightAngularRadius);
|
||||
lm->setDirection(light, normalize(settings.sunlightDirection));
|
||||
lm->setColor(light, settings.sunlightColor);
|
||||
lm->setShadowCaster(light, settings.enableShadows);
|
||||
lm->setShadowOptions(light, settings.shadowOptions);
|
||||
lm->setIntensity(light, settings.sunlight.intensity);
|
||||
lm->setSunHaloSize(light, settings.sunlight.sunHaloSize);
|
||||
lm->setSunHaloFalloff(light, settings.sunlight.sunHaloFalloff);
|
||||
lm->setSunAngularRadius(light, settings.sunlight.sunAngularRadius);
|
||||
lm->setDirection(light, normalize(settings.sunlight.direction));
|
||||
lm->setColor(light, settings.sunlight.color);
|
||||
lm->setShadowCaster(light, settings.sunlight.castShadows && settings.enableShadows);
|
||||
lm->setShadowOptions(light, settings.sunlight.shadowOptions);
|
||||
}
|
||||
if (ibl) {
|
||||
ibl->setIntensity(settings.iblIntensity);
|
||||
@@ -652,8 +881,13 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
|
||||
for (size_t i = 0; i < sceneLightCount; i++) {
|
||||
auto const li = lm->getInstance(sceneLights[i]);
|
||||
if (li) {
|
||||
// Note: We don't have individual settings for these "scene lights" (IBL maps etc)
|
||||
// in the current structure unless we map them by index or name.
|
||||
// For now, we only enforce the global shadow toggle.
|
||||
lm->setShadowCaster(li, settings.enableShadows);
|
||||
lm->setShadowOptions(li, settings.shadowOptions);
|
||||
// We removed the global shadowOptions. If they need defaults, they should be set elsewhere or
|
||||
// we should keep a "defaultShadowOptions" but for now assuming individual control via automation
|
||||
// or existing defaults.
|
||||
}
|
||||
}
|
||||
view->setSoftShadowOptions(settings.softShadowOptions);
|
||||
@@ -676,36 +910,57 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera
|
||||
if (skybox) {
|
||||
skybox->setLayerMask(0xff, settings.skyboxEnabled ? 0xff : 0x00);
|
||||
}
|
||||
if (camera) {
|
||||
camera->setExposure(
|
||||
settings.cameraAperture,
|
||||
1.0f / settings.cameraSpeed,
|
||||
settings.cameraISO);
|
||||
|
||||
camera->setFocusDistance(settings.cameraFocusDistance);
|
||||
}
|
||||
engine->setAutomaticInstancingEnabled(settings.autoInstancingEnabled);
|
||||
}
|
||||
|
||||
void applySettings(Engine* engine, const DebugOptions& settings, Renderer* renderer) {
|
||||
if (!renderer->getFrameToSkipCount()) {
|
||||
renderer->skipNextFrames(settings.skipFrames);
|
||||
}
|
||||
}
|
||||
|
||||
void applySettings(Engine* engine, const CameraSettings& settings, Camera* camera,
|
||||
double aspectRatio) {
|
||||
camera->setExposure(settings.aperture, 1.0f / settings.shutterSpeed, settings.sensitivity);
|
||||
|
||||
camera->setFocusDistance(settings.focusDistance);
|
||||
|
||||
// Eyes are rendered from left-to-right, i.e., eye 0 is rendered to the left side of the
|
||||
// window.
|
||||
// For testing, we want to render a side-by-side layout so users can view with
|
||||
// "cross-eyed" stereo.
|
||||
// For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye.
|
||||
const auto od = settings.cameraEyeOcularDistance;
|
||||
const auto toeIn = settings.cameraEyeToeIn;
|
||||
const auto eyeCount = engine->getConfig().stereoscopicEyeCount;
|
||||
const double3 up = double3(0.0, 1.0, 0.0);
|
||||
const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}) * mat4::rotation( toeIn, up);
|
||||
const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}) * mat4::rotation(-toeIn, up);
|
||||
const mat4 modelMatrices[2] = { rightEye, leftEye };
|
||||
for (int i = 0; i < eyeCount; i++) {
|
||||
double const od = settings.eyeOcularDistance;
|
||||
double const toeIn = settings.eyeToeIn;
|
||||
size_t const eyeCount = engine->getConfig().stereoscopicEyeCount;
|
||||
double3 const up = double3(0.0, 1.0, 0.0);
|
||||
mat4 const rightEye = mat4::translation(double3{ od, 0.0, 0.0 }) * mat4::rotation(toeIn, up);
|
||||
mat4 const leftEye = mat4::translation(double3{ -od, 0.0, 0.0 }) * mat4::rotation(-toeIn, up);
|
||||
mat4 const modelMatrices[2] = { rightEye, leftEye };
|
||||
for (size_t i = 0; i < eyeCount; i++) {
|
||||
camera->setEyeModelMatrix(i, modelMatrices[i % 2]);
|
||||
}
|
||||
}
|
||||
|
||||
void applySettings(Engine* engine, const DebugOptions& settings, Renderer* renderer) {
|
||||
if (!renderer->getFrameToSkipCount()) {
|
||||
renderer->skipNextFrames(settings.skipFrames);
|
||||
if (settings.enabled && aspectRatio > 0.0) {
|
||||
camera->lookAt(settings.center, settings.lookAt, settings.up);
|
||||
if (settings.projection == CameraProjection::PERSPECTIVE) {
|
||||
if (settings.focalLength > 0.0f) {
|
||||
camera->setLensProjection(settings.focalLength, aspectRatio, settings.near,
|
||||
settings.far);
|
||||
} else {
|
||||
camera->setProjection(settings.horizontalFov, aspectRatio, settings.near,
|
||||
settings.far, Camera::Fov::VERTICAL);
|
||||
}
|
||||
} else {
|
||||
// ORTHO
|
||||
double const left = -settings.scaling.x * aspectRatio;
|
||||
double const right = settings.scaling.x * aspectRatio;
|
||||
double const bottom = -settings.scaling.y;
|
||||
double const top = settings.scaling.y;
|
||||
camera->setProjection(Camera::Projection::ORTHO, left + settings.shift.x,
|
||||
right + settings.shift.x, bottom + settings.shift.y, top + settings.shift.y,
|
||||
settings.near, settings.far);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,6 +1021,25 @@ static std::ostream& operator<<(std::ostream& out, CGQL in) {
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, CameraProjection in) {
|
||||
switch (in) {
|
||||
case CameraProjection::ORTHO: return out << "\"ORTHO\"";
|
||||
case CameraProjection::PERSPECTIVE: return out << "\"PERSPECTIVE\"";
|
||||
}
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, LightManager::Type in) {
|
||||
switch (in) {
|
||||
case LightManager::Type::SUN: return out << "\"SUN\"";
|
||||
case LightManager::Type::DIRECTIONAL: return out << "\"DIRECTIONAL\"";
|
||||
case LightManager::Type::POINT: return out << "\"POINT\"";
|
||||
case LightManager::Type::FOCUSED_SPOT: return out << "\"FOCUSED_SPOT\"";
|
||||
case LightManager::Type::SPOT: return out << "\"SPOT\"";
|
||||
}
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, ToneMapping in) {
|
||||
switch (in) {
|
||||
case ToneMapping::LINEAR: return out << "\"LINEAR\"";
|
||||
@@ -907,41 +1181,74 @@ static std::ostream& operator<<(std::ostream& out, const MaterialSettings& in) {
|
||||
return out << result;
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const LightSettings& in) {
|
||||
static std::ostream& operator<<(std::ostream& out, const LightDefinition& in) {
|
||||
return out << "{\n"
|
||||
<< "\"type\": " << in.type << ",\n"
|
||||
<< "\"position\": " << in.position << ",\n"
|
||||
<< "\"direction\": " << in.direction << ",\n"
|
||||
<< "\"color\": " << in.color << ",\n"
|
||||
<< "\"intensity\": " << in.intensity << ",\n"
|
||||
<< "\"falloff\": " << in.falloff << ",\n"
|
||||
<< "\"spotInner\": " << in.spotInner << ",\n"
|
||||
<< "\"spotOuter\": " << in.spotOuter << ",\n"
|
||||
<< "\"sunHaloSize\": " << in.sunHaloSize << ",\n"
|
||||
<< "\"sunHaloFalloff\": " << in.sunHaloFalloff << ",\n"
|
||||
<< "\"sunAngularRadius\": " << in.sunAngularRadius << ",\n"
|
||||
<< "\"castShadows\": " << to_string(in.castShadows) << ",\n"
|
||||
<< "\"shadowOptions\": " << in.shadowOptions << "\n"
|
||||
<< "}";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const LightSettings& in) {
|
||||
out << "{\n"
|
||||
<< "\"enableShadows\": " << to_string(in.enableShadows) << ",\n"
|
||||
<< "\"enableSunlight\": " << to_string(in.enableSunlight) << ",\n"
|
||||
<< "\"shadowOptions\": " << (in.shadowOptions) << ",\n"
|
||||
<< "\"softShadowOptions\": " << (in.softShadowOptions) << ",\n"
|
||||
<< "\"sunlightIntensity\": " << (in.sunlightIntensity) << ",\n"
|
||||
<< "\"sunlightHaloSize\": " << (in.sunlightHaloSize) << ",\n"
|
||||
<< "\"sunlightHaloFalloff\": " << (in.sunlightHaloFalloff) << ",\n"
|
||||
<< "\"sunlightAngularRadius\": " << (in.sunlightAngularRadius) << ",\n"
|
||||
<< "\"sunlightDirection\": " << (in.sunlightDirection) << ",\n"
|
||||
<< "\"sunlightColor\": " << (in.sunlightColor) << ",\n"
|
||||
<< "\"sunlight\": " << (in.sunlight) << ",\n"
|
||||
<< "\"iblIntensity\": " << (in.iblIntensity) << ",\n"
|
||||
<< "\"iblRotation\": " << (in.iblRotation) << "\n"
|
||||
<< "}";
|
||||
<< "\"iblRotation\": " << (in.iblRotation) << ",\n"
|
||||
<< "\"lights\": [\n";
|
||||
for (size_t i = 0; i < in.lights.size(); ++i) {
|
||||
out << in.lights[i];
|
||||
if (i < in.lights.size() - 1) {
|
||||
out << ",";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
return out << "]\n" << "}";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const CameraSettings& in) {
|
||||
return out << "{\n"
|
||||
<< "\"center\": " << in.center << ",\n"
|
||||
<< "\"lookAt\": " << in.lookAt << ",\n"
|
||||
<< "\"up\": " << in.up << ",\n"
|
||||
<< "\"horizontalFov\": " << in.horizontalFov << ",\n"
|
||||
<< "\"near\": " << in.near << ",\n"
|
||||
<< "\"far\": " << in.far << ",\n"
|
||||
<< "\"focalLength\": " << in.focalLength << ",\n"
|
||||
<< "\"aperture\": " << in.aperture << ",\n"
|
||||
<< "\"shutterSpeed\": " << in.shutterSpeed << ",\n"
|
||||
<< "\"sensitivity\": " << in.sensitivity << ",\n"
|
||||
<< "\"focusDistance\": " << in.focusDistance << ",\n"
|
||||
<< "\"eyeOcularDistance\": " << in.eyeOcularDistance << ",\n"
|
||||
<< "\"eyeToeIn\": " << in.eyeToeIn << ",\n"
|
||||
<< "\"projection\": " << in.projection << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
|
||||
<< "\"scaling\": " << in.scaling << ",\n"
|
||||
<< "\"shift\": " << in.shift << "\n"
|
||||
<< "}";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const ViewerOptions& in) {
|
||||
return out << "{\n"
|
||||
<< "\"cameraAperture\": " << (in.cameraAperture) << ",\n"
|
||||
<< "\"cameraSpeed\": " << (in.cameraSpeed) << ",\n"
|
||||
<< "\"cameraISO\": " << (in.cameraISO) << ",\n"
|
||||
<< "\"cameraNear\": " << (in.cameraNear) << ",\n"
|
||||
<< "\"cameraFar\": " << (in.cameraFar) << ",\n"
|
||||
<< "\"cameraEyeOcularDistance\": " << (in.cameraEyeOcularDistance) << ",\n"
|
||||
<< "\"cameraEyeToeIn\": " << (in.cameraEyeToeIn) << ",\n"
|
||||
<< "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n"
|
||||
<< "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n"
|
||||
<< "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n"
|
||||
<< "\"backgroundColor\": " << (in.backgroundColor) << ",\n"
|
||||
<< "\"cameraFocalLength\": " << (in.cameraFocalLength) << ",\n"
|
||||
<< "\"cameraFocusDistance\": " << (in.cameraFocusDistance) << ",\n"
|
||||
<< "\"autoInstancingEnabled\": " << to_string(in.autoInstancingEnabled) << ",\n"
|
||||
<< "\"autoScaleEnabled\": " << to_string(in.autoScaleEnabled) << "\n"
|
||||
<< "}";
|
||||
<< "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n"
|
||||
<< "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n"
|
||||
<< "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n"
|
||||
<< "\"backgroundColor\": " << (in.backgroundColor) << ",\n"
|
||||
<< "\"autoInstancingEnabled\": " << to_string(in.autoInstancingEnabled) << ",\n"
|
||||
<< "\"autoScaleEnabled\": " << to_string(in.autoScaleEnabled) << "\n"
|
||||
<< "}";
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const DebugOptions& in) {
|
||||
@@ -983,12 +1290,12 @@ static std::ostream& operator<<(std::ostream& out, const ViewSettings& in) {
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const Settings& in) {
|
||||
return out << "{\n"
|
||||
<< "\"view\": " << (in.view) << ",\n"
|
||||
<< "\"material\": " << (in.material) << ",\n"
|
||||
<< "\"lighting\": " << (in.lighting) << ",\n"
|
||||
<< "\"viewer\": " << (in.viewer) << ",\n"
|
||||
<< "\"debug\": " << (in.debug)
|
||||
<< "}";
|
||||
<< "\"view\": " << (in.view) << ",\n"
|
||||
<< "\"material\": " << (in.material) << ",\n"
|
||||
<< "\"lighting\": " << (in.lighting) << ",\n"
|
||||
<< "\"viewer\": " << (in.viewer) << ",\n"
|
||||
<< "\"camera\": " << (in.camera) << ",\n"
|
||||
<< "\"debug\": " << (in.debug) << "}";
|
||||
}
|
||||
|
||||
bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings& rhs) const {
|
||||
|
||||
@@ -429,13 +429,13 @@ ViewerGui::ViewerGui(filament::Engine* engine, filament::Scene* scene, filament:
|
||||
|
||||
using namespace filament;
|
||||
LightManager::Builder(LightManager::Type::SUN)
|
||||
.color(mSettings.lighting.sunlightColor)
|
||||
.intensity(mSettings.lighting.sunlightIntensity)
|
||||
.direction(normalize(mSettings.lighting.sunlightDirection))
|
||||
.color(mSettings.lighting.sunlight.color)
|
||||
.intensity(mSettings.lighting.sunlight.intensity)
|
||||
.direction(normalize(mSettings.lighting.sunlight.direction))
|
||||
.castShadows(true)
|
||||
.sunAngularRadius(mSettings.lighting.sunlightAngularRadius)
|
||||
.sunHaloSize(mSettings.lighting.sunlightHaloSize)
|
||||
.sunHaloFalloff(mSettings.lighting.sunlightHaloFalloff)
|
||||
.sunAngularRadius(mSettings.lighting.sunlight.sunAngularRadius)
|
||||
.sunHaloSize(mSettings.lighting.sunlight.sunHaloSize)
|
||||
.sunHaloFalloff(mSettings.lighting.sunlight.sunHaloFalloff)
|
||||
.build(*engine, mSunlight);
|
||||
if (mSettings.lighting.enableSunlight) {
|
||||
mScene->addEntity(mSunlight);
|
||||
@@ -510,9 +510,9 @@ void ViewerGui::setIndirectLight(filament::IndirectLight* ibl,
|
||||
bool const dIsValid = std::none_of(std::begin(d.v), std::end(d.v), is_not_a_number);
|
||||
bool const cIsValid = std::none_of(std::begin(c.v), std::end(c.v), is_not_a_number);
|
||||
if (dIsValid && cIsValid) {
|
||||
mSettings.lighting.sunlightDirection = d;
|
||||
mSettings.lighting.sunlightColor = c.rgb;
|
||||
mSettings.lighting.sunlightIntensity = c[3] * ibl->getIntensity();
|
||||
mSettings.lighting.sunlight.direction = d;
|
||||
mSettings.lighting.sunlight.color = c.rgb;
|
||||
mSettings.lighting.sunlight.intensity = c[3] * ibl->getIntensity();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -945,31 +945,31 @@ void ViewerGui::updateUserInterface() {
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Sunlight")) {
|
||||
ImGui::Checkbox("Enable sunlight", &light.enableSunlight);
|
||||
ImGui::SliderFloat("Sun intensity", &light.sunlightIntensity, 0.0f, 150000.0f);
|
||||
ImGui::SliderFloat("Halo size", &light.sunlightHaloSize, 1.01f, 40.0f);
|
||||
ImGui::SliderFloat("Halo falloff", &light.sunlightHaloFalloff, 4.0f, 1024.0f);
|
||||
ImGui::SliderFloat("Sun radius", &light.sunlightAngularRadius, 0.1f, 10.0f);
|
||||
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
|
||||
ImGui::SliderFloat("Shadow Far", &light.shadowOptions.shadowFar, 0.0f,
|
||||
mSettings.viewer.cameraFar);
|
||||
ImGui::SliderFloat("Sun intensity", &light.sunlight.intensity, 0.0f, 150000.0f);
|
||||
ImGui::SliderFloat("Halo size", &light.sunlight.sunHaloSize, 1.01f, 40.0f);
|
||||
ImGui::SliderFloat("Halo falloff", &light.sunlight.sunHaloFalloff, 4.0f, 1024.0f);
|
||||
ImGui::SliderFloat("Sun radius", &light.sunlight.sunAngularRadius, 0.1f, 10.0f);
|
||||
ImGuiExt::DirectionWidget("Sun direction", light.sunlight.direction.v);
|
||||
ImGui::SliderFloat("Shadow Far", &light.sunlight.shadowOptions.shadowFar, 0.0f,
|
||||
mSettings.camera.far);
|
||||
|
||||
if (ImGui::CollapsingHeader("Shadow direction")) {
|
||||
float3 shadowDirection = light.shadowOptions.transform * light.sunlightDirection;
|
||||
float3 shadowDirection = light.sunlight.shadowOptions.transform * light.sunlight.direction;
|
||||
ImGuiExt::DirectionWidget("Shadow direction", shadowDirection.v);
|
||||
light.shadowOptions.transform = normalize(quatf{
|
||||
cross(light.sunlightDirection, shadowDirection),
|
||||
sqrt(length2(light.sunlightDirection) * length2(shadowDirection))
|
||||
+ dot(light.sunlightDirection, shadowDirection)
|
||||
light.sunlight.shadowOptions.transform = normalize(quatf{
|
||||
cross(light.sunlight.direction, shadowDirection),
|
||||
sqrt(length2(light.sunlight.direction) * length2(shadowDirection))
|
||||
+ dot(light.sunlight.direction, shadowDirection)
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Shadows")) {
|
||||
ImGui::Checkbox("Enable shadows", &light.enableShadows);
|
||||
int mapSize = light.shadowOptions.mapSize;
|
||||
int mapSize = light.sunlight.shadowOptions.mapSize;
|
||||
ImGui::SliderInt("Shadow map size", &mapSize, 32, 1024);
|
||||
light.shadowOptions.mapSize = mapSize;
|
||||
ImGui::Checkbox("Stable Shadows", &light.shadowOptions.stable);
|
||||
ImGui::Checkbox("Enable LiSPSM", &light.shadowOptions.lispsm);
|
||||
light.sunlight.shadowOptions.mapSize = mapSize;
|
||||
ImGui::Checkbox("Stable Shadows", &light.sunlight.shadowOptions.stable);
|
||||
ImGui::Checkbox("Enable LiSPSM", &light.sunlight.shadowOptions.lispsm);
|
||||
|
||||
int shadowType = (int)mSettings.view.shadowType;
|
||||
ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0PCFd\0\0");
|
||||
@@ -977,7 +977,7 @@ void ViewerGui::updateUserInterface() {
|
||||
|
||||
if (mSettings.view.shadowType == ShadowType::VSM) {
|
||||
ImGui::Checkbox("High precision", &mSettings.view.vsmShadowOptions.highPrecision);
|
||||
ImGui::Checkbox("ELVSM", &mSettings.lighting.shadowOptions.vsm.elvsm);
|
||||
ImGui::Checkbox("ELVSM", &mSettings.lighting.sunlight.shadowOptions.vsm.elvsm);
|
||||
char label[32];
|
||||
snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2);
|
||||
ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label);
|
||||
@@ -989,7 +989,7 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label);
|
||||
mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy;
|
||||
ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping);
|
||||
ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
|
||||
ImGui::SliderFloat("VSM blur", &light.sunlight.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
|
||||
|
||||
// These are not very useful in practice (defaults are good), but we keep them here for debugging
|
||||
//ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f);
|
||||
@@ -1000,15 +1000,15 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderFloat("Penumbra Ratio scale", &light.softShadowOptions.penumbraRatioScale, 1.0f, 100.0f);
|
||||
}
|
||||
|
||||
int shadowCascades = light.shadowOptions.shadowCascades;
|
||||
int shadowCascades = light.sunlight.shadowOptions.shadowCascades;
|
||||
ImGui::SliderInt("Cascades", &shadowCascades, 1, 4);
|
||||
ImGui::Checkbox("Debug cascades",
|
||||
debug.getPropertyAddress<bool>("d.shadowmap.visualize_cascades"));
|
||||
ImGui::Checkbox("Enable contact shadows", &light.shadowOptions.screenSpaceContactShadows);
|
||||
ImGui::SliderFloat("Split pos 0", &light.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Split pos 1", &light.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Split pos 2", &light.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
|
||||
light.shadowOptions.shadowCascades = shadowCascades;
|
||||
ImGui::Checkbox("Enable contact shadows", &light.sunlight.shadowOptions.screenSpaceContactShadows);
|
||||
ImGui::SliderFloat("Split pos 0", &light.sunlight.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Split pos 1", &light.sunlight.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Split pos 2", &light.sunlight.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
|
||||
light.sunlight.shadowOptions.shadowCascades = shadowCascades;
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
@@ -1083,12 +1083,12 @@ void ViewerGui::updateUserInterface() {
|
||||
if (ImGui::CollapsingHeader("Camera")) {
|
||||
ImGui::Indent();
|
||||
|
||||
ImGui::SliderFloat("Focal length (mm)", &mSettings.viewer.cameraFocalLength, 16.0f, 90.0f);
|
||||
ImGui::SliderFloat("Aperture", &mSettings.viewer.cameraAperture, 1.0f, 32.0f);
|
||||
ImGui::SliderFloat("Speed (1/s)", &mSettings.viewer.cameraSpeed, 1000.0f, 1.0f);
|
||||
ImGui::SliderFloat("ISO", &mSettings.viewer.cameraISO, 25.0f, 6400.0f);
|
||||
ImGui::SliderFloat("Near", &mSettings.viewer.cameraNear, 0.001f, 1.0f);
|
||||
ImGui::SliderFloat("Far", &mSettings.viewer.cameraFar, 1.0f, 10000.0f);
|
||||
ImGui::SliderFloat("Focal length (mm)", &mSettings.camera.focalLength, 16.0f, 90.0f);
|
||||
ImGui::SliderFloat("Aperture", &mSettings.camera.aperture, 1.0f, 32.0f);
|
||||
ImGui::SliderFloat("Speed (1/s)", &mSettings.camera.shutterSpeed, 1000.0f, 1.0f);
|
||||
ImGui::SliderFloat("ISO", &mSettings.camera.sensitivity, 25.0f, 6400.0f);
|
||||
ImGui::SliderFloat("Near", &mSettings.camera.near, 0.001f, 1.0f);
|
||||
ImGui::SliderFloat("Far", &mSettings.camera.far, 1.0f, 10000.0f);
|
||||
|
||||
if (ImGui::CollapsingHeader("DoF")) {
|
||||
bool dofMedian = mSettings.view.dof.filter == View::DepthOfFieldOptions::Filter::MEDIAN;
|
||||
@@ -1097,7 +1097,7 @@ void ViewerGui::updateUserInterface() {
|
||||
if (!dofRingCount) dofRingCount = 5;
|
||||
if (!dofMaxCoC) dofMaxCoC = 32;
|
||||
ImGui::Checkbox("Enabled##dofEnabled", &mSettings.view.dof.enabled);
|
||||
ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f);
|
||||
ImGui::SliderFloat("Focus distance", &mSettings.camera.focusDistance, 0.0f, 30.0f);
|
||||
ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f);
|
||||
ImGui::SliderFloat("CoC aspect-ratio", &mSettings.view.dof.cocAspectRatio, 0.25f, 4.0f);
|
||||
ImGui::SliderInt("Ring count", &dofRingCount, 1, 17);
|
||||
@@ -1162,12 +1162,11 @@ void ViewerGui::updateUserInterface() {
|
||||
debug.getPropertyAddress<bool>("d.stereo.combine_multiview_images"));
|
||||
ImGui::Unindent();
|
||||
#endif
|
||||
ImGui::SliderFloat("Ocular distance",
|
||||
&mSettings.viewer.cameraEyeOcularDistance, 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Ocular distance", &mSettings.camera.eyeOcularDistance, 0.0f, 1.0f);
|
||||
|
||||
float toeInDegrees = mSettings.viewer.cameraEyeToeIn / f::PI * 180.0f;
|
||||
float toeInDegrees = mSettings.camera.eyeToeIn / f::PI * 180.0f;
|
||||
ImGui::SliderFloat("Toe in", &toeInDegrees, 0.0f, 30.0, "%.3f°");
|
||||
mSettings.viewer.cameraEyeToeIn = toeInDegrees / 180.0f * f::PI;
|
||||
mSettings.camera.eyeToeIn = toeInDegrees / 180.0f * f::PI;
|
||||
|
||||
ImGui::Unindent();
|
||||
#endif
|
||||
@@ -1185,6 +1184,9 @@ void ViewerGui::updateUserInterface() {
|
||||
// At this point, all View settings have been modified,
|
||||
// so we can now push them into the Filament View.
|
||||
applySettings(mEngine, mSettings.view, mView);
|
||||
double const aspect =
|
||||
(double) mView->getViewport().width / (double) mView->getViewport().height;
|
||||
applySettings(mEngine, mSettings.camera, &mView->getCamera(), aspect);
|
||||
|
||||
auto lights = utils::FixedCapacityVector<utils::Entity>::with_capacity(mScene->getEntityCount());
|
||||
mScene->forEach([&](utils::Entity entity) {
|
||||
|
||||
@@ -221,3 +221,88 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
static const char* JSON_TEST_CAMERA = R"TXT(
|
||||
{
|
||||
"camera": {
|
||||
"aperture": 4.0,
|
||||
"shutterSpeed": 200,
|
||||
"sensitivity": 400,
|
||||
"focalLength": 50,
|
||||
"focusDistance": 5.0,
|
||||
"projection": "ORTHO",
|
||||
"near": 0.5,
|
||||
"far": 1000.0,
|
||||
"scaling": [0.5, 0.6],
|
||||
"shift": [0.1, 0.2]
|
||||
}
|
||||
}
|
||||
)TXT";
|
||||
|
||||
TEST_F(ViewSettingsTest, JsonTestCamera) {
|
||||
JsonSerializer serializer;
|
||||
Settings settings;
|
||||
ASSERT_TRUE(serializer.readJson(JSON_TEST_CAMERA, strlen(JSON_TEST_CAMERA), &settings));
|
||||
|
||||
EXPECT_FLOAT_EQ(settings.camera.aperture, 4.0f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.shutterSpeed, 200.0f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.sensitivity, 400.0f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.focalLength, 50.0f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.focusDistance, 5.0f);
|
||||
EXPECT_EQ(settings.camera.projection, CameraProjection::ORTHO);
|
||||
EXPECT_FLOAT_EQ(settings.camera.near, 0.5f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.far, 1000.0f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.scaling.x, 0.5f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.scaling.y, 0.6f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.shift.x, 0.1f);
|
||||
EXPECT_FLOAT_EQ(settings.camera.shift.y, 0.2f);
|
||||
}
|
||||
|
||||
static const char* JSON_TEST_LIGHTS = R"TXT(
|
||||
{
|
||||
"lighting": {
|
||||
"enableSunlight": false,
|
||||
"lights": [
|
||||
{
|
||||
"type": "POINT",
|
||||
"position": [1.0, 2.0, 3.0],
|
||||
"color": [1.0, 0.0, 0.0],
|
||||
"intensity": 1000.0,
|
||||
"falloff": 5.0,
|
||||
"castShadows": true
|
||||
},
|
||||
{
|
||||
"type": "SPOT",
|
||||
"direction": [0.0, -1.0, 0.0],
|
||||
"spotInner": 0.5,
|
||||
"spotOuter": 0.8
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)TXT";
|
||||
|
||||
TEST_F(ViewSettingsTest, JsonTestLights) {
|
||||
JsonSerializer serializer;
|
||||
Settings settings;
|
||||
ASSERT_TRUE(serializer.readJson(JSON_TEST_LIGHTS, strlen(JSON_TEST_LIGHTS), &settings));
|
||||
|
||||
EXPECT_FALSE(settings.lighting.enableSunlight);
|
||||
ASSERT_EQ(settings.lighting.lights.size(), 2);
|
||||
|
||||
const auto& light0 = settings.lighting.lights[0];
|
||||
EXPECT_EQ(light0.type, LightManager::Type::POINT);
|
||||
EXPECT_FLOAT_EQ(light0.position.x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(light0.position.y, 2.0f);
|
||||
EXPECT_FLOAT_EQ(light0.position.z, 3.0f);
|
||||
EXPECT_FLOAT_EQ(light0.color.r, 1.0f);
|
||||
EXPECT_FLOAT_EQ(light0.intensity, 1000.0f);
|
||||
EXPECT_FLOAT_EQ(light0.falloff, 5.0f);
|
||||
EXPECT_TRUE(light0.castShadows);
|
||||
|
||||
const auto& light1 = settings.lighting.lights[1];
|
||||
EXPECT_EQ(light1.type, LightManager::Type::SPOT);
|
||||
EXPECT_FLOAT_EQ(light1.direction.y, -1.0f);
|
||||
EXPECT_FLOAT_EQ(light1.spotInner, 0.5f);
|
||||
EXPECT_FLOAT_EQ(light1.spotOuter, 0.8f);
|
||||
}
|
||||
|
||||
@@ -781,7 +781,7 @@ int main(int argc, char** argv) {
|
||||
? AutomationEngine::Options::ExportFormat::PPM
|
||||
: AutomationEngine::Options::ExportFormat::TIFF;
|
||||
app.automationEngine->setOptions(options);
|
||||
app.viewer->stopAnimation();
|
||||
app.viewer->getSettings().animation.enabled = false;
|
||||
}
|
||||
|
||||
if (!app.settingsFile.empty()) {
|
||||
@@ -1084,7 +1084,16 @@ int main(int argc, char** argv) {
|
||||
// Gradually add renderables to the scene as their textures become ready.
|
||||
app.viewer->populateScene();
|
||||
|
||||
app.viewer->applyAnimation(now);
|
||||
auto const& animSettings = app.viewer->getSettings().animation;
|
||||
if (animSettings.enabled) {
|
||||
double animTime = now;
|
||||
if (animSettings.time >= 0.0f) {
|
||||
animTime = animSettings.time;
|
||||
} else {
|
||||
animTime *= animSettings.speed;
|
||||
}
|
||||
app.viewer->applyAnimation(animTime);
|
||||
}
|
||||
};
|
||||
|
||||
auto resize = [&app](Engine*, View* view) {
|
||||
@@ -1116,13 +1125,21 @@ int main(int argc, char** argv) {
|
||||
|
||||
// Note that this focal length might be different from the slider value because the
|
||||
// automation engine applies Camera::computeEffectiveFocalLength when DoF is enabled.
|
||||
FilamentApp::get().setCameraFocalLength(viewerOptions.cameraFocalLength);
|
||||
FilamentApp::get().setCameraNearFar(viewerOptions.cameraNear, viewerOptions.cameraFar);
|
||||
float focalLength = app.viewer->getSettings().camera.focalLength;
|
||||
float const focusDistance = app.viewer->getSettings().camera.focusDistance;
|
||||
if (app.viewer->getSettings().view.dof.enabled) {
|
||||
focalLength = Camera::computeEffectiveFocalLength(focalLength / 1000.0,
|
||||
std::max(0.1f, focusDistance)) *
|
||||
1000.0;
|
||||
}
|
||||
FilamentApp::get().setCameraFocalLength(focalLength);
|
||||
FilamentApp::get().setCameraNearFar(app.viewer->getSettings().camera.near,
|
||||
app.viewer->getSettings().camera.far);
|
||||
|
||||
const size_t cameraCount = app.asset->getCameraEntityCount();
|
||||
size_t const cameraCount = app.asset->getCameraEntityCount();
|
||||
view->setCamera(app.mainCamera);
|
||||
|
||||
const int currentCamera = app.viewer->getCurrentCamera();
|
||||
int const currentCamera = app.viewer->getCurrentCamera();
|
||||
if (currentCamera > 0 && currentCamera <= cameraCount) {
|
||||
const utils::Entity* cameras = app.asset->getCameraEntities();
|
||||
Camera* camera = engine->getCameraComponent(cameras[currentCamera - 1]);
|
||||
@@ -1150,8 +1167,11 @@ int main(int argc, char** argv) {
|
||||
Camera& camera = view->getCamera();
|
||||
Skybox* skybox = scene->getSkybox();
|
||||
applySettings(engine, app.viewer->getSettings().viewer, &camera, skybox, renderer);
|
||||
double const aspect =
|
||||
(double) view->getViewport().width / (double) view->getViewport().height;
|
||||
applySettings(engine, app.viewer->getSettings().camera, &camera, aspect);
|
||||
|
||||
// FIMXE: This applySettings() is done here instead of in AutomationEngine.cpp because
|
||||
// FIXME: This applySettings() is done here instead of in AutomationEngine.cpp because
|
||||
// we need access to the Renderer, which AutomationEngine does not provide.
|
||||
applySettings(engine, app.viewer->getSettings().debug, renderer);
|
||||
|
||||
@@ -1183,7 +1203,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
};
|
||||
|
||||
auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) {
|
||||
auto postRender = [&app](Engine* engine, View* view, Scene* scene, Renderer* renderer) {
|
||||
if (app.screenshot) {
|
||||
std::ostringstream stringStream;
|
||||
stringStream << "screenshot" << std::setfill('0') << std::setw(2) << +app.screenshotSeq;
|
||||
@@ -1202,6 +1222,12 @@ int main(int argc, char** argv) {
|
||||
.renderer = renderer,
|
||||
.materials = app.instance->getMaterialInstances(),
|
||||
.materialCount = app.instance->getMaterialInstanceCount(),
|
||||
.lightManager = &engine->getLightManager(),
|
||||
.scene = scene,
|
||||
.indirectLight = app.viewer->getIndirectLight(),
|
||||
.sunlight = app.viewer->getSunlight(),
|
||||
.assetLights = app.asset->getLightEntities(),
|
||||
.assetLightCount = app.asset->getLightEntityCount(),
|
||||
};
|
||||
app.automationEngine->tick(engine, content, ImGui::GetIO().DeltaTime);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"name": "base",
|
||||
"models": ["Box", "BoxTextured", "FlightHelmet", "lucy"],
|
||||
"rendering": {
|
||||
"viewer.cameraFocalLength": 35.0,
|
||||
"camera.focalLength": 35.0,
|
||||
"view.postProcessingEnabled": true,
|
||||
"view.dithering": "NONE"
|
||||
},
|
||||
@@ -56,7 +56,7 @@
|
||||
"description": "transmission",
|
||||
"apply_presets": ["base", "transmission_models"],
|
||||
"rendering": {
|
||||
"viewer.cameraFocalLength": 52.0
|
||||
"camera.focalLength": 52.0
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"models": ["lucy", "DamagedHelmet"], // [optional] Base name for the gltf file used in the test. For
|
||||
// example, source files are lucy.glb and DamagedHelmet.gltf
|
||||
"rendering": { // [required] Rendering settings used in the test. The json format
|
||||
"viewer.cameraFocusDistance": 0, // is taken from AutomationSpec in libs/viewer
|
||||
"camera.focusDistance": 0, // is taken from AutomationSpec in libs/viewer
|
||||
"view.postProcessingEnabled": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user