diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f8406e979c..5cc55704df 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -35,6 +35,7 @@ A new header is inserted each time a *tag* is created. light's intensity in candela. - Fixed an issue where some `ShadowOptions` were not being respected when passed to `LightManager::Builder`. +- Added a Depth of Field post-processing effect ## v1.5.2 diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index d67c934622..f57807f9cb 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -252,3 +252,10 @@ Java_com_google_android_filament_View_nSetBlendMode(JNIEnv *, jclass , jlong nat View* view = (View*) nativeView; view->setBlendMode((View::BlendMode)blendMode); } + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_View_nSetDepthOfFieldOptions(JNIEnv *, jclass , + jlong nativeView, jfloat focusDistance, jfloat blurScale, jboolean enabled) { + View* view = (View*) nativeView; + view->setDepthOfFieldOptions({ .focusDistance = focusDistance, .blurScale = blurScale, .enabled = (bool)enabled }); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index be1f7ca734..a1aa3df253 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -69,6 +69,7 @@ public class View { private FogOptions mFogOptions; private RenderTarget mRenderTarget; private BlendMode mBlendMode; + private DepthOfFieldOptions mDepthOfFieldOptions; /** * Generic Quality Level @@ -310,6 +311,23 @@ public class View { public boolean enabled = false; } + /** + * Options to control Depth of Field (DoF) effect in the scene + * + * @see View#setDepthOfFieldOptions + */ + public static class DepthOfFieldOptions { + + /** focus distance in world units */ + public float focusDistance = 10.0f; + + /** scale factor controlling the amount of blur (values other than 1.0 are not physically correct)*/ + public float blurScale = 1.0f; + + /** enable or disable Depth of field effect */ + public boolean enabled = false; + }; + /** * Structure used to set the color precision for the rendering of a View. * @@ -977,6 +995,32 @@ public class View { } + /** + * Sets Depth of Field options. + * + * @param options Options for depth of field effect. + * @see #getDepthOfFieldOptions + */ + public void setDepthOfFieldOptions(@NonNull DepthOfFieldOptions options) { + mDepthOfFieldOptions = options; + nSetDepthOfFieldOptions(getNativeObject(), options.focusDistance, options.blurScale, options.enabled); + } + + /** + * Gets the Depth of Field options + * + * @return Depth of Field options currently set. + * @see #setDepthOfFieldOptions + */ + @NonNull + public DepthOfFieldOptions getDepthOfFieldOptions() { + if (mDepthOfFieldOptions == null) { + mDepthOfFieldOptions = new DepthOfFieldOptions(); + } + return mDepthOfFieldOptions; + } + + public long getNativeObject() { if (mNativeObject == 0) { throw new IllegalStateException("Calling method on destroyed View"); @@ -1016,4 +1060,5 @@ public class View { private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled); private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, boolean enabled); private static native void nSetBlendMode(long nativeView, int blendMode); + private static native void nSetDepthOfFieldOptions(long nativeView, float focusDistance, float blurScale, boolean enabled); } diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 380ae0af78..813555f5c5 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -143,6 +143,8 @@ set(PRIVATE_HDRS set(MATERIAL_SRCS src/materials/defaultMaterial.mat + src/materials/dof.mat + src/materials/dofBlur.mat src/materials/blitLow.mat src/materials/blitMedium.mat src/materials/blitHigh.mat @@ -226,6 +228,18 @@ add_custom_command( APPEND ) +add_custom_command( + OUTPUT "${MATERIAL_DIR}/dof.filamat" + DEPENDS src/materials/dofUtils.fs + APPEND +) + +add_custom_command( + OUTPUT "${MATERIAL_DIR}/dofBlur.filamat" + DEPENDS src/materials/dofUtils.fs + APPEND +) + add_custom_command( OUTPUT ${RESGEN_OUTPUTS} COMMAND resgen ${RESGEN_FLAGS} ${MATERIAL_BINS} diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index b6c7ffc9d9..dd9bec1b54 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -165,6 +165,15 @@ public: bool enabled = false; //!< enable or disable fog }; + /** + * Options to control Depth of Field (DoF) effect in the scene + */ + struct DepthOfFieldOptions { + float focusDistance = 10.0f; //!< focus distance in world units + float blurScale = 1.0f; //!< a scale factor for the amount of blur + bool enabled = false; //!< enable or disable Depth of field effect + }; + /** * Structure used to set the precision of the color buffer and related quality settings. * @@ -485,6 +494,13 @@ public: */ void setFogOptions(FogOptions options) noexcept; + /** + * Enables or disables Depth of Field. Disabled by default. + * + * @param options options + */ + void setDepthOfFieldOptions(DepthOfFieldOptions options) noexcept; + /** * Queries the bloom options. * diff --git a/filament/src/Camera.cpp b/filament/src/Camera.cpp index 572f470770..c0a0a9e69f 100644 --- a/filament/src/Camera.cpp +++ b/filament/src/Camera.cpp @@ -25,6 +25,7 @@ #include #include +#include using namespace filament::math; using namespace utils; @@ -60,7 +61,7 @@ void UTILS_NOINLINE FCamera::setProjection(double fov, double aspect, double nea void FCamera::setLensProjection(double focalLength, double aspect, double near, double far) noexcept { // a 35mm camera has a 36x24mm wide frame size - double theta = 2.0 * std::atan(24.0 / (2.0 * focalLength)); + double theta = 2.0 * std::atan(SENSOR_SIZE * 1000.0f / (2.0 * focalLength)); theta *= 180.0 / math::F_PI; FCamera::setProjection(theta, aspect, near, far, Fov::VERTICAL); } @@ -234,6 +235,34 @@ Frustum FCamera::getFrustum(mat4 const& projection, mat4f const& viewMatrix) noe return Frustum(mat4f{ projection * viewMatrix }); } +// ------------------------------------------------------------------------------------------------ + +CameraInfo::CameraInfo(FCamera const& camera) noexcept { + projection = mat4f{ camera.getProjectionMatrix() }; + cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; + model = camera.getModelMatrix(); + view = camera.getViewMatrix(); + zn = camera.getNear(); + zf = camera.getCullingFar(); + ev100 = Exposure::ev100(camera); + f = (FCamera::SENSOR_SIZE * (float)projection[1][1]) * 0.5f; + A = f / camera.getAperture(); +} + +CameraInfo::CameraInfo(FCamera const& camera, const math::mat4f& worldOriginCamera) noexcept { + const mat4f modelMatrix{ worldOriginCamera * camera.getModelMatrix() }; + projection = mat4f{ camera.getProjectionMatrix() }; + cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; + model = modelMatrix; + view = FCamera::getViewMatrix(model); + zn = camera.getNear(); + zf = camera.getCullingFar(); + ev100 = Exposure::ev100(camera); + f = (FCamera::SENSOR_SIZE * (float)projection[1][1]) * 0.5f; + A = f / camera.getAperture(); + worldOffset = camera.getPosition(); + worldOrigin = worldOriginCamera; +} } // namespace details diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 6eeb32b3ca..1cc67e6440 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -118,6 +118,8 @@ void PostProcessManager::init() noexcept { mBlit[2] = PostProcessMaterial(mEngine, MATERIALS_BLITHIGH_DATA, MATERIALS_BLITHIGH_SIZE); mTonemapping = PostProcessMaterial(mEngine, MATERIALS_TONEMAPPING_DATA, MATERIALS_TONEMAPPING_SIZE); mFxaa = PostProcessMaterial(mEngine, MATERIALS_FXAA_DATA, MATERIALS_FXAA_SIZE); + mDoFBlur = PostProcessMaterial(mEngine, MATERIALS_DOFBLUR_DATA, MATERIALS_DOFBLUR_SIZE); + mDoF = PostProcessMaterial(mEngine, MATERIALS_DOF_DATA, MATERIALS_DOF_SIZE); // UBO storage size. // The effective kernel size is (kMaxPositiveKernelSize - 1) * 4 + 1. @@ -157,6 +159,8 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept { mBlit[2].terminate(engine); mTonemapping.terminate(engine); mFxaa.terminate(engine); + mDoFBlur.terminate(engine); + mDoF.terminate(engine); } // ------------------------------------------------------------------------------------------------ @@ -316,6 +320,148 @@ FrameGraphId PostProcessManager::fxaa(FrameGraph& fg, return ppFXAA.getData().output; } +FrameGraphId PostProcessManager::dof(FrameGraph& fg, + FrameGraphId input, + const View::DepthOfFieldOptions& dofOptions, + const CameraInfo& cameraInfo) noexcept { + + FEngine& engine = mEngine; + Handle const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive(); + + FrameGraphId depth = fg.getBlackboard().get("structure"); + assert(depth.isValid()); + + const size_t sampleCount = 25; // (keep in sync with dofUtils.fs) + const float focusDistance = std::max(cameraInfo.zn, dofOptions.focusDistance); + auto const& desc = fg.getDescriptor(input); + const float Kc = (cameraInfo.A * cameraInfo.f) / (focusDistance - cameraInfo.f); + const float Ks = ((float)desc.height / sampleCount) / FCamera::SENSOR_SIZE; + const float2 cocParams{ + // we use 1/zn instead of (zf - zn) / (zf * zn), because in reality we're using + // a projection with an infinite far plane + (dofOptions.blurScale * Ks * Kc) * focusDistance / cameraInfo.zn, + (dofOptions.blurScale * Ks * Kc) * (1.0f - focusDistance / cameraInfo.zn) + }; + + struct PostProcessDoFBlur { + FrameGraphId color; + FrameGraphId depth; + FrameGraphId vertical; + FrameGraphId diagonal; + FrameGraphRenderTargetHandle rt; + }; + + auto& ppDoFBlur = fg.addPass("dofblur", + [&](FrameGraph::Builder& builder, auto& data) { + auto const& inputDesc = fg.getDescriptor(input); + data.color = builder.sample(input); + data.depth = builder.sample(depth); + data.vertical = builder.createTexture("dof vertical output", { + .width = inputDesc.width, + .height = inputDesc.height, + .format = inputDesc.format + }); + data.vertical = builder.write(data.vertical); + data.diagonal = builder.createTexture("dof diagonal output", { + .width = inputDesc.width, + .height = inputDesc.height, + .format = inputDesc.format + }); + data.diagonal = builder.write(data.diagonal); + data.rt = builder.createRenderTarget("DoF Target", { + .attachments = { + { data.vertical, data.diagonal }, {}, {} + } + }); + }, + [=](FrameGraphPassResources const& resources, + auto const& data, DriverApi& driver) { + auto const& desc = resources.getDescriptor(data.color); + auto const& color = resources.getTexture(data.color); + auto const& depth = resources.getTexture(data.depth); + auto const& out = resources.get(data.rt); + + PostProcessMaterial& material = mDoFBlur; + FMaterialInstance* const mi = material.getMaterialInstance(); + mi->setParameter("color", color, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + mi->setParameter("depth", depth, { + .filterMin = SamplerMinFilter::NEAREST + }); + mi->setParameter("resolution", float4{ + desc.width, desc.height, 1.0f / desc.width, 1.0f / desc.height }); + mi->setParameter("cocParams", cocParams); + mi->commit(driver); + mi->use(driver); + + PipelineState pipeline(material.getPipelineState()); + driver.beginRenderPass(out.target, out.params); + driver.draw(pipeline, fullScreenRenderPrimitive); + driver.endRenderPass(); + }); + + struct PostProcessDoF { + FrameGraphId vertical; + FrameGraphId diagonal; + FrameGraphId depth; + FrameGraphId output; + FrameGraphRenderTargetHandle rt; + }; + + auto& ppDoF = fg.addPass("dof", + [&](FrameGraph::Builder& builder, auto& data) { + auto const& inputDesc = fg.getDescriptor(input); + data.vertical = builder.sample(ppDoFBlur.getData().vertical); + data.diagonal = builder.sample(ppDoFBlur.getData().diagonal); + data.depth = builder.sample(depth); + data.output = builder.createTexture("dof output", { + .width = inputDesc.width, + .height = inputDesc.height, + .format = inputDesc.format + }); + data.output = builder.write(data.output); + data.rt = builder.createRenderTarget("DoF Target", { + .attachments = { data.output } + }); + }, + [=](FrameGraphPassResources const& resources, + auto const& data, DriverApi& driver) { + auto const& desc = resources.getDescriptor(data.vertical); + auto const& vertical = resources.getTexture(data.vertical); + auto const& diagonal = resources.getTexture(data.diagonal); + auto const& depth = resources.getTexture(data.depth); + auto const& out = resources.get(data.rt); + + PostProcessMaterial& material = mDoF; + FMaterialInstance* const mi = material.getMaterialInstance(); + mi->setParameter("buffer0", vertical, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + mi->setParameter("buffer1", diagonal, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + mi->setParameter("depth", depth, { + .filterMin = SamplerMinFilter::NEAREST + }); + mi->setParameter("resolution", float4{ + desc.width, desc.height, 1.0f / desc.width, 1.0f / desc.height }); + mi->setParameter("cocParams", cocParams); + mi->commit(driver); + mi->use(driver); + + PipelineState pipeline(material.getPipelineState()); + driver.beginRenderPass(out.target, out.params); + driver.draw(pipeline, fullScreenRenderPrimitive); + driver.endRenderPass(); + }); + + return ppDoF.getData().output; +} + FrameGraphId PostProcessManager::opaqueBlit(FrameGraph& fg, FrameGraphId input, FrameGraphTexture::Descriptor outDesc) noexcept { @@ -770,7 +916,7 @@ FrameGraphId PostProcessManager::gaussianBlurPass(FrameGraph& FrameGraphId output, uint8_t dstLevel, bool reinhard, size_t kernelWidth, float sigmaRatio) noexcept { - const float sigma = (kernelWidth + 1) / sigmaRatio; + const float sigma = (kernelWidth + 1.0f) / sigmaRatio; Handle fullScreenRenderPrimitive = mEngine.getFullScreenRenderPrimitive(); diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 982104061d..ca365a8914 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -53,6 +53,11 @@ public: FrameGraphId input, backend::TextureFormat outFormat, bool translucent) noexcept; + FrameGraphId dof(FrameGraph& fg, + FrameGraphId input, + const View::DepthOfFieldOptions& dofOptions, + const details::CameraInfo& cameraInfo) noexcept; + FrameGraphId opaqueBlit(FrameGraph& fg, FrameGraphId input, FrameGraphTexture::Descriptor outDesc) noexcept; @@ -134,6 +139,8 @@ private: PostProcessMaterial mBlit[3]; PostProcessMaterial mTonemapping; PostProcessMaterial mFxaa; + PostProcessMaterial mDoFBlur; + PostProcessMaterial mDoF; backend::Handle mDummyOneTexture; backend::Handle mDummyZeroTexture; diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index d0d04ae5c3..675f7e957e 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -204,14 +204,17 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { uint8_t msaa = view.getSampleCount(); float2 scale = view.updateScale(mFrameInfoManager.getLastFrameInfo()); const View::QualityLevel upscalingQuality = view.getDynamicResolutionOptions().quality; + auto bloomOptions = view.getBloomOptions(); + auto dofOptions = view.getDepthOfFieldOptions(); auto aoOptions = view.getAmbientOcclusionOptions(); if (!hasPostProcess) { - // dynamic scaling and FXAA are part of the post-process phase and can't happen if - // it's disabled. - fxaa = false; - dithering = false; - scale = 1.0f; + // disable all effects that are part of post-processing msaa = 1; + dofOptions.enabled = false; + bloomOptions.enabled = false; + dithering = false; + fxaa = false; + scale = 1.0f; } const bool scaled = any(notEqual(scale, float2(1.0f))); @@ -382,9 +385,12 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { TextureFormat::RGBA8 : getLdrFormat(translucent); // e.g. RGB8 or RGBA8 if (hasPostProcess) { + if (dofOptions.enabled) { + input = ppm.dof(fg, input, dofOptions, cameraInfo); + } if (toneMapping) { input = ppm.toneMapping(fg, input, ldrFormat, translucent, fxaa, scale, - view.getBloomOptions(), dithering); + bloomOptions, dithering); } if (fxaa) { input = ppm.fxaa(fg, input, ldrFormat, !toneMapping || translucent); diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index 557473078b..570840dcbc 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -80,16 +80,9 @@ void ShadowMap::render(DriverApi& driver, Handle rt, params.viewport = viewport; FCamera const& camera = getCamera(); - details::CameraInfo cameraInfo = { - .projection = mat4f{ camera.getProjectionMatrix() }, - .cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }, - .model = camera.getModelMatrix(), - .view = camera.getViewMatrix(), - .zn = camera.getNear(), - .zf = camera.getCullingFar(), - }; - pass.setCamera(cameraInfo); + details::CameraInfo cameraInfo(camera); + pass.setCamera(cameraInfo); pass.setGeometry(scene.getRenderableData(), range, scene.getRenderableUBO()); view.updatePrimitivesLod(engine, cameraInfo, scene.getRenderableData(), range); diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 2f7fd48e0e..3175d27418 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -391,32 +391,10 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are } // Note: for debugging (i.e. visualize what the camera / objects are doing, using - // the viewing camera), we can set worldOriginCamera to identity when mViewingCamera - // is set: e.g. - // worldOriginCamera = mViewingCamera ? mat4f{} : worldOriginScene + // the viewing camera), we can set worldOriginScene to identity when mViewingCamera + // is set + mViewingCameraInfo = CameraInfo(*camera, worldOriginScene); - const mat4f worldOriginCamera = worldOriginScene; - const mat4f model{ worldOriginCamera * camera->getModelMatrix() }; - mViewingCameraInfo = CameraInfo{ - // projection with infinite z-far - .projection = mat4f{ camera->getProjectionMatrix() }, - // projection used for culling, with finite z-far - .cullingProjection = mat4f{ camera->getCullingProjectionMatrix() }, - // camera model matrix -- apply the world origin to it - .model = model, - // camera view matrix - .view = FCamera::getViewMatrix(model), - // near plane - .zn = camera->getNear(), - // far plane - .zf = camera->getCullingFar(), - // exposure - .ev100 = Exposure::ev100(*camera), - // world offset to allow users to determine the API-level camera position - .worldOffset = camera->getPosition(), - // world origin transform, use only for debugging - .worldOrigin = worldOriginCamera - }; mCullingFrustum = FCamera::getFrustum( mCullingCamera->getCullingProjectionMatrix(), FCamera::getViewMatrix(worldOriginScene * mCullingCamera->getModelMatrix())); @@ -431,7 +409,7 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are * Light culling: runs in parallel with Renderable culling (below) */ - auto prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr, + auto *prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr, [&frustum = mCullingFrustum, &engine, scene](JobSystem& js, JobSystem::Job*) { FView::prepareVisibleLights( engine.getLightManager(), js, frustum, scene->getLightData()); @@ -981,6 +959,10 @@ void View::setFogOptions(View::FogOptions options) noexcept { upcast(this)->setFogOptions(options); } +void View::setDepthOfFieldOptions(DepthOfFieldOptions options) noexcept { + upcast(this)->setDepthOfFieldOptions(options); +} + View::BloomOptions View::getBloomOptions() const noexcept { return upcast(this)->getBloomOptions(); } @@ -993,5 +975,4 @@ View::BlendMode View::getBlendMode() const noexcept { return upcast(this)->getBlendMode(); } - } // namespace filament diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index 787d89c993..578bc4910c 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -39,6 +39,8 @@ class FEngine; */ class FCamera : public Camera { public: + // a 35mm camera has a 36x24mm wide frame size + static constexpr const float SENSOR_SIZE = 0.024f; // 24mm FCamera(FEngine& engine, utils::Entity e); @@ -169,14 +171,20 @@ private: }; struct CameraInfo { - math::mat4f projection; - math::mat4f cullingProjection; - math::mat4f model; - math::mat4f view; - float zn; - float zf; - float ev100 = 0.0f; - math::float3 worldOffset; + CameraInfo() noexcept = default; + explicit CameraInfo(FCamera const& camera) noexcept; + CameraInfo(FCamera const& camera, const math::mat4f& worldOriginCamera) noexcept; + + math::mat4f projection; // projection matrix for drawing (infinite zfar) + math::mat4f cullingProjection; // projection matrix for culling + math::mat4f model; // camera model matrix + math::mat4f view; // camera view matrix + float zn{}; // distance (positive) to the near plane + float zf{}; // distance (positive) to the far plane + float ev100{}; // exposure + float f{}; // focal length (in m) + float A{}; // aperture diameter (in m) + math::float3 worldOffset{}; // world offset, API-level camera position math::float3 const& getPosition() const noexcept { return model[3].xyz; } math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); } diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 24f1ec858d..4bd3abe572 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -270,6 +270,10 @@ public: mBloomOptions = options; } + BloomOptions getBloomOptions() const noexcept { + return mBloomOptions; + } + void setFogOptions(FogOptions options) noexcept { options.distance = std::max(0.0f, options.distance); options.maximumOpacity = math::clamp(options.maximumOpacity, 0.0f, 1.0f); @@ -280,8 +284,14 @@ public: mFogOptions = options; } - BloomOptions getBloomOptions() const noexcept { - return mBloomOptions; + void setDepthOfFieldOptions(DepthOfFieldOptions options) noexcept { + options.focusDistance = std::max(0.0f, options.focusDistance); + options.blurScale = std::max(0.0f, options.blurScale); + mDepthOfFieldOptions = options; + } + + DepthOfFieldOptions getDepthOfFieldOptions() const noexcept { + return mDepthOfFieldOptions; } void setBlendMode(BlendMode blendMode) noexcept { @@ -378,6 +388,7 @@ private: AmbientOcclusionOptions mAmbientOcclusionOptions{}; BloomOptions mBloomOptions; FogOptions mFogOptions; + DepthOfFieldOptions mDepthOfFieldOptions; BlendMode mBlendMode = BlendMode::OPAQUE; DynamicResolutionOptions mDynamicResolution; diff --git a/filament/src/materials/dof.mat b/filament/src/materials/dof.mat new file mode 100644 index 0000000000..0a06f7f18b --- /dev/null +++ b/filament/src/materials/dof.mat @@ -0,0 +1,63 @@ +material { + name : DepthOfField, + parameters : [ + { + type : sampler2d, + name : buffer0, + precision: medium + }, + { + type : sampler2d, + name : buffer1, + precision: medium + }, + { + type : sampler2d, + name : depth, + precision: medium + }, + { + type : float4, + name : resolution, + precision: high + }, + { + type : float2, + name : cocParams + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV; + } +} + +fragment { + +#include "dofUtils.fs" + +const vec2 kDirVertical= unitvec(PI / 2.0 + BOKEH_ROTATION_ANGLE); +const vec2 kDirDiamond = unitvec(3.0 * PI / 4.0 + BOKEH_ROTATION_ANGLE); + +void postProcess(inout PostProcessInputs postProcess) { + highp vec2 uv = variable_vertex.xy; + + float depth = textureLod(materialParams_depth, uv, 0.0).r; + float coc = getCOC(depth, materialParams.cocParams); + + vec4 box = blurTexture(materialParams_buffer0, uv, kDirVertical, depth, coc); + vec4 diamond = blurTexture(materialParams_buffer1, uv, kDirDiamond, depth, coc); + vec4 dof = min(box, diamond); + + postProcess.color = dof; +} + +} diff --git a/filament/src/materials/dofBlur.mat b/filament/src/materials/dofBlur.mat new file mode 100644 index 0000000000..0e18fa869f --- /dev/null +++ b/filament/src/materials/dofBlur.mat @@ -0,0 +1,75 @@ +material { + name : DepthOfFieldBlur, + parameters : [ + { + type : sampler2d, + name : color, + precision: medium + }, + { + type : sampler2d, + name : depth, + precision: medium + }, + { + type : float4, + name : resolution, + precision: high + }, + { + type : float2, + name : cocParams + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV; + } +} + +fragment { + +/* + * Separable octogonal blur + * + * This uses a lot of ideas from + * "Hexagonal Bokeh Blur Revisited" by Colin Barré-Brisebois + * (https://colinbarrebrisebois.com/2017/04/18/hexagonal-bokeh-blur-revisited-part-1-basic-3-pass-version/) + * + * "Efficiently Simulating the Bokeh of Polygonal Apertures in a Post-Process Depth of Field Shader" + * by L. McIntosh, B. E. Riecke and S. DiPaola + */ + +layout(location = 1) out vec4 fragColor1; + +#include "dofUtils.fs" + +const vec2 kDirHorizontal = unitvec(0.0 + BOKEH_ROTATION_ANGLE); +const vec2 kDirDiagonal = unitvec(PI / 4.0 + BOKEH_ROTATION_ANGLE); + +void postProcess(inout PostProcessInputs postProcess) { + highp vec2 uv = variable_vertex.xy; + + float depth = textureLod(materialParams_depth, uv, 0.0).r; + float coc = getCOC(depth, materialParams.cocParams); + + vec4 horizontal = blurTexture(materialParams_color, uv, kDirHorizontal, depth, coc); + vec4 diagonal = blurTexture(materialParams_color, uv, kDirDiagonal, depth, coc); + + // Output to MRTs + postProcess.color = horizontal; +#if defined(TARGET_MOBILE) + diagonal = clamp(diagonal, 0.0, MEDIUMP_FLT_MAX); +#endif + fragColor1 = diagonal; +} + +} diff --git a/filament/src/materials/dofUtils.fs b/filament/src/materials/dofUtils.fs new file mode 100644 index 0000000000..5ffff60a0a --- /dev/null +++ b/filament/src/materials/dofUtils.fs @@ -0,0 +1,75 @@ + +/* + * DoF blur + * + * This uses a lot of ideas from + * "Bokeh depth of field in a single pass" by Dennis Gustafsson + * (http://blog.tuxedolabs.com/2018/05/04/bokeh-depth-of-field-in-single-pass.html) + * + * needs: + * materialParams_depth : depth texture + * materialParams.cocParams : coc scale and bias + * materialParams.resolution : screen resolution + */ + +// Filter sample count, prefer odd values +// (keep in sync with PostProcessManager.cpp:dof()) +const float SAMPLE_COUNT = 25.0; + +// A value > 1.0 allows a larger blur w/ dithering +const float BLUR_SCALE = 1.0; + +// This is here just for aesthetic reasons +const float BOKEH_ROTATION_ANGLE = PI / 6.0; + +#define unitvec(angle) vec2(cos(angle), sin(angle)) + +// random number between 0 and 1, using interleaved gradient noise +float random(const highp vec2 w) { + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(m.z * fract(dot(w, m.xy))); +} + +float getCOC(float depth, vec2 cocParams) { + float CoC = abs(depth * cocParams.x + cocParams.y); + return saturate(CoC); +} + +void tap(inout vec4 finalColor, inout float blurAmount, float radius, + highp vec2 uv, sampler2D colorBuffer, float centerDepth, float centerCoc) { + float depth = textureLod(materialParams_depth, uv, 0.0).r; + float coc = getCOC(depth, materialParams.cocParams); + vec4 color = textureLod(colorBuffer, uv, 0.0); + + // prevent blurry background to bleed onto sharp foreground + if (depth > centerDepth) { + coc = clamp(coc, 0.0, centerCoc * 2.0); + } + + float m = step(radius * BLUR_SCALE, coc * (SAMPLE_COUNT * BLUR_SCALE)); + finalColor += mix(finalColor * (1.0 / blurAmount), color, m); + blurAmount += 1.0; +} + +vec4 blurTexture(sampler2D colorBuffer, highp vec2 uv, highp vec2 direction, float centerDepth, float centerCoc) { + float blurAmount = 1.0; + vec4 finalColor = textureLod(colorBuffer, uv, 0.0); + + highp vec2 unit = materialParams.resolution.zw; + direction *= unit * BLUR_SCALE; + + float noise = 0.0; + if (BLUR_SCALE != 1.0) { + // we span 2 samples because the first sample is always fixed (no noise added) + noise = (random(gl_FragCoord.xy) - 0.5) * 2.0; + uv += direction * noise; + } + + vec4 tc = uv.xyxy; + for (float radius = 1.0 ; radius < (SAMPLE_COUNT * 0.5); radius += 1.0) { + tc += vec4(direction, -direction); + tap(finalColor, blurAmount, radius + noise, tc.xy, colorBuffer, centerDepth, centerCoc); + tap(finalColor, blurAmount, radius - noise, tc.zw, colorBuffer, centerDepth, centerCoc); + } + return finalColor * (1.0 / blurAmount); +} diff --git a/filament/src/materials/sao.mat b/filament/src/materials/sao.mat index a5e2a109f5..c97712407c 100644 --- a/filament/src/materials/sao.mat +++ b/filament/src/materials/sao.mat @@ -94,7 +94,7 @@ fragment { } // random number between 0 and 1, using interleaved gradient noise - float random(vec2 w) { + float random(const highp vec2 w) { const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); return fract(m.z * fract(dot(w, m.xy))); } diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 21263b810f..22c6ef81e9 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -82,6 +82,8 @@ struct App { sRGBColor backgroundColor = { 0.0f }; } viewOptions; + View::DepthOfFieldOptions dofOptions; + struct Scene { Entity groundPlane; VertexBuffer* groundVertexBuffer; @@ -396,10 +398,13 @@ int main(int argc, char** argv) { } if (ImGui::CollapsingHeader("Camera")) { - ImGui::SliderFloat("Focal length", &FilamentApp::get().getCameraFocalLength(), 16.0f, 90.0f); + ImGui::SliderFloat("Focal length (mm)", &FilamentApp::get().getCameraFocalLength(), 16.0f, 90.0f); ImGui::SliderFloat("Aperture", &app.viewOptions.cameraAperture, 1.0f, 32.0f); - ImGui::SliderFloat("Speed", &app.viewOptions.cameraSpeed, 800.0f, 1.0f); + ImGui::SliderFloat("Speed (1/s)", &app.viewOptions.cameraSpeed, 1000.0f, 1.0f); ImGui::SliderFloat("ISO", &app.viewOptions.cameraISO, 25.0f, 6400.0f); + ImGui::Checkbox("DoF", &app.dofOptions.enabled); + ImGui::SliderFloat("Focus distance", &app.dofOptions.focusDistance, 0.0f, 30.0f); + ImGui::SliderFloat("Blur scale", &app.dofOptions.blurScale, 0.1f, 10.0f); } }); @@ -451,6 +456,8 @@ int main(int argc, char** argv) { 1.0f / app.viewOptions.cameraSpeed, app.viewOptions.cameraISO); + view->setDepthOfFieldOptions(app.dofOptions); + app.scene.groundMaterial->setDefaultParameter( "strength", app.viewOptions.groundShadowStrength);