Improve Bokeh example (#2377)

* display bokeh sample pattern, add bokeh shape, improve look

draw sample pattern to texture and display in ui to see number of samples and their arrangment

add bokeh shape controls

remove adhoc 'sqrt' pattern since display makes existing pattern easier to understand and it looks nicer.

switch to floating point color texture and leave lighting results in linear space until after dof is performed. provides better results and bright spots can make more noticeable bokeh shapes.

change default values to use take more samples at reduced resolution so initial experience when loading the sample is better looking image

* update screenshot, minor change to ui

fix height of ui element so scrollbar not required by default layout
update screenshot

* fix typo in texturev

atleast, i'm pretty sure that's a typo don't see a reason to set width twice
This commit is contained in:
elvencache 2021-02-04 20:28:54 -08:00 committed by GitHub
parent eee065c59f
commit 7f758a06a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 219 additions and 111 deletions

View File

@ -43,25 +43,19 @@ namespace {
enum Meshes
{
MeshCube = 0,
MeshTree,
MeshHollowCube,
MeshBunny
MeshHollowCube
};
static const char * s_meshPaths[] =
{
"meshes/cube.bin",
"meshes/tree.bin",
"meshes/hollowcube.bin",
"meshes/bunny.bin"
"meshes/hollowcube.bin"
};
static const float s_meshScale[] =
{
0.45f,
0.25f,
0.30f,
0.25f
0.30f
};
// Vertex decl for our screen space quad (used in deferred rendering)
@ -107,9 +101,9 @@ struct PassUniforms
{
struct
{
/* 0 */ struct { float m_depthUnpackConsts[2]; float m_frameIdx; float m_unused0; };
/* 0 */ struct { float m_depthUnpackConsts[2]; float m_frameIdx; float m_lobeRotation; };
/* 1 */ struct { float m_ndcToViewMul[2]; float m_ndcToViewAdd[2]; };
/* 2 */ struct { float m_blurSteps; float m_samplePattern; float m_unused3[2]; };
/* 2 */ struct { float m_blurSteps; float m_lobeCount; float m_lobeRadiusMin; float m_lobeRadiusDelta2x; };
/* 3 */ struct { float m_maxBlurSize; float m_focusPoint; float m_focusScale; float m_radiusScale; };
};
@ -275,6 +269,7 @@ public:
m_forwardProgram = loadProgram("vs_bokeh_forward", "fs_bokeh_forward");
m_gridProgram = loadProgram("vs_bokeh_forward", "fs_bokeh_forward_grid");
m_copyProgram = loadProgram("vs_bokeh_screenquad", "fs_bokeh_copy");
m_copyLinearToGammaProgram = loadProgram("vs_bokeh_screenquad", "fs_bokeh_copy_linear_to_gamma");
m_linearDepthProgram = loadProgram("vs_bokeh_screenquad", "fs_bokeh_linear_depth");
m_dofSinglePassProgram = loadProgram("vs_bokeh_screenquad", "fs_bokeh_dof_single_pass");
m_dofDownsampleProgram = loadProgram("vs_bokeh_screenquad", "fs_bokeh_dof_downsample");
@ -311,6 +306,9 @@ public:
const bgfx::RendererType::Enum renderer = bgfx::getRendererType();
m_texelHalf = bgfx::RendererType::Direct3D9 == renderer ? 0.5f : 0.0f;
m_bokehTexture.idx = bgfx::kInvalidHandle;
updateDisplayBokehTexture(m_radiusScale, m_maxBlurSize, m_lobeCount, (1.0f-m_lobePinch), 1.0f, m_lobeRotation);
imguiCreate();
}
@ -323,10 +321,12 @@ public:
bgfx::destroy(m_normalTexture);
bgfx::destroy(m_groundTexture);
bgfx::destroy(m_bokehTexture);
bgfx::destroy(m_forwardProgram);
bgfx::destroy(m_gridProgram);
bgfx::destroy(m_copyProgram);
bgfx::destroy(m_copyLinearToGammaProgram);
bgfx::destroy(m_linearDepthProgram);
bgfx::destroy(m_dofSinglePassProgram);
bgfx::destroy(m_dofDownsampleProgram);
@ -400,7 +400,6 @@ public:
bx::mtxProj(m_proj, m_fovY, float(m_size[0]) / float(m_size[1]), 0.01f, 100.0f, caps->homogeneousDepth);
bx::mtxProj(m_proj2, m_fovY, float(m_size[0]) / float(m_size[1]), 0.01f, 100.0f, false);
bgfx::ViewId view = 0;
// Draw models into scene
@ -408,7 +407,7 @@ public:
bgfx::setViewName(view, "forward scene");
bgfx::setViewClear(view
, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
, 0xbbddffff // clear to a sky blue
, 0x7fb8ffff // clear to a sky blue
, 1.0f
, 0
);
@ -481,7 +480,7 @@ public:
);
bgfx::setTexture(0, s_color, m_frameBufferTex[FRAMEBUFFER_RT_COLOR]);
screenSpaceQuad(float(m_width), float(m_height), m_texelHalf, caps->originBottomLeft);
bgfx::submit(view, m_copyProgram);
bgfx::submit(view, m_copyLinearToGammaProgram);
++view;
}
@ -503,7 +502,7 @@ public:
, ImGuiCond_FirstUseEver
);
ImGui::SetNextWindowSize(
ImVec2(m_width / 4.0f, m_height / 1.6f)
ImVec2(m_width / 4.0f, m_height / 1.35f)
, ImGuiCond_FirstUseEver
);
ImGui::Begin("Settings"
@ -537,8 +536,10 @@ public:
}
ImGui::Separator();
bool isChanged = false;
ImGui::Text("blur controls:");
ImGui::SliderFloat("max blur size", &m_maxBlurSize, 10.0f, 50.0f);
isChanged |= ImGui::SliderFloat("max blur size", &m_maxBlurSize, 10.0f, 50.0f);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("maximum blur size in screen pixels");
@ -551,51 +552,32 @@ public:
ImGui::SetTooltip("multiply focus calculation, larger=tighter focus");
ImGui::Separator();
ImGui::Text("sample pattern controls:");
ImGui::Combo("pattern", &m_samplePattern, "original\0sqrt\0\0");
ImGui::Text("bokeh shape and sample controls:");
isChanged |= ImGui::SliderFloat("radiusScale", &m_radiusScale, 0.5f, 4.0f);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("controls number of samples taken");
isChanged |= ImGui::SliderInt("lobe count", &m_lobeCount, 1, 8);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("using triangle lobes to emulate aperture blades");
isChanged |= ImGui::SliderFloat("lobe pinch", &m_lobePinch, 0.0f, 1.0f);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("adjust lobe shape, 0=round, 1=starry");
isChanged |= ImGui::SliderFloat("lobe rotation", &m_lobeRotation, -1.0f, 1.0f);
if (isChanged)
{
ImGui::BeginTooltip();
ImGui::Text("original");
ImGui::BulletText("pattern descibed by blogpost");
ImGui::Text("sqrt");
ImGui::BulletText("use sqrt instead of linear steps");
ImGui::EndTooltip();
updateDisplayBokehTexture(m_radiusScale, m_maxBlurSize, m_lobeCount, (1.0f-m_lobePinch), 1.0f, m_lobeRotation);
}
if (0 == m_samplePattern)
{
ImGui::SliderFloat("radiusScale", &m_radiusScale, 0.5f, 4.0f);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("controls number of samples taken");
ImGui::Text("number of samples taken: %d", m_sampleCount);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("number of sample taps as determined by radiusScale and maxBlurSize");
ImGui::TextWrapped(
"in original blog post, sample code has radius increasing by (radiusScale/currentRadius). "
"which should result in smaller steps farther from center. and fewer steps as radiusScale "
"increases. but it's less clear exactly how many steps that is, can be many."
);
const float maxRadius = m_maxBlurSize;
float radius = m_radiusScale;
int counter = 0;
while (radius < maxRadius)
{
++counter;
radius += m_radiusScale / radius;
}
ImGui::Text("number of samples taken: %d", counter);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("number of sample taps as determined by radiusScale");
}
else // 1 == samplePattern
{
ImGui::TextWrapped(
"when using sqrt pattern, take a fixed number of steps. sqrt refers to how the radius "
"is derived. in both cases, the sample pattern is a spiral out from the center."
);
ImGui::SliderFloat("blur steps", &m_blurSteps, 10.f, 100.0f);
}
ImGui::Image(m_bokehTexture, ImVec2(128.0f, 128.0f) );
}
ImGui::End();
@ -815,8 +797,8 @@ public:
| BGFX_SAMPLER_V_CLAMP
;
m_frameBufferTex[FRAMEBUFFER_RT_COLOR] = bgfx::createTexture2D(uint16_t(m_size[0]), uint16_t(m_size[1]), false, 1, bgfx::TextureFormat::BGRA8, bilinearFlags);
m_frameBufferTex[FRAMEBUFFER_RT_DEPTH] = bgfx::createTexture2D(uint16_t(m_size[0]), uint16_t(m_size[1]), false, 1, bgfx::TextureFormat::D32F, bilinearFlags);
m_frameBufferTex[FRAMEBUFFER_RT_COLOR] = bgfx::createTexture2D(uint16_t(m_size[0]), uint16_t(m_size[1]), false, 1, bgfx::TextureFormat::RGBA16F, bilinearFlags);
m_frameBufferTex[FRAMEBUFFER_RT_DEPTH] = bgfx::createTexture2D(uint16_t(m_size[0]), uint16_t(m_size[1]), false, 1, bgfx::TextureFormat::D32F, bilinearFlags);
m_frameBuffer = bgfx::createFrameBuffer(BX_COUNTOF(m_frameBufferTex), m_frameBufferTex, true);
m_linearDepth.init(m_size[0], m_size[1], bgfx::TextureFormat::R16F, bilinearFlags);
@ -882,14 +864,112 @@ public:
// reduce dimensions by half to go along with smaller render target
const float blurScale = (m_useSinglePassBokehDof) ? 1.0f : 0.5f;
m_uniforms.m_blurSteps = m_blurSteps;
m_uniforms.m_samplePattern = float(m_samplePattern);
m_uniforms.m_lobeCount = float(m_lobeCount);
m_uniforms.m_lobeRadiusMin = (1.0f - m_lobePinch);
m_uniforms.m_lobeRadiusDelta2x = 2.0f * m_lobePinch;
m_uniforms.m_maxBlurSize = m_maxBlurSize * blurScale;
m_uniforms.m_focusPoint = m_focusPoint;
m_uniforms.m_focusScale = m_focusScale;
m_uniforms.m_radiusScale = m_radiusScale * blurScale;
m_uniforms.m_lobeRotation = m_lobeRotation;
}
}
static float bokehShapeFromAngle (int _lobeCount, float _radiusMin, float _radiusDelta2x, float _rotation, float _theta)
{
// don't shape for 0, 1 blades...
if (_lobeCount <= 1)
{
return 1.0f;
}
// divide edge into some number of lobes
const float invPeriod = float(_lobeCount) / (bx::kPi2);
float periodFraction = bx::fract(_theta * invPeriod + _rotation);
// apply triangle shape to each lobe to approximate blades of a camera aperture
periodFraction = bx::abs(periodFraction - 0.5f);
return periodFraction * _radiusDelta2x + _radiusMin;
}
void updateDisplayBokehTexture(
float _radiusScale,
float _maxBlurSize,
int _lobeCount,
float _lobeRadiusMin,
float _lobeRadiusMax,
float _lobeRotation)
{
if (m_bokehTexture.idx != bgfx::kInvalidHandle)
{
bgfx::destroy(m_bokehTexture);
}
BX_ASSERT(0 < _lobeCount);
const uint32_t bokehSize = 128;
const bgfx::Memory* mem = bgfx::alloc(bokehSize*bokehSize*4);
bx::memSet(mem->data, 0x00, bokehSize*bokehSize*4);
const float thetaStep = 2.39996323f; // golden angle
float loopValue = _radiusScale;
const float loopEnd = _maxBlurSize;
float theta = 0.0f;
// bokeh shape function multiples this by half later
const float radiusDelta2x = 2.0f * (_lobeRadiusMax - _lobeRadiusMin);
int32_t counter = 0;
while (loopValue < loopEnd)
{
float radius = loopValue;
// apply shape to circular distribution
const float shapeScale = bokehShapeFromAngle(_lobeCount, _lobeRadiusMin, radiusDelta2x, _lobeRotation, theta);
BX_ASSERT(_lobeRadiusMin <= shapeScale);
BX_ASSERT(shapeScale <= _maxRadius);
float spiralCoordX = bx::cos(theta) * (radius * shapeScale);
float spiralCoordY = bx::sin(theta) * (radius * shapeScale);
// normalize for texture display
spiralCoordX /= _maxBlurSize;
spiralCoordY /= _maxBlurSize;
// scale from -1,1 into 0,1 normalized texture space
spiralCoordX = spiralCoordX * 0.5f + 0.5f;
spiralCoordY = spiralCoordY * 0.5f + 0.5f;
// convert to pixel coordinates
int32_t pixelCoordX = int32_t(bx::floor(spiralCoordX * float(bokehSize-1) + 0.5f));
int32_t pixelCoordY = int32_t(bx::floor(spiralCoordY * float(bokehSize-1) + 0.5f));
BX_ASSERT(0 <= pixelCoordX);
BX_ASSERT(0 <= pixelCoordY);
BX_ASSERT(pixelCoordX < bokehSize);
BX_ASSERT(pixelCoordY < bokehSize);
// plot sample position, track for total samples
uint32_t offset = (pixelCoordY * bokehSize + pixelCoordX) * 4;
mem->data[offset + 0] = 0xff;
mem->data[offset + 1] = 0xff;
mem->data[offset + 2] = 0xff;
mem->data[offset + 3] = 0xff;
++counter;
theta += thetaStep;
loopValue += (_radiusScale / loopValue);
}
m_sampleCount = counter;
// hoping texture deals with mem
m_bokehTexture = bgfx::createTexture2D(bokehSize, bokehSize, false, 1
, bgfx::TextureFormat::BGRA8
, 0
| BGFX_SAMPLER_MIN_POINT
| BGFX_SAMPLER_MIP_POINT
| BGFX_SAMPLER_MAG_POINT
, mem
);
}
uint32_t m_width;
uint32_t m_height;
@ -902,6 +982,7 @@ public:
bgfx::ProgramHandle m_forwardProgram;
bgfx::ProgramHandle m_gridProgram;
bgfx::ProgramHandle m_copyProgram;
bgfx::ProgramHandle m_copyLinearToGammaProgram;
bgfx::ProgramHandle m_linearDepthProgram;
bgfx::ProgramHandle m_dofSinglePassProgram;
bgfx::ProgramHandle m_dofDownsampleProgram;
@ -936,6 +1017,7 @@ public:
Mesh* m_meshes[BX_COUNTOF(s_meshPaths)];
bgfx::TextureHandle m_groundTexture;
bgfx::TextureHandle m_normalTexture;
bgfx::TextureHandle m_bokehTexture;
uint32_t m_currFrame;
float m_lightRotation = 0.0f;
@ -951,14 +1033,17 @@ public:
// UI parameters
bool m_useBokehDof = true;
bool m_useSinglePassBokehDof = true;
bool m_useSinglePassBokehDof = false;
float m_maxBlurSize = 20.0f;
float m_focusPoint = 5.0f;
float m_focusScale = 3.0f;
float m_radiusScale = 3.856f;//0.5f;
float m_radiusScale = 0.5f;
float m_blurSteps = 50.0f;
int32_t m_samplePattern = 0;
bool m_showDebugVisualization = false;
int32_t m_lobeCount = 6;
float m_lobePinch = 0.2f;
float m_lobeRotation = 0.0f;
int32_t m_sampleCount = 0;
};
} // namespace

View File

@ -83,13 +83,29 @@ void GetColorAndBlurSize (
#endif
}
float BokehShapeFromAngle (float lobeCount, float radiusMin, float radiusDelta2x, float rotation, float angle)
{
// don't shape for 0, 1 blades...
if (lobeCount <= 1.0f)
{
return 1.0f;
}
// divide edge into some number of lobes
float invPeriod = lobeCount / (2.0 * 3.1415926);
float periodFraction = fract(angle * invPeriod + rotation);
// apply triangle shape to each lobe to approximate blades of a camera aperture
periodFraction = abs(periodFraction - 0.5);
return periodFraction*radiusDelta2x + radiusMin;
}
vec4 DepthOfField(
sampler2D samplerColor,
sampler2D samplerDepth,
vec2 texCoord,
float focusPoint,
float focusScale,
float samplePattern
float focusScale
) {
vec3 color;
float centerSize;
@ -112,40 +128,19 @@ vec4 DepthOfField(
float total = 1.0;
float totalSampleSize = 0.0;
// support two options for sample distribution ===========================
float loopValue;
float loopEnd;
if (0.5 < samplePattern)
{
// in sqrt distribution, take fixed number of steps, step count
// is fraction of full radius, with curve adjusted by sqrt function
loopValue = 0.5 / u_blurSteps; // radiusFraction
loopEnd = 1.0;
}
else
{
// in original distribution, looping value is radius directly,
// but radius grows in non-linear way
loopValue = u_radiusScale;
loopEnd = u_maxBlurSize;
}
//========================================================================
float loopValue = u_radiusScale;
float loopEnd = u_maxBlurSize;
while (loopValue < loopEnd)
{
//====================================================================
float radius;
if (0.5 < samplePattern) {
radius = sqrt(loopValue) * u_maxBlurSize;
}
else
{
radius = loopValue;
}
//====================================================================
vec2 spiralCoord = texCoord + vec2(cos(theta), sin(theta)) * u_viewTexel.xy * radius;
float radius = loopValue;
float shapeScale = BokehShapeFromAngle(
u_lobeCount,
u_lobeRadiusMin,
u_lobeRadiusDelta2x,
u_lobeRotation,
theta);
vec2 spiralCoord = texCoord + vec2(cos(theta), sin(theta)) * u_viewTexel.xy * (radius * shapeScale);
vec3 sampleColor;
float sampleSize;
@ -170,16 +165,7 @@ vec4 DepthOfField(
total += 1.0;
theta += thetaStep;
//====================================================================
if (0.5 < samplePattern)
{
loopValue += (1.0 / u_blurSteps); // radiusFraction
}
else
{
loopValue += (u_radiusScale/loopValue); // radius
}
//====================================================================
loopValue += (u_radiusScale/loopValue);
}
color *= 1.0/total;

View File

@ -0,0 +1,22 @@
$input v_texcoord0
/*
* Copyright 2021 elven cache. All rights reserved.
* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
*/
#include "../common/common.sh"
#include "parameters.sh"
SAMPLER2D(s_color, 0);
void main()
{
vec2 texCoord = v_texcoord0;
vec4 linearColor = texture2D(s_color, texCoord);
// this pass is writing directly out to backbuffer, convert from linear to gamma
vec4 color = vec4(toGamma(linearColor.xyz), linearColor.w);
gl_FragColor = color;
}

View File

@ -9,18 +9,21 @@ $input v_texcoord0
#include "parameters.sh"
SAMPLER2D(s_color, 0);
SAMPLER2D(s_previousColor, 1);
SAMPLER2D(s_blurredColor, 1);
void main()
{
vec2 texCoord = v_texcoord0.xy;
vec4 color = texture2D(s_color, texCoord);
vec4 dofColorSize = texture2D(s_previousColor, texCoord);
vec4 dofColorSize = texture2D(s_blurredColor, texCoord);
vec3 dofColor = dofColorSize.xyz;
float sampleSize = dofColorSize.w;
float m = saturate(sampleSize-1.0);
color.xyz = mix(color.xyz, dofColor, m);
// this pass is writing directly out to backbuffer, convert from linear to gamma
color.xyz = toGamma(color.xyz);
gl_FragColor = color;
}

View File

@ -18,6 +18,7 @@ void main()
// desaturate color to make tinted color stand out
vec3 color = texture2D(s_color, texCoord).xyz;
color = toGamma(color);
color = vec3_splat(dot(color, vec3(0.33, 0.34, 0.33)));
// get circle of confusion from depth

View File

@ -17,7 +17,9 @@ void main()
{
vec2 texCoord = v_texcoord0.xy;
vec4 outColor = DepthOfField(s_color, s_color, texCoord, u_focusPoint, u_focusScale, u_samplePattern);
vec4 outColor = DepthOfField(s_color, s_color, texCoord, u_focusPoint, u_focusScale);
// this pass isn't writing final output, leave in linear space for combining with scene color
gl_FragColor = outColor;
}

View File

@ -16,7 +16,10 @@ void main()
{
vec2 texCoord = v_texcoord0.xy;
vec3 outColor = DepthOfField(s_color, s_depth, texCoord, u_focusPoint, u_focusScale, u_samplePattern).xyz;
vec3 outColor = DepthOfField(s_color, s_depth, texCoord, u_focusPoint, u_focusScale).xyz;
// this pass is writing directly out to backbuffer, convert from linear to gamma
outColor = toGamma(outColor);
gl_FragColor = vec4(outColor, 1.0);
}

View File

@ -73,7 +73,8 @@ void main()
float lightAmount = ambient + diffuse;
vec3 color = u_color * albedo * lightAmount + specular;
color = toGamma(color);
// leave color in linear space for better dof filter result
gl_FragColor = vec4(color, 1.0);
}

View File

@ -51,7 +51,8 @@ void main()
float lightAmount = ambient + diffuse;
vec3 color = gridColor * lightAmount + specular;
color = toGamma(color);
// leave color in linear space for better dof filter result
gl_FragColor = vec4(color, 1.0);
}

View File

@ -11,10 +11,14 @@ uniform vec4 u_params[13];
#define u_depthUnpackConsts (u_params[0].xy)
#define u_frameIdx (u_params[0].z)
#define u_lobeRotation (u_params[0].w)
#define u_ndcToViewMul (u_params[1].xy)
#define u_ndcToViewAdd (u_params[1].zw)
#define u_blurSteps (u_params[2].x)
#define u_lobeCount (u_params[2].y)
#define u_lobeRadiusMin (u_params[2].z)
#define u_lobeRadiusDelta2x (u_params[2].w)
#define u_samplePattern (u_params[2].y)
#define u_maxBlurSize (u_params[3].x)
#define u_focusPoint (u_params[3].y)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 255 KiB

View File

@ -1292,7 +1292,7 @@ int _main_(int _argc, char** _argv)
bgfx::Init init;
init.resolution.width = view.m_width;
init.resolution.width = view.m_height;
init.resolution.height = view.m_height;
init.resolution.reset = BGFX_RESET_VSYNC;
bgfx::init(init);