mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-12-02 00:41:57 +00:00
348 lines
11 KiB
C++
348 lines
11 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2024 Gian 'myT' Schellenbaum
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Challenge Quake 3 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
// Cinematic Rendering Pipeline - accumulation depth of field
|
|
|
|
|
|
#include "crp_local.h"
|
|
#include "compshaders/crp/fullscreen.h"
|
|
#include "compshaders/crp/accumdof_accum.h"
|
|
#include "compshaders/crp/accumdof_norm.h"
|
|
#include "compshaders/crp/accumdof_debug.h"
|
|
|
|
|
|
#pragma pack(push, 4)
|
|
|
|
struct DOFAccumRC
|
|
{
|
|
uint32_t textureIndex;
|
|
};
|
|
|
|
struct DOFNormRC
|
|
{
|
|
uint32_t textureIndex;
|
|
};
|
|
|
|
struct DOFDebugRC
|
|
{
|
|
float mvp[16]; // displaced view, to project to CS
|
|
float invMvp[16]; // main view, to unproject to WS
|
|
uint32_t colorTextureIndex;
|
|
uint32_t depthTextureIndex;
|
|
uint32_t debugMode; // 1: colorized coc, 2: constant intensity far field
|
|
uint32_t tcScale;
|
|
float focusDist;
|
|
float linearDepthA; // main view, to unproject to WS
|
|
float linearDepthB;
|
|
float maxNearCocCS;
|
|
float maxFarCocCS;
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
// the input is in [0,1]^2, the output polygon is centered at the origin
|
|
static void MapUnitSquareToPolygon(const vec2_t square01, float apertureBladeCount, float apertureAngleRad, vec2_t polygon)
|
|
{
|
|
// needed to avoid inf/nan propagation through theta for samples
|
|
// that are exactly in the middle of the quad on either axis
|
|
// (i.e. square.x|y == 0.5f gets remapped to 0.0f)
|
|
const float epsilon = 0.000001f;
|
|
|
|
// morph into a square in [-1,1]^2
|
|
vec2_t square;
|
|
square[0] = square01[0] * 2.0f - 1.0f;
|
|
square[1] = square01[1] * 2.0f - 1.0f;
|
|
|
|
// morph the square into a disk
|
|
// "A Low Distortion Map Between Disk and Square" by Peter Shirley and Kenneth Chiu
|
|
float radius, angle;
|
|
vec2_t square2;
|
|
square2[0] = square[0] * square[0];
|
|
square2[1] = square[1] * square[1];
|
|
if(square2[0] > square2[1])
|
|
{
|
|
// left and right quadrants
|
|
radius = square[0];
|
|
angle = (square[1] * M_PI_D4) / (square[0] + epsilon);
|
|
}
|
|
else
|
|
{
|
|
// top and bottom quadrants
|
|
radius = square[1];
|
|
angle = M_PI_D2 - (square[0] * M_PI_D4) / (square[1] + epsilon);
|
|
}
|
|
if(radius < 0.0f)
|
|
{
|
|
radius = -radius;
|
|
angle += M_PI;
|
|
}
|
|
|
|
// morph the disk into a polygon
|
|
// "Graphics Gems from CryENGINE 3" by Tiago Sousa
|
|
const float edgeCount = apertureBladeCount;
|
|
if(edgeCount >= 3.0f)
|
|
{
|
|
const float num = cosf(M_PI / edgeCount);
|
|
const float den0 = M_PI_M2 / edgeCount;
|
|
const float den1 = (angle * edgeCount + M_PI) / M_PI_M2;
|
|
const float den = angle - (den0 * floorf(den1));
|
|
radius *= num / cosf(den);
|
|
angle += apertureAngleRad;
|
|
}
|
|
|
|
polygon[0] = cosf(angle) * radius;
|
|
polygon[1] = sinf(angle) * radius;
|
|
}
|
|
|
|
static int GetResolutionScale()
|
|
{
|
|
switch(crp_accumDof_preview->integer)
|
|
{
|
|
case 0: return 1;
|
|
case 1: return 2;
|
|
case 2: return 4;
|
|
default: Q_assert(0); return 1;
|
|
}
|
|
}
|
|
|
|
|
|
void AccumDepthOfField::Init()
|
|
{
|
|
{
|
|
GraphicsPipelineDesc desc("DOF Accumulate");
|
|
desc.shortLifeTime = true;
|
|
desc.vertexShader = ShaderByteCode(g_fullscreen_vs);
|
|
desc.pixelShader = ShaderByteCode(g_accum_ps);
|
|
desc.depthStencil.DisableDepth();
|
|
desc.rasterizer.cullMode = CT_TWO_SIDED;
|
|
desc.AddRenderTarget(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE, crp.renderTargetFormat);
|
|
accumPipeline = CreateGraphicsPipeline(desc);
|
|
}
|
|
|
|
{
|
|
GraphicsPipelineDesc desc("DOF Normalize");
|
|
desc.shortLifeTime = true;
|
|
desc.vertexShader = ShaderByteCode(g_fullscreen_vs);
|
|
desc.pixelShader = ShaderByteCode(g_norm_ps);
|
|
desc.depthStencil.DisableDepth();
|
|
desc.rasterizer.cullMode = CT_TWO_SIDED;
|
|
desc.AddRenderTarget(0, crp.renderTargetFormat);
|
|
normPipeline = CreateGraphicsPipeline(desc);
|
|
}
|
|
|
|
{
|
|
GraphicsPipelineDesc desc("DOF Debug");
|
|
desc.shortLifeTime = true;
|
|
desc.vertexShader = ShaderByteCode(g_fullscreen_vs);
|
|
desc.pixelShader = ShaderByteCode(g_debug_ps);
|
|
desc.depthStencil.DisableDepth();
|
|
desc.rasterizer.cullMode = CT_TWO_SIDED;
|
|
desc.AddRenderTarget(0, crp.renderTargetFormat);
|
|
debugPipeline = CreateGraphicsPipeline(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("DOF accumulation", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit;
|
|
Vector4Clear(desc.clearColor);
|
|
desc.usePreferredClearValue = true;
|
|
desc.committedResource = true;
|
|
desc.format = crp.renderTargetFormat;
|
|
desc.shortLifeTime = true;
|
|
accumTexture = CreateTexture(desc);
|
|
}
|
|
}
|
|
|
|
void AccumDepthOfField::Begin(const drawSceneViewCommand_t& cmd)
|
|
{
|
|
CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(accumTexture, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
CmdClearColorTarget(accumTexture, vec4_zero);
|
|
|
|
// project a point a few units in front of the main view onto a viewpoint with maximum displacement
|
|
// the distance to the clip space center (i.e. the 2D origin) is the maximum expected CoC
|
|
vec3_t testPoint;
|
|
VectorMA(cmd.viewParms.world.viewOrigin, 16.0f, cmd.viewParms.orient.axis[0], testPoint);
|
|
drawSceneViewCommand_t newCmd;
|
|
FixCommand(newCmd, cmd, 0, 0);
|
|
vec4_t eye, clip;
|
|
R_TransformModelToClip(testPoint, newCmd.viewParms.world.modelMatrix, newCmd.viewParms.projectionMatrix, eye, clip);
|
|
Vector4Scale(clip, 1.0f / clip[3], clip);
|
|
maxNearCocCS = sqrtf(clip[0] * clip[0] + clip[1] * clip[1]);
|
|
|
|
// same thing for a point far away
|
|
VectorMA(cmd.viewParms.world.viewOrigin, 69420.0f, cmd.viewParms.orient.axis[0], testPoint);
|
|
FixCommand(newCmd, cmd, 0, 0);
|
|
R_TransformModelToClip(testPoint, newCmd.viewParms.world.modelMatrix, newCmd.viewParms.projectionMatrix, eye, clip);
|
|
Vector4Scale(clip, 1.0f / clip[3], clip);
|
|
maxFarCocCS = sqrtf(clip[0] * clip[0] + clip[1] * clip[1]);
|
|
}
|
|
|
|
uint32_t AccumDepthOfField::GetSampleCount()
|
|
{
|
|
switch(crp_accumDof_preview->integer)
|
|
{
|
|
case 1: return 3;
|
|
case 2: return 5;
|
|
default: break;
|
|
}
|
|
|
|
return 2 * crp_accumDof_samples->integer + 1;
|
|
}
|
|
|
|
void AccumDepthOfField::FixCommand(drawSceneViewCommand_t& newCmd, const drawSceneViewCommand_t& cmd, uint32_t x, uint32_t y)
|
|
{
|
|
const float radius = crp_accumDof_radius->value;
|
|
const float tcScale = 1.0f / (float)(GetSampleCount() - 1);
|
|
|
|
vec2_t square01;
|
|
square01[0] = x * tcScale;
|
|
square01[1] = y * tcScale;
|
|
vec2_t polygon;
|
|
MapUnitSquareToPolygon(square01, crp_dof_blades->value, DEG2RAD(crp_dof_angle->value), polygon);
|
|
|
|
// 0=forward, 1=left, 2=up
|
|
vec3_t axis[3];
|
|
VectorCopy(cmd.viewParms.orient.axis[0], axis[0]);
|
|
VectorCopy(cmd.viewParms.orient.axis[1], axis[1]);
|
|
VectorCopy(cmd.viewParms.orient.axis[2], axis[2]);
|
|
|
|
vec3_t origin;
|
|
VectorMA(cmd.viewParms.world.viewOrigin, radius * polygon[0], axis[1], origin);
|
|
VectorMA(origin, radius * polygon[1], axis[2], origin);
|
|
|
|
vec3_t focusPoint;
|
|
VectorMA(cmd.viewParms.world.viewOrigin, crp_accumDof_focusDist->value, axis[0], focusPoint);
|
|
VectorSubtract(focusPoint, origin, axis[0]); // forward
|
|
VectorNormalize(axis[0]);
|
|
CrossProduct(axis[2], axis[0], axis[1]); // left
|
|
VectorNormalize(axis[1]);
|
|
CrossProduct(axis[0], axis[1], axis[2]); // up
|
|
VectorNormalize(axis[2]);
|
|
|
|
newCmd = cmd;
|
|
VectorCopy(origin, newCmd.viewParms.orient.origin);
|
|
VectorCopy(origin, newCmd.viewParms.world.viewOrigin);
|
|
R_CreateWorldModelMatrix(origin, axis, newCmd.viewParms.world.modelMatrix);
|
|
newCmd.viewParms.viewportWidth /= GetResolutionScale();
|
|
newCmd.viewParms.viewportHeight /= GetResolutionScale();
|
|
|
|
if(x == 0 && y == 0)
|
|
{
|
|
memcpy(modelViewMatrix, newCmd.viewParms.world.modelMatrix, sizeof(modelViewMatrix));
|
|
memcpy(projMatrix, newCmd.viewParms.projectionMatrix, sizeof(projMatrix));
|
|
}
|
|
}
|
|
|
|
void AccumDepthOfField::Accumulate()
|
|
{
|
|
srp.renderMode = RenderMode::None;
|
|
|
|
SCOPED_RENDER_PASS("DOF Accum", 0.5f, 1.0f, 0.5f);
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(crp.renderTarget, ResourceStates::PixelShaderAccessBit);
|
|
CmdTextureBarrier(accumTexture, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
DOFAccumRC rc = {};
|
|
rc.textureIndex = GetTextureIndexSRV(crp.renderTarget);
|
|
|
|
CmdBindRenderTargets(1, &accumTexture, NULL);
|
|
CmdBindPipeline(accumPipeline);
|
|
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
|
|
CmdDraw(3, 0);
|
|
|
|
}
|
|
|
|
void AccumDepthOfField::Normalize()
|
|
{
|
|
srp.renderMode = RenderMode::None;
|
|
|
|
{
|
|
SCOPED_RENDER_PASS("DOF Norm", 0.5f, 1.0f, 0.5f);
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(accumTexture, ResourceStates::PixelShaderAccessBit);
|
|
CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
DOFNormRC rc = {};
|
|
rc.textureIndex = GetTextureIndexSRV(accumTexture);
|
|
|
|
CmdBindRenderTargets(1, &crp.renderTarget, NULL);
|
|
CmdBindPipeline(normPipeline);
|
|
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
|
|
CmdDraw(3, 0);
|
|
}
|
|
|
|
if(crp_accumDof_preview->integer)
|
|
{
|
|
crp.SwapRenderTargets();
|
|
const float scale = 1.0f / (float)GetResolutionScale();
|
|
const vec2_t tcScale = { scale, scale };
|
|
crp.Blit(crp.GetWriteRenderTarget(), crp.GetReadRenderTarget(), "DOF Upscale", true, tcScale, vec2_zero);
|
|
}
|
|
}
|
|
|
|
void AccumDepthOfField::DrawDebug()
|
|
{
|
|
if(crp_dof_overlay->integer == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
srp.renderMode = RenderMode::None;
|
|
|
|
SCOPED_RENDER_PASS("DOF Debug", 0.5f, 1.0f, 0.5f);
|
|
|
|
crp.SwapRenderTargets();
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(crp.GetReadRenderTarget(), ResourceStates::PixelShaderAccessBit);
|
|
CmdTextureBarrier(crp.depthTexture, ResourceStates::PixelShaderAccessBit);
|
|
CmdTextureBarrier(crp.GetWriteRenderTarget(), ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
float mvp[16];
|
|
DOFDebugRC rc = {};
|
|
rc.colorTextureIndex = GetTextureIndexSRV(crp.GetReadRenderTarget());
|
|
rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture);
|
|
rc.debugMode = crp_dof_overlay->integer;
|
|
rc.focusDist = crp_accumDof_focusDist->value;
|
|
rc.maxNearCocCS = maxNearCocCS;
|
|
rc.maxFarCocCS = maxFarCocCS;
|
|
rc.tcScale = GetResolutionScale();
|
|
R_MultMatrix(modelViewMatrix, projMatrix, rc.mvp);
|
|
RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB);
|
|
R_MultMatrix(backEnd.viewParms.world.modelMatrix, backEnd.viewParms.projectionMatrix, mvp);
|
|
R_InvMatrix(mvp, rc.invMvp);
|
|
|
|
CmdBindRenderTargets(1, &crp.renderTarget, NULL);
|
|
CmdBindPipeline(debugPipeline);
|
|
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
|
|
CmdDraw(3, 0);
|
|
}
|