Files

289 lines
9.3 KiB
GLSL

layout(location = 1) in vec2 f_uv;
layout(location = 5) in vec3 f_normal;
layout(location = 8) in vec4 f_world_position;
layout(location = 3) flat in int f_material_id;
layout(location = 0) out vec2 o_displace_mask;
layout(location = 1) out vec4 o_displace_ssr;
layout(push_constant) uniform Constants
{
vec4 m_displace_direction;
} u_push_constants;
#include "utils/camera.glsl"
#include "utils/constants_utils.glsl"
#include "utils/sample_mesh_texture.glsl"
#include "../utils/displace_utils.frag"
#include "../utils/screen_space_reflection.frag"
layout (set = 2, binding = 2) uniform samplerCube u_skybox_texture;
layout (set = 3, binding = 0) uniform sampler2D u_displace_color;
layout (set = 3, binding = 1) uniform sampler2DShadow u_depth;
layout (set = 3, binding = 2) uniform sampler2D u_hiz_depth;
#ifdef PBR_ENABLED
// Start tracing in this level.
#define HIZ_START_LEVEL 0
// Stop tracing if current level is higher than this. (higher level means lower value)
#define HIZ_STOP_LEVEL 0
#define HIZ_MAX_LEVEL 6
// Set to 1 to disable HiZ and perform naive linear search.
#define DEBUG_LINEAR_SEARCH 0
#define MAX_THICKNESS 0.001
vec3 intersectDepthPlane(vec3 o, vec3 d, float z)
{
return o + d * z;
}
// Index of the cell that contains the given 2D position.
ivec2 getCell(vec2 screenUV, ivec2 cellCount)
{
return ivec2(screenUV * cellCount);
}
// The number of cells in the quad tree at the given level.
ivec2 getCellCount(int level)
{
return textureSize(u_hiz_depth, level);
}
// Returns screen space position of the intersection
// between o + d*t and the closest cell boundary at current HiZ level.
vec3 intersectCellBoundary(
vec3 pos, vec3 dir,
ivec2 cell, ivec2 cellCount,
vec2 crossStep, vec2 crossOffset)
{
vec3 intersection = vec3(0.0);
vec2 index = cell + crossStep;
vec2 boundary = index / vec2(cellCount); // Screen space position of the boundary
boundary += crossOffset;
vec2 delta = boundary - pos.xy;
delta /= dir.xy;
float t = min(delta.x, delta.y);
intersection = intersectDepthPlane(pos, dir, t);
return intersection;
}
bool crossedCellBoundary(ivec2 oldCellIx, ivec2 newCellIx)
{
return any(notEqual(oldCellIx, newCellIx));
}
// Minimum depth of the current cell in the current HiZ level.
float getMinDepthPlane(ivec2 cellIx, int level)
{
return texelFetch(u_hiz_depth, cellIx, level).x;
}
float getMaxTraceDistance(vec3 p, vec3 v)
{
vec3 traceDistances;
if (v.x < 0.0)
traceDistances.x = p.x / (-v.x);
else
traceDistances.x = (1.0 - p.x) / v.x;
if (v.y < 0.0)
traceDistances.y = p.y / (-v.y);
else
traceDistances.y = (1.0 - p.y) / v.y;
if (v.z < 0.0)
traceDistances.z = p.z / (-v.z);
else
traceDistances.z = (1.0 - p.z) / v.z;
return min(traceDistances.x, min(traceDistances.y, traceDistances.z));
}
// p : Screen space position
// v : Screen space reflection direction
// hitPointSS : Returns screen space hit point
// Return value : Whether RT actually hit a surface
bool traceHiZ(vec3 p, vec3 v, out vec2 hitPointSS)
{
const int maxLevel = min(HIZ_MAX_LEVEL, textureQueryLevels(u_hiz_depth) - 1); // Last mip level
float maxTraceDistance = getMaxTraceDistance(p, v);
// Get the cell cross direction and a small offset to enter
// the next cell when doing cell crossing.
vec2 crossStep = vec2(v.x >= 0 ? 1 : -1, v.y >= 0 ? 1 : -1);
vec2 crossOffset = crossStep / u_camera.m_viewport.zw / 128.;
crossStep = clamp(crossStep, 0.0, 1.0);
// Set current ray to the original screen coordinate and depth.
vec3 ray = p;
float minZ = ray.z;
float maxZ = ray.z + v.z * maxTraceDistance;
float deltaZ = maxZ - minZ;
vec3 o = ray;
vec3 d = v * maxTraceDistance;
int level = HIZ_START_LEVEL;
int deepestLevel = level;
#if DEBUG_LINEAR_SEARCH
level = 0;
#endif
uint iterations = 0;
bool isBackwardRay = v.z < 0;
float rayDir = isBackwardRay ? -1.0 : 1.0;
// Cross to next cell s.t. we don't get a self-intersection immediately.
ivec2 startCellCount = getCellCount(level);
ivec2 rayCell = getCell(ray.xy, startCellCount);
ray = intersectCellBoundary(o, d, rayCell, startCellCount, crossStep, crossOffset * 64.);
while (level >= HIZ_STOP_LEVEL && ray.z * rayDir <= maxZ * rayDir &&
iterations < u_hiz_iterations)
{
// Get the cell number of our current ray.
ivec2 cellCount = getCellCount(level);
ivec2 oldCellIx = getCell(ray.xy, cellCount);
// Get the minimum depth plane in which the current ray resides.
float cellMinZ = getMinDepthPlane(oldCellIx, level);
// Intersect only if ray depth is below the minimum depth plane.
vec3 tempRay;
if (cellMinZ > ray.z && !isBackwardRay)
tempRay = intersectDepthPlane(o, d, (cellMinZ - minZ) / deltaZ);
else
tempRay = ray;
ivec2 newCellIx = getCell(tempRay.xy, cellCount);
float thickness = level == 0 ? (ray.z - cellMinZ) : 0;
bool crossed = (isBackwardRay && (cellMinZ > ray.z))
|| (thickness > MAX_THICKNESS) || crossedCellBoundary(oldCellIx, newCellIx);
if (crossed)
{
ray = intersectCellBoundary(o, d, oldCellIx, cellCount, crossStep, crossOffset);
level = min(maxLevel, level + 1);
deepestLevel = max(deepestLevel, level);
#if DEBUG_LINEAR_SEARCH
level = 0;
#endif
}
else
{
ray = tempRay;
level = level - 1;
}
iterations += 1;
}
// Results
//debugDeepestLevel = deepestLevel;
//debugIterations = iterations;
hitPointSS = ray.xy;
return level < HIZ_STOP_LEVEL && iterations < u_hiz_iterations;
}
#endif
void main()
{
#ifdef PBR_ENABLED
float horiz = sampleMeshTexture2(f_material_id, f_uv + u_push_constants.m_displace_direction.xy * 150.).x;
float vert = sampleMeshTexture2(f_material_id, (f_uv.yx + u_push_constants.m_displace_direction.zw * 150.) * vec2(0.9)).x;
vec2 mask = getDisplaceShift(horiz, vert);
mask = (mask + 1.0) * 0.5;
o_displace_mask = mask;
if (u_ssr)
{
float alpha = sampleMeshTexture0(f_material_id, f_uv).a;
if (alpha == 0.0)
{
o_displace_ssr = vec4(0.0);
return;
}
// eye-space position
vec3 xpos = (u_camera.m_view_matrix * f_world_position).xyz;
// eye-space view direction (points from surface toward eye at origin)
vec3 eyedir = -normalize(xpos);
// eye-space normal
vec3 normal = (u_camera.m_view_matrix * vec4(normalize(f_normal), 0)).xyz;
// bail out immediately if normal is facing away from the camera,
// dot(normal, eyedir) <= 0 means back-facing
float NdotV = dot(normal, eyedir);
if (NdotV <= 0.0)
{
o_displace_ssr = vec4(0.0);
return;
}
// compute reflection in eye-space
vec3 reflected = reflect(-eyedir, normal);
// bring it back into world-space
vec3 world_reflection = (u_camera.m_inverse_view_matrix *
vec4(reflected, 0.0)).xyz;
// fallback to skybox
vec4 fallback = texture(u_skybox_texture, world_reflection);
// early exit if normal is facing camera too directly (no meaningful reflection)
if (normal.z < -0.75)
{
o_displace_ssr = fallback;
return;
}
vec4 result;
vec2 viewport_scale = u_camera.m_viewport.zw / u_camera.m_screensize;
vec2 viewport_offset = u_camera.m_viewport.xy / u_camera.m_screensize;
bool hit = true;
vec2 coords;
if (u_hiz_iterations == 0)
{
coords = RayCast(reflected, xpos, u_camera.m_projection_matrix,
viewport_scale, viewport_offset, u_depth);
}
else
{
vec3 positionSS = CalcCoordFromPosition(xpos,
u_camera.m_projection_matrix, vec2(1.0), vec2(0.0));
vec3 positionCS = positionSS;
positionCS.xy = 2.0 * positionCS.xy - 1.0;
vec3 position2VS = xpos + 1000.0 * reflected;
vec4 position2CS = u_camera.m_projection_matrix * vec4(position2VS, 1.0);
position2CS /= position2CS.w;
vec3 position2SS = position2CS.xyz;
position2SS.xy = vec2(0.5) + 0.5 * position2SS.xy;
vec3 reflectionDirSS = normalize(position2SS - positionSS);
// Trace HiZ to find the hit point.
hit = traceHiZ(positionSS, reflectionDirSS, coords);
coords = coords * viewport_scale + viewport_offset;
}
vec2 viewport_coords = (coords - viewport_offset) / viewport_scale;
if (!hit || viewport_coords.x < 0. || viewport_coords.x > 1. ||
viewport_coords.y < 0. || viewport_coords.y > 1.)
{
result = fallback;
}
else
{
result = texture(u_displace_color, coords);
float edge = GetEdgeFade(coords, viewport_scale, viewport_offset);
//float fresnel = pow(1.0 - NdotV, 2.0);
float fresnel = (1.0 - NdotV) * (1.0 - NdotV);
float blend_weight = edge * fresnel;
result = mix(fallback, result, blend_weight);
}
o_displace_ssr = result;
}
#endif
}