harfang3d/harfang/engine/forward_pipeline.cpp
2021-10-13 14:40:31 +02:00

487 lines
19 KiB
C++

// HARFANG(R) Copyright (C) 2021 Emmanuel Julien, NWNC HARFANG. Released under GPL/LGPL/Commercial Licence, see licence.txt for details.
#include "engine/forward_pipeline.h"
#include "engine/assets_rw_interface.h"
#include "engine/render_pipeline.h"
#include "engine/scene.h"
#include "engine/taa.h"
#include "foundation/clock.h"
#include "foundation/file_rw_interface.h"
#include "foundation/format.h"
#include "foundation/math.h"
#include "foundation/path_tools.h"
#include "foundation/projection.h"
#include <bgfx/bgfx.h>
#include <math.h>
#include <vector>
namespace hg {
enum ForwardPipelineUniformValue {
UV_Clock,
UV_FogColor,
UV_FogState,
UV_AmbientColor,
UV_LightPos,
UV_LightDir,
UV_LightDiffuse,
UV_LightSpecular,
UV_LinearShadowMatrix,
UV_LinearShadowSlice,
UV_SpotShadowMatrix,
UV_ShadowState,
UV_Resolution,
UV_Projection,
UV_MainProjection,
UV_MainInvProjection,
UV_PreviousViewProjection,
UV_ViewProjUnjittered, // for TAA
UV_AAAParams, // [0].x: ssgi ratio, [0].y: ssr ratio, [0].z: temporal AA weight, [0].w: motion blur strength, [1].x: exposure, [1].y: 1/gamma
UV_MainInvView,
UV_Count
};
enum ForwardPipelineUniformTexture {
UT_IrradianceMap,
UT_RadianceMap,
UT_BrdfMap,
UT_NoiseMap,
UT_AmbientOcclusion,
UT_LinearShadowMap,
UT_SpotShadowMap,
UT_Count
};
//
ForwardPipelineLight MakeForwardPipelinePointLight(const hg::Mat4 &world, const hg::Color &diffuse, const hg::Color &specular, float radius, float priority,
ForwardPipelineShadowType shadow_type, float shadow_bias) {
return {FPLT_Point, shadow_type, world, diffuse, specular, radius, 0.f, 0.f, hg::Vec4::Zero, priority, shadow_bias};
}
ForwardPipelineLight MakeForwardPipelineSpotLight(const hg::Mat4 &world, const hg::Color &diffuse, const hg::Color &specular, float radius, float inner_angle,
float outer_angle, float priority, ForwardPipelineShadowType shadow_type, float shadow_bias) {
return {FPLT_Spot, shadow_type, world, diffuse, specular, radius, inner_angle, outer_angle, hg::Vec4::Zero, priority, shadow_bias};
}
ForwardPipelineLight MakeForwardPipelineLinearLight(const hg::Mat4 &world, const hg::Color &diffuse, const hg::Color &specular, const hg::Vec4 &pssm_split,
float priority, ForwardPipelineShadowType shadow_type, float shadow_bias) {
return {FPLT_Linear, shadow_type, world, diffuse, specular, 0.f, 0.f, 0.f, pssm_split, priority, shadow_bias};
}
//
ForwardPipelineLights PrepareForwardPipelineLights(const std::vector<ForwardPipelineLight> &lights) {
ForwardPipelineLights ls{};
// separate lights per type
std::vector<int> i_linear, i_point;
i_linear.reserve(8);
i_point.reserve(64);
for (size_t i = 0; i < lights.size(); ++i)
if (lights[i].type == FPLT_Linear)
i_linear.push_back(int(i));
else
i_point.push_back(int(i));
// slot 0: single linear light
std::sort(std::begin(i_linear), std::end(i_linear), [&](const auto &a, const auto &b) { return lights[a].priority > lights[b].priority; });
if (!i_linear.empty()) {
const auto &l = lights[i_linear.front()];
ls.pos[0] = Vec4(GetT(l.world), 0.f);
ls.dir[0] = Vec4(GetZ(l.world), 0.f);
ls.diff[0] = {l.diffuse.r, l.diffuse.g, l.diffuse.b, 0.f};
ls.spec[0] = {l.specular.r, l.specular.g, l.specular.b, 0.f};
ls.lights[0] = l;
}
// slot 1-n: point/spot
std::sort(std::begin(i_point), std::end(i_point), [&](const auto &a, const auto &b) { return lights[a].priority > lights[b].priority; });
size_t slot_idx = 1;
for (auto i : i_point) {
const auto &l = lights[i];
ls.pos[slot_idx] = Vec4(GetT(l.world), l.radius ? 1.f / l.radius : 0.f);
ls.dir[slot_idx] = Vec4(GetZ(l.world), l.type == FPLT_Spot ? Cos(l.inner_angle) : 0.f);
ls.diff[slot_idx] = {l.diffuse.r, l.diffuse.g, l.diffuse.b, l.type == FPLT_Spot ? Cos(l.outer_angle) : 0.f};
ls.spec[slot_idx] = {l.specular.r, l.specular.g, l.specular.b, 0.f};
ls.lights[slot_idx] = l;
if (++slot_idx == forward_light_count)
break; // all slots filled
}
return ls;
}
//
void UpdateForwardPipeline(ForwardPipeline &pipeline, const ForwardPipelineShadowData &shadow_data, const Color &ambient, const ForwardPipelineLights &lights,
const ForwardPipelineFog &fog) {
pipeline.uniform_values[UV_Clock].value[0] = time_to_sec_f(get_clock());
pipeline.uniform_values[UV_FogColor].value = {fog.color.r, fog.color.g, fog.color.b, fog.color.a};
pipeline.uniform_values[UV_FogState].value = {fog.near, (fog.far - fog.near) ? 1.f / (fog.far - fog.near) : 0.f, 0.f, 0.f};
pipeline.uniform_values[UV_AmbientColor].value = {ambient.r, ambient.g, ambient.b, ambient.a};
for (size_t i = 0; i < forward_light_count; ++i) {
memcpy(pipeline.uniform_values[UV_LightPos].value.data() + i * 4, &lights.pos[i].x, sizeof(float) * 4);
memcpy(pipeline.uniform_values[UV_LightDir].value.data() + i * 4, &lights.dir[i].x, sizeof(float) * 4);
memcpy(pipeline.uniform_values[UV_LightDiffuse].value.data() + i * 4, &lights.diff[i].x, sizeof(float) * 4);
memcpy(pipeline.uniform_values[UV_LightSpecular].value.data() + i * 4, &lights.spec[i].x, sizeof(float) * 4);
}
for (size_t i = 0; i < 4; ++i)
memcpy(pipeline.uniform_values[UV_LinearShadowMatrix].value.data() + i * 16, to_bgfx(shadow_data.linear_shadow_mtx[i]).data(), sizeof(float) * 16);
memcpy(pipeline.uniform_values[UV_LinearShadowSlice].value.data(), &shadow_data.linear_shadow_slice.x, sizeof(float) * 4);
memcpy(pipeline.uniform_values[UV_SpotShadowMatrix].value.data(), to_bgfx(shadow_data.spot_shadow_mtx).data(), sizeof(float) * 16);
pipeline.uniform_values[UV_ShadowState].value = {
1.f / pipeline.shadow_map_resolution, 1.f / pipeline.shadow_map_resolution, lights.lights[0].shadow_bias, lights.lights[1].shadow_bias};
const auto stats = bgfx::getStats();
pipeline.uniform_values[UV_Resolution].value = {float(stats->width), float(stats->height), -1, -1};
}
void UpdateForwardPipelinePBRProbe(ForwardPipeline &pipeline, Texture irradiance, Texture radiance, Texture brdf) {
pipeline.uniform_textures[UT_IrradianceMap].texture = irradiance;
pipeline.uniform_textures[UT_RadianceMap].texture = radiance;
pipeline.uniform_textures[UT_BrdfMap].texture = brdf;
}
void UpdateForwardPipelineNoise(ForwardPipeline &pipeline, Texture noise) { pipeline.uniform_textures[UT_NoiseMap].texture = noise; }
void UpdateForwardPipelineAO(ForwardPipeline &pipeline, Texture ao) { pipeline.uniform_textures[UT_AmbientOcclusion].texture = ao; }
//
static float backbuffer_ratio[bgfx::BackbufferRatio::Count] = {1.f, 2.f, 4.f, 8.f, 16.f, 0.5f};
void UpdateForwardPipelineAAA(ForwardPipeline &pipeline, const iRect &rect, const Mat4 &view, const Mat44 &proj, const Mat4 &prv_view, const Mat44 &prv_proj,
const Vec2 &jitter, bgfx::BackbufferRatio::Enum ssgi_ratio, bgfx::BackbufferRatio::Enum ssr_ratio, float temporal_aa_weight, float motion_blur_strength,
float exposure, float gamma, int sample_count, float max_distance) {
pipeline.uniform_values[UV_Projection].value = {1.f / proj.m[0][0], 1.f / proj.m[1][1], proj.m[2][2], proj.m[2][3]};
memcpy(pipeline.uniform_values[UV_ViewProjUnjittered].value.data(), to_bgfx(proj * view).data(), sizeof(float) * 16);
memcpy(pipeline.uniform_values[UV_PreviousViewProjection].value.data(), to_bgfx(prv_proj * prv_view).data(), sizeof(float) * 16);
memcpy(pipeline.uniform_values[UV_MainProjection].value.data(), to_bgfx(proj).data(), sizeof(float) * 16);
memcpy(pipeline.uniform_values[UV_MainInvProjection].value.data(), to_bgfx(Inverse(proj)).data(), sizeof(float) * 16);
pipeline.uniform_values[UV_AAAParams].value = {backbuffer_ratio[ssgi_ratio], backbuffer_ratio[ssr_ratio], temporal_aa_weight, motion_blur_strength,
exposure, 1.f / gamma, float(sample_count), max_distance};
memcpy(pipeline.uniform_values[UV_MainInvView].value.data(), to_bgfx(InverseFast(view)).data(), sizeof(float) * 16);
}
//
void SubmitModelToForwardPipeline(bgfx::ViewId view_id, const Model &mdl, const ForwardPipeline &pipeline, const PipelineProgram &prg, uint32_t prg_variant,
uint8_t pipeline_stage, const Color &ambient, const ForwardPipelineLights &lights, const ForwardPipelineFog &fog, const Mat4 &mtx) {
const auto _mtx = to_bgfx(mtx);
/*
const auto pipeline_set_draw_env = [&](int mat_idx) {
UpdateForwardPipelineUniforms(pipeline.uniforms, ambient, lights, fog);
set_draw_env(mat_idx);
};
RenderModel(view_id, mdl, prg.variants[prg_variant][pipeline_stage], pipeline_set_draw_env, mtx);
*/
}
static hg::Mat4 ComputeCropMatrix() {
const bgfx::Caps *caps = bgfx::getCaps();
const float sy = caps->originBottomLeft ? 0.5f : -0.5f;
const float sz = caps->homogeneousDepth ? 0.5f : 1.0f;
const float tz = caps->homogeneousDepth ? 0.5f : 0.0f;
return TranslationMat4({0.5, 0.5, tz}) * ScaleMat4({0.5, sy, sz});
}
static const hg::Vec4 frustum_corners[8] = {{-1.f, 1.f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}, {1.f, -1.f, 0.f, 1.f}, {-1.f, -1.f, 0.f, 1.f}, {-1.f, 1.f, 1.f, 1.f},
{1.f, 1.f, 1.f, 1.f}, {1.f, -1.f, 1.f, 1.f}, {-1.f, -1.f, 1.f, 1.f}};
void GenerateLinearShadowMapForForwardPipeline(bgfx::ViewId &view_id, const ViewState &view_state, const std::vector<ModelDisplayList> &display_lists,
const std::vector<SkinnedModelDisplayList> &skinned_display_lists, const std::vector<Mat4> &mtxs, const ForwardPipelineLights &lights,
const ForwardPipeline &pipeline, const PipelineResources &res, ForwardPipelineShadowPassViewId &views, ForwardPipelineShadowData &shadow_data,
const char *debug_name) {
const bgfx::Caps *caps = bgfx::getCaps();
std::fill(std::begin(views), std::end(views), 65535);
const auto &framebuffers_end = std::end(pipeline.framebuffers);
const auto &light = lights.lights[0];
if (light.type == FPLT_None)
return;
if (light.shadow_type == FPST_None)
return;
// SLOT 0: linear light
const auto &linear_buffer = pipeline.framebuffers.find("linear_shadow_map");
if (linear_buffer != framebuffers_end) {
const int resolution = pipeline.shadow_map_resolution;
float near, far;
ExtractZRangeFromProjectionMatrix(view_state.proj, near, far);
Mat4 offset = {0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f};
const Mat4 world = InverseFast(view_state.view);
bool inv_result;
const Mat44 inv_proj = Inverse(view_state.proj, inv_result);
if (inv_result) {
Vec3 view_dir = GetZ(world);
Vec3 view_orig = GetT(world);
Vec3 light_up = GetY(light.world);
Vec3 light_dir = GetZ(light.world);
Mat4 inv_light_base = InverseFast(light.world);
// project frustum corners back to view space.
Vec3 corners[8];
for (int i = 0; i < 8; i++) {
Vec4 tmp = inv_proj * frustum_corners[i];
corners[i] = MakeVec3(tmp) / tmp.w;
}
// adjust to shadow range
float scale = light.pssm_split[3] / (corners[4].z - corners[0].z);
for (int i = 0; i < 4; i++)
corners[i + 4] = (corners[i + 4] - corners[i]) * scale;
// project back to world space
for (int i = 0; i < 8; i++)
corners[i] = world * corners[i];
const bgfx::Caps *caps = bgfx::getCaps();
float c0 = near;
for (int i = 0; i < 4; i++) {
const float c1 = light.pssm_split[i];
// compute split frustum and its center
Vec3 s[8], center = Vec3::Zero;
for (int j = 0; j < 4; j++) {
s[j] = corners[j] + (corners[j + 4] - corners[j]) * (c0 - near) / (light.pssm_split[3] - near);
s[j + 4] = corners[j] + (corners[j + 4] - corners[j]) * (c1 - near) / (light.pssm_split[3] - near);
center += s[j] + s[j + 4];
}
center /= 8.f;
// compute split radius
float radius = 0.f;
for (const auto v : s)
radius = Max(radius, Dist(center, v));
// [todo] find the most distant shadow caster for this slice
const float backup_dist = Mtr(200.f); // 4.f * radius;
Vec3 light_position = inv_light_base * (center - backup_dist * light_dir);
// stabilization (snap to shadow map texels)
light_position.x -= fmod(light_position.x, 2.f * radius / (float)resolution);
light_position.y -= fmod(light_position.y, 2.f * radius / (float)resolution);
Mat4 light_view = light.world;
SetTranslation(light_view, light.world * light_position);
const Mat4 inv_light = InverseFast(light_view);
const Mat44 light_projection = ComputeOrthographicProjectionMatrix(0.f, backup_dist + radius, 2.f * radius, Vec2::One);
const Mat4 crop_mtx = ComputeCropMatrix();
const Mat44 light_viewproj = light_projection * inv_light;
// select which part of the shadow map we will render to
offset.m[0][3] = (i & 1) ? 0.5f : 0.0f;
offset.m[1][3] = (i & 2) ? 0.5f : 0.0f;
shadow_data.linear_shadow_mtx[i] = offset * crop_mtx * light_viewproj;
shadow_data.linear_shadow_slice[i] = c1;
const auto frustum = MakeFrustum(light_projection, light_view);
c0 = c1;
if (debug_name)
bgfx::setViewName(view_id, format("Shadow map slot 0 (slice %1) for %2").arg(i).arg(debug_name));
uint16_t view_x = (i & 1) * resolution;
uint16_t view_y = ((i >> 1) & 1) * resolution;
if (caps->originBottomLeft)
view_y = resolution - view_y;
bgfx::touch(view_id);
bgfx::setViewMode(bgfx::ViewMode::Default);
bgfx::setViewRect(view_id, view_x, view_y, resolution, resolution);
bgfx::setViewClear(view_id, BGFX_CLEAR_DEPTH, 0x0, 1.f, 0);
bgfx::setViewTransform(view_id, to_bgfx(inv_light).data(), to_bgfx(light_projection).data());
bgfx::setViewFrameBuffer(view_id, linear_buffer->second);
std::vector<ModelDisplayList> culled_display_lists = display_lists;
CullModelDisplayLists(frustum, culled_display_lists, mtxs, res);
DrawModelDisplayLists(view_id, culled_display_lists, 0, pipeline.uniform_values, pipeline.uniform_textures, mtxs, res);
// FIXME cull skinned models!
DrawSkinnedModelDisplayLists(view_id, skinned_display_lists, 0, pipeline.uniform_values, pipeline.uniform_textures, mtxs, res);
views[FPSP_Slot0LinearSplit0 + i] = view_id++;
}
}
}
}
//
void GenerateSpotShadowMapForForwardPipeline(bgfx::ViewId &view_id, const std::vector<ModelDisplayList> &display_lists,
const std::vector<SkinnedModelDisplayList> &skinned_display_lists, const std::vector<Mat4> &mtxs, const ForwardPipelineLights &lights,
const ForwardPipeline &pipeline, const PipelineResources &res, ForwardPipelineShadowPassViewId &views, ForwardPipelineShadowData &shadow_data,
const char *debug_name) {
const bgfx::Caps *caps = bgfx::getCaps();
std::fill(std::begin(views), std::end(views), 65535);
const auto &framebuffers_end = std::end(pipeline.framebuffers);
const auto &light = lights.lights[1];
if (light.type == FPLT_None)
return;
// SLOT 1: spot light
const auto &spot_buffer = pipeline.framebuffers.find("spot_shadow_map");
if (spot_buffer != framebuffers_end) {
const auto view = InverseFast(light.world);
const auto proj = ComputePerspectiveProjectionMatrix(0.1f, 100.f, FovToZoomFactor(2.f * light.outer_angle), {1.f, 1.f});
const auto frustum = MakeFrustum(proj, light.world);
const auto crop_mtx = ComputeCropMatrix();
shadow_data.spot_shadow_mtx = crop_mtx * (proj * view);
if (debug_name)
bgfx::setViewName(view_id, format("Shadow map slot 1 for %1").arg(debug_name));
bgfx::touch(view_id);
bgfx::setViewMode(bgfx::ViewMode::Default);
bgfx::setViewRect(view_id, 0, 0, pipeline.shadow_map_resolution, pipeline.shadow_map_resolution);
bgfx::setViewClear(view_id, BGFX_CLEAR_DEPTH, 0x0, 1.f, 0);
bgfx::setViewTransform(view_id, to_bgfx(view).data(), to_bgfx(proj).data());
bgfx::setViewFrameBuffer(view_id, spot_buffer->second);
std::vector<ModelDisplayList> culled_display_lists = display_lists;
CullModelDisplayLists(frustum, culled_display_lists, mtxs, res);
DrawModelDisplayLists(view_id, culled_display_lists, 0, pipeline.uniform_values, pipeline.uniform_textures, mtxs, res);
// FIXME cull skinned models!
DrawSkinnedModelDisplayLists(view_id, skinned_display_lists, 0, pipeline.uniform_values, pipeline.uniform_textures, mtxs, res);
views[FPSP_Slot1Spot] = view_id++;
}
}
// Forward pipeline configurations
static const PipelineInfo forward_pipeline_info = {
"forward",
{
// AAA rendering prepass
{"FORWARD_PIPELINE_AAA_PREPASS=1", "FORWARD_PIPELINE_AAA=1", "FORWARD_PIPELINE=1"},
// basic rendering
{"FORWARD_PIPELINE=1"},
{"FORWARD_PIPELINE=1", "SLOT0_SHADOWS=1"},
{"FORWARD_PIPELINE=1", "SLOT1_SHADOWS=1"},
{"FORWARD_PIPELINE=1", "SLOT0_SHADOWS=1", "SLOT1_SHADOWS=1"},
// AAA rendering
{"FORWARD_PIPELINE_AAA=1", "FORWARD_PIPELINE=1"},
{"FORWARD_PIPELINE_AAA=1", "FORWARD_PIPELINE=1", "SLOT0_SHADOWS=1"},
{"FORWARD_PIPELINE_AAA=1", "FORWARD_PIPELINE=1", "SLOT1_SHADOWS=1"},
{"FORWARD_PIPELINE_AAA=1", "FORWARD_PIPELINE=1", "SLOT0_SHADOWS=1", "SLOT1_SHADOWS=1"},
},
};
const PipelineInfo &GetForwardPipelineInfo() { return forward_pipeline_info; }
//
ForwardPipeline CreateForwardPipeline(int shadow_map_resolution, bool spot_16bit_shadow_map) {
ForwardPipeline pipeline;
pipeline.shadow_map_resolution = shadow_map_resolution;
pipeline.textures = {
{"linear_shadow_map", bgfx::createTexture2D(pipeline.shadow_map_resolution * 2, pipeline.shadow_map_resolution * 2, false, 1, bgfx::TextureFormat::D32,
BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL)}, // this map is an atlas of 4 splits
{"spot_shadow_map", bgfx::createTexture2D(pipeline.shadow_map_resolution, pipeline.shadow_map_resolution, false, 1,
spot_16bit_shadow_map ? bgfx::TextureFormat::D16 : bgfx::TextureFormat::D32, BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL)},
};
for (auto &i : pipeline.textures)
bgfx::setName(i.second, i.first.c_str());
bgfx::TextureHandle linear_shadow_map_texs[] = {pipeline.textures["linear_shadow_map"]};
bgfx::TextureHandle spot_shadow_map_texs[] = {pipeline.textures["spot_shadow_map"]};
pipeline.framebuffers = {
{"linear_shadow_map", bgfx::createFrameBuffer(1, linear_shadow_map_texs)},
{"spot_shadow_map", bgfx::createFrameBuffer(1, spot_shadow_map_texs)},
};
for (auto &i : pipeline.framebuffers)
bgfx::setName(i.second, i.first.c_str());
pipeline.uniform_values = {
MakeUniformSetValue("uClock", Vec4{}),
MakeUniformSetValue("uFogColor", Vec4{}),
MakeUniformSetValue("uFogState", Vec4{}),
MakeUniformSetValue("uAmbientColor", Vec4{}),
MakeUniformSetValue("uLightPos", Vec4{}, 8),
MakeUniformSetValue("uLightDir", Vec4{}, 8),
MakeUniformSetValue("uLightDiffuse", Vec4{}, 8),
MakeUniformSetValue("uLightSpecular", Vec4{}, 8),
MakeUniformSetValue("uLinearShadowMatrix", Mat4{}, 4),
MakeUniformSetValue("uLinearShadowSlice", Vec4{}),
MakeUniformSetValue("uSpotShadowMatrix", Mat4{}),
MakeUniformSetValue("uShadowState", Vec4{}),
// advanced
MakeUniformSetValue("uResolution", Vec4{}),
MakeUniformSetValue("uProjection", Vec4{}),
MakeUniformSetValue("uMainProjection", Mat4{}),
MakeUniformSetValue("uMainInvProjection", Mat4{}),
MakeUniformSetValue("uPreviousViewProjection", Mat4{}),
MakeUniformSetValue("uViewProjUnjittered", Mat4{}),
MakeUniformSetValue("uAAAParams", Vec4{}, 2), // [2]
MakeUniformSetValue("uMainInvView", Mat4{}),
};
__ASSERT__(pipeline.uniform_values.size() == UV_Count);
pipeline.uniform_textures = {
MakeUniformSetTexture("uIrradianceMap", {}, 8),
MakeUniformSetTexture("uRadianceMap", {}, 9),
MakeUniformSetTexture("uBrdfMap", {}, 10),
MakeUniformSetTexture("uNoiseMap", {}, 11),
MakeUniformSetTexture("uAmbientOcclusion", {}, 13),
MakeUniformSetTexture("uLinearShadowMap", {BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL, pipeline.textures["linear_shadow_map"]}, 14),
MakeUniformSetTexture("uSpotShadowMap", {BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL, pipeline.textures["spot_shadow_map"]}, 15),
};
__ASSERT__(pipeline.uniform_textures.size() == UT_Count);
return pipeline;
}
} // namespace hg