Skip to content

Instantly share code, notes, and snippets.

@PeterTh
Last active December 29, 2017 07:18
Show Gist options
  • Save PeterTh/6bdaa1a5331dbe1dfaea35bce1ba411e to your computer and use it in GitHub Desktop.
Save PeterTh/6bdaa1a5331dbe1dfaea35bce1ba411e to your computer and use it in GitHub Desktop.
Nier Automata Bloom Fix
// Overview:
// The bloom pyramid in Nier:A is built up of 5 buffers, which are sized
// 800x450, 400x225, 200x112, 100x56 and 50x28, regardless of resolution
// the mismatch between the largest buffer size and the screen resolution (in e.g. 2560x1440 or even 1920x1080)
// leads to some really ugly artifacts.
//
// To change this, we need to
// 1) Replace the rendertarget textures in question at their creation point
// 2) Adjust the viewport and some constant shader parameter each time they are rendered to
//
// Examples here:
// http://abload.de/img/bloom_defaultjhuq9.jpg
// http://abload.de/img/bloom_fixedp7uef.jpg
//
// Note that there are more low-res 800x450 buffers not yet handled by this,
// but which could probably be handled similarly. Primarily, SSAO.
//
// Also note that the "correct" way to fix this on the engine level would be to make
// the number of pyramid levels dynamic based on the rendering resolution, but this way
// (keeping a fixed number of levels and scaling by a bit more than factor 2/4 between
// them if necessary) is good enough, and the results speak for themselves.
HRESULT WrappedID3D11Device::CreateTexture2D(const D3D11_TEXTURE2D_DESC *pDesc,
const D3D11_SUBRESOURCE_DATA *pInitialData,
ID3D11Texture2D **ppTexture2D)
{
// validation, returns S_FALSE for valid params, or an error code
if(ppTexture2D == NULL)
return m_pDevice->CreateTexture2D(pDesc, pInitialData, NULL);
static UINT resW = 2560; // horizontal resolution, should be automatically determined
static float resFactor = resW / 1600.0f; // the factor required to scale to the largest part of the pyramid
// R11G11B10 float textures of these sizes are part of the BLOOM PYRAMID
// Note: we do not manipulate the 50x28 buffer
// -- it's read by a compute shader and the whole screen white level can be off if it is the wrong size
if(pDesc->Format == DXGI_FORMAT_R11G11B10_FLOAT) {
if((pDesc->Width == 800 && pDesc->Height == 450)
|| (pDesc->Width == 400 && pDesc->Height == 225)
|| (pDesc->Width == 200 && pDesc->Height == 112)
|| (pDesc->Width == 100 && pDesc->Height == 56)
/*|| (pDesc->Width == 50 && pDesc->Height == 28)*/) {
D3D11_TEXTURE2D_DESC copy = *pDesc;
// Scale the upper parts of the pyramid fully
// and lower levels progressively less
float pyramidLevelFactor = (pDesc->Width-50)/750.0f;
float scalingFactor = 1.0f + (resFactor-1.0f)*pyramidLevelFactor;
copy.Width = (UINT)(copy.Width*scalingFactor);
copy.Height = (UINT)(copy.Height*scalingFactor);
pDesc = ©
}
}
/// ..................
}
// High level description:
// IF we have
// - 1 viewport
// - with the size of one of the 4 elements of the pyramid we changed
// - and a primary rendertarget of type R11G11B10
// - which is associated with a texture of a size different from the viewport
// THEN
// - set the viewport to the texture size
// - adjust the pixel shader constant buffer in slot #12 to this format (4 floats):
// [ 0.5f / W, 0.5f / H, W, H ] (half-pixel size and total dimensions)
void PreDraw(WrappedID3D11DeviceContext* context)
{
UINT numViewports = 0;
context->RSGetViewports(&numViewports, NULL);
if(numViewports == 1) {
D3D11_VIEWPORT vp;
context->RSGetViewports(&numViewports, &vp);
if((vp.Width == 800 && vp.Height == 450)
|| (vp.Width == 400 && vp.Height == 225)
|| (vp.Width == 200 && vp.Height == 112)
|| (vp.Width == 100 && vp.Height == 56)) {
ID3D11RenderTargetView *rtView = NULL;
context->OMGetRenderTargets(1, &rtView, NULL);
if(rtView) {
D3D11_RENDER_TARGET_VIEW_DESC desc;
rtView->GetDesc(&desc);
if(desc.Format == DXGI_FORMAT_R11G11B10_FLOAT) {
ID3D11Resource *rt = NULL;
rtView->GetResource(&rt);
if(rt) {
ID3D11Texture2D *rttex = NULL;
rt->QueryInterface<ID3D11Texture2D>(&rttex);
if(rttex) {
D3D11_TEXTURE2D_DESC texdesc;
rttex->GetDesc(&texdesc);
if(texdesc.Width != vp.Width) {
// Here we go!
// Viewport is the easy part
vp.Width = (float)texdesc.Width;
vp.Height = (float)texdesc.Height;
context->RSSetViewports(1, &vp);
// The constant buffer is a bit more difficult
// We don't want to create a new buffer every frame,
// but we also can't use the game's because they are read-only
// this just-in-time initialized map is a rather ugly solution,
// but it works as long as the game only renders from 1 thread (which it does)
// NOTE: rather than storing them statically here (basically a global) the lifetime should probably be managed
static std::map<UINT, ID3D11Buffer*> buffers;
auto iter = buffers.find(texdesc.Width);
if(iter == buffers.cend()) {
float constants[4] = {0.5f / vp.Width, 0.5f / vp.Height, (float)vp.Width, (float)vp.Height};
ID3D11Device* dev;
context->GetDevice(&dev);
D3D11_BUFFER_DESC buffdesc;
buffdesc.ByteWidth = 16;
buffdesc.Usage = D3D11_USAGE_IMMUTABLE;
buffdesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
buffdesc.CPUAccessFlags = 0;
buffdesc.MiscFlags = 0;
buffdesc.StructureByteStride = 16;
D3D11_SUBRESOURCE_DATA initialdata;
initialdata.pSysMem = constants;
ID3D11Buffer *replacementbuffer = NULL;
dev->CreateBuffer(&buffdesc, &initialdata, &replacementbuffer);
buffers[texdesc.Width] = replacementbuffer;
}
context->PSSetConstantBuffers(12, 1, &buffers[texdesc.Width]);
}
}
rt->Release();
}
}
rtView->Release();
}
}
}
}
void WrappedID3D11DeviceContext::DrawIndexed(UINT IndexCount, UINT StartIndexLocation,
INT BaseVertexLocation)
{
if(IndexCount == 4 && StartIndexLocation == 0 && BaseVertexLocation == 0) PreDraw(this);
// ...
}
void WrappedID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation)
{
if(VertexCount == 4 && StartVertexLocation == 0) PreDraw(this);
// ...
}
@kyprime
Copy link

kyprime commented Dec 29, 2017

not on the topic, but do you know what the graphical setting "Effects" does in Nier Automata? it says that it affects the quality of various effects, but i am unable to identify what effects, i don't see a change, i can't find the answer anywhere so maybe you know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment