Files
WickedEngine/WickedEngine/wiTrailRenderer.cpp
T
2025-06-11 07:41:04 +02:00

444 lines
14 KiB
C++

#include "wiTrailRenderer.h"
#include "wiEventHandler.h"
#include "wiBacklog.h"
#include "wiTimer.h"
#include "wiRenderer.h"
#include "wiTextureHelper.h"
#include "wiScene.h"
using namespace wi::graphics;
using namespace wi::enums;
using namespace wi::scene;
namespace wi
{
static Shader vertexShader;
static Shader pixelShader;
static InputLayout inputLayout;
static BlendState blendStates[BLENDMODE_COUNT];
static RasterizerState rasterizerState;
static RasterizerState wireFrameRS;
static DepthStencilState depthStencilState;
static PipelineState PSO[BLENDMODE_COUNT];
static PipelineState PSO_wire;
void TrailRenderer::Clear()
{
points.clear();
cuts.clear();
}
void TrailRenderer::AddPoint(const XMFLOAT3& position, float width, const XMFLOAT4& color, const XMFLOAT4& rotation)
{
TrailPoint& point = points.emplace_back();
point.position = position;
point.width = width;
point.color = color;
point.rotation = rotation;
}
void TrailRenderer::Cut(bool looped)
{
if (points.empty())
return;
if (cuts.empty() || cuts.back().id != points.size())
{
CutSegment cut;
cut.id = (uint32_t)points.size();
cut.looped = looped;
cuts.push_back(cut);
}
}
void TrailRenderer::Fade(float amount)
{
if (points.size() > 1)
{
for (auto& point : points)
{
point.color.w = saturate(point.color.w - amount);
}
if (points[0].color.w <= 0 && points[1].color.w <= 0)
{
points.erase(points.begin());
for (auto& cut : cuts)
{
if (cut.id > 0)
{
cut.id--;
}
}
if (!cuts.empty() && cuts[0].id == 0)
{
cuts.erase(cuts.begin());
}
}
}
}
void TrailRenderer::Draw(const CameraComponent& camera, CommandList cmd) const
{
if (points.size() < 2)
return;
GraphicsDevice* device = GetDevice();
device->EventBegin("TrailRenderer", cmd);
if (wi::renderer::IsWireRender())
{
device->BindPipelineState(&PSO_wire, cmd);
}
else
{
device->BindPipelineState(&PSO[blendMode], cmd);
}
TrailRendererCB sb = {};
sb.g_xTrailTransform = camera.VP;
sb.g_xTrailColor = color;
sb.g_xTrailTexMulAdd = texMulAdd;
sb.g_xTrailTexMulAdd2 = texMulAdd2;
sb.g_xTrailDepthSoften = 1.0f / (width * depth_soften);
sb.g_xTrailTextureIndex1 = device->GetDescriptorIndex(texture.IsValid() ? &texture : wi::texturehelper::getWhite(), SubresourceType::SRV);
sb.g_xTrailTextureIndex2 = device->GetDescriptorIndex(texture2.IsValid() ? &texture2 : wi::texturehelper::getWhite(), SubresourceType::SRV);
sb.g_xTrailLinearDepthTextureIndex = camera.texture_lineardepth_index;
sb.g_xTrailCameraFar = camera.zFarP;
device->BindDynamicConstantBuffer(sb, CBSLOT_TRAILRENDERER, cmd);
struct Vertex
{
XMFLOAT3 position;
uint16_t uvx;
uint16_t uvy;
XMHALF4 color;
};
const uint32_t num_segments = uint32_t(points.size());
const uint32_t subdivision = std::max(1u, this->subdivision);
const uint32_t vertexCountAlloc = num_segments * subdivision * 2;
const uint32_t indexCountAlloc = num_segments * (subdivision - 1) * 6;
auto mem = device->AllocateGPU(sizeof(Vertex) * vertexCountAlloc + sizeof(uint32_t) * indexCountAlloc, cmd);
Vertex* vertices = (Vertex*)mem.data;
uint32_t* indices = (uint32_t*)(vertices + vertexCountAlloc);
const float resolution_rcp = 1.0f / subdivision;
const XMVECTOR CAM = camera.GetEye();
uint32_t next_cut = 0;
uint32_t vertexCount = 0;
uint32_t indexCount = 0;
int i = 0;
while (next_cut <= cuts.size())
{
const int startcut = i;
const int count = next_cut < cuts.size() ? cuts[next_cut].id : (uint32_t)points.size();
const bool looped = next_cut < cuts.size() ? (bool)cuts[next_cut].looped : false;
const float subdiv_count_within_cut = looped ? float((count - startcut) * subdivision) : float((count - 1 - startcut) * subdivision);
float subdiv_within_cut = 0;
int last = looped ? count : (count - 1);
for (; i < last; ++i)
{
int t0 = 0; // prev
int t1 = 0; // current
int t2 = 1; // next
int t3 = 1; // after next
if (looped)
{
int cut_index = i - startcut;
int cut_count = count - startcut;
t0 = startcut + (cut_index + cut_count - 1) % cut_count;
t1 = startcut + (cut_index + cut_count) % cut_count;
t2 = startcut + (cut_index + cut_count + 1) % cut_count;
t3 = startcut + (cut_index + cut_count + 2) % cut_count;
}
else
{
t0 = clamp(i - 1, startcut, last);
t1 = clamp(i, startcut, last);
t2 = clamp(i + 1, startcut, last);
t3 = clamp(i + 2, startcut, last);
}
XMVECTOR P0 = XMLoadFloat3(&points[t0].position);
XMVECTOR P1 = XMLoadFloat3(&points[t1].position);
XMVECTOR P2 = XMLoadFloat3(&points[t2].position);
XMVECTOR P3 = XMLoadFloat3(&points[t3].position);
XMVECTOR W0 = XMVectorReplicate(points[t0].width);
XMVECTOR W1 = XMVectorReplicate(points[t1].width);
XMVECTOR W2 = XMVectorReplicate(points[t2].width);
XMVECTOR W3 = XMVectorReplicate(points[t3].width);
XMVECTOR C0 = XMLoadFloat4(&points[t0].color);
XMVECTOR C1 = XMLoadFloat4(&points[t1].color);
XMVECTOR C2 = XMLoadFloat4(&points[t2].color);
XMVECTOR C3 = XMLoadFloat4(&points[t3].color);
XMVECTOR Q0 = XMLoadFloat4(&points[t0].rotation);
XMVECTOR Q1 = XMLoadFloat4(&points[t1].rotation);
XMVECTOR Q2 = XMLoadFloat4(&points[t2].rotation);
XMVECTOR Q3 = XMLoadFloat4(&points[t3].rotation);
const bool rotation_enabled = points[i].rotation.x != 0 || points[i].rotation.y != 0 || points[i].rotation.z != 0 || points[i].rotation.w != 0;
if (rotation_enabled)
{
Q0 = XMQuaternionNormalize(Q0);
Q1 = XMQuaternionNormalize(Q1);
Q2 = XMQuaternionNormalize(Q2);
Q3 = XMQuaternionNormalize(Q3);
}
if (!looped)
{
if (i == startcut)
{
// when P0 == P1, centripetal catmull doesn't work, so we have to do a dummy control point
P0 += P1 - P2;
}
if (i >= count - 2)
{
// when P2 == P3, centripetal catmull doesn't work, so we have to do a dummy control point
P3 += P2 - P1;
}
}
for (uint32_t j = 0; j < subdivision; ++j)
{
float t = float(j) / float(subdivision - 1);
const XMVECTOR P = wi::math::CatmullRomCentripetal(P0, P1, P2, P3, t);
const XMVECTOR P_prev = wi::math::CatmullRomCentripetal(P0, P1, P2, P3, t - resolution_rcp);
const XMVECTOR P_next = wi::math::CatmullRomCentripetal(P0, P1, P2, P3, t + resolution_rcp);
const float width_interpolated = XMVectorGetX(XMVectorCatmullRom(W0, W1, W2, W3, t));
const XMVECTOR C = XMVectorCatmullRom(C0, C1, C2, C3, t);
XMFLOAT4 color_interpolated;
XMStoreFloat4(&color_interpolated, C);
XMVECTOR N;
if (rotation_enabled)
{
XMVECTOR Q = XMVectorCatmullRom(Q0, Q1, Q2, Q3, t);
Q = XMQuaternionNormalize(Q);
N = XMVector3Rotate(XMVectorSet(0, 1, 0, 0), Q);
}
else
{
N = P - CAM; // camera facing
}
N = XMVector3Normalize(N);
XMVECTOR T = XMVector3Normalize(P_next - P_prev);
XMVECTOR B = XMVector3Normalize(XMVector3Cross(T, N));
N = XMVector3Normalize(XMVector3Cross(B, T)); // re-orthogonalize
B *= width_interpolated * width;
if (j < subdivision - 1)
{
indices[indexCount++] = vertexCount;
indices[indexCount++] = vertexCount + 1;
indices[indexCount++] = vertexCount + 2;
indices[indexCount++] = vertexCount + 2;
indices[indexCount++] = vertexCount + 1;
indices[indexCount++] = vertexCount + 3;
}
const float cut_percent = subdiv_within_cut / subdiv_count_within_cut;
subdiv_within_cut += 1;
Vertex vert = {};
vert.uvx = uint16_t(cut_percent * 65535);
vert.color.x = XMConvertFloatToHalf(color_interpolated.x);
vert.color.y = XMConvertFloatToHalf(color_interpolated.y);
vert.color.z = XMConvertFloatToHalf(color_interpolated.z);
vert.color.w = XMConvertFloatToHalf(color_interpolated.w);
vert.uvy = 0;
XMStoreFloat3(&vert.position, XMVectorSetW(P - B, 1));
std::memcpy(vertices + vertexCount, &vert, sizeof(vert));
vertexCount++;
vert.uvy = 65535;
XMStoreFloat3(&vert.position, XMVectorSetW(P + B, 1));
std::memcpy(vertices + vertexCount, &vert, sizeof(vert));
vertexCount++;
}
}
next_cut++;
if (!looped)
i++;
}
assert(vertexCount <= vertexCountAlloc);
assert(indexCount <= indexCountAlloc);
const GPUBuffer* vbs[] = {
&mem.buffer,
};
const uint32_t strides[] = {
sizeof(Vertex),
};
const uint64_t offsets[] = {
mem.offset,
};
device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, offsets, cmd);
device->BindIndexBuffer(&mem.buffer, IndexBufferFormat::UINT32, mem.offset + sizeof(Vertex) * vertexCountAlloc, cmd);
device->DrawIndexed(indexCount, 0, 0, cmd);
device->EventEnd(cmd);
}
namespace TrailRenderer_Internal
{
void LoadShaders()
{
wi::renderer::LoadShader(ShaderStage::VS, vertexShader, "trailVS.cso");
wi::renderer::LoadShader(ShaderStage::PS, pixelShader, "trailPS.cso");
inputLayout.elements = {
{ "POSITION", 0, Format::R32G32B32_FLOAT, 0, InputLayout::APPEND_ALIGNED_ELEMENT, InputClassification::PER_VERTEX_DATA },
{ "TEXCOORD", 0, Format::R16G16_UNORM, 0, InputLayout::APPEND_ALIGNED_ELEMENT, InputClassification::PER_VERTEX_DATA },
{ "COLOR", 0, Format::R16G16B16A16_FLOAT, 0, InputLayout::APPEND_ALIGNED_ELEMENT, InputClassification::PER_VERTEX_DATA },
};
GraphicsDevice* device = wi::graphics::GetDevice();
for (int i = 0; i < BLENDMODE_COUNT; ++i)
{
PipelineStateDesc desc;
desc.pt = PrimitiveTopology::TRIANGLELIST;
desc.vs = &vertexShader;
desc.ps = &pixelShader;
desc.il = &inputLayout;
desc.bs = &blendStates[i];
desc.rs = &rasterizerState;
desc.dss = &depthStencilState;
device->CreatePipelineState(&desc, &PSO[i]);
}
{
PipelineStateDesc desc;
desc.pt = PrimitiveTopology::TRIANGLELIST;
desc.vs = &vertexShader;
desc.ps = &pixelShader;
desc.il = &inputLayout;
desc.bs = &blendStates[BLENDMODE_ALPHA];
desc.rs = &wireFrameRS;
desc.dss = &depthStencilState;
device->CreatePipelineState(&desc, &PSO_wire);
}
}
}
void TrailRenderer::Initialize()
{
wi::Timer timer;
RasterizerState rs;
rs.fill_mode = FillMode::SOLID;
rs.cull_mode = CullMode::NONE;
rs.front_counter_clockwise = true;
rs.depth_bias = 0;
rs.depth_bias_clamp = 0;
rs.slope_scaled_depth_bias = 0;
rs.depth_clip_enable = false;
rs.multisample_enable = false;
rs.antialiased_line_enable = false;
rasterizerState = rs;
rs.fill_mode = FillMode::WIREFRAME;
rs.cull_mode = CullMode::NONE;
rs.front_counter_clockwise = true;
rs.depth_bias = 0;
rs.depth_bias_clamp = 0;
rs.slope_scaled_depth_bias = 0;
rs.depth_clip_enable = false;
rs.multisample_enable = false;
rs.antialiased_line_enable = false;
wireFrameRS = rs;
DepthStencilState dsd;
dsd.depth_enable = true;
dsd.depth_write_mask = DepthWriteMask::ZERO;
dsd.depth_func = ComparisonFunc::GREATER_EQUAL;
dsd.stencil_enable = false;
depthStencilState = dsd;
BlendState bd;
bd.render_target[0].blend_enable = true;
bd.render_target[0].src_blend = Blend::SRC_ALPHA;
bd.render_target[0].dest_blend = Blend::INV_SRC_ALPHA;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::ONE;
bd.render_target[0].dest_blend_alpha = Blend::INV_SRC_ALPHA;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.independent_blend_enable = false;
blendStates[BLENDMODE_ALPHA] = bd;
bd.render_target[0].blend_enable = true;
bd.render_target[0].src_blend = Blend::SRC_ALPHA;
bd.render_target[0].dest_blend = Blend::ONE;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::ZERO;
bd.render_target[0].dest_blend_alpha = Blend::ONE;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.independent_blend_enable = false;
blendStates[BLENDMODE_ADDITIVE] = bd;
bd.render_target[0].blend_enable = true;
bd.render_target[0].src_blend = Blend::ONE;
bd.render_target[0].dest_blend = Blend::INV_SRC_ALPHA;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::ONE;
bd.render_target[0].dest_blend_alpha = Blend::ONE;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.independent_blend_enable = false;
blendStates[BLENDMODE_PREMULTIPLIED] = bd;
bd.render_target[0].src_blend = Blend::DEST_COLOR;
bd.render_target[0].dest_blend = Blend::ZERO;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::DEST_ALPHA;
bd.render_target[0].dest_blend_alpha = Blend::ZERO;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].blend_enable = true;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.alpha_to_coverage_enable = false;
bd.independent_blend_enable = false;
blendStates[BLENDMODE_MULTIPLY] = bd;
bd.render_target[0].src_blend = Blend::INV_DEST_COLOR;
bd.render_target[0].dest_blend = Blend::ZERO;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::DEST_ALPHA;
bd.render_target[0].dest_blend_alpha = Blend::ZERO;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].blend_enable = true;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.alpha_to_coverage_enable = false;
bd.independent_blend_enable = false;
blendStates[BLENDMODE_INVERSE] = bd;
bd.render_target[0].blend_enable = false;
blendStates[BLENDMODE_OPAQUE] = bd;
static wi::eventhandler::Handle handle = wi::eventhandler::Subscribe(wi::eventhandler::EVENT_RELOAD_SHADERS, [](uint64_t userdata) { TrailRenderer_Internal::LoadShaders(); });
TrailRenderer_Internal::LoadShaders();
wilog("wi::TrailRenderer Initialized (%d ms)", (int)std::round(timer.elapsed()));
}
}