feat(antialias): AA geomètric a les línies amb edge attribute i smoothstep
Afegim antialias geomètric (sense MSAA) al pipeline de línies aprofitant que la línia ja es construeix com a quad extruït a CPU: - LineVertex: nou camp edge_dist (±1 als laterals del quad, 0 al centre). - pushLine: extrudeix 0.5px extra per banda (AA_PADDING) per allotjar el fade sense menjar gruix nominal. - line.vert: passa l'edge_dist al fragment com a varying. - line.frag: alpha *= 1 - smoothstep(0.7, 1.0, |edge_dist|) — fade Hermite C¹ als bords, sense banding. AA actiu per defecte. El toggle a runtime (F5) ve en el commit següent.
This commit is contained in:
+17
-4
@@ -1,12 +1,25 @@
|
||||
#version 450
|
||||
|
||||
// Fragment shader para líneas vectoriales.
|
||||
// Pinta el color interpolado per-vertex que viene del vertex shader.
|
||||
// (Postprocesado / bloom / glow son responsabilidad de la Fase 8.)
|
||||
// Fragment shader per a línies vectorials.
|
||||
//
|
||||
// Antialias geomètric: rebem `frag_edge_dist` interpolat (±1 als laterals del
|
||||
// quad, 0 a l'eix central). Apliquem un smoothstep d'1 píxel d'amplada perquè
|
||||
// el gruix nominal (els |edge_dist| < threshold) quedi totalment opac i només
|
||||
// el píxel extruit als laterals faci la transició suau.
|
||||
//
|
||||
// La línia ja ve extruïda amb thickness + 1px a CPU; el threshold equival a
|
||||
// (thickness)/(thickness+1), però no el coneixem aquí per vèrtex. En el cas
|
||||
// general (línies fines), fade lineal entre 0.0 i 1.0 dóna prou bon resultat
|
||||
// visualment sense necessitat d'un uniform per línia.
|
||||
|
||||
layout(location = 0) in vec4 frag_color;
|
||||
layout(location = 1) in float frag_edge_dist;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = frag_color;
|
||||
// |edge_dist|=0 → totalment opac; |edge_dist|=1 → totalment transparent.
|
||||
// smoothstep dóna un fade Hermite C¹ que evita banding.
|
||||
float d = abs(frag_edge_dist);
|
||||
float alpha = 1.0 - smoothstep(0.7, 1.0, d);
|
||||
out_color = vec4(frag_color.rgb, frag_color.a * alpha);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ layout(set = 1, binding = 0) uniform UBO {
|
||||
vec2 _padding; // alineamiento a 16 bytes
|
||||
} ubo;
|
||||
|
||||
layout(location = 0) in vec2 in_position; // píxeles lógicos
|
||||
layout(location = 1) in vec4 in_color; // RGBA 0..1
|
||||
layout(location = 0) in vec2 in_position; // píxeles lógicos
|
||||
layout(location = 1) in vec4 in_color; // RGBA 0..1
|
||||
layout(location = 2) in float in_edge_dist; // ±1 als laterals, 0 al centre
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
layout(location = 1) out float frag_edge_dist;
|
||||
|
||||
void main() {
|
||||
// Píxeles lógicos -> NDC (-1..+1)
|
||||
@@ -26,4 +28,5 @@ void main() {
|
||||
ndc.y = -ndc.y;
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
frag_color = in_color;
|
||||
frag_edge_dist = in_edge_dist;
|
||||
}
|
||||
|
||||
@@ -243,16 +243,21 @@ namespace Rendering::GPU {
|
||||
return;
|
||||
}
|
||||
|
||||
const float HALF = thickness * 0.5F;
|
||||
// Antialias geomètric: extruim 0.5 píxel extra a cada banda del gruix
|
||||
// demanat, i el fragment shader fa fade als bords usant edge_dist (±1
|
||||
// als laterals, 0 al centre). Així el gruix "ple" coincideix amb el
|
||||
// demanat i el píxel extra només aporta la transició suau.
|
||||
const float AA_PADDING = 0.5F;
|
||||
const float HALF = (thickness * 0.5F) + AA_PADDING;
|
||||
const float NX = -DY / LEN * HALF;
|
||||
const float NY = DX / LEN * HALF;
|
||||
|
||||
const auto BASE_INDEX = static_cast<uint16_t>(vertices_.size());
|
||||
|
||||
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a});
|
||||
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a});
|
||||
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a});
|
||||
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a});
|
||||
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a, +1.0F});
|
||||
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a, -1.0F});
|
||||
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a, +1.0F});
|
||||
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a, -1.0F});
|
||||
|
||||
indices_.push_back(BASE_INDEX + 0);
|
||||
indices_.push_back(BASE_INDEX + 1);
|
||||
|
||||
@@ -11,107 +11,111 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||
|
||||
auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
SDL_GPUTextureFormat target_format) -> bool {
|
||||
owner_ = device.get();
|
||||
if (owner_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUShader* vert = device.loadShader("line.vert.spv",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX,
|
||||
/*num_uniform_buffers=*/1);
|
||||
SDL_GPUShader* frag = device.loadShader("line.frag.spv",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||
/*num_uniform_buffers=*/0);
|
||||
if ((vert == nullptr) || (frag == nullptr)) {
|
||||
if (vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(owner_, vert);
|
||||
auto GpuLinePipeline::init(const GpuDevice& device,
|
||||
SDL_GPUTextureFormat target_format) -> bool {
|
||||
owner_ = device.get();
|
||||
if (owner_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(owner_, frag);
|
||||
|
||||
SDL_GPUShader* vert = device.loadShader("line.vert.spv",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX,
|
||||
/*num_uniform_buffers=*/1);
|
||||
SDL_GPUShader* frag = device.loadShader("line.frag.spv",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||
/*num_uniform_buffers=*/0);
|
||||
if ((vert == nullptr) || (frag == nullptr)) {
|
||||
if (vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(owner_, vert);
|
||||
}
|
||||
if (frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(owner_, frag);
|
||||
}
|
||||
std::cerr << "[GpuLinePipeline] Error cargando shaders\n";
|
||||
return false;
|
||||
}
|
||||
std::cerr << "[GpuLinePipeline] Error cargando shaders\n";
|
||||
return false;
|
||||
|
||||
// Vertex layout: pos (vec2) + color (vec4) + edge_dist (float) → 7 floats per vèrtex.
|
||||
SDL_GPUVertexBufferDescription vbo_desc{};
|
||||
vbo_desc.slot = 0;
|
||||
vbo_desc.pitch = sizeof(LineVertex);
|
||||
vbo_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vbo_desc.instance_step_rate = 0;
|
||||
|
||||
SDL_GPUVertexAttribute attrs[3];
|
||||
attrs[0].location = 0; // in_position
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1; // in_color
|
||||
attrs[1].buffer_slot = 0;
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[1].offset = sizeof(float) * 2;
|
||||
attrs[2].location = 2; // in_edge_dist
|
||||
attrs[2].buffer_slot = 0;
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT;
|
||||
attrs[2].offset = sizeof(float) * 6;
|
||||
|
||||
SDL_GPUVertexInputState vertex_input{};
|
||||
vertex_input.vertex_buffer_descriptions = &vbo_desc;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs;
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
// Color target = formato pasado por el caller (offscreen u otro).
|
||||
// Blending alpha estándar.
|
||||
SDL_GPUColorTargetDescription color_target{};
|
||||
color_target.format = target_format;
|
||||
color_target.blend_state.enable_blend = true;
|
||||
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_target.blend_state.color_write_mask =
|
||||
SDL_GPU_COLORCOMPONENT_R | SDL_GPU_COLORCOMPONENT_G |
|
||||
SDL_GPU_COLORCOMPONENT_B | SDL_GPU_COLORCOMPONENT_A;
|
||||
|
||||
SDL_GPUGraphicsPipelineTargetInfo target_info{};
|
||||
target_info.color_target_descriptions = &color_target;
|
||||
target_info.num_color_targets = 1;
|
||||
target_info.has_depth_stencil_target = false;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo info{};
|
||||
info.vertex_shader = vert;
|
||||
info.fragment_shader = frag;
|
||||
info.vertex_input_state = vertex_input;
|
||||
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; // No backface culling para 2D
|
||||
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
info.depth_stencil_state = {};
|
||||
info.target_info = target_info;
|
||||
|
||||
pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info);
|
||||
|
||||
// Los shaders se pueden liberar tras crear el pipeline (SDL los retiene
|
||||
// internamente mientras el pipeline esté vivo).
|
||||
SDL_ReleaseGPUShader(owner_, vert);
|
||||
SDL_ReleaseGPUShader(owner_, frag);
|
||||
|
||||
if (pipeline_ == nullptr) {
|
||||
std::cerr << "[GpuLinePipeline] SDL_CreateGPUGraphicsPipeline: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vertex layout: pos (vec2) + color (vec4) → 6 floats por vertex.
|
||||
SDL_GPUVertexBufferDescription vbo_desc{};
|
||||
vbo_desc.slot = 0;
|
||||
vbo_desc.pitch = sizeof(LineVertex);
|
||||
vbo_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vbo_desc.instance_step_rate = 0;
|
||||
|
||||
SDL_GPUVertexAttribute attrs[2];
|
||||
attrs[0].location = 0; // in_position
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1; // in_color
|
||||
attrs[1].buffer_slot = 0;
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[1].offset = sizeof(float) * 2;
|
||||
|
||||
SDL_GPUVertexInputState vertex_input{};
|
||||
vertex_input.vertex_buffer_descriptions = &vbo_desc;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs;
|
||||
vertex_input.num_vertex_attributes = 2;
|
||||
|
||||
// Color target = formato pasado por el caller (offscreen u otro).
|
||||
// Blending alpha estándar.
|
||||
SDL_GPUColorTargetDescription color_target{};
|
||||
color_target.format = target_format;
|
||||
color_target.blend_state.enable_blend = true;
|
||||
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_target.blend_state.color_write_mask =
|
||||
SDL_GPU_COLORCOMPONENT_R | SDL_GPU_COLORCOMPONENT_G |
|
||||
SDL_GPU_COLORCOMPONENT_B | SDL_GPU_COLORCOMPONENT_A;
|
||||
|
||||
SDL_GPUGraphicsPipelineTargetInfo target_info{};
|
||||
target_info.color_target_descriptions = &color_target;
|
||||
target_info.num_color_targets = 1;
|
||||
target_info.has_depth_stencil_target = false;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo info{};
|
||||
info.vertex_shader = vert;
|
||||
info.fragment_shader = frag;
|
||||
info.vertex_input_state = vertex_input;
|
||||
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; // No backface culling para 2D
|
||||
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
info.depth_stencil_state = {};
|
||||
info.target_info = target_info;
|
||||
|
||||
pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info);
|
||||
|
||||
// Los shaders se pueden liberar tras crear el pipeline (SDL los retiene
|
||||
// internamente mientras el pipeline esté vivo).
|
||||
SDL_ReleaseGPUShader(owner_, vert);
|
||||
SDL_ReleaseGPUShader(owner_, frag);
|
||||
|
||||
if (pipeline_ == nullptr) {
|
||||
std::cerr << "[GpuLinePipeline] SDL_CreateGPUGraphicsPipeline: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
void GpuLinePipeline::destroy() {
|
||||
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||
}
|
||||
pipeline_ = nullptr;
|
||||
owner_ = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuLinePipeline::destroy() {
|
||||
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||
}
|
||||
pipeline_ = nullptr;
|
||||
owner_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
@@ -11,28 +11,34 @@
|
||||
|
||||
namespace Rendering::GPU {
|
||||
|
||||
class GpuDevice;
|
||||
class GpuDevice;
|
||||
|
||||
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||
struct LineVertex {
|
||||
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||
//
|
||||
// edge_dist: distància normalitzada a l'eix central de la línia, ±1 als bords
|
||||
// i 0 al centre. El fragment shader la fa servir per fer un fade als bords
|
||||
// (antialias geomètric). Els vèrtexs del costat "+normal" porten +1 i els del
|
||||
// costat "-normal" porten -1.
|
||||
struct LineVertex {
|
||||
float x;
|
||||
float y;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
};
|
||||
float edge_dist;
|
||||
};
|
||||
|
||||
// Uniform buffer del vertex shader (debe coincidir con UBO en line.vert.glsl).
|
||||
struct LineUniforms {
|
||||
// Uniform buffer del vertex shader (debe coincidir con UBO en line.vert.glsl).
|
||||
struct LineUniforms {
|
||||
float viewport_width;
|
||||
float viewport_height;
|
||||
float padding_0;
|
||||
float padding_1; // Alineamiento a 16 bytes
|
||||
};
|
||||
};
|
||||
|
||||
class GpuLinePipeline {
|
||||
public:
|
||||
class GpuLinePipeline {
|
||||
public:
|
||||
GpuLinePipeline() = default;
|
||||
~GpuLinePipeline();
|
||||
|
||||
@@ -45,14 +51,14 @@ class GpuLinePipeline {
|
||||
// (swapchain o offscreen). Por defecto coincide con el del swapchain
|
||||
// del device.
|
||||
[[nodiscard]] auto init(const GpuDevice& device,
|
||||
SDL_GPUTextureFormat target_format) -> bool;
|
||||
SDL_GPUTextureFormat target_format) -> bool;
|
||||
void destroy();
|
||||
|
||||
[[nodiscard]] auto get() const -> SDL_GPUGraphicsPipeline* { return pipeline_; }
|
||||
|
||||
private:
|
||||
private:
|
||||
SDL_GPUDevice* owner_{nullptr}; // No-owning; el GpuDevice es el dueño
|
||||
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Rendering::GPU
|
||||
|
||||
Reference in New Issue
Block a user