Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce4199b5f7 | ||
|
|
1c2ffc9ed4 | ||
|
|
574518ea74 | ||
|
|
d5a274fa25 | ||
|
|
3351db1178 | ||
|
|
b70aa43727 | ||
|
|
1f9a11802d | ||
|
|
89835a7a67 | ||
|
|
28ef805e5d | ||
|
|
6c0bd360b3 | ||
|
|
063affb612 | ||
|
|
9c857f64ae | ||
|
|
5966b5dd8f | ||
|
|
1795c40591 | ||
|
|
26f4239d8c | ||
|
|
ad29b9c70a | ||
|
|
1c817026f2 | ||
|
|
44a954b559 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.53.5'
|
||||
implementation 'com.google.android.filament:filament-android:1.54.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.53.5'
|
||||
pod 'Filament', '~> 1.54.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
+4
-1
@@ -7,7 +7,10 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.53.6
|
||||
## v1.54.1
|
||||
|
||||
|
||||
## v1.54.0
|
||||
|
||||
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
|
||||
- Fix a crash when compiling shaders on IMG devices
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.53.5
|
||||
VERSION_NAME=1.54.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -1058,7 +1058,7 @@ struct RasterState {
|
||||
bool inverseFrontFaces : 1; // 31
|
||||
|
||||
//! padding, must be 0
|
||||
uint8_t padding : 1; // 32
|
||||
bool depthClamp : 1; // 32
|
||||
};
|
||||
uint32_t u = 0;
|
||||
};
|
||||
|
||||
@@ -305,6 +305,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
|
||||
@@ -112,6 +112,7 @@ struct MetalContext {
|
||||
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
DepthClampStateTracker depthClampState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
|
||||
@@ -834,6 +834,10 @@ bool MetalDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthClampSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -1751,6 +1755,13 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
[mContext->currentRenderPassEncoder setFrontFacingWinding:winding];
|
||||
}
|
||||
|
||||
// depth clip mode
|
||||
MTLDepthClipMode depthClipMode = rs.depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
|
||||
mContext->depthClampState.updateState(depthClipMode);
|
||||
if (mContext->depthClampState.stateChanged()) {
|
||||
[mContext->currentRenderPassEncoder setDepthClipMode:depthClipMode];
|
||||
}
|
||||
|
||||
// Set the depth-stencil state, if a state change is needed.
|
||||
DepthStencilState depthState;
|
||||
if (depthAttachment) {
|
||||
|
||||
@@ -382,6 +382,7 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
|
||||
|
||||
using CullModeStateTracker = StateTracker<MTLCullMode>;
|
||||
using WindingStateTracker = StateTracker<MTLWinding>;
|
||||
using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
|
||||
|
||||
// Argument encoder
|
||||
|
||||
|
||||
@@ -202,6 +202,10 @@ bool NoopDriver::isProtectedTexturesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthClampSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -541,9 +541,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
bugs->delay_fbo_destruction = true;
|
||||
// PowerVR seems to have no problem with this (which is good for us)
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
// PowerVR doesn't respect lengths passed to glShaderSource, so concatenate them into a
|
||||
// single string.
|
||||
bugs->concatenate_shader_strings = true;
|
||||
} else if (strstr(renderer, "Apple")) {
|
||||
// Apple GPU
|
||||
} else if (strstr(renderer, "Tegra") ||
|
||||
@@ -679,6 +676,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
@@ -749,6 +747,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||
ext->EXT_clip_cull_distance = true;
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext->EXT_depth_clamp = true;
|
||||
ext->EXT_discard_framebuffer = false;
|
||||
ext->EXT_disjoint_timer_query = true;
|
||||
ext->EXT_multisampled_render_to_texture = false;
|
||||
|
||||
@@ -220,8 +220,9 @@ public:
|
||||
bool EXT_color_buffer_float;
|
||||
bool EXT_color_buffer_half_float;
|
||||
bool EXT_debug_marker;
|
||||
bool EXT_disjoint_timer_query;
|
||||
bool EXT_depth_clamp;
|
||||
bool EXT_discard_framebuffer;
|
||||
bool EXT_disjoint_timer_query;
|
||||
bool EXT_multisampled_render_to_texture2;
|
||||
bool EXT_multisampled_render_to_texture;
|
||||
bool EXT_protected_textures;
|
||||
@@ -239,10 +240,10 @@ public:
|
||||
bool KHR_parallel_shader_compile;
|
||||
bool KHR_texture_compression_astc_hdr;
|
||||
bool KHR_texture_compression_astc_ldr;
|
||||
bool OES_depth_texture;
|
||||
bool OES_depth24;
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_EGL_image_external_essl3;
|
||||
bool OES_depth24;
|
||||
bool OES_depth_texture;
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_rgb8_rgba8;
|
||||
bool OES_standard_derivatives;
|
||||
bool OES_texture_npot;
|
||||
@@ -315,12 +316,6 @@ public:
|
||||
// bugs or performance issues.
|
||||
bool force_feature_level0;
|
||||
|
||||
// Some drivers don't respect the length argument of glShaderSource() and (apparently)
|
||||
// require each shader source string to be null-terminated. This works around the issue by
|
||||
// concatenating the strings into a single null-terminated string before passing it to
|
||||
// glShaderSource().
|
||||
bool concatenate_shader_strings;
|
||||
|
||||
} bugs = {};
|
||||
|
||||
// state getters -- as needed.
|
||||
@@ -567,9 +562,6 @@ private:
|
||||
{ bugs.force_feature_level0,
|
||||
"force_feature_level0",
|
||||
""},
|
||||
{ bugs.concatenate_shader_strings,
|
||||
"concatenate_shader_strings",
|
||||
""},
|
||||
}};
|
||||
|
||||
// this is chosen to minimize code size
|
||||
@@ -636,6 +628,7 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
case GL_PROGRAM_POINT_SIZE: index = 10; break;
|
||||
#endif
|
||||
case GL_DEPTH_CLAMP: index = 11; break;
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < state.enables.caps.size());
|
||||
|
||||
@@ -451,6 +451,14 @@ void OpenGLDriver::setRasterState(RasterState rs) noexcept {
|
||||
} else {
|
||||
gl.disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
|
||||
}
|
||||
|
||||
if (gl.ext.EXT_depth_clamp) {
|
||||
if (rs.depthClamp) {
|
||||
gl.enable(GL_DEPTH_CLAMP);
|
||||
} else {
|
||||
gl.disable(GL_DEPTH_CLAMP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::setStencilState(StencilState ss) noexcept {
|
||||
@@ -2119,6 +2127,10 @@ bool OpenGLDriver::isProtectedTexturesSupported() {
|
||||
return getContext().ext.EXT_protected_textures;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthClampSupported() {
|
||||
return getContext().ext.EXT_depth_clamp;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
|
||||
@@ -597,40 +597,30 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
version = "#version 310 es\n";
|
||||
}
|
||||
|
||||
const std::array<const char*, 5> sources = {
|
||||
version.data(),
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
std::array<std::string_view, 5> sources = {
|
||||
version,
|
||||
prolog,
|
||||
specializationConstantString,
|
||||
packingFunctions,
|
||||
{ body.data(), body.size() - 1 } // null-terminated
|
||||
};
|
||||
|
||||
const std::array<GLint, 5> lengths = {
|
||||
(GLint)version.length(),
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
(GLint)body.length() - 1 // null terminated
|
||||
};
|
||||
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
||||
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
||||
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
||||
auto partitionPoint = std::stable_partition(
|
||||
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
||||
size_t count = std::distance(sources.begin(), partitionPoint);
|
||||
|
||||
std::array<const char*, 5> shaderStrings;
|
||||
std::array<GLint, 5> lengths;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
shaderStrings[i] = sources[i].data();
|
||||
lengths[i] = sources[i].size();
|
||||
}
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
|
||||
if (UTILS_UNLIKELY(context.bugs.concatenate_shader_strings)) {
|
||||
size_t totalSize = 0;
|
||||
for (size_t i = 0; i < sources.size(); i++) {
|
||||
totalSize += lengths[i];
|
||||
}
|
||||
std::string concatenatedShaderSource;
|
||||
concatenatedShaderSource.reserve(totalSize);
|
||||
for (size_t i = 0; i < sources.size(); i++) {
|
||||
concatenatedShaderSource.append(sources[i], lengths[i]);
|
||||
}
|
||||
const GLchar* ptr = concatenatedShaderSource.c_str();
|
||||
GLint length = concatenatedShaderSource.length();
|
||||
glShaderSource(shaderId, 1, &ptr, &length);
|
||||
} else {
|
||||
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
|
||||
}
|
||||
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
||||
|
||||
glCompileShader(shaderId);
|
||||
|
||||
|
||||
@@ -201,6 +201,12 @@ using namespace glext;
|
||||
# define GL_CLIP_DISTANCE1 0x3001
|
||||
#endif
|
||||
|
||||
#if defined(GL_EXT_depth_clamp)
|
||||
# define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
|
||||
#else
|
||||
# define GL_DEPTH_CLAMP 0x864F
|
||||
#endif
|
||||
|
||||
#if defined(GL_KHR_debug)
|
||||
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
|
||||
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
|
||||
|
||||
@@ -125,6 +125,10 @@ public:
|
||||
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDepthClampSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDebugMarkersSupported() const noexcept {
|
||||
return mDebugMarkersSupported;
|
||||
}
|
||||
|
||||
@@ -944,6 +944,10 @@ bool VulkanDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthClampSupported() {
|
||||
return mContext.isDepthClampSupported();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -1816,6 +1820,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
|
||||
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
||||
.rasterizationSamples = rt->getSamples(),
|
||||
.depthClamp = rasterState.depthClamp,
|
||||
.reserved = 0,
|
||||
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
|
||||
.colorBlendOp = rasterState.blendEquationRGB,
|
||||
.alphaBlendOp = rasterState.blendEquationAlpha,
|
||||
|
||||
@@ -184,6 +184,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
vkRaster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
vkRaster.cullMode = raster.cullMode;
|
||||
vkRaster.frontFace = raster.frontFace;
|
||||
vkRaster.depthClampEnable = raster.depthClamp;
|
||||
vkRaster.depthBiasEnable = raster.depthBiasEnable;
|
||||
vkRaster.depthBiasConstantFactor = raster.depthBiasConstantFactor;
|
||||
vkRaster.depthBiasClamp = 0.0f;
|
||||
|
||||
@@ -90,7 +90,9 @@ public:
|
||||
VkBlendFactor srcAlphaBlendFactor : 5;
|
||||
VkBlendFactor dstAlphaBlendFactor : 5;
|
||||
VkColorComponentFlags colorWriteMask : 4;
|
||||
uint8_t rasterizationSamples; // offset = 4 bytes
|
||||
uint8_t rasterizationSamples : 4;// offset = 4 bytes
|
||||
uint8_t depthClamp : 1;
|
||||
uint8_t reserved : 3;
|
||||
uint8_t colorTargetCount; // offset = 5 bytes
|
||||
BlendEquation colorBlendOp : 4; // offset = 6 bytes
|
||||
BlendEquation alphaBlendOp : 4;
|
||||
|
||||
@@ -325,6 +325,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
.depthClamp = features.depthClamp,
|
||||
.samplerAnisotropy = features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.textureCompressionBC,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
@@ -727,7 +728,7 @@ public:
|
||||
void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept;
|
||||
|
||||
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
|
||||
Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept;
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
|
||||
/** Result of a picking query */
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
|
||||
#include "FrameSkipper.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
FrameSkipper::FrameSkipper(size_t latency) noexcept
|
||||
: mLast(latency - 1) {
|
||||
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
|
||||
assert_invariant(latency <= MAX_FRAME_LATENCY);
|
||||
}
|
||||
|
||||
@@ -41,31 +47,32 @@ void FrameSkipper::terminate(DriverApi& driver) noexcept {
|
||||
|
||||
bool FrameSkipper::beginFrame(DriverApi& driver) noexcept {
|
||||
auto& fences = mDelayedFences;
|
||||
auto fence = fences.front();
|
||||
if (fence) {
|
||||
auto status = driver.getFenceStatus(fence);
|
||||
if (status == FenceStatus::TIMEOUT_EXPIRED) {
|
||||
// Sync not ready, skip frame
|
||||
if (fences.front()) {
|
||||
// Do we have a latency old fence?
|
||||
auto status = driver.getFenceStatus(fences.front());
|
||||
if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) {
|
||||
// The fence hasn't signaled yet, skip this frame
|
||||
return false;
|
||||
}
|
||||
assert_invariant(status == FenceStatus::CONDITION_SATISFIED);
|
||||
driver.destroyFence(fence);
|
||||
}
|
||||
// shift all fences down by 1
|
||||
std::move(fences.begin() + 1, fences.end(), fences.begin());
|
||||
fences.back() = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameSkipper::endFrame(DriverApi& driver) noexcept {
|
||||
// If the user produced a new frame despite the fact that the previous one wasn't finished
|
||||
// (i.e. FrameSkipper::beginFrame() returned false), we need to make sure to replace
|
||||
// a fence that might be here already)
|
||||
auto& fence = mDelayedFences[mLast];
|
||||
if (fence) {
|
||||
driver.destroyFence(fence);
|
||||
auto& fences = mDelayedFences;
|
||||
size_t const last = mLast;
|
||||
|
||||
// pop the oldest fence and advance the other ones
|
||||
if (fences.front()) {
|
||||
driver.destroyFence(fences.front());
|
||||
}
|
||||
fence = driver.createFence();
|
||||
std::move(fences.begin() + 1, fences.end(), fences.begin());
|
||||
|
||||
// add a new fence to the end
|
||||
assert_invariant(!fences[last]);
|
||||
|
||||
fences[last] = driver.createFence();
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
/*
|
||||
@@ -60,7 +63,7 @@ public:
|
||||
private:
|
||||
using Container = std::array<backend::Handle<backend::HwFence>, MAX_FRAME_LATENCY>;
|
||||
mutable Container mDelayedFences{};
|
||||
size_t mLast;
|
||||
uint8_t const mLast;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#include <math/mat4.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
@@ -120,6 +122,8 @@ void PerViewUniforms::prepareViewport(
|
||||
s.resolution = { physical.zw, 1.0f / physical.zw };
|
||||
s.logicalViewportScale = physical.zw / logical.zw;
|
||||
s.logicalViewportOffset = -logical.xy / logical.zw;
|
||||
utils::slog.e << "resolution=" << s.resolution << " logical=" <<
|
||||
s.logicalViewportScale << " " << s.logicalViewportOffset << utils::io::endl;
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareTime(FEngine& engine, math::float4 const& userTime) noexcept {
|
||||
|
||||
@@ -494,7 +494,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
|
||||
struct StructureMipmapData {
|
||||
FrameGraphId<FrameGraphTexture> depth;
|
||||
uint32_t rt[8];
|
||||
};
|
||||
|
||||
fg.addPass<StructureMipmapData>("StructureMipmap",
|
||||
@@ -505,7 +504,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
.level = uint8_t(i)
|
||||
});
|
||||
out = builder.write(out, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
data.rt[i - 1] = builder.declareRenderPass("Structure mip target", {
|
||||
builder.declareRenderPass("Structure mip target", {
|
||||
.attachments = { .depth = out }
|
||||
});
|
||||
}
|
||||
@@ -2456,18 +2455,24 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
auto yy = float4{
|
||||
(float)vp.left / input.width,
|
||||
(float)vp.bottom / input.height,
|
||||
(float)vp.width / input.width,
|
||||
(float)vp.height / input.height
|
||||
};
|
||||
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("bloom", bloomParameters);
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("fxaa", colorGradingConfig.fxaa);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
mi->setParameter("viewport", float4{
|
||||
(float)vp.left / input.width,
|
||||
(float)vp.bottom / input.height,
|
||||
(float)vp.width / input.width,
|
||||
(float)vp.height / input.height
|
||||
});
|
||||
mi->setParameter("viewport", yy);
|
||||
|
||||
utils::slog.e <<"yy------------yy in=" <<
|
||||
input.width << "x" << input.height << " viewport=" << yy << utils::io::endl;
|
||||
|
||||
|
||||
const uint8_t variant = uint8_t(colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
@@ -2509,6 +2514,17 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
|
||||
auto xx = float4{
|
||||
(float)vp.left / inDesc.width,
|
||||
(float)vp.bottom / inDesc.height,
|
||||
(float)vp.width / inDesc.width,
|
||||
(float)vp.height / inDesc.height
|
||||
};
|
||||
|
||||
utils::slog.e <<"xx-----------xx" <<
|
||||
"in=" << inDesc.width <<"x" << inDesc.height << " vp=" << vp << " viewport=" << xx << utils::io::endl;
|
||||
|
||||
mi->setParameter("viewport", float4{
|
||||
(float)vp.left / inDesc.width,
|
||||
(float)vp.bottom / inDesc.height,
|
||||
|
||||
@@ -419,7 +419,8 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
UTILS_ALWAYS_INLINE // This function exists only to make the code more readable. we want it inlined.
|
||||
inline // and we don't need it in the compilation unit
|
||||
void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
FMaterialInstance const* const UTILS_RESTRICT mi, bool inverseFrontFaces) noexcept {
|
||||
FMaterialInstance const* const UTILS_RESTRICT mi,
|
||||
bool inverseFrontFaces, bool hasDepthClamp) noexcept {
|
||||
|
||||
FMaterial const * const UTILS_RESTRICT ma = mi->getMaterial();
|
||||
variant = Variant::filterVariant(variant, ma->isVariantLit());
|
||||
@@ -460,6 +461,7 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc();
|
||||
cmdDraw.info.rasterState.depthClamp = hasDepthClamp;
|
||||
cmdDraw.info.materialVariant = variant;
|
||||
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
|
||||
}
|
||||
@@ -558,6 +560,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
bool const hasInstancedStereo =
|
||||
renderFlags & IS_INSTANCED_STEREOSCOPIC;
|
||||
|
||||
bool const hasDepthClamp =
|
||||
renderFlags & HAS_DEPTH_CLAMP;
|
||||
|
||||
float const cameraPositionDotCameraForward = dot(cameraPosition, cameraForward);
|
||||
|
||||
auto const* const UTILS_RESTRICT soaWorldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
|
||||
@@ -577,6 +582,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.rasterState.depthWrite = true;
|
||||
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
||||
cmd.info.rasterState.alphaToCoverage = false;
|
||||
cmd.info.rasterState.depthClamp = hasDepthClamp;
|
||||
}
|
||||
|
||||
for (uint32_t i = range.first; i < range.last; ++i) {
|
||||
@@ -610,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
// Here, objects close to the camera (but behind) will be drawn first.
|
||||
// An alternative that keeps the mathematical ordering is given here:
|
||||
// distanceBits ^= ((int32_t(distanceBits) >> 31) | 0x80000000u);
|
||||
float const distance = -dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward;
|
||||
float const distance = -(dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward);
|
||||
uint32_t const distanceBits = reinterpret_cast<uint32_t const&>(distance);
|
||||
|
||||
// calculate the per-primitive face winding order inversion
|
||||
@@ -691,7 +697,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.morphingOffset = primitive.getMorphingBufferOffset();
|
||||
|
||||
if constexpr (isColorPass) {
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi,
|
||||
inverseFrontFaces, hasDepthClamp);
|
||||
const bool blendPass = Pass(cmd.key & PASS_MASK) == Pass::BLENDED;
|
||||
if (blendPass) {
|
||||
// TODO: at least for transparent objects, AABB should be per primitive
|
||||
|
||||
@@ -284,6 +284,7 @@ public:
|
||||
static constexpr RenderFlags HAS_SHADOWING = 0x01;
|
||||
static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02;
|
||||
static constexpr RenderFlags IS_INSTANCED_STEREOSCOPIC = 0x04;
|
||||
static constexpr RenderFlags HAS_DEPTH_CLAMP = 0x08;
|
||||
|
||||
// Arena used for commands
|
||||
using Arena = utils::Arena<
|
||||
@@ -444,7 +445,7 @@ private:
|
||||
uint8_t instancedStereoEyeCount) noexcept;
|
||||
|
||||
static void setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
FMaterialInstance const* mi, bool inverseFrontFaces) noexcept;
|
||||
FMaterialInstance const* mi, bool inverseFrontFaces, bool hasDepthClamp) noexcept;
|
||||
|
||||
static void updateSummedPrimitiveCounts(
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> vr) noexcept;
|
||||
|
||||
@@ -75,7 +75,6 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
|
||||
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
|
||||
TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL;
|
||||
uint8_t layerCount = 1;
|
||||
|
||||
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
|
||||
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
|
||||
@@ -172,9 +171,6 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
|
||||
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
|
||||
layerCount = engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is a bit of magic happening here regarding the viewport used.
|
||||
@@ -196,7 +192,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
.stencil = data.stencil },
|
||||
.clearColor = config.clearColor,
|
||||
.samples = config.msaa,
|
||||
.layerCount = layerCount,
|
||||
.layerCount = static_cast<uint8_t>(colorBufferDesc.depth),
|
||||
.clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags});
|
||||
blackboard["depth"] = data.depth;
|
||||
},
|
||||
|
||||
+67
-49
@@ -57,9 +57,6 @@ namespace filament {
|
||||
using namespace math;
|
||||
using namespace backend;
|
||||
|
||||
// do this only if depth-clamp is available
|
||||
static constexpr bool USE_DEPTH_CLAMP = false;
|
||||
|
||||
ShadowMap::ShadowMap(FEngine& engine) noexcept
|
||||
: mPerShadowMapUniforms(engine),
|
||||
mShadowType(ShadowType::DIRECTIONAL),
|
||||
@@ -126,7 +123,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
FScene::LightSoa const& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept {
|
||||
|
||||
// reset the visible shadow status
|
||||
mHasVisibleShadows = false;
|
||||
@@ -137,13 +135,20 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
|
||||
|
||||
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
|
||||
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
|
||||
auto const [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
|
||||
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo,
|
||||
useDepthClamp);
|
||||
|
||||
if (UTILS_UNLIKELY(!visibleShadows)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// if the 2D bounds of the intersection is empty seen from the light, we have no shadow
|
||||
Aabb const bounds = compute2DBounds(Mv, lsClippedShadowVolume.data(), vertexCount);
|
||||
if (UTILS_UNLIKELY(!(bounds.min.x < bounds.max.x && bounds.min.y < bounds.max.y))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the light's projection matrix
|
||||
* (directional/point lights, i.e. projection to use, including znear/zfar clip planes)
|
||||
@@ -161,12 +166,17 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
mat4f W, Wp, L;
|
||||
// We can't use LISPSM in stable mode
|
||||
const bool useLispsm = params.options.lispsm && !params.options.stable;
|
||||
if (useLispsm) {
|
||||
|
||||
if (useLispsm ||
|
||||
(!params.options.stable && !engine.debug.shadowmap.disable_light_frustum_align)) {
|
||||
// Orient the shadow map in the direction of the view vector by constructing a
|
||||
const float3 lsCameraFwd = Mv.upperLeft() * camera.getForwardVector();
|
||||
L = computeLightRotation(lsCameraFwd);
|
||||
LMp = L * Mp;
|
||||
LMpMv = LMp * Mv;
|
||||
}
|
||||
|
||||
if (useLispsm) {
|
||||
W = applyLISPSM(Wp, camera, params,
|
||||
LMp, Mv, LMpMv, lsClippedShadowVolume, vertexCount, direction);
|
||||
}
|
||||
@@ -184,8 +194,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
// In LiPSM mode, we're using the warped space here.
|
||||
float4 f = computeFocusParams(LMpMv, WLMp,
|
||||
lsClippedShadowVolume, vertexCount,
|
||||
camera, sceneInfo.csNearFar,
|
||||
params.options.shadowFar, params.options.stable);
|
||||
camera, params.options.shadowFar, params.options.stable);
|
||||
|
||||
if (params.options.stable) {
|
||||
const auto lsRef = lightData.elementAt<FScene::SHADOW_REF>(index);
|
||||
@@ -386,7 +395,8 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
|
||||
float3 const direction,
|
||||
FLightManager::ShadowParams params,
|
||||
filament::CameraInfo const& camera,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept {
|
||||
|
||||
// we use aligned_storage<> here to avoid the default initialization of std::array<>
|
||||
using Storage = std::aligned_storage<sizeof(FrustumBoxIntersection)>::type;
|
||||
@@ -448,36 +458,49 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
|
||||
|
||||
// Compute the intersection of the view volume with the intersection of receivers and casters
|
||||
// in light space. This returns a set of points on the convex-hull of the intersection.
|
||||
mat4f const projection = camera.cullingProjection *
|
||||
(camera.view * FCamera::rigidTransformInverse(MvAtOrigin));
|
||||
size_t const vertexCount = intersectFrustumWithBox(lsClippedShadowVolume,
|
||||
camera.cullingProjection * camera.view * FCamera::rigidTransformInverse(MvAtOrigin),
|
||||
sceneInfo.csNearFar, lsLightFrustumBounds);
|
||||
projection, lsLightFrustumBounds);
|
||||
|
||||
if (UTILS_UNLIKELY(vertexCount < 4)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
|
||||
* (near/far correspond to max/min because the light looks down the -z axis).
|
||||
* - The Near plane is set to the shadow casters max z (i.e. closest to the light)
|
||||
* - The Far plane is set to the closest of the farthest shadow casters and receivers
|
||||
* i.e.: shadow casters behind the last receivers can't cast any shadows
|
||||
*
|
||||
* If "depth clamp" is supported, we can further tighten the near plane to the
|
||||
* shadow receiver.
|
||||
*
|
||||
* Note: L has no influence here, since we're only interested in z values
|
||||
* (L is a rotation around z)
|
||||
*/
|
||||
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
|
||||
* (near/far correspond to max/min because the light looks down the -z axis).
|
||||
*
|
||||
* - For the Far plane, the light frustum is already set to the closest of receivers and
|
||||
* casters (i.e.: shadow casters behind the last receivers can't cast any shadows).
|
||||
* But it can be moved closer up to the farthest point in the camera frustum & light
|
||||
* frustum intersection.
|
||||
*
|
||||
* - The Near plane is already set to the shadow casters max z (i.e. closest to the light)
|
||||
*
|
||||
* If "depth clamp" is supported, we can further tighten the near plane to the
|
||||
* shadow receiver (i.e. to the closest point of camera frustum & light frustum intersection).
|
||||
*
|
||||
* Note: L has no influence here, since we're only interested in z values
|
||||
* (L is a rotation around z)
|
||||
*/
|
||||
|
||||
float lsClippedShadowVolumeFarthest = std::numeric_limits<float>::max();
|
||||
float lsClippedShadowVolumeNearest = std::numeric_limits<float>::lowest();
|
||||
for (size_t i = 0; i < vertexCount; ++i) {
|
||||
float3 const v = lsClippedShadowVolume[i];
|
||||
// far: figure out the farthest shadow receivers
|
||||
lsLightFrustumBounds.min.z = min(lsLightFrustumBounds.min.z, v.z);
|
||||
if constexpr (USE_DEPTH_CLAMP) {
|
||||
lsLightFrustumBounds.max.z = max(lsLightFrustumBounds.max.z, v.z);
|
||||
}
|
||||
lsClippedShadowVolumeFarthest = std::min(lsClippedShadowVolumeFarthest, v.z);
|
||||
lsClippedShadowVolumeNearest = std::max(lsClippedShadowVolumeNearest, v.z);
|
||||
}
|
||||
|
||||
lsLightFrustumBounds.min.z =
|
||||
std::max(lsLightFrustumBounds.min.z, lsClippedShadowVolumeFarthest);
|
||||
|
||||
if (useDepthClamp) {
|
||||
lsLightFrustumBounds.max.z =
|
||||
std::min(lsLightFrustumBounds.max.z, lsClippedShadowVolumeNearest);
|
||||
}
|
||||
|
||||
if (engine.debug.shadowmap.far_uses_shadowcasters) {
|
||||
// far: closest of the farthest shadow casters and receivers
|
||||
lsLightFrustumBounds.min.z =
|
||||
@@ -611,7 +634,7 @@ math::float4 ShadowMap::computeFocusParams(
|
||||
mat4f const& LMpMv,
|
||||
mat4f const& WLMp,
|
||||
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
|
||||
filament::CameraInfo const& camera, float2 const& csNearFar,
|
||||
filament::CameraInfo const& camera,
|
||||
float shadowFar, bool stable) noexcept {
|
||||
float2 s, o;
|
||||
if (stable) {
|
||||
@@ -626,7 +649,7 @@ math::float4 ShadowMap::computeFocusParams(
|
||||
return wsViewVolumeBoundingSphere;
|
||||
} else {
|
||||
mat4f const viewFromClip = inverse(camera.cullingProjection);
|
||||
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
|
||||
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip);
|
||||
float4 const wsViewVolumeBoundingSphere =
|
||||
computeBoundingSphere(wsFrustumVertices.vertices, 8);
|
||||
return wsViewVolumeBoundingSphere;
|
||||
@@ -794,26 +817,25 @@ Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
|
||||
}
|
||||
|
||||
ShadowMap::Corners ShadowMap::computeFrustumCorners(
|
||||
mat4f const& projectionInverse,
|
||||
float2 csNearFar) noexcept {
|
||||
mat4f const& projectionInverse) noexcept {
|
||||
|
||||
Corners out;
|
||||
|
||||
// compute view frustum in world space (from its NDC)
|
||||
// matrix to convert: ndc -> camera -> world
|
||||
float const near = csNearFar.x;
|
||||
float const far = csNearFar.y;
|
||||
float const near = -1.0f;
|
||||
float const far = 1.0f;
|
||||
|
||||
Corners const csViewFrustumCorners = {
|
||||
.vertices = {
|
||||
{ -1, -1, far },
|
||||
{ 1, -1, far },
|
||||
{ -1, 1, far },
|
||||
{ 1, 1, far },
|
||||
{ 1, -1, far },
|
||||
{ -1, 1, far },
|
||||
{ 1, 1, far },
|
||||
{ -1, -1, near },
|
||||
{ 1, -1, near },
|
||||
{ -1, 1, near },
|
||||
{ 1, 1, near },
|
||||
{ 1, -1, near },
|
||||
{ -1, 1, near },
|
||||
{ 1, 1, near },
|
||||
}
|
||||
};
|
||||
|
||||
@@ -895,11 +917,11 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
|
||||
size_t ShadowMap::intersectFrustumWithBox(
|
||||
FrustumBoxIntersection& UTILS_RESTRICT outVertices,
|
||||
mat4f const& UTILS_RESTRICT projection, math::float2 const& UTILS_RESTRICT csNearFar,
|
||||
mat4f const& UTILS_RESTRICT projection,
|
||||
Aabb const& UTILS_RESTRICT box)
|
||||
{
|
||||
Frustum const frustum{ projection };
|
||||
Corners const frustumVertices{ computeFrustumCorners(inverse(projection), csNearFar) };
|
||||
Corners const frustumVertices{ computeFrustumCorners(inverse(projection)) };
|
||||
|
||||
constexpr const float EPSILON = 1.0f / 8192.0f; // ~0.012 mm
|
||||
size_t vertexCount = 0;
|
||||
@@ -1203,9 +1225,8 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
|
||||
}
|
||||
|
||||
ShadowMap::SceneInfo::SceneInfo(
|
||||
FScene const& scene, uint8_t visibleLayers, mat4f const& viewMatrix) noexcept
|
||||
: vsNearFar(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()),
|
||||
visibleLayers(visibleLayers) {
|
||||
FScene const& scene, uint8_t visibleLayers) noexcept
|
||||
: visibleLayers(visibleLayers) {
|
||||
|
||||
// the code below only works with affine transforms
|
||||
// Filament's API requires viewMatrix to be rigid (and thus affine).
|
||||
@@ -1224,9 +1245,6 @@ ShadowMap::SceneInfo::SceneInfo(
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
|
||||
auto r = Aabb::transform(viewMatrix.upperLeft(), viewMatrix[3].xyz, receiver);
|
||||
vsNearFar.x = std::max(vsNearFar.x, r.max.z);
|
||||
vsNearFar.y = std::min(vsNearFar.y, r.min.z);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,20 +109,13 @@ public:
|
||||
struct SceneInfo {
|
||||
|
||||
SceneInfo() noexcept = default;
|
||||
SceneInfo(FScene const& scene, uint8_t visibleLayers, math::mat4f const& viewMatrix) noexcept;
|
||||
|
||||
// scratch data: The near and far planes, in clip space, to use for this shadow map
|
||||
math::float2 csNearFar = { -1.0f, 1.0f };
|
||||
SceneInfo(FScene const& scene, uint8_t visibleLayers) noexcept;
|
||||
|
||||
// scratch data: light's near/far expressed in light-space, calculated from the scene's
|
||||
// content assuming the light is at the origin.
|
||||
math::float2 lsCastersNearFar;
|
||||
math::float2 lsReceiversNearFar;
|
||||
|
||||
// Viewing camera's near/far expressed in view-space, calculated from the
|
||||
// scene's content.
|
||||
math::float2 vsNearFar;
|
||||
|
||||
// World-space shadow-casters volume
|
||||
Aabb wsShadowCastersVolume;
|
||||
|
||||
@@ -154,7 +147,8 @@ public:
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept;
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept;
|
||||
|
||||
ShaderParameters updateSpot(FEngine& engine,
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
@@ -244,7 +238,8 @@ private:
|
||||
math::float3 direction,
|
||||
FLightManager::ShadowParams params,
|
||||
filament::CameraInfo const& camera,
|
||||
SceneInfo const& sceneInfo) noexcept;
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept;
|
||||
|
||||
static math::mat4f applyLISPSM(math::mat4f& Wp,
|
||||
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
|
||||
@@ -260,7 +255,7 @@ private:
|
||||
math::mat4f const& LMpMv,
|
||||
math::mat4f const& WLMp,
|
||||
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
|
||||
filament::CameraInfo const& camera, math::float2 const& csNearFar,
|
||||
filament::CameraInfo const& camera,
|
||||
float shadowFar, bool stable) noexcept;
|
||||
|
||||
static inline void snapLightFrustum(math::float2& s, math::float2& o,
|
||||
@@ -271,8 +266,7 @@ private:
|
||||
SceneInfo const& sceneInfo,
|
||||
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept;
|
||||
|
||||
static Corners computeFrustumCorners(const math::mat4f& projectionInverse,
|
||||
math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
|
||||
static Corners computeFrustumCorners(const math::mat4f& projectionInverse) noexcept;
|
||||
|
||||
static inline math::float2 computeNearFar(math::mat4f const& view,
|
||||
math::float3 const* wsVertices, size_t count) noexcept;
|
||||
@@ -307,7 +301,7 @@ private:
|
||||
|
||||
static size_t intersectFrustumWithBox(
|
||||
FrustumBoxIntersection& outVertices,
|
||||
math::mat4f const& projection, math::float2 const& csNearFar,
|
||||
math::mat4f const& projection,
|
||||
Aabb const& box);
|
||||
|
||||
static math::mat4f warpFrustum(float n, float f) noexcept;
|
||||
|
||||
@@ -39,17 +39,20 @@
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <math/half.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec4.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
@@ -63,12 +66,15 @@ namespace filament {
|
||||
using namespace backend;
|
||||
using namespace math;
|
||||
|
||||
ShadowMapManager::ShadowMapManager(FEngine& engine) {
|
||||
ShadowMapManager::ShadowMapManager(FEngine& engine)
|
||||
: mIsDepthClampSupported(engine.getDriverApi().isDepthClampSupported()) {
|
||||
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
|
||||
debugRegistry.registerProperty("d.shadowmap.visualize_cascades",
|
||||
&engine.debug.shadowmap.visualize_cascades);
|
||||
debugRegistry.registerProperty("d.shadowmap.tightly_bound_scene",
|
||||
&engine.debug.shadowmap.tightly_bound_scene);
|
||||
debugRegistry.registerProperty("d.shadowmap.disable_light_frustum_align",
|
||||
&engine.debug.shadowmap.disable_light_frustum_align);
|
||||
debugRegistry.registerProperty("d.shadowmap.depth_clamp",
|
||||
&engine.debug.shadowmap.depth_clamp);
|
||||
}
|
||||
|
||||
ShadowMapManager::~ShadowMapManager() {
|
||||
@@ -147,7 +153,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
|
||||
calculateTextureRequirements(engine, view, lightData);
|
||||
|
||||
// Compute scene-dependent values shared across all shadow maps
|
||||
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers(), cameraInfo.view };
|
||||
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers() };
|
||||
|
||||
shadowTechnique |= updateCascadeShadowMaps(
|
||||
engine, view, cameraInfo, renderableData, lightData, info);
|
||||
@@ -361,7 +367,22 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
// generate and sort the commands for rendering the shadow map
|
||||
|
||||
RenderPass::RenderFlags renderPassFlags{};
|
||||
if (view.isFrontFaceWindingInverted()) {
|
||||
renderPassFlags |= RenderPass::HAS_INVERSE_FRONT_FACES;
|
||||
}
|
||||
|
||||
bool const canUseDepthClamp =
|
||||
!view.hasVSM() &&
|
||||
mIsDepthClampSupported &&
|
||||
engine.debug.shadowmap.depth_clamp;
|
||||
|
||||
if (canUseDepthClamp) {
|
||||
renderPassFlags |= RenderPass::HAS_DEPTH_CLAMP;
|
||||
}
|
||||
|
||||
RenderPass const pass = passBuilder
|
||||
.renderFlags(renderPassFlags)
|
||||
.camera(cameraInfo)
|
||||
.visibilityMask(entry.visibilityMask)
|
||||
.geometry(scene->getRenderableData(),
|
||||
@@ -543,65 +564,9 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
|
||||
|
||||
// Adjust the camera's projection for the light's shadowFar
|
||||
cameraInfo.zf = params.options.shadowFar > 0.0f ? params.options.shadowFar : cameraInfo.zf;
|
||||
if (UTILS_UNLIKELY(params.options.shadowFar > 0.0f)) {
|
||||
cameraInfo.zf = params.options.shadowFar;
|
||||
float const n = cameraInfo.zn;
|
||||
float const f = cameraInfo.zf;
|
||||
|
||||
/*
|
||||
* Updating a projection matrix near and far planes:
|
||||
*
|
||||
* We assume that the near and far plane equations are of the form:
|
||||
* N = { 0, 0, 1, n }
|
||||
* F = { 0, 0, -1, -f }
|
||||
*
|
||||
* We also assume that the lower-left 2x2 of the projection is all 0:
|
||||
* P = A 0 C 0
|
||||
* 0 B D 0
|
||||
* 0 0 E F
|
||||
* 0 0 G H
|
||||
*
|
||||
* It result that we need to calculate E and F while leaving all other parameter unchanged.
|
||||
*
|
||||
* We know that:
|
||||
* with N, F the near/far normalized plane equation parameters
|
||||
* sn, sf arbitrary scale factors (they don't affect the planes)
|
||||
* m is the transpose of projection (see Frustum.cpp)
|
||||
*
|
||||
* sn * N == -m[3] - m[2]
|
||||
* sf * F == -m[3] + m[2]
|
||||
*
|
||||
* sn * N + sf * F == -2 * m[3]
|
||||
* sn * N - sf * F == -2 * m[2]
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * m[3].z
|
||||
* sn * N.w + sf * F.w == -2 * m[3].w
|
||||
* sn * N.z - sf * F.z == -2 * m[2].z
|
||||
* sn * N.w - sf * F.w == -2 * m[2].w
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * p[2].w
|
||||
* sn * N.w + sf * F.w == -2 * p[3].w
|
||||
* sn * N.z - sf * F.z == -2 * p[2].z
|
||||
* sn * N.w - sf * F.w == -2 * p[3].z
|
||||
*
|
||||
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
|
||||
*
|
||||
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
|
||||
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
|
||||
* p[2].z == (sf * F.z - sn * N.z) / 2
|
||||
* p[3].z == (sf * F.w - sn * N.w) / 2
|
||||
*/
|
||||
auto& p = cameraInfo.cullingProjection;
|
||||
float4 const N = { 0, 0, 1, n }; // near plane equation
|
||||
float4 const F = { 0, 0, -1, -f }; // far plane equation
|
||||
// near plane equation scale factor
|
||||
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
|
||||
// far plane equation scale factor
|
||||
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
|
||||
// New values for the projection
|
||||
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
|
||||
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
|
||||
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
|
||||
}
|
||||
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
@@ -631,8 +596,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
// Compute scene-dependent values shared across all cascades
|
||||
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
|
||||
|
||||
// we always do culling without depth clamp, because objects behind the camera
|
||||
// must be rendered regardless
|
||||
shadowMap.updateDirectional(engine,
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo, false);
|
||||
|
||||
hasVisibleShadows = shadowMap.hasVisibleShadows();
|
||||
|
||||
@@ -648,15 +615,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
uint32_t cascadeHasVisibleShadows = 0;
|
||||
|
||||
if (hasVisibleShadows) {
|
||||
// Adjust the near and far planes to tightly bound the scene.
|
||||
float vsNear = -cameraInfo.zn;
|
||||
float vsFar = -cameraInfo.zf;
|
||||
if (engine.debug.shadowmap.tightly_bound_scene && !params.options.stable) {
|
||||
vsNear = std::min(vsNear, sceneInfo.vsNearFar.x);
|
||||
vsFar = std::max(vsFar, sceneInfo.vsNearFar.y);
|
||||
}
|
||||
|
||||
const size_t cascadeCount = cascadedShadowMaps.size();
|
||||
uint32_t const cascadeCount = cascadedShadowMaps.size();
|
||||
|
||||
// We divide the camera frustum into N cascades. This gives us N + 1 split positions.
|
||||
// The first split position is the near plane; the last split position is the far plane.
|
||||
@@ -667,9 +626,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
}
|
||||
|
||||
const CascadeSplits splits({
|
||||
.proj = cameraInfo.cullingProjection,
|
||||
.near = vsNear,
|
||||
.far = vsFar,
|
||||
.near = -cameraInfo.zn,
|
||||
.far = -cameraInfo.zf,
|
||||
.cascadeCount = cascadeCount,
|
||||
.splitPositions = splitPercentages
|
||||
});
|
||||
@@ -679,10 +637,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
static_assert(CONFIG_MAX_SHADOW_CASCADES <= 5,
|
||||
"At most, a float4 can fit 4 split positions for 5 shadow cascades");
|
||||
float4 wsSplitPositionUniform{ -std::numeric_limits<float>::infinity() };
|
||||
std::copy(splits.beginWs() + 1, splits.endWs(), &wsSplitPositionUniform[0]);
|
||||
|
||||
float csSplitPosition[CONFIG_MAX_SHADOW_CASCADES + 1];
|
||||
std::copy(splits.beginCs(), splits.endCs(), csSplitPosition);
|
||||
std::copy(splits.begin() + 1, splits.end(), &wsSplitPositionUniform[0]);
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
@@ -695,10 +650,20 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
ShadowMap& shadowMap = cascadedShadowMaps[i];
|
||||
assert_invariant(shadowMap.getLightIndex() == 0);
|
||||
|
||||
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
|
||||
// update cameraInfo culling projection for the cascade
|
||||
float const* nearFarPlanes = splits.begin();
|
||||
cameraInfo.zn = -nearFarPlanes[i];
|
||||
cameraInfo.zf = -nearFarPlanes[i + 1];
|
||||
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
|
||||
|
||||
bool const canUseDepthClamp =
|
||||
!view.hasVSM() &&
|
||||
mIsDepthClampSupported &&
|
||||
engine.debug.shadowmap.depth_clamp;
|
||||
|
||||
auto shaderParameters = shadowMap.updateDirectional(engine,
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo,
|
||||
canUseDepthClamp);
|
||||
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
@@ -1069,8 +1034,78 @@ ShadowMapManager::CascadeSplits::CascadeSplits(Params const& params) noexcept
|
||||
: mSplitCount(params.cascadeCount + 1) {
|
||||
for (size_t s = 0; s < mSplitCount; s++) {
|
||||
mSplitsWs[s] = params.near + (params.far - params.near) * params.splitPositions[s];
|
||||
mSplitsCs[s] = mat4f::project(params.proj, float3(0.0f, 0.0f, mSplitsWs[s])).z;
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMapManager::updateNearFarPlanes(mat4f* projection,
|
||||
float nearDistance, float farDistance) noexcept {
|
||||
float const n = nearDistance;
|
||||
float const f = farDistance;
|
||||
|
||||
/*
|
||||
* Updating a projection matrix near and far planes:
|
||||
*
|
||||
* We assume that the near and far plane equations are of the form:
|
||||
* N = { 0, 0, 1, n }
|
||||
* F = { 0, 0, -1, -f }
|
||||
*
|
||||
* We also assume that the lower-left 2x2 of the projection is all 0:
|
||||
* P = A 0 C 0
|
||||
* 0 B D 0
|
||||
* 0 0 E F
|
||||
* 0 0 G H
|
||||
*
|
||||
* It result that we need to calculate E and F while leaving all other parameter unchanged.
|
||||
*
|
||||
* We know that:
|
||||
* with N, F the near/far normalized plane equation parameters
|
||||
* sn, sf arbitrary scale factors (they don't affect the planes)
|
||||
* m is the transpose of projection (see Frustum.cpp)
|
||||
*
|
||||
* sn * N == -m[3] - m[2]
|
||||
* sf * F == -m[3] + m[2]
|
||||
*
|
||||
* sn * N + sf * F == -2 * m[3]
|
||||
* sn * N - sf * F == -2 * m[2]
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * m[3].z
|
||||
* sn * N.w + sf * F.w == -2 * m[3].w
|
||||
* sn * N.z - sf * F.z == -2 * m[2].z
|
||||
* sn * N.w - sf * F.w == -2 * m[2].w
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * p[2].w
|
||||
* sn * N.w + sf * F.w == -2 * p[3].w
|
||||
* sn * N.z - sf * F.z == -2 * p[2].z
|
||||
* sn * N.w - sf * F.w == -2 * p[3].z
|
||||
*
|
||||
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
|
||||
*
|
||||
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
|
||||
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
|
||||
* p[2].z == (sf * F.z - sn * N.z) / 2
|
||||
* p[3].z == (sf * F.w - sn * N.w) / 2
|
||||
*/
|
||||
auto& p = *projection;
|
||||
float4 const N = { 0, 0, 1, n }; // near plane equation
|
||||
float4 const F = { 0, 0, -1, -f }; // far plane equation
|
||||
// near plane equation scale factor
|
||||
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
|
||||
// far plane equation scale factor
|
||||
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
|
||||
// New values for the projection
|
||||
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
|
||||
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Camera const*>
|
||||
ShadowMapManager::getDirectionalShadowCameras() const noexcept {
|
||||
if (!mInitialized) return {};
|
||||
auto const csm = getCascadedShadowMap();
|
||||
auto result = utils::FixedCapacityVector<Camera const*>::with_capacity(csm.size());
|
||||
for (ShadowMap const& sm : csm) {
|
||||
result.push_back(sm.hasVisibleShadows() ? sm.getDebugCamera() : nullptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
@@ -58,6 +59,7 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
class Camera;
|
||||
class FCamera;
|
||||
class FView;
|
||||
class FrameGraph;
|
||||
@@ -138,16 +140,16 @@ public:
|
||||
bool hasSpotShadows() const { return !mSpotShadowMapCount; }
|
||||
|
||||
// for debugging only
|
||||
FCamera const* getDirectionalShadowCamera() const noexcept {
|
||||
if (!mInitialized) return nullptr;
|
||||
return getShadowMap(0).getDebugCamera();
|
||||
}
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
private:
|
||||
explicit ShadowMapManager(FEngine& engine);
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
static void updateNearFarPlanes(math::mat4f* projection,
|
||||
float nearDistance, float farDistance) noexcept;
|
||||
|
||||
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
|
||||
@@ -186,27 +188,21 @@ private:
|
||||
constexpr static size_t SPLIT_COUNT = CONFIG_MAX_SHADOW_CASCADES + 1;
|
||||
|
||||
struct Params {
|
||||
math::mat4f proj;
|
||||
float near = 0.0f;
|
||||
float far = 0.0f;
|
||||
size_t cascadeCount = 1;
|
||||
uint32_t cascadeCount = 1;
|
||||
std::array<float, SPLIT_COUNT> splitPositions = { 0.0f };
|
||||
};
|
||||
|
||||
explicit CascadeSplits(Params const& params) noexcept;
|
||||
|
||||
// Split positions in world-space.
|
||||
const float* beginWs() const { return mSplitsWs; }
|
||||
const float* endWs() const { return mSplitsWs + mSplitCount; }
|
||||
|
||||
// Split positions in clip-space.
|
||||
const float* beginCs() const { return mSplitsCs; }
|
||||
const float* endCs() const { return mSplitsCs + mSplitCount; }
|
||||
const float* begin() const { return mSplitsWs; }
|
||||
const float* end() const { return mSplitsWs + mSplitCount; }
|
||||
|
||||
private:
|
||||
float mSplitsWs[SPLIT_COUNT];
|
||||
float mSplitsCs[SPLIT_COUNT];
|
||||
size_t mSplitCount;
|
||||
uint32_t mSplitCount;
|
||||
};
|
||||
|
||||
// Atlas requirements, updated in ShadowMapManager::update(),
|
||||
@@ -236,6 +232,7 @@ private:
|
||||
ShadowMapCacheContainer mShadowMapCache;
|
||||
uint32_t mDirectionalShadowMapCount = 0;
|
||||
uint32_t mSpotShadowMapCount = 0;
|
||||
bool const mIsDepthClampSupported;
|
||||
bool mInitialized = false;
|
||||
|
||||
ShadowMap& getShadowMap(size_t index) noexcept {
|
||||
@@ -248,10 +245,14 @@ private:
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getCascadedShadowMap() noexcept {
|
||||
ShadowMap* const p = &getShadowMap(0);
|
||||
ShadowMap const* const p = &getShadowMap(0);
|
||||
return { p, mDirectionalShadowMapCount };
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getCascadedShadowMap() const noexcept {
|
||||
return const_cast<ShadowMapManager*>(this)->getCascadedShadowMap();
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getSpotShadowMaps() noexcept {
|
||||
ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
|
||||
return { p, mSpotShadowMapCount };
|
||||
|
||||
@@ -73,8 +73,8 @@ const char* View::getName() const noexcept {
|
||||
return downcast(this)->getName();
|
||||
}
|
||||
|
||||
Camera const* View::getDirectionalShadowCamera() const noexcept {
|
||||
return downcast(this)->getDirectionalShadowCamera();
|
||||
utils::FixedCapacityVector<Camera const*> View::getDirectionalShadowCameras() const noexcept {
|
||||
return downcast(this)->getDirectionalShadowCameras();
|
||||
}
|
||||
|
||||
void View::setShadowingEnabled(bool enabled) noexcept {
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#ifndef NDEBUG
|
||||
# define DEBUG_PROPERTIES_WRITABLE true
|
||||
#else
|
||||
# define DEBUG_PROPERTIES_WRITABLE false
|
||||
#endif
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
@@ -79,17 +73,15 @@ bool FDebugRegistry::hasProperty(const char* name) const noexcept {
|
||||
|
||||
template<typename T>
|
||||
bool FDebugRegistry::setProperty(const char* name, T v) noexcept {
|
||||
if constexpr (DEBUG_PROPERTIES_WRITABLE) {
|
||||
auto info = getPropertyInfo(name);
|
||||
T* const addr = static_cast<T*>(info.first);
|
||||
if (addr) {
|
||||
auto old = *addr;
|
||||
*addr = v;
|
||||
if (info.second && old != v) {
|
||||
info.second();
|
||||
}
|
||||
return true;
|
||||
auto info = getPropertyInfo(name);
|
||||
T* const addr = static_cast<T*>(info.first);
|
||||
if (addr) {
|
||||
auto old = *addr;
|
||||
*addr = v;
|
||||
if (info.second && old != v) {
|
||||
info.second();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -118,19 +110,32 @@ template bool FDebugRegistry::getProperty<float2>(const char* name, float2* v) c
|
||||
template bool FDebugRegistry::getProperty<float3>(const char* name, float3* v) const noexcept;
|
||||
template bool FDebugRegistry::getProperty<float4>(const char* name, float4* v) const noexcept;
|
||||
|
||||
void FDebugRegistry::registerDataSource(std::string_view name,
|
||||
bool FDebugRegistry::registerDataSource(std::string_view name,
|
||||
void const* data, size_t count) noexcept {
|
||||
auto& dataSourceMap = mDataSourceMap;
|
||||
if (dataSourceMap.find(name) == dataSourceMap.end()) {
|
||||
bool const found = dataSourceMap.find(name) == dataSourceMap.end();
|
||||
if (found) {
|
||||
dataSourceMap[name] = { data, count };
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void FDebugRegistry::registerDataSource(std::string_view name,
|
||||
bool FDebugRegistry::registerDataSource(std::string_view name,
|
||||
utils::Invocable<DataSource()>&& creator) noexcept {
|
||||
mDataSourceCreatorMap[name] = std::move(creator);
|
||||
auto& dataSourceCreatorMap = mDataSourceCreatorMap;
|
||||
bool const found = dataSourceCreatorMap.find(name) == dataSourceCreatorMap.end();
|
||||
if (found) {
|
||||
dataSourceCreatorMap[name] = std::move(creator);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void FDebugRegistry::unregisterDataSource(std::string_view name) noexcept {
|
||||
mDataSourceCreatorMap.erase(name);
|
||||
mDataSourceMap.erase(name);
|
||||
}
|
||||
|
||||
|
||||
DebugRegistry::DataSource FDebugRegistry::getDataSource(const char* name) const noexcept {
|
||||
std::string_view const key{ name };
|
||||
auto& dataSourceMap = mDataSourceMap;
|
||||
|
||||
@@ -101,12 +101,14 @@ public:
|
||||
}
|
||||
|
||||
// registers a DataSource directly
|
||||
void registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
|
||||
bool registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
|
||||
|
||||
// registers a DataSource lazily
|
||||
void registerDataSource(std::string_view name,
|
||||
bool registerDataSource(std::string_view name,
|
||||
utils::Invocable<DataSource()>&& creator) noexcept;
|
||||
|
||||
void unregisterDataSource(std::string_view name) noexcept;
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
private:
|
||||
#endif
|
||||
|
||||
@@ -590,7 +590,8 @@ private:
|
||||
Config mConfig;
|
||||
|
||||
public:
|
||||
// these are the debug properties used by FDebug. They're accessed directly by modules who need them.
|
||||
// These are the debug properties used by FDebug.
|
||||
// They're accessed directly by modules who need them.
|
||||
struct {
|
||||
struct {
|
||||
bool debug_directional_shadowmap = false;
|
||||
@@ -598,7 +599,8 @@ public:
|
||||
bool far_uses_shadowcasters = true;
|
||||
bool focus_shadowcasters = true;
|
||||
bool visualize_cascades = false;
|
||||
bool tightly_bound_scene = true;
|
||||
bool disable_light_frustum_align = false;
|
||||
bool depth_clamp = true;
|
||||
float dzn = -1.0f;
|
||||
float dzf = 1.0f;
|
||||
float display_shadow_texture_scale = 0.25f;
|
||||
|
||||
@@ -597,6 +597,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
bool hasColorGrading = hasPostProcess;
|
||||
bool hasDithering = view.getDithering() == Dithering::TEMPORAL;
|
||||
bool hasFXAA = view.getAntiAliasing() == AntiAliasing::FXAA;
|
||||
// bool hasFXAA = false;//view.getAntiAliasing() == AntiAliasing::FXAA;
|
||||
float2 scale = view.updateScale(engine, mFrameInfoManager.getLastFrameInfo(), mFrameRateOptions, mDisplayInfo);
|
||||
auto msaaOptions = view.getMultiSampleAntiAliasingOptions();
|
||||
auto dsrOptions = view.getDynamicResolutionOptions();
|
||||
@@ -610,6 +611,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
auto guardBandOptions = view.getGuardBandOptions();
|
||||
const bool isRenderingMultiview = view.hasStereo() &&
|
||||
engine.getConfig().stereoscopicType == backend::StereoscopicType::MULTIVIEW;
|
||||
utils::slog.e <<"hasFXAA=" <<hasFXAA << " view=" << &view << utils::io::endl;
|
||||
// FIXME: This is to override some settings that are not supported for multiview at the moment.
|
||||
// Remove this when all features are supported.
|
||||
if (isRenderingMultiview) {
|
||||
@@ -703,6 +705,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
const bool noBufferPadding = (colorGradingConfig.asSubpass && !hasFXAA && !scaled)
|
||||
|| engine.debug.renderer.disable_buffer_padding;
|
||||
|
||||
// guardBandOptions.enabled = !noBufferPadding;
|
||||
// guardBand must be a multiple of 16 to guarantee the same exact rendering up to 4 mip levels.
|
||||
float const guardBand = guardBandOptions.enabled ? 16.0f : 0.0f;
|
||||
|
||||
@@ -719,7 +722,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
// the SwapChain, and we might want to keep it this way.
|
||||
|
||||
auto round = [](uint32_t x) {
|
||||
constexpr uint32_t rounding = 16u;
|
||||
constexpr uint32_t rounding = 256u;
|
||||
return (x + (rounding - 1u)) & ~(rounding - 1u);
|
||||
};
|
||||
|
||||
@@ -727,6 +730,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
const float width = float(round(svp.width )) + 2.0f * guardBand;
|
||||
const float height = float(round(svp.height)) + 2.0f * guardBand;
|
||||
|
||||
utils::slog.e << "new dim=" << width <<"x" << height <<
|
||||
" old dim=" << svp.width << "x" << svp.height << utils::io::endl;
|
||||
|
||||
// scale the field-of-view up, so it covers exactly the extra pixels
|
||||
const float3 clipSpaceScaling{
|
||||
float(svp.width) / width,
|
||||
@@ -744,9 +750,14 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
1.0f - clipSpaceScaling.y - 2.0f * guardBand / height
|
||||
};
|
||||
|
||||
utils::slog.e <<"clipspace scaling=" << clipSpaceScaling << utils::io::endl;
|
||||
utils::slog.e <<"clipspace translation=" << clipSpaceTranslation << utils::io::endl;
|
||||
|
||||
mat4f ts = mat4f::scaling(clipSpaceScaling);
|
||||
ts[3].xy = -clipSpaceTranslation;
|
||||
|
||||
utils::slog.e <<"mat=" << ts << utils::io::endl;
|
||||
|
||||
// update the camera projection
|
||||
cameraInfo.projection = highPrecisionMultiply(ts, cameraInfo.projection);
|
||||
|
||||
@@ -759,6 +770,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
svp.height = uint32_t(height);
|
||||
xvp.left = int32_t(guardBand);
|
||||
xvp.bottom = int32_t(guardBand);
|
||||
utils::slog.e <<"xvp.left=" << int32_t(guardBand) << utils::io::endl;
|
||||
}
|
||||
|
||||
view.prepare(engine, driver, rootArenaScope, svp, cameraInfo, getShaderUserTime(), needsAlphaChannel);
|
||||
@@ -870,6 +882,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
|
||||
const TextureFormat hdrFormat = getHdrFormat(view, needsAlphaChannel);
|
||||
|
||||
utils::slog.e <<"padding=" << !noBufferPadding << " svp=" << svp << utils::io::endl;
|
||||
utils::slog.e <<"padding=" << !noBufferPadding << " xvp=" << xvp << utils::io::endl;
|
||||
|
||||
// the clearFlags and clearColor specified below will only apply when rendering into the
|
||||
// temporary color buffer. In particular, they won't apply when rendering into the main
|
||||
// swapchain (imported render target above)
|
||||
@@ -1074,6 +1089,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
.format = config.hdrFormat
|
||||
};
|
||||
|
||||
utils::slog.e <<"color buffer output=" << colorBufferDesc.width <<"x" << colorBufferDesc.height
|
||||
<< utils::io::endl;
|
||||
|
||||
// Set the depth to the number of layers if we're rendering multiview.
|
||||
if (isRenderingMultiview) {
|
||||
colorBufferDesc.depth = engine.getConfig().stereoscopicEyeCount;
|
||||
@@ -1227,11 +1245,13 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
bloomOptions, vignetteOptions);
|
||||
// the padded buffer is resolved now
|
||||
xvp.left = xvp.bottom = 0;
|
||||
utils::slog.e <<"color grading is resolved" << utils::io::endl;
|
||||
svp = xvp;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFXAA) {
|
||||
utils::slog.e <<"fxaa=" << xvp << utils::io::endl;
|
||||
input = ppm.fxaa(fg, input, xvp, colorGradingConfig.ldrFormat,
|
||||
!hasColorGrading || needsAlphaChannel);
|
||||
// the padded buffer is resolved now
|
||||
@@ -1245,6 +1265,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
.width = viewport.width, .height = viewport.height,
|
||||
.format = colorGradingConfig.ldrFormat }, SamplerMagFilter::LINEAR);
|
||||
xvp.left = xvp.bottom = 0;
|
||||
utils::slog.e <<"scaled" << utils::io::endl;
|
||||
svp = xvp;
|
||||
}
|
||||
}
|
||||
@@ -1277,6 +1298,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
const bool outputIsSwapChain =
|
||||
(input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle);
|
||||
if (mightNeedFinalBlit) {
|
||||
utils::slog.e <<"inside might need final blit xvp!=svp=" << (xvp != svp) <<
|
||||
" xvp=" << xvp << " svp=" << svp <<
|
||||
utils::io::endl;
|
||||
if (blendModeTranslucent ||
|
||||
xvp != svp ||
|
||||
(outputIsSwapChain &&
|
||||
@@ -1285,6 +1309,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
hasScreenSpaceRefraction ||
|
||||
ssReflectionsOptions.enabled))) {
|
||||
assert_invariant(!scaled);
|
||||
utils::slog.e <<"doing final blit " << svp << " " << xvp << utils::io::endl;
|
||||
input = ppm.blit(fg, blendModeTranslucent, input, xvp, {
|
||||
.width = vp.width, .height = vp.height,
|
||||
.format = colorGradingConfig.ldrFormat },
|
||||
@@ -1314,6 +1339,10 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
|
||||
fg.forwardResource(fgViewRenderTarget, input);
|
||||
|
||||
utils::slog.e <<"present view=" << &view << " scaled=" << scaled << " mightNeedFinalBlit=" <<
|
||||
mightNeedFinalBlit <<
|
||||
utils::io::endl;
|
||||
|
||||
fg.present(fgViewRenderTarget);
|
||||
|
||||
fg.compile();
|
||||
|
||||
@@ -31,15 +31,17 @@
|
||||
#include "details/Skybox.h"
|
||||
|
||||
#include <filament/Exposure.h>
|
||||
#include <filament/DebugRegistry.h>
|
||||
#include <filament/TextureSampler.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
#include <private/filament/UibStructs.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Profiler.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Zip2Iterator.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
@@ -47,6 +49,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
@@ -78,21 +81,29 @@ FView::FView(FEngine& engine)
|
||||
mPidController.setOutputDeadBand(-0.01f, 0.05f);
|
||||
|
||||
#ifndef NDEBUG
|
||||
debugRegistry.registerDataSource("d.view.frame_info",
|
||||
[this]() -> DebugRegistry::DataSource {
|
||||
assert_invariant(!mDebugFrameHistory);
|
||||
mDebugFrameHistory = std::make_unique<std::array<DebugRegistry::FrameHistory, 5*60>>();
|
||||
return { mDebugFrameHistory->data(), mDebugFrameHistory->size() };
|
||||
// This can fail if another view has already registered this data source
|
||||
mDebugState->owner = debugRegistry.registerDataSource("d.view.frame_info",
|
||||
[weak = std::weak_ptr<DebugState>(mDebugState)]() -> DebugRegistry::DataSource {
|
||||
// the View could have been destroyed by the time we do this
|
||||
auto const state = weak.lock();
|
||||
if (!state) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
// Lazily allocate the buffer for the debug data source, and mark this
|
||||
// data source as active. It can never go back to inactive.
|
||||
assert_invariant(!state->debugFrameHistory);
|
||||
state->active = true;
|
||||
state->debugFrameHistory =
|
||||
std::make_unique<std::array<DebugRegistry::FrameHistory, 5 * 60>>();
|
||||
return { state->debugFrameHistory->data(), state->debugFrameHistory->size() };
|
||||
});
|
||||
debugRegistry.registerProperty("d.view.pid.kp", &engine.debug.view.pid.kp);
|
||||
debugRegistry.registerProperty("d.view.pid.ki", &engine.debug.view.pid.ki);
|
||||
debugRegistry.registerProperty("d.view.pid.kd", &engine.debug.view.pid.kd);
|
||||
// default parameters for debugging UI
|
||||
engine.debug.view.pid.kp = 1.0f - std::exp(-1.0f / 8.0f);
|
||||
engine.debug.view.pid.ki = PID_CONTROLLER_Ki;
|
||||
engine.debug.view.pid.kd = PID_CONTROLLER_Kd;
|
||||
mPidController.setParallelGains(
|
||||
engine.debug.view.pid.kp, engine.debug.view.pid.ki, engine.debug.view.pid.kd);
|
||||
|
||||
if (UTILS_UNLIKELY(mDebugState->owner)) {
|
||||
// publish the properties (they will be initialized in the main loop)
|
||||
debugRegistry.registerProperty("d.view.pid.kp", &engine.debug.view.pid.kp);
|
||||
debugRegistry.registerProperty("d.view.pid.ki", &engine.debug.view.pid.ki);
|
||||
debugRegistry.registerProperty("d.view.pid.kd", &engine.debug.view.pid.kd);
|
||||
}
|
||||
#endif
|
||||
|
||||
// allocate UBOs
|
||||
@@ -126,6 +137,12 @@ void FView::terminate(FEngine& engine) {
|
||||
mFroxelizer.terminate(driver);
|
||||
|
||||
engine.getEntityManager().destroy(mFogEntity);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_UNLIKELY(mDebugState->owner)) {
|
||||
engine.getDebugRegistry().unregisterDataSource("d.view.frame_info");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FView::setViewport(filament::Viewport const& viewport) noexcept {
|
||||
@@ -167,6 +184,16 @@ float2 FView::updateScale(FEngine& engine,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_LIKELY(!mDebugState->active)) {
|
||||
// if we're not active, update the debug properties with the normal values
|
||||
// and use that for configuring the PID controller.
|
||||
engine.debug.view.pid.kp = 1.0f - std::exp(-frameRateOptions.scaleRate);
|
||||
engine.debug.view.pid.ki = PID_CONTROLLER_Ki;
|
||||
engine.debug.view.pid.kd = PID_CONTROLLER_Kd;
|
||||
}
|
||||
#endif
|
||||
|
||||
DynamicResolutionOptions const& options = mDynamicResolution;
|
||||
if (options.enabled) {
|
||||
if (!UTILS_UNLIKELY(info.valid)) {
|
||||
@@ -250,14 +277,15 @@ float2 FView::updateScale(FEngine& engine,
|
||||
|
||||
#ifndef NDEBUG
|
||||
// only for debugging...
|
||||
if (mDebugFrameHistory) {
|
||||
if (UTILS_UNLIKELY(mDebugState->active && mDebugState->debugFrameHistory)) {
|
||||
auto* const debugFrameHistory = mDebugState->debugFrameHistory.get();
|
||||
using namespace std::chrono;
|
||||
using duration_ms = duration<float, std::milli>;
|
||||
const float target = (1000.0f * float(frameRateOptions.interval)) / displayInfo.refreshRate;
|
||||
const float targetWithHeadroom = target * (1.0f - frameRateOptions.headRoomRatio);
|
||||
std::move(mDebugFrameHistory->begin() + 1,
|
||||
mDebugFrameHistory->end(), mDebugFrameHistory->begin());
|
||||
mDebugFrameHistory->back() = {
|
||||
std::move(debugFrameHistory->begin() + 1,
|
||||
debugFrameHistory->end(), debugFrameHistory->begin());
|
||||
debugFrameHistory->back() = {
|
||||
.target = target,
|
||||
.targetWithHeadroom = targetWithHeadroom,
|
||||
.frameTime = duration_cast<duration_ms>(info.frameTime).count(),
|
||||
|
||||
@@ -201,9 +201,9 @@ public:
|
||||
|
||||
void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
|
||||
|
||||
FCamera const* getDirectionalShadowCamera() const noexcept {
|
||||
if (!mShadowMapManager) return nullptr;
|
||||
return mShadowMapManager->getDirectionalShadowCamera();
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept {
|
||||
if (!mShadowMapManager) return {};
|
||||
return mShadowMapManager->getDirectionalShadowCameras();
|
||||
}
|
||||
|
||||
void setRenderTarget(FRenderTarget* renderTarget) noexcept {
|
||||
@@ -576,7 +576,12 @@ private:
|
||||
}};
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::unique_ptr<std::array<DebugRegistry::FrameHistory, 5*60>> mDebugFrameHistory;
|
||||
struct DebugState {
|
||||
std::unique_ptr<std::array<DebugRegistry::FrameHistory, 5*60>> debugFrameHistory{};
|
||||
bool owner = false;
|
||||
bool active = false;
|
||||
};
|
||||
std::shared_ptr<DebugState> mDebugState{ new DebugState };
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -190,10 +190,10 @@ void RenderPassNode::resolve() noexcept {
|
||||
minHeight = std::min(minHeight, h);
|
||||
maxHeight = std::max(maxHeight, h);
|
||||
}
|
||||
// additionally, clear implies discardStart
|
||||
rt.backend.params.flags.discardStart |= (
|
||||
rt.descriptor.clearFlags & rt.targetBufferFlags);
|
||||
}
|
||||
// additionally, clear implies discardStart
|
||||
rt.backend.params.flags.discardStart |= (
|
||||
rt.descriptor.clearFlags & rt.targetBufferFlags);
|
||||
|
||||
assert_invariant(minWidth == maxWidth);
|
||||
assert_invariant(minHeight == maxHeight);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.53.5"
|
||||
spec.version = "1.54.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.5/filament-v1.53.5-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.0/filament-v1.54.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,8 +28,14 @@
|
||||
|
||||
class Cube {
|
||||
public:
|
||||
|
||||
Cube(filament::Engine& engine, filament::Material const* material, filament::math::float3 linearColor, bool culling = true);
|
||||
|
||||
Cube(Cube const&) = delete;
|
||||
Cube& operator=(Cube const&) = delete;
|
||||
|
||||
Cube(Cube&& rhs) noexcept;
|
||||
|
||||
utils::Entity getSolidRenderable() {
|
||||
return mSolidRenderable;
|
||||
}
|
||||
@@ -55,8 +61,8 @@ private:
|
||||
filament::Material const* mMaterial = nullptr;
|
||||
filament::MaterialInstance* mMaterialInstanceSolid = nullptr;
|
||||
filament::MaterialInstance* mMaterialInstanceWireFrame = nullptr;
|
||||
utils::Entity mSolidRenderable;
|
||||
utils::Entity mWireFrameRenderable;
|
||||
utils::Entity mSolidRenderable{};
|
||||
utils::Entity mWireFrameRenderable{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -211,11 +211,11 @@ private:
|
||||
filament::Camera* mOrthoCamera;
|
||||
|
||||
std::vector<std::unique_ptr<CView>> mViews;
|
||||
CView* mMainView;
|
||||
CView* mUiView;
|
||||
CView* mMainView; // well, the main view
|
||||
CView* mUiView; // the imgui ui
|
||||
CView* mDepthView;
|
||||
GodView* mGodView;
|
||||
CView* mOrthoView;
|
||||
GodView* mGodView; // the debug view with "god" camera
|
||||
CView* mOrthoView; // directional shadow map view
|
||||
|
||||
size_t mWidth = 0;
|
||||
size_t mHeight = 0;
|
||||
|
||||
@@ -104,11 +104,23 @@ Cube::Cube(Engine& engine, filament::Material const* material, float3 linearColo
|
||||
.build(engine, mWireFrameRenderable);
|
||||
}
|
||||
|
||||
Cube::Cube(Cube&& rhs) noexcept
|
||||
: mEngine(rhs.mEngine) {
|
||||
using std::swap;
|
||||
swap(rhs.mVertexBuffer, mVertexBuffer);
|
||||
swap(rhs.mIndexBuffer, mIndexBuffer);
|
||||
swap(rhs.mMaterial, mMaterial);
|
||||
swap(rhs.mMaterialInstanceSolid, mMaterialInstanceSolid);
|
||||
swap(rhs.mMaterialInstanceWireFrame, mMaterialInstanceWireFrame);
|
||||
swap(rhs.mSolidRenderable, mSolidRenderable);
|
||||
swap(rhs.mWireFrameRenderable, mWireFrameRenderable);
|
||||
}
|
||||
|
||||
void Cube::mapFrustum(filament::Engine& engine, Camera const* camera) {
|
||||
// the Camera far plane is at infinity, but we want it closer for display
|
||||
const mat4 vm(camera->getModelMatrix());
|
||||
mat4 p(vm * inverse(camera->getCullingProjectionMatrix()));
|
||||
return mapFrustum(engine, p);
|
||||
mapFrustum(engine, p);
|
||||
}
|
||||
|
||||
void Cube::mapFrustum(filament::Engine& engine, filament::math::mat4 const& transform) {
|
||||
@@ -122,7 +134,7 @@ void Cube::mapFrustum(filament::Engine& engine, filament::math::mat4 const& tran
|
||||
|
||||
void Cube::mapAabb(filament::Engine& engine, filament::Box const& box) {
|
||||
mat4 p = mat4::translation(box.center) * mat4::scaling(box.halfExtent);
|
||||
return mapFrustum(engine, p);
|
||||
mapFrustum(engine, p);
|
||||
}
|
||||
|
||||
Cube::~Cube() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <memory>
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
@@ -56,6 +55,13 @@
|
||||
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "generated/resources/filamentapp.h"
|
||||
|
||||
using namespace filament;
|
||||
@@ -138,10 +144,17 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
.package(FILAMENTAPP_TRANSPARENTCOLOR_DATA, FILAMENTAPP_TRANSPARENTCOLOR_SIZE)
|
||||
.build(*mEngine);
|
||||
|
||||
std::unique_ptr<Cube> cameraCube(new Cube(*mEngine, mTransparentMaterial, {1,0,0}));
|
||||
std::unique_ptr<Cube> cameraCube{ new Cube(*mEngine, mTransparentMaterial, { 1, 0, 0 }) };
|
||||
|
||||
// we can't cull the light-frustum because it's not applied a rigid transform
|
||||
// and currently, filament assumes that for culling
|
||||
std::unique_ptr<Cube> lightmapCube(new Cube(*mEngine, mTransparentMaterial, {0,1,0}, false));
|
||||
std::vector<Cube> lightmapCubes;
|
||||
lightmapCubes.reserve(4);
|
||||
lightmapCubes.emplace_back(*mEngine, mTransparentMaterial, float3{ 0, 1, 0 }, false);
|
||||
lightmapCubes.emplace_back(*mEngine, mTransparentMaterial, float3{ 0, 0, 1 }, false);
|
||||
lightmapCubes.emplace_back(*mEngine, mTransparentMaterial, float3{ 1, 1, 0 }, false);
|
||||
lightmapCubes.emplace_back(*mEngine, mTransparentMaterial, float3{ 1, 0, 0 }, false);
|
||||
|
||||
mScene = mEngine->createScene();
|
||||
|
||||
window->mMainView->getView()->setVisibleLayers(0x4, 0x4);
|
||||
@@ -151,16 +164,15 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
|
||||
rcm.setLayerMask(rcm.getInstance(cameraCube->getSolidRenderable()), 0x3, 0x2);
|
||||
rcm.setLayerMask(rcm.getInstance(cameraCube->getWireFrameRenderable()), 0x3, 0x2);
|
||||
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCube->getSolidRenderable()), 0x3, 0x2);
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCube->getWireFrameRenderable()), 0x3, 0x2);
|
||||
|
||||
// Create the camera mesh
|
||||
mScene->addEntity(cameraCube->getWireFrameRenderable());
|
||||
mScene->addEntity(cameraCube->getSolidRenderable());
|
||||
|
||||
mScene->addEntity(lightmapCube->getWireFrameRenderable());
|
||||
mScene->addEntity(lightmapCube->getSolidRenderable());
|
||||
for (auto&& cube : lightmapCubes) {
|
||||
rcm.setLayerMask(rcm.getInstance(cube.getSolidRenderable()), 0x3, 0x2);
|
||||
rcm.setLayerMask(rcm.getInstance(cube.getWireFrameRenderable()), 0x3, 0x2);
|
||||
mScene->addEntity(cube.getWireFrameRenderable());
|
||||
mScene->addEntity(cube.getSolidRenderable());
|
||||
}
|
||||
|
||||
window->mDepthView->getView()->setVisibleLayers(0x4, 0x4);
|
||||
window->mGodView->getView()->setVisibleLayers(0x6, 0x6);
|
||||
@@ -425,10 +437,26 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
window->mDebugCamera->lookAt(eye, center, up);
|
||||
|
||||
// Update the cube distortion matrix used for frustum visualization.
|
||||
const Camera* lightmapCamera = window->mMainView->getView()->getDirectionalShadowCamera();
|
||||
if (lightmapCamera) {
|
||||
lightmapCube->mapFrustum(*mEngine, lightmapCamera);
|
||||
auto& rcm = mEngine->getRenderableManager();
|
||||
auto const csm = window->mMainView->getView()->getDirectionalShadowCameras();
|
||||
// show/hide the cascades
|
||||
for (size_t i = 0 ; i < 4; i++) {
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getSolidRenderable()), 0x3, 0x0);
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getWireFrameRenderable()), 0x3, 0x0);
|
||||
}
|
||||
if (!csm.empty()) {
|
||||
for (size_t i = 0, c = csm.size(); i < c; i++) {
|
||||
if (csm[i]) {
|
||||
lightmapCubes[i].mapFrustum(*mEngine, csm[i]);
|
||||
}
|
||||
uint8_t const layer = csm[i] ? 0x2 : 0x0;
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getSolidRenderable()),
|
||||
0x3, layer);
|
||||
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getWireFrameRenderable()),
|
||||
0x3, layer);
|
||||
}
|
||||
}
|
||||
|
||||
cameraCube->mapFrustum(*mEngine, window->mMainCamera);
|
||||
|
||||
// Delay rendering for roughly one monitor refresh interval
|
||||
@@ -452,11 +480,14 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
|
||||
if (config.splitView) {
|
||||
if(!window->mOrthoView->getView()->hasCamera()) {
|
||||
Camera const* debugDirectionalShadowCamera =
|
||||
window->mMainView->getView()->getDirectionalShadowCamera();
|
||||
if (debugDirectionalShadowCamera) {
|
||||
window->mOrthoView->setCamera(
|
||||
const_cast<Camera*>(debugDirectionalShadowCamera));
|
||||
auto const csm = window->mMainView->getView()->getDirectionalShadowCameras();
|
||||
if (!csm.empty()) {
|
||||
// here we could choose the cascade
|
||||
Camera const* debugDirectionalShadowCamera = csm[0];
|
||||
if (debugDirectionalShadowCamera) {
|
||||
window->mOrthoView->setCamera(
|
||||
const_cast<Camera*>(debugDirectionalShadowCamera));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,7 +515,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
cleanupCallback(mEngine, window->mMainView->getView(), mScene);
|
||||
|
||||
cameraCube.reset();
|
||||
lightmapCube.reset();
|
||||
lightmapCubes.clear();
|
||||
window.reset();
|
||||
|
||||
mIBL.reset();
|
||||
@@ -736,12 +767,6 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
|
||||
mGodView->setCamera(mMainCamera);
|
||||
mGodView->setGodCamera(mDebugCamera);
|
||||
mGodView->setCameraManipulator(mDebugCameraMan);
|
||||
|
||||
// Ortho view obviously uses an ortho camera
|
||||
Camera const* debugDirectionalShadowCamera = mMainView->getView()->getDirectionalShadowCamera();
|
||||
if (debugDirectionalShadowCamera) {
|
||||
mOrthoView->setCamera(const_cast<Camera *>(debugDirectionalShadowCamera));
|
||||
}
|
||||
}
|
||||
|
||||
// configure the cameras
|
||||
@@ -907,9 +932,7 @@ void FilamentApp::Window::configureCamerasForWindow() {
|
||||
|
||||
const bool splitview = mViews.size() > 2;
|
||||
|
||||
// To trigger a floating-point exception, users could shrink the window to be smaller than
|
||||
// the sidebar. To prevent this we simply clamp the width of the main viewport.
|
||||
const uint32_t mainWidth = splitview ? width : std::max(1, (int) width - sidebar);
|
||||
const uint32_t mainWidth = std::max(2, (int) width - sidebar);
|
||||
|
||||
double near = mFilamentApp->mCameraNear;
|
||||
double far = mFilamentApp->mCameraFar;
|
||||
@@ -924,7 +947,7 @@ void FilamentApp::Window::configureCamerasForWindow() {
|
||||
} else {
|
||||
mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
|
||||
}
|
||||
mDebugCamera->setProjection(45.0, double(width) / height, 0.0625, 4096, Camera::Fov::VERTICAL);
|
||||
mDebugCamera->setProjection(45.0, double(mainWidth) / height, 0.0625, 4096, Camera::Fov::VERTICAL);
|
||||
|
||||
auto aspectRatio = double(mainWidth) / height;
|
||||
if (mMainView->getView()->getStereoscopicOptions().enabled) {
|
||||
@@ -935,12 +958,12 @@ void FilamentApp::Window::configureCamerasForWindow() {
|
||||
|
||||
// We're in split view when there are more views than just the Main and UI views.
|
||||
if (splitview) {
|
||||
uint32_t vpw = width / 2;
|
||||
uint32_t vph = height / 2;
|
||||
mMainView->setViewport ({ 0, 0, vpw, vph });
|
||||
mDepthView->setViewport({ int32_t(vpw), 0, width - vpw, vph });
|
||||
mGodView->setViewport ({ int32_t(vpw), int32_t(vph), width - vpw, height - vph });
|
||||
mOrthoView->setViewport({ 0, int32_t(vph), vpw, height - vph });
|
||||
uint32_t const vpw = mainWidth / 2;
|
||||
uint32_t const vph = height / 2;
|
||||
mMainView->setViewport ({ sidebar + 0, 0, vpw, vph });
|
||||
mDepthView->setViewport({ sidebar + int32_t(vpw), 0, vpw, vph });
|
||||
mGodView->setViewport ({ sidebar + int32_t(vpw), int32_t(vph), vpw, vph });
|
||||
mOrthoView->setViewport({ sidebar + 0, int32_t(vph), vpw, vph });
|
||||
} else {
|
||||
mMainView->setViewport({ sidebar, 0, mainWidth, height });
|
||||
}
|
||||
|
||||
@@ -224,6 +224,9 @@ public:
|
||||
*/
|
||||
static void exportSettings(const Settings& settings, const char* filename);
|
||||
|
||||
static void exportScreenshot(View* view, Renderer* renderer, std::string filename,
|
||||
bool autoclose, AutomationEngine* automationEngine);
|
||||
|
||||
Options getOptions() const { return mOptions; }
|
||||
bool isRunning() const { return mIsRunning; }
|
||||
size_t currentTest() const { return mCurrentTest; }
|
||||
|
||||
@@ -56,7 +56,7 @@ static void convertRGBAtoRGB(void* buffer, uint32_t width, uint32_t height) {
|
||||
}
|
||||
}
|
||||
|
||||
static void exportScreenshot(View* view, Renderer* renderer, std::string filename,
|
||||
void AutomationEngine::exportScreenshot(View* view, Renderer* renderer, std::string filename,
|
||||
bool autoclose, AutomationEngine* automationEngine) {
|
||||
const Viewport& vp = view->getViewport();
|
||||
const size_t byteCount = vp.width * vp.height * 4;
|
||||
@@ -244,7 +244,8 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
|
||||
}
|
||||
|
||||
if (mOptions.exportScreenshots) {
|
||||
exportScreenshot(content.view, content.renderer, prefix + ".ppm", isLastTest, this);
|
||||
AutomationEngine::exportScreenshot(
|
||||
content.view, content.renderer, prefix + ".ppm", isLastTest, this);
|
||||
}
|
||||
|
||||
if (isLastTest) {
|
||||
|
||||
@@ -562,6 +562,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out)
|
||||
}
|
||||
|
||||
void applySettings(Engine* engine, const ViewSettings& settings, View* dest) {
|
||||
utils::slog.e << "antialiasing=" << (int) settings.antiAliasing << " view=" << dest
|
||||
<< utils::io::endl;
|
||||
dest->setAntiAliasing(settings.antiAliasing);
|
||||
dest->setTemporalAntiAliasingOptions(settings.taa);
|
||||
dest->setMultiSampleAntiAliasingOptions(settings.msaa);
|
||||
|
||||
@@ -276,7 +276,7 @@ if (NOT ANDROID)
|
||||
target_link_libraries(hellopbr PRIVATE filameshio suzanne-resources)
|
||||
target_link_libraries(image_viewer PRIVATE viewer imageio)
|
||||
target_link_libraries(multiple_windows PRIVATE filameshio suzanne-resources)
|
||||
target_link_libraries(rendertarget PRIVATE filameshio suzanne-resources)
|
||||
target_link_libraries(rendertarget PRIVATE gltf-demo-resources filameshio suzanne-resources gltfio viewer)
|
||||
target_link_libraries(sample_cloth PRIVATE filameshio)
|
||||
target_link_libraries(sample_normal_map PRIVATE filameshio)
|
||||
target_link_libraries(suzanne PRIVATE filameshio suzanne-resources)
|
||||
|
||||
+25
-4
@@ -61,8 +61,10 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "generated/resources/gltf_demo.h"
|
||||
@@ -137,6 +139,8 @@ struct App {
|
||||
|
||||
AutomationSpec* automationSpec = nullptr;
|
||||
AutomationEngine* automationEngine = nullptr;
|
||||
bool screenshot = false;
|
||||
uint8_t screenshotSeq = 0;
|
||||
};
|
||||
|
||||
static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b";
|
||||
@@ -877,10 +881,15 @@ int main(int argc, char** argv) {
|
||||
|
||||
if (ImGui::CollapsingHeader("Debug")) {
|
||||
auto& debug = engine->getDebugRegistry();
|
||||
if (ImGui::Button("Capture frame")) {
|
||||
bool* captureFrame =
|
||||
debug.getPropertyAddress<bool>("d.renderer.doFrameCapture");
|
||||
*captureFrame = true;
|
||||
if (engine->getBackend() == Engine::Backend::METAL) {
|
||||
if (ImGui::Button("Capture frame")) {
|
||||
bool* captureFrame =
|
||||
debug.getPropertyAddress<bool>("d.renderer.doFrameCapture");
|
||||
*captureFrame = true;
|
||||
}
|
||||
}
|
||||
if (ImGui::Button("Screenshot")) {
|
||||
app.screenshot = true;
|
||||
}
|
||||
ImGui::Checkbox("Disable buffer padding",
|
||||
debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
|
||||
@@ -894,6 +903,10 @@ int main(int argc, char** argv) {
|
||||
debug.getPropertyAddress<bool>("d.shadowmap.far_uses_shadowcasters"));
|
||||
ImGui::Checkbox("Focus shadow casters",
|
||||
debug.getPropertyAddress<bool>("d.shadowmap.focus_shadowcasters"));
|
||||
ImGui::Checkbox("Disable light frustum alignment",
|
||||
debug.getPropertyAddress<bool>("d.shadowmap.disable_light_frustum_align"));
|
||||
ImGui::Checkbox("Depth clamp",
|
||||
debug.getPropertyAddress<bool>("d.shadowmap.depth_clamp"));
|
||||
|
||||
bool debugDirectionalShadowmap;
|
||||
if (debug.getProperty("d.shadowmap.debug_directional_shadowmap",
|
||||
@@ -1134,6 +1147,14 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) {
|
||||
if (app.screenshot) {
|
||||
std::ostringstream stringStream;
|
||||
stringStream << "screenshot" << std::setfill('0') << std::setw(2) << +app.screenshotSeq;
|
||||
AutomationEngine::exportScreenshot(
|
||||
view, renderer, stringStream.str() + ".ppm", false, app.automationEngine);
|
||||
++app.screenshotSeq;
|
||||
app.screenshot = false;
|
||||
}
|
||||
if (app.automationEngine->shouldClose()) {
|
||||
FilamentApp::get().close();
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,8 @@ material {
|
||||
uv0
|
||||
],
|
||||
shadingModel : unlit,
|
||||
vertexDomain: device,
|
||||
// vertexDomainDeviceJittered: true,
|
||||
}
|
||||
|
||||
fragment {
|
||||
@@ -20,17 +22,11 @@ fragment {
|
||||
if (gl_FrontFacing) {
|
||||
|
||||
// Make a lookup into the mirror texture.
|
||||
vec2 uv = getResolution().zw * gl_FragCoord.xy;
|
||||
// vec2 uv = getResolution().zw * gl_FragCoord.xy;
|
||||
vec2 uv = getNormalizedViewportCoord().xy;
|
||||
material.baseColor.rgb = texture(materialParams_albedo, uv).rgb;
|
||||
|
||||
// Add black borders to the mirror.
|
||||
vec2 st = getUV0();
|
||||
float minDist0 = min(st.x, st.y);
|
||||
float minDist1 = min(1.0 - st.x, 1.0 - st.y);
|
||||
float minDist = min(minDist0, minDist1);
|
||||
material.baseColor.rgb *= smoothstep(0.0, 0.1, minDist);
|
||||
|
||||
} else {
|
||||
material.baseColor = vec4(1, 0, 0, 1);
|
||||
}
|
||||
material.baseColor.a = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
+198
-195
@@ -28,10 +28,17 @@
|
||||
#include <filament/VertexBuffer.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
#include <gltfio/AssetLoader.h>
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/ResourceLoader.h>
|
||||
#include <gltfio/TextureProvider.h>
|
||||
#include <utils/NameComponentManager.h>
|
||||
#include <utils/EntityManager.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <filagui/ImGuiExtensions.h>
|
||||
#include <viewer/ViewerGui.h>
|
||||
#include <filameshio/MeshReader.h>
|
||||
|
||||
#include <filamentapp/Config.h>
|
||||
#include <filamentapp/FilamentApp.h>
|
||||
|
||||
@@ -40,11 +47,16 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "generated/resources/resources.h"
|
||||
#include "generated/resources/monkey.h"
|
||||
//#include "generated/resources/monkey.h"
|
||||
#include "generated/resources/gltf_demo.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filamesh;
|
||||
using namespace filament::math;
|
||||
using namespace filament::viewer;
|
||||
using namespace filament::gltfio;
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
struct Vertex {
|
||||
float3 position;
|
||||
@@ -55,8 +67,6 @@ struct App {
|
||||
utils::Entity lightEntity;
|
||||
Material* meshMaterial;
|
||||
MaterialInstance* meshMatInstance;
|
||||
MeshReader::Mesh monkeyMesh;
|
||||
utils::Entity reflectedMonkey;
|
||||
mat4f transform;
|
||||
|
||||
Texture* offscreenColorTexture = nullptr;
|
||||
@@ -66,12 +76,6 @@ struct App {
|
||||
Scene* offscreenScene = nullptr;
|
||||
Camera* offscreenCamera = nullptr;
|
||||
|
||||
enum class ReflectionMode {
|
||||
RENDERABLES,
|
||||
CAMERA,
|
||||
};
|
||||
|
||||
ReflectionMode mode = ReflectionMode::CAMERA;
|
||||
Config config;
|
||||
|
||||
utils::Entity quadEntity;
|
||||
@@ -80,46 +84,22 @@ struct App {
|
||||
Material* quadMaterial = nullptr;
|
||||
MaterialInstance* quadMatInstance = nullptr;
|
||||
|
||||
float3 quadCenter;
|
||||
float3 quadNormal;
|
||||
MaterialProvider* materials;
|
||||
|
||||
AssetLoader* assetLoader;
|
||||
ResourceLoader* resourceLoader;
|
||||
FilamentAsset* asset = nullptr;
|
||||
utils::NameComponentManager* names;
|
||||
gltfio::TextureProvider* stbDecoder = nullptr;
|
||||
gltfio::TextureProvider* ktxDecoder = nullptr;
|
||||
|
||||
Engine* engine;
|
||||
|
||||
ViewerGui* viewer;
|
||||
utils::Entity rootTransformEntity;
|
||||
};
|
||||
|
||||
static mat4f reflectionMatrix(float4 plane) {
|
||||
mat4f m;
|
||||
m[0][0] = -2 * plane.x * plane.x + 1;
|
||||
m[0][1] = -2 * plane.x * plane.y;
|
||||
m[0][2] = -2 * plane.x * plane.z;
|
||||
m[0][3] = -2 * plane.x * plane.w;
|
||||
m[1][0] = -2 * plane.x * plane.y;
|
||||
m[1][1] = -2 * plane.y * plane.y + 1;
|
||||
m[1][2] = -2 * plane.y * plane.z;
|
||||
m[1][3] = -2 * plane.y * plane.w;
|
||||
m[2][0] = -2 * plane.z * plane.x;
|
||||
m[2][1] = -2 * plane.z * plane.y;
|
||||
m[2][2] = -2 * plane.z * plane.z + 1;
|
||||
m[2][3] = -2 * plane.z * plane.w;
|
||||
m[3][0] = 0;
|
||||
m[3][1] = 0;
|
||||
m[3][2] = 0;
|
||||
m[3][3] = 1;
|
||||
return transpose(m);
|
||||
}
|
||||
|
||||
static void setReflectionMode(App& app, App::ReflectionMode mode) {
|
||||
switch (mode) {
|
||||
case App::ReflectionMode::RENDERABLES:
|
||||
app.offscreenScene->addEntity(app.reflectedMonkey);
|
||||
app.offscreenScene->remove(app.monkeyMesh.renderable);
|
||||
app.offscreenView->setFrontFaceWindingInverted(false);
|
||||
break;
|
||||
case App::ReflectionMode::CAMERA:
|
||||
app.offscreenScene->addEntity(app.monkeyMesh.renderable);
|
||||
app.offscreenScene->remove(app.reflectedMonkey);
|
||||
app.offscreenView->setFrontFaceWindingInverted(true);
|
||||
break;
|
||||
}
|
||||
app.mode = mode;
|
||||
}
|
||||
static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b";
|
||||
|
||||
static void printUsage(char* name) {
|
||||
std::string exec_name(utils::Path(name).getName());
|
||||
@@ -172,14 +152,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (arg == "camera") {
|
||||
app->mode = App::ReflectionMode::CAMERA;
|
||||
} else if (arg == "renderables") {
|
||||
app->mode = App::ReflectionMode::RENDERABLES;
|
||||
} else {
|
||||
std::cerr << "Unrecognized mode. Must be 'camera'|'renderables'.\n";
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -189,126 +161,182 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
int main(int argc, char** argv) {
|
||||
App app{};
|
||||
app.config.title = "rendertarget";
|
||||
app.config.iblDirectory = FilamentApp::getRootAssetsPath() + DEFAULT_IBL;
|
||||
handleCommandLineArguments(argc, argv, &app);
|
||||
|
||||
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
|
||||
auto gui = [&app](Engine*, View*) {
|
||||
app.viewer->updateUserInterface();
|
||||
FilamentApp::get().setSidebarWidth(app.viewer->getSidebarWidth());
|
||||
};
|
||||
|
||||
auto loadResources = [&app] () {
|
||||
// Load external textures and buffers.
|
||||
std::string const gltfPath;
|
||||
ResourceConfiguration configuration = {};
|
||||
configuration.engine = app.engine;
|
||||
configuration.gltfPath = gltfPath.c_str();
|
||||
configuration.normalizeSkinningWeights = true;
|
||||
|
||||
app.resourceLoader = new gltfio::ResourceLoader(configuration);
|
||||
app.stbDecoder = createStbProvider(app.engine);
|
||||
app.ktxDecoder = createKtx2Provider(app.engine);
|
||||
app.resourceLoader->addTextureProvider("image/png", app.stbDecoder);
|
||||
app.resourceLoader->addTextureProvider("image/jpeg", app.stbDecoder);
|
||||
app.resourceLoader->addTextureProvider("image/ktx2", app.ktxDecoder);
|
||||
|
||||
if (!app.resourceLoader->loadResources(app.asset)) {
|
||||
std::cerr << "Unable to start loading resources" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
app.asset->getInstance()->recomputeBoundingBoxes();
|
||||
app.asset->releaseSourceData();
|
||||
|
||||
// Enable stencil writes on all material instances.
|
||||
auto instance = app.asset->getInstance();
|
||||
const size_t matInstanceCount = instance->getMaterialInstanceCount();
|
||||
MaterialInstance* const* const instances = instance->getMaterialInstances();
|
||||
for (int mi = 0; mi < matInstanceCount; mi++) {
|
||||
instances[mi]->setStencilWrite(true);
|
||||
instances[mi]->setStencilOpDepthStencilPass(MaterialInstance::StencilOperation::INCR);
|
||||
}
|
||||
|
||||
auto ibl = FilamentApp::get().getIBL();
|
||||
if (ibl) {
|
||||
app.viewer->setIndirectLight(ibl->getIndirectLight(), ibl->getSphericalHarmonics());
|
||||
app.viewer->getSettings().view.fogSettings.fogColorTexture = ibl->getFogTexture();
|
||||
}
|
||||
};
|
||||
|
||||
auto setup = [&app, loadResources](Engine* engine, View* view, Scene* scene) {
|
||||
app.engine = engine;
|
||||
auto& em = utils::EntityManager::get();
|
||||
auto& tcm = engine->getTransformManager();
|
||||
auto& rcm = engine->getRenderableManager();
|
||||
auto& em = utils::EntityManager::get();
|
||||
auto vp = view->getViewport();
|
||||
|
||||
// Offscreen view
|
||||
{
|
||||
// Instantiate offscreen render target.
|
||||
app.offscreenView = engine->createView();
|
||||
app.offscreenScene = engine->createScene();
|
||||
app.offscreenView->setScene(app.offscreenScene);
|
||||
|
||||
// Instantiate offscreen render target.
|
||||
app.offscreenView = engine->createView();
|
||||
app.offscreenScene = engine->createScene();
|
||||
app.offscreenView->setScene(app.offscreenScene);
|
||||
app.offscreenView->setPostProcessingEnabled(false);
|
||||
app.offscreenColorTexture = Texture::Builder()
|
||||
.width(vp.width).height(vp.height).levels(1)
|
||||
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
|
||||
.format(Texture::InternalFormat::RGBA8).build(*engine);
|
||||
app.offscreenDepthTexture = Texture::Builder()
|
||||
.width(vp.width).height(vp.height).levels(1)
|
||||
.usage(Texture::Usage::DEPTH_ATTACHMENT)
|
||||
.format(Texture::InternalFormat::DEPTH24).build(*engine);
|
||||
app.offscreenRenderTarget = RenderTarget::Builder()
|
||||
.texture(RenderTarget::AttachmentPoint::COLOR, app.offscreenColorTexture)
|
||||
.texture(RenderTarget::AttachmentPoint::DEPTH, app.offscreenDepthTexture)
|
||||
.build(*engine);
|
||||
app.offscreenView->setRenderTarget(app.offscreenRenderTarget);
|
||||
app.offscreenView->setViewport({0, 0, vp.width, vp.height});
|
||||
app.offscreenCamera = engine->createCamera(em.create());
|
||||
app.offscreenView->setCamera(app.offscreenCamera);
|
||||
FilamentApp::get().addOffscreenView(app.offscreenView);
|
||||
app.viewer = new ViewerGui(engine, app.offscreenScene, app.offscreenView, 410);
|
||||
app.viewer->setUiCallback([&app, scene, view, engine]() {
|
||||
if (ImGui::CollapsingHeader("Debug")) {
|
||||
auto& debug = engine->getDebugRegistry();
|
||||
ImGui::Checkbox("Disable buffer padding",
|
||||
debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
|
||||
ImGui::Checkbox("Disable sub-passes",
|
||||
debug.getPropertyAddress<bool>("d.renderer.disable_subpasses"));
|
||||
}
|
||||
});
|
||||
Viewport vp = view->getViewport();
|
||||
vp.width = vp.width - app.viewer->getSidebarWidth();
|
||||
|
||||
// Position and orient the mirror in an interesting way.
|
||||
float3 c = app.quadCenter = {-2, 0, -5};
|
||||
float3 n = app.quadNormal = normalize(float3 {1, 0, 2});
|
||||
float3 u = normalize(cross(app.quadNormal, float3(0, 1, 0)));
|
||||
float3 v = cross(n, u);
|
||||
u = 1.5 * u;
|
||||
v = 1.5 * v;
|
||||
static Vertex kQuadVertices[4] = { {{}, {1, 0}}, {{}, {0, 0}}, {{}, {1, 1}}, {{}, {0, 1}} };
|
||||
kQuadVertices[0].position = c - u - v;
|
||||
kQuadVertices[1].position = c + u - v;
|
||||
kQuadVertices[2].position = c - u + v;
|
||||
kQuadVertices[3].position = c + u + v;
|
||||
app.offscreenColorTexture =
|
||||
Texture::Builder()
|
||||
.width(vp.width)
|
||||
.height(vp.height)
|
||||
.levels(1)
|
||||
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
|
||||
.format(Texture::InternalFormat::RGBA8)
|
||||
.build(*engine);
|
||||
app.offscreenDepthTexture = Texture::Builder()
|
||||
.width(vp.width)
|
||||
.height(vp.height)
|
||||
.levels(1)
|
||||
.usage(Texture::Usage::DEPTH_ATTACHMENT)
|
||||
.format(Texture::InternalFormat::DEPTH32F)
|
||||
.build(*engine);
|
||||
app.offscreenRenderTarget = RenderTarget::Builder()
|
||||
.texture(RenderTarget::AttachmentPoint::COLOR,
|
||||
app.offscreenColorTexture)
|
||||
.texture(RenderTarget::AttachmentPoint::DEPTH,
|
||||
app.offscreenDepthTexture)
|
||||
.build(*engine);
|
||||
app.offscreenView->setRenderTarget(app.offscreenRenderTarget);
|
||||
app.offscreenView->setViewport({0, 0, vp.width, vp.height});
|
||||
app.offscreenCamera = engine->createCamera(em.create());
|
||||
app.offscreenView->setCamera(app.offscreenCamera);
|
||||
FilamentApp::get().addOffscreenView(app.offscreenView);
|
||||
}
|
||||
|
||||
// Create quad vertex buffer.
|
||||
static_assert(sizeof(Vertex) == 20, "Strange vertex size.");
|
||||
app.quadVb = VertexBuffer::Builder()
|
||||
.vertexCount(4)
|
||||
.bufferCount(1)
|
||||
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3, 0, 20)
|
||||
.attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 12, 20)
|
||||
.build(*engine);
|
||||
app.quadVb->setBufferAt(*engine, 0,
|
||||
VertexBuffer::BufferDescriptor(kQuadVertices, 80, nullptr));
|
||||
// damaged helmet gltf
|
||||
{
|
||||
app.names = new utils::NameComponentManager(em);
|
||||
app.materials = createJitShaderProvider(engine, true);
|
||||
app.assetLoader = AssetLoader::create({engine, app.materials, app.names});
|
||||
|
||||
// Create quad index buffer.
|
||||
static constexpr uint16_t kQuadIndices[6] = { 0, 1, 2, 3, 2, 1 };
|
||||
app.quadIb = IndexBuffer::Builder()
|
||||
.indexCount(6)
|
||||
.bufferType(IndexBuffer::IndexType::USHORT)
|
||||
.build(*engine);
|
||||
app.quadIb->setBuffer(*engine, IndexBuffer::BufferDescriptor(kQuadIndices, 12, nullptr));
|
||||
app.asset = app.assetLoader->createAsset(GLTF_DEMO_DAMAGEDHELMET_DATA,
|
||||
GLTF_DEMO_DAMAGEDHELMET_SIZE);
|
||||
|
||||
// Create quad material and renderable.
|
||||
app.quadMaterial = Material::Builder()
|
||||
.package(RESOURCES_MIRROR_DATA, RESOURCES_MIRROR_SIZE)
|
||||
.build(*engine);
|
||||
app.quadMatInstance = app.quadMaterial->createInstance();
|
||||
TextureSampler sampler(TextureSampler::MinFilter::LINEAR, TextureSampler::MagFilter::LINEAR);
|
||||
app.quadMatInstance->setParameter("albedo", app.offscreenColorTexture, sampler);
|
||||
app.quadEntity = em.create();
|
||||
RenderableManager::Builder(1)
|
||||
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
|
||||
.material(0, app.quadMatInstance)
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.quadVb, app.quadIb, 0, 6)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.build(*engine, app.quadEntity);
|
||||
scene->addEntity(app.quadEntity);
|
||||
loadResources();
|
||||
|
||||
// Instantiate mesh material.
|
||||
app.meshMaterial = Material::Builder()
|
||||
.package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE).build(*engine);
|
||||
auto mi = app.meshMatInstance = app.meshMaterial->createInstance();
|
||||
mi->setParameter("baseColor", RgbType::LINEAR, {0.8, 1.0, 1.0});
|
||||
mi->setParameter("metallic", 0.0f);
|
||||
mi->setParameter("roughness", 0.4f);
|
||||
mi->setParameter("reflectance", 0.5f);
|
||||
app.offscreenScene->addEntities(app.asset->getLightEntities(),
|
||||
app.asset->getLightEntityCount());
|
||||
static constexpr int kNumAvailable = 128;
|
||||
utils::Entity renderables[kNumAvailable];
|
||||
gltfio::FilamentAsset::SceneMask mask;
|
||||
mask.set(0);
|
||||
while (size_t numWritten = app.asset->popRenderables(renderables, kNumAvailable)) {
|
||||
app.asset->addEntitiesToScene(*app.offscreenScene, renderables, numWritten, mask);
|
||||
}
|
||||
|
||||
// Add monkey into the scene.
|
||||
app.monkeyMesh = MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, mi);
|
||||
auto ti = tcm.getInstance(app.monkeyMesh.renderable);
|
||||
app.transform = mat4f{ mat3f(1), float3(0, 0, -4) } * tcm.getWorldTransform(ti);
|
||||
rcm.setCastShadows(rcm.getInstance(app.monkeyMesh.renderable), false);
|
||||
scene->addEntity(app.monkeyMesh.renderable);
|
||||
app.rootTransformEntity = engine->getEntityManager().create();
|
||||
tcm.create(app.rootTransformEntity);
|
||||
TransformManager::Instance const& root = tcm.getInstance(app.rootTransformEntity);
|
||||
tcm.setParent(tcm.getInstance(app.asset->getRoot()), root);
|
||||
tcm.setTransform(root, mat4::translation(float3(0, 0, -4)));
|
||||
}
|
||||
|
||||
// Create a reflected monkey, which is used only for App::ReflectionMode::RENDERABLES.
|
||||
app.reflectedMonkey = em.create();
|
||||
RenderableManager::Builder(1)
|
||||
.boundingBox({{ -2, -2, -2 }, { 2, 2, 2 }})
|
||||
.material(0, mi)
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.monkeyMesh.vertexBuffer, app.monkeyMesh.indexBuffer)
|
||||
.receiveShadows(true)
|
||||
.castShadows(false)
|
||||
.build(*engine, app.reflectedMonkey);
|
||||
setReflectionMode(app, app.mode);
|
||||
// full-screen quad
|
||||
{
|
||||
static Vertex kQuadVertices[4] = {{{1, 1, 0}, {1, 1}}, {{1, -1, 0}, {1, 0}},
|
||||
{{-1, -1, 0}, {0, 0}}, {{-1, 1, 0}, {0, 1}}};
|
||||
// Create quad vertex buffer.
|
||||
static_assert(sizeof(Vertex) == 20, "Strange vertex size.");
|
||||
app.quadVb = VertexBuffer::Builder()
|
||||
.vertexCount(4)
|
||||
.bufferCount(1)
|
||||
.attribute(VertexAttribute::POSITION, 0,
|
||||
VertexBuffer::AttributeType::FLOAT3, 0, 20)
|
||||
.attribute(VertexAttribute::UV0, 0,
|
||||
VertexBuffer::AttributeType::FLOAT2, 12, 20)
|
||||
.build(*engine);
|
||||
app.quadVb->setBufferAt(*engine, 0,
|
||||
VertexBuffer::BufferDescriptor(kQuadVertices, 80, nullptr));
|
||||
|
||||
// Add light source to both scenes.
|
||||
// NOTE: this is slightly wrong when the reflection mode is RENDERABLES.
|
||||
app.lightEntity = em.create();
|
||||
LightManager::Builder(LightManager::Type::SUN)
|
||||
.color(Color::toLinear<ACCURATE>(sRGBColor(0.98f, 0.92f, 0.89f)))
|
||||
.intensity(110000)
|
||||
.direction({ 0.7, -1, -0.8 })
|
||||
.sunAngularRadius(1.9f)
|
||||
.castShadows(false)
|
||||
.build(*engine, app.lightEntity);
|
||||
scene->addEntity(app.lightEntity);
|
||||
app.offscreenScene->addEntity(app.lightEntity);
|
||||
// Create quad index buffer.
|
||||
static constexpr uint16_t kQuadIndices[6] = {0, 2, 1, 2, 0, 3};
|
||||
app.quadIb = IndexBuffer::Builder()
|
||||
.indexCount(6)
|
||||
.bufferType(IndexBuffer::IndexType::USHORT)
|
||||
.build(*engine);
|
||||
app.quadIb->setBuffer(*engine,
|
||||
IndexBuffer::BufferDescriptor(kQuadIndices, 12, nullptr));
|
||||
|
||||
// Create quad material and renderable.
|
||||
// NOTE: this material is VertexDomain=device
|
||||
app.quadMaterial = Material::Builder()
|
||||
.package(RESOURCES_MIRROR_DATA, RESOURCES_MIRROR_SIZE)
|
||||
.build(*engine);
|
||||
app.quadMatInstance = app.quadMaterial->createInstance();
|
||||
TextureSampler sampler(TextureSampler::MinFilter::LINEAR,
|
||||
TextureSampler::MagFilter::LINEAR);
|
||||
app.quadMatInstance->setParameter("albedo", app.offscreenColorTexture, sampler);
|
||||
app.quadEntity = em.create();
|
||||
RenderableManager::Builder(1)
|
||||
.boundingBox({{-1, -1, -1}, {1, 1, 1}})
|
||||
.material(0, app.quadMatInstance)
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.quadVb,
|
||||
app.quadIb, 0, 6)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.build(*engine, app.quadEntity);
|
||||
scene->addEntity(app.quadEntity);
|
||||
}
|
||||
};
|
||||
|
||||
auto cleanup = [&app](Engine* engine, View*, Scene*) {
|
||||
@@ -318,14 +346,10 @@ int main(int argc, char** argv) {
|
||||
engine->destroyCameraComponent(camera);
|
||||
em.destroy(camera);
|
||||
|
||||
engine->destroy(app.reflectedMonkey);
|
||||
engine->destroy(app.lightEntity);
|
||||
engine->destroy(app.quadEntity);
|
||||
engine->destroy(app.meshMatInstance);
|
||||
engine->destroy(app.meshMaterial);
|
||||
engine->destroy(app.monkeyMesh.renderable);
|
||||
engine->destroy(app.monkeyMesh.vertexBuffer);
|
||||
engine->destroy(app.monkeyMesh.indexBuffer);
|
||||
engine->destroy(app.offscreenColorTexture);
|
||||
engine->destroy(app.offscreenDepthTexture);
|
||||
engine->destroy(app.offscreenRenderTarget);
|
||||
@@ -337,43 +361,22 @@ int main(int argc, char** argv) {
|
||||
engine->destroy(app.quadMaterial);
|
||||
};
|
||||
|
||||
auto preRender = [&app](Engine*, View*, Scene*, Renderer* renderer) {
|
||||
auto preRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) {
|
||||
renderer->setClearOptions({.clearColor = {0.1,0.2,0.4,1.0}, .clear = true});
|
||||
};
|
||||
|
||||
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
|
||||
auto& tcm = engine->getTransformManager();
|
||||
utils::slog.e <<"offscreen view=" << app.offscreenView << " primary view=" <<
|
||||
view << utils::io::endl;
|
||||
|
||||
// Animate the monkey by spinning and sliding back and forth along Z.
|
||||
auto ti = tcm.getInstance(app.monkeyMesh.renderable);
|
||||
mat4f xlate = mat4f::translation(float3(0, 0, 0.5 + sin(now)));
|
||||
mat4f xform = app.transform * xlate * mat4f::rotation(now, float3{ 0, 1, 0 });
|
||||
tcm.setTransform(ti, xform);
|
||||
|
||||
// Generate a reflection matrix from the plane equation Ax + By + Cz + D = 0.
|
||||
const float3 planeNormal = app.quadNormal;
|
||||
const float4 planeEquation(planeNormal, -dot(planeNormal, app.quadCenter));
|
||||
const mat4f reflection = reflectionMatrix(planeEquation);
|
||||
|
||||
// Apply the reflection matrix to either the renderable or the camera, depending on mode.
|
||||
Camera const& camera = view->getCamera();
|
||||
const auto model = camera.getModelMatrix();
|
||||
const auto renderingProjection = camera.getProjectionMatrix();
|
||||
const auto cullingProjection = camera.getCullingProjectionMatrix();
|
||||
app.offscreenCamera->setCustomProjection(renderingProjection, cullingProjection,
|
||||
camera.getNear(), camera.getCullingFar());
|
||||
switch (app.mode) {
|
||||
case App::ReflectionMode::RENDERABLES:
|
||||
tcm.setTransform(tcm.getInstance(app.reflectedMonkey), reflection * xform);
|
||||
app.offscreenCamera->setModelMatrix(model);
|
||||
break;
|
||||
case App::ReflectionMode::CAMERA:
|
||||
app.offscreenCamera->setModelMatrix(reflection * model);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const auto model = camera.getModelMatrix();
|
||||
app.offscreenCamera->setModelMatrix(model);
|
||||
};
|
||||
|
||||
FilamentApp::get().run(app.config, setup, cleanup, FilamentApp::ImGuiCallback(), preRender);
|
||||
FilamentApp::get().run(app.config, setup, cleanup, gui, preRender);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.53.5",
|
||||
"version": "1.54.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user