A new depth of field (DoF) post-process effect

This commit is contained in:
Pixelflinger
2020-04-23 20:39:07 -07:00
committed by Mathias Agopian
parent 64609cc4aa
commit 4e873e8007
18 changed files with 541 additions and 57 deletions

View File

@@ -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

View File

@@ -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 });
}

View File

@@ -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 <code>View</code>.
*
@@ -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);
}

View File

@@ -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}

View File

@@ -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.
*

View File

@@ -25,6 +25,7 @@
#include <utils/Panic.h>
#include <math/scalar.h>
#include <filament/Exposure.h>
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

View File

@@ -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<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
return ppFXAA.getData().output;
}
FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input,
const View::DepthOfFieldOptions& dofOptions,
const CameraInfo& cameraInfo) noexcept {
FEngine& engine = mEngine;
Handle<HwRenderPrimitive> const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive();
FrameGraphId<FrameGraphTexture> depth = fg.getBlackboard().get<FrameGraphTexture>("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<FrameGraphTexture>(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<FrameGraphTexture> color;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> vertical;
FrameGraphId<FrameGraphTexture> diagonal;
FrameGraphRenderTargetHandle rt;
};
auto& ppDoFBlur = fg.addPass<PostProcessDoFBlur>("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<FrameGraphTexture> vertical;
FrameGraphId<FrameGraphTexture> diagonal;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> output;
FrameGraphRenderTargetHandle rt;
};
auto& ppDoF = fg.addPass<PostProcessDoF>("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<FrameGraphTexture> PostProcessManager::opaqueBlit(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input, FrameGraphTexture::Descriptor outDesc) noexcept {
@@ -770,7 +916,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
FrameGraphId<FrameGraphTexture> 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<HwRenderPrimitive> fullScreenRenderPrimitive = mEngine.getFullScreenRenderPrimitive();

View File

@@ -53,6 +53,11 @@ public:
FrameGraphId<FrameGraphTexture> input, backend::TextureFormat outFormat,
bool translucent) noexcept;
FrameGraphId<FrameGraphTexture> dof(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input,
const View::DepthOfFieldOptions& dofOptions,
const details::CameraInfo& cameraInfo) noexcept;
FrameGraphId<FrameGraphTexture> opaqueBlit(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input, FrameGraphTexture::Descriptor outDesc) noexcept;
@@ -134,6 +139,8 @@ private:
PostProcessMaterial mBlit[3];
PostProcessMaterial mTonemapping;
PostProcessMaterial mFxaa;
PostProcessMaterial mDoFBlur;
PostProcessMaterial mDoF;
backend::Handle<backend::HwTexture> mDummyOneTexture;
backend::Handle<backend::HwTexture> mDummyZeroTexture;

View File

@@ -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);

View File

@@ -80,16 +80,9 @@ void ShadowMap::render(DriverApi& driver, Handle<HwRenderTarget> 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);

View File

@@ -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

View File

@@ -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); }

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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)));
}

View File

@@ -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);