Files

116 lines
3.6 KiB
GLSL

#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_nonuniform_qualifier : enable
#include "bindless.glsl"
// The selection image is R32_UINT, read through the same bindless array as an unsigned texture
layout (set = 0, binding = 0) uniform utexture2D uintTextures[];
layout (location = 0) in vec2 inUV;
layout (location = 0) out vec4 outColor;
// Must match PostProcessPipeline::PushConstants and the Tonemapper enum
#define TONEMAPPER_NONE 0
#define TONEMAPPER_ACES 1
#define TONEMAPPER_PBR_NEUTRAL 2
#define NO_TEXTURE 0xFFFFFFFFu
#define SELECTED_FLAG 0x80000000u
layout (push_constant) uniform PostPushConstants {
uint sceneColorTexture;
uint selectionTexture;
uint tonemapper;
float exposure;
ivec2 hoverPixel;
} pcs;
// Narkowicz 2015, a fit of the ACES filmic curve
vec3 tonemapAces(vec3 color)
{
const float a = 2.51;
const float b = 0.03;
const float c = 2.43;
const float d = 0.59;
const float e = 0.14;
return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0);
}
// Khronos PBR Neutral, keeps base colors as they are up to about 0.8 and only rolls off the highlights
vec3 tonemapPbrNeutral(vec3 color)
{
const float startCompression = 0.8 - 0.04;
const float desaturation = 0.15;
float x = min(color.r, min(color.g, color.b));
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
color -= offset;
float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) {
return color;
}
const float d = 1.0 - startCompression;
float newPeak = 1.0 - d * d / (peak + d - startCompression);
color *= newPeak / peak;
float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
return mix(color, vec3(newPeak), g);
}
uint objectAt(ivec2 pixel, ivec2 size)
{
pixel = clamp(pixel, ivec2(0), size - 1);
return texelFetch(nonuniformEXT(usampler2D(uintTextures[pcs.selectionTexture], samplers[NEAREST_SAMPLER_ID])), pixel, 0).r;
}
vec3 applyOutlines(vec3 color, ivec2 pixel)
{
if (pcs.selectionTexture == NO_TEXTURE) {
return color;
}
ivec2 size = textureSize(nonuniformEXT(usampler2D(uintTextures[pcs.selectionTexture], samplers[NEAREST_SAMPLER_ID])), 0);
uint center = objectAt(pixel, size);
uint hovered = pcs.hoverPixel.x >= 0 ? (objectAt(pcs.hoverPixel, size) & ~SELECTED_FLAG) : 0u;
bool selectedEdge = false;
bool hoveredEdge = false;
for (int y = -2; y <= 2; ++y) {
for (int x = -2; x <= 2; ++x) {
if (abs(x) + abs(y) > 3) {
continue;
}
uint neighbor = objectAt(pixel + ivec2(x, y), size);
selectedEdge = selectedEdge || ((neighbor & SELECTED_FLAG) != 0u && (center & SELECTED_FLAG) == 0u);
hoveredEdge = hoveredEdge || (hovered != 0u && (neighbor & ~SELECTED_FLAG) == hovered &&
(center & ~SELECTED_FLAG) != hovered);
}
}
if (selectedEdge) {
return vec3(1.0, 0.55, 0.1);
}
if (hoveredEdge) {
return mix(color, vec3(1.0), 0.65);
}
return color;
}
void main()
{
ivec2 pixel = ivec2(gl_FragCoord.xy);
vec3 color = texelFetch(nonuniformEXT(sampler2D(textures[pcs.sceneColorTexture], samplers[NEAREST_SAMPLER_ID])), pixel, 0).rgb;
color *= pcs.exposure;
if (pcs.tonemapper == TONEMAPPER_ACES) {
color = tonemapAces(color);
} else if (pcs.tonemapper == TONEMAPPER_PBR_NEUTRAL) {
color = tonemapPbrNeutral(color);
}
outColor = vec4(applyOutlines(clamp(color, 0.0, 1.0), pixel), 1.0);
}