diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a8862d..fa68d7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,21 @@ set(ZDRAY_SOURCES src/lightmapper/gpuraytracer.h src/lightmapper/stacktrace.cpp src/lightmapper/stacktrace.h + src/lightmapper/glsl/binding_lightmapper.glsl.h + src/lightmapper/glsl/binding_raytrace.glsl.h + src/lightmapper/glsl/binding_textures.glsl.h + src/lightmapper/glsl/frag_blur.glsl.h + src/lightmapper/glsl/frag_copy.glsl.h + src/lightmapper/glsl/frag_raytrace.glsl.h + src/lightmapper/glsl/frag_resolve.glsl.h + src/lightmapper/glsl/polyfill_rayquery.glsl.h + src/lightmapper/glsl/trace_ambient_occlusion.glsl.h + src/lightmapper/glsl/trace_levelmesh.glsl.h + src/lightmapper/glsl/trace_light.glsl.h + src/lightmapper/glsl/trace_sunlight.glsl.h + src/lightmapper/glsl/vert_copy.glsl.h + src/lightmapper/glsl/vert_raytrace.glsl.h + src/lightmapper/glsl/vert_screenquad.glsl.h src/models/model.cpp src/models/model.h src/models/model_md2.h @@ -97,6 +112,7 @@ source_group("src\\Platform" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sr source_group("src\\Platform\\Windows" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/.+") source_group("src\\Wad" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/wad/.+") source_group("src\\Lightmapper" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/lightmapper/.+") +source_group("src\\Lightmapper\\glsl" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/lightmapper/glsl/.+") source_group("src\\Models" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/models/.+") source_group("thirdparty" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/.+") diff --git a/src/lightmapper/glsl/binding_lightmapper.glsl.h b/src/lightmapper/glsl/binding_lightmapper.glsl.h new file mode 100644 index 0000000..2ed8b7c --- /dev/null +++ b/src/lightmapper/glsl/binding_lightmapper.glsl.h @@ -0,0 +1,91 @@ +static const char* binding_lightmapper_glsl = R"glsl( + +layout(set = 0, binding = 0) uniform Uniforms +{ + vec3 SunDir; + float Padding1; + vec3 SunColor; + float SunIntensity; +}; + +struct SurfaceInfo +{ + vec3 Normal; + float Sky; + float SamplingDistance; + uint PortalIndex; + int TextureIndex; + float Alpha; +}; + +struct PortalInfo +{ + mat4 Transformation; +}; + +struct LightInfo +{ + vec3 Origin; + float Padding0; + vec3 RelativeOrigin; + float Padding1; + float Radius; + float Intensity; + float InnerAngleCos; + float OuterAngleCos; + vec3 SpotDir; + float Padding2; + vec3 Color; + float Padding3; +}; + +layout(set = 0, binding = 1) buffer SurfaceIndexBuffer { uint surfaceIndices[]; }; +layout(set = 0, binding = 2) buffer SurfaceBuffer { SurfaceInfo surfaces[]; }; +layout(set = 0, binding = 3) buffer LightBuffer { LightInfo lights[]; }; +layout(set = 0, binding = 4) buffer PortalBuffer { PortalInfo portals[]; }; + +#if defined(USE_DRAWINDIRECT) + +struct LightmapRaytracePC +{ + uint LightStart; + uint LightEnd; + int SurfaceIndex; + int PushPadding1; + vec3 WorldToLocal; + float TextureSize; + vec3 ProjLocalToU; + float PushPadding2; + vec3 ProjLocalToV; + float PushPadding3; + float TileX; + float TileY; + float TileWidth; + float TileHeight; +}; + +layout(std430, set = 0, binding = 5) buffer ConstantsBuffer { LightmapRaytracePC constants[]; }; + +#else + +layout(push_constant) uniform LightmapRaytracePC +{ + uint LightStart; + uint LightEnd; + int SurfaceIndex; + int PushPadding1; + vec3 WorldToLocal; + float TextureSize; + vec3 ProjLocalToU; + float PushPadding2; + vec3 ProjLocalToV; + float PushPadding3; + float TileX; + float TileY; + float TileWidth; + float TileHeight; +}; + +#endif + +)glsl"; diff --git a/src/lightmapper/glsl/binding_raytrace.glsl.h b/src/lightmapper/glsl/binding_raytrace.glsl.h new file mode 100644 index 0000000..8032abf --- /dev/null +++ b/src/lightmapper/glsl/binding_raytrace.glsl.h @@ -0,0 +1,42 @@ +static const char* binding_raytrace_glsl = R"glsl( + +#if defined(USE_RAYQUERY) + +layout(set = 1, binding = 0) uniform accelerationStructureEXT acc; + +#else + +struct CollisionNode +{ + vec3 center; + float padding1; + vec3 extents; + float padding2; + int left; + int right; + int element_index; + int padding3; +}; + +layout(std430, set = 1, binding = 0) buffer NodeBuffer +{ + int nodesRoot; + int nodebufferPadding1; + int nodebufferPadding2; + int nodebufferPadding3; + CollisionNode nodes[]; +}; + +#endif + +struct SurfaceVertex +{ + vec4 pos; + vec2 uv; + float Padding1, Padding2; +}; + +layout(std430, set = 1, binding = 1) buffer VertexBuffer { SurfaceVertex vertices[]; }; +layout(std430, set = 1, binding = 2) buffer ElementBuffer { int elements[]; }; + +)glsl"; diff --git a/src/lightmapper/glsl/binding_textures.glsl.h b/src/lightmapper/glsl/binding_textures.glsl.h new file mode 100644 index 0000000..9df5d90 --- /dev/null +++ b/src/lightmapper/glsl/binding_textures.glsl.h @@ -0,0 +1,5 @@ +static const char* binding_textures_glsl = R"glsl( + +layout(set = 2, binding = 0) uniform sampler2D textures[]; + +)glsl"; diff --git a/src/lightmapper/glsl/frag_blur.glsl.h b/src/lightmapper/glsl/frag_blur.glsl.h new file mode 100644 index 0000000..913d5b8 --- /dev/null +++ b/src/lightmapper/glsl/frag_blur.glsl.h @@ -0,0 +1,35 @@ +static const char* frag_blur_glsl = R"glsl( + +layout(set = 0, binding = 0) uniform sampler2D tex; + +layout(location = 0) in vec2 TexCoord; +layout(location = 0) out vec4 fragcolor; + +vec4 centerFragColor; + +vec4 clampedSample(vec4 f) +{ + return f != vec4(0, 0, 0, 0) ? f : centerFragColor; +} + +void main() +{ + ivec2 size = textureSize(tex, 0); + vec2 texCoord = gl_FragCoord.xy / vec2(size); + + centerFragColor = textureOffset(tex, texCoord, ivec2(0, 0)); + +#if defined(BLUR_HORIZONTAL) + fragcolor = + centerFragColor * 0.5 + + clampedSample(textureOffset(tex, texCoord, ivec2( 1, 0))) * 0.25 + + clampedSample(textureOffset(tex, texCoord, ivec2(-1, 0))) * 0.25; +#else + fragcolor = + centerFragColor * 0.5 + + clampedSample(textureOffset(tex, texCoord, ivec2(0, 1))) * 0.25 + + clampedSample(textureOffset(tex, texCoord, ivec2(0,-1))) * 0.25; +#endif +} + +)glsl"; diff --git a/src/lightmapper/glsl/frag_copy.glsl.h b/src/lightmapper/glsl/frag_copy.glsl.h new file mode 100644 index 0000000..47edc0b --- /dev/null +++ b/src/lightmapper/glsl/frag_copy.glsl.h @@ -0,0 +1,13 @@ +static const char* frag_copy_glsl = R"glsl( + +layout(set = 0, binding = 0) uniform sampler2D Tex; + +layout(location = 0) in vec2 TexCoord; +layout(location = 0) out vec4 FragColor; + +void main() +{ + FragColor = texture(Tex, TexCoord); +} + +)glsl"; diff --git a/src/lightmapper/glsl/frag_raytrace.glsl.h b/src/lightmapper/glsl/frag_raytrace.glsl.h new file mode 100644 index 0000000..93ea402 --- /dev/null +++ b/src/lightmapper/glsl/frag_raytrace.glsl.h @@ -0,0 +1,50 @@ +static const char* frag_raytrace_glsl = R"glsl( + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(USE_DRAWINDIRECT) + +layout(location = 1) in flat int InstanceIndex; + +#endif + +layout(location = 0) centroid in vec3 worldpos; +layout(location = 0) out vec4 fragcolor; + +void main() +{ +#if defined(USE_DRAWINDIRECT) + uint LightStart = constants[InstanceIndex].LightStart; + uint LightEnd = constants[InstanceIndex].LightEnd; + int SurfaceIndex = constants[InstanceIndex].SurfaceIndex; +#endif + + vec3 normal = surfaces[SurfaceIndex].Normal; + vec3 origin = worldpos + normal * 0.1; + +#if defined(USE_SUNLIGHT) + vec3 incoming = TraceSunLight(origin, normal, SurfaceIndex); +#else + vec3 incoming = vec3(0.0); +#endif + + for (uint j = LightStart; j < LightEnd; j++) + { + incoming += TraceLight(origin, normal, lights[j], SurfaceIndex); + } + +#if defined(USE_AO) + incoming.rgb *= TraceAmbientOcclusion(origin, normal); +#endif + + fragcolor = vec4(incoming, 1.0); +} + +)glsl"; diff --git a/src/lightmapper/glsl/frag_resolve.glsl.h b/src/lightmapper/glsl/frag_resolve.glsl.h new file mode 100644 index 0000000..028f0fb --- /dev/null +++ b/src/lightmapper/glsl/frag_resolve.glsl.h @@ -0,0 +1,49 @@ +static const char* frag_resolve_glsl = R"glsl( + +layout(set = 0, binding = 0) uniform sampler2DMS tex; + +layout(location = 0) in vec2 TexCoord; +layout(location = 0) out vec4 fragcolor; + +vec4 samplePixel(ivec2 pos, int count) +{ + vec4 c = vec4(0.0); + for (int i = 0; i < count; i++) + { + c += texelFetch(tex, pos, i); + } + if (c.a > 0.0) + c /= c.a; + return c; +} + +void main() +{ + int count = textureSamples(tex); + ivec2 size = textureSize(tex); + ivec2 pos = ivec2(gl_FragCoord.xy); + + vec4 c = samplePixel(pos, count); + if (c.a == 0.0) + { + for (int y = -1; y <= 1; y++) + { + for (int x = -1; x <= 1; x++) + { + if (x != 0 || y != 0) + { + ivec2 pos2; + pos2.x = clamp(pos.x + x, 0, size.x - 1); + pos2.y = clamp(pos.y + y, 0, size.y - 1); + c += samplePixel(pos2, count); + } + } + } + if (c.a > 0.0) + c /= c.a; + } + + fragcolor = c; +} + +)glsl"; diff --git a/src/lightmapper/glsl/polyfill_rayquery.glsl.h b/src/lightmapper/glsl/polyfill_rayquery.glsl.h new file mode 100644 index 0000000..2ca10b4 --- /dev/null +++ b/src/lightmapper/glsl/polyfill_rayquery.glsl.h @@ -0,0 +1,269 @@ +static const char* polyfill_rayquery_glsl = R"glsl( + +#if defined(USE_RAYQUERY) + +int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float t, out vec3 primitiveWeights) +{ + rayQueryEXT rayQuery; + rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsCullBackFacingTrianglesEXT, 0xFF, origin, tmin, dir, tmax); + + while(rayQueryProceedEXT(rayQuery)) + { + if (rayQueryGetIntersectionTypeEXT(rayQuery, false) == gl_RayQueryCommittedIntersectionTriangleEXT) + { + rayQueryConfirmIntersectionEXT(rayQuery); + } + } + + if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT) + { + t = rayQueryGetIntersectionTEXT(rayQuery, true); + + primitiveWeights.xy = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); + primitiveWeights.z = 1.0 - primitiveWeights.x - primitiveWeights.y; + + return rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true); + } + else + { + t = tmax; + return -1; + } +} + +/* +bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) +{ + rayQueryEXT rayQuery; + rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsCullBackFacingTrianglesEXT, 0xFF, origin, tmin, dir, tmax); + while(rayQueryProceedEXT(rayQuery)) { } + return rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT; +} +*/ + +#else + +struct RayBBox +{ + vec3 start, end; + vec3 c, w, v; +}; + +RayBBox create_ray(vec3 ray_start, vec3 ray_end) +{ + RayBBox ray; + ray.start = ray_start; + ray.end = ray_end; + ray.c = (ray_start + ray_end) * 0.5; + ray.w = ray_end - ray.c; + ray.v = abs(ray.w); + return ray; +} + +bool overlap_bv_ray(RayBBox ray, int a) +{ + vec3 v = ray.v; + vec3 w = ray.w; + vec3 h = nodes[a].extents; + vec3 c = ray.c - nodes[a].center; + + if (abs(c.x) > v.x + h.x || + abs(c.y) > v.y + h.y || + abs(c.z) > v.z + h.z) + { + return false; + } + + if (abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y || + abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x || + abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x) + { + return false; + } + + return true; +} + +#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT_EPSILON != 1.0 + +float intersect_triangle_ray(RayBBox ray, int a, out float barycentricB, out float barycentricC) +{ + int start_element = nodes[a].element_index; + + vec3 p[3]; + p[0] = vertices[elements[start_element]].pos.xyz; + p[1] = vertices[elements[start_element + 1]].pos.xyz; + p[2] = vertices[elements[start_element + 2]].pos.xyz; + + // Moeller-Trumbore ray-triangle intersection algorithm: + + vec3 D = ray.end - ray.start; + + // Find vectors for two edges sharing p[0] + vec3 e1 = p[1] - p[0]; + vec3 e2 = p[2] - p[0]; + + // Begin calculating determinant - also used to calculate u parameter + vec3 P = cross(D, e2); + float det = dot(e1, P); + + // Backface check + if (det < 0.0f) + return 1.0f; + + // If determinant is near zero, ray lies in plane of triangle + if (det > -FLT_EPSILON && det < FLT_EPSILON) + return 1.0f; + + float inv_det = 1.0f / det; + + // Calculate distance from p[0] to ray origin + vec3 T = ray.start - p[0]; + + // Calculate u parameter and test bound + float u = dot(T, P) * inv_det; + + // Check if the intersection lies outside of the triangle + if (u < 0.f || u > 1.f) + return 1.0f; + + // Prepare to test v parameter + vec3 Q = cross(T, e1); + + // Calculate V parameter and test bound + float v = dot(D, Q) * inv_det; + + // The intersection lies outside of the triangle + if (v < 0.f || u + v > 1.f) + return 1.0f; + + float t = dot(e2, Q) * inv_det; + if (t <= FLT_EPSILON) + return 1.0f; + + // Return hit location on triangle in barycentric coordinates + barycentricB = u; + barycentricC = v; + return t; +} + +bool is_leaf(int node_index) +{ + return nodes[node_index].element_index != -1; +} + +/* +bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) +{ + if (tmax <= 0.0f) + return false; + + RayBBox ray = create_ray(origin, origin + dir * tmax); + tmin /= tmax; + + int stack[64]; + int stackIndex = 0; + stack[stackIndex++] = nodesRoot; + do + { + int a = stack[--stackIndex]; + if (overlap_bv_ray(ray, a)) + { + if (is_leaf(a)) + { + float baryB, baryC; + float t = intersect_triangle_ray(ray, a, baryB, baryC); + if (t >= tmin && t < 1.0) + { + return true; + } + } + else + { + stack[stackIndex++] = nodes[a].right; + stack[stackIndex++] = nodes[a].left; + } + } + } while (stackIndex > 0); + return false; +} +*/ + +struct TraceHit +{ + float fraction; + int triangle; + float b; + float c; +}; + +TraceHit find_first_hit(RayBBox ray) +{ + TraceHit hit; + hit.fraction = 1.0; + hit.triangle = -1; + hit.b = 0.0; + hit.c = 0.0; + + int stack[64]; + int stackIndex = 0; + stack[stackIndex++] = nodesRoot; + do + { + int a = stack[--stackIndex]; + if (overlap_bv_ray(ray, a)) + { + if (is_leaf(a)) + { + float baryB, baryC; + float t = intersect_triangle_ray(ray, a, baryB, baryC); + if (t < hit.fraction) + { + hit.fraction = t; + hit.triangle = nodes[a].element_index / 3; + hit.b = baryB; + hit.c = baryC; + } + } + else + { + stack[stackIndex++] = nodes[a].right; + stack[stackIndex++] = nodes[a].left; + } + } + } while (stackIndex > 0); + return hit; +} + +int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float tparam, out vec3 primitiveWeights) +{ + // Perform segmented tracing to keep the ray AABB box smaller + vec3 ray_start = origin; + vec3 ray_end = origin + dir * tmax; + vec3 ray_dir = dir; + float tracedist = tmax; + float segmentlen = max(200.0, tracedist / 20.0); + for (float t = 0.0; t < tracedist; t += segmentlen) + { + float segstart = t; + float segend = min(t + segmentlen, tracedist); + + RayBBox ray = create_ray(ray_start + ray_dir * segstart, ray_start + ray_dir * segend); + TraceHit hit = find_first_hit(ray); + if (hit.fraction < 1.0) + { + tparam = hit.fraction = segstart * (1.0 - hit.fraction) + segend * hit.fraction; + primitiveWeights.x = hit.b; + primitiveWeights.y = hit.c; + primitiveWeights.z = 1.0 - hit.b - hit.c; + return hit.triangle; + } + } + + tparam = tracedist; + return -1; +} + +#endif + +)glsl"; diff --git a/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h b/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h new file mode 100644 index 0000000..760b844 --- /dev/null +++ b/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h @@ -0,0 +1,57 @@ +static const char* trace_ambient_occlusion_glsl = R"glsl( + +vec2 Hammersley(uint i, uint N); +float RadicalInverse_VdC(uint bits); + +float TraceAmbientOcclusion(vec3 origin, vec3 normal) +{ + const float minDistance = 0.05; + const float aoDistance = 100; + const int SampleCount = 128; + + vec3 N = normal; + vec3 up = abs(N.x) < abs(N.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + float ambience = 0.0f; + for (uint i = 0; i < SampleCount; i++) + { + vec2 Xi = Hammersley(i, SampleCount); + vec3 H = normalize(vec3(Xi.x * 2.0f - 1.0f, Xi.y * 2.0f - 1.0f, 1.5 - length(Xi))); + vec3 L = H.x * tangent + H.y * bitangent + H.z * N; + + float hitDistance; + int primitiveID = TraceFirstHitTriangleT(origin, minDistance, L, aoDistance, hitDistance); + if (primitiveID != -1) + { + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + if (surface.Sky == 0.0) + { + ambience += clamp(hitDistance / aoDistance, 0.0, 1.0); + } + } + else + { + ambience += 1.0; + } + } + return ambience / float(SampleCount); +} + +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i) / float(N), RadicalInverse_VdC(i)); +} + +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10f; // / 0x100000000 +} + +)glsl"; diff --git a/src/lightmapper/glsl/trace_levelmesh.glsl.h b/src/lightmapper/glsl/trace_levelmesh.glsl.h new file mode 100644 index 0000000..b8fb4a6 --- /dev/null +++ b/src/lightmapper/glsl/trace_levelmesh.glsl.h @@ -0,0 +1,139 @@ +static const char* trace_levelmesh_glsl = R"glsl( + +vec4 rayColor; + +vec4 alphaBlend(vec4 a, vec4 b); +vec4 BeerLambertSimple(vec4 medium, vec4 ray_color); +vec4 blend(vec4 a, vec4 b); + +int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t) +{ + int primitiveID = -1; + vec3 primitiveWeights; + for (int i = 0; i < 4; i++) + { + primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); + + if(primitiveID < 0) + { + break; + } + + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + + if(surface.PortalIndex == 0) + { + int index = primitiveID * 3; + vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; + + if (surface.TextureIndex < 0) + { + break; + } + + vec4 color = texture(textures[surface.TextureIndex], uv); + color.w *= surface.Alpha; + + if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) + { + break; + } + + rayColor = blend(color, rayColor); + } + + // Portal was hit: Apply transformation onto the ray + mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; + + origin = (transformationMatrix * vec4(origin + dir * t, 1.0)).xyz; + dir = (transformationMatrix * vec4(dir, 0.0)).xyz; + tmax -= t; + } + return primitiveID; +} + +int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax) +{ + float t; + return TraceFirstHitTriangleT(origin, tmin, dir, tmax, t); +} + +bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) +{ + return TraceFirstHitTriangle(origin, tmin, dir, tmax) >= 0; +} + +bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax) +{ + int primitiveID; + float t; + vec3 primitiveWeights; + for (int i = 0; i < 4; i++) + { + t = tmax; + primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); + + origin += dir * t; + tmax -= t; + + if(primitiveID < 0) + { + // We didn't hit anything + break; + } + + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + + if (surface.PortalIndex == 0) + { + int index = primitiveID * 3; + vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; + + if (surface.TextureIndex < 0) + { + break; + } + + vec4 color = texture(textures[surface.TextureIndex], uv); + color.w *= surface.Alpha; + + if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) + { + break; + } + + rayColor = blend(color, rayColor); + } + + if(dot(surface.Normal, dir) >= 0.0) + { + continue; + } + + mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; + origin = (transformationMatrix * vec4(origin, 1.0)).xyz; + dir = (transformationMatrix * vec4(dir, 0.0)).xyz; + } + + return distance(origin, target) <= 1.0; +} + +vec4 alphaBlend(vec4 a, vec4 b) +{ + float na = a.w + b.w * (1.0 - a.w); + return vec4((a.xyz * a.w + b.xyz * b.w * (1.0 - a.w)) / na, max(0.001, na)); +} + +vec4 BeerLambertSimple(vec4 medium, vec4 ray_color) // based on Beer-Lambert law +{ + float z = medium.w; + ray_color.rgb *= exp(-medium.rgb * vec3(z)); + return ray_color; +} + +vec4 blend(vec4 a, vec4 b) +{ + return BeerLambertSimple(vec4(1.0 - a.rgb, a.w), b); +} + +)glsl"; diff --git a/src/lightmapper/glsl/trace_light.glsl.h b/src/lightmapper/glsl/trace_light.glsl.h new file mode 100644 index 0000000..77bd6a4 --- /dev/null +++ b/src/lightmapper/glsl/trace_light.glsl.h @@ -0,0 +1,79 @@ +static const char* trace_light_glsl = R"glsl( + +vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); + +vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) +{ + const float minDistance = 0.01; + vec3 incoming = vec3(0.0); + float dist = distance(light.RelativeOrigin, origin); + if (dist > minDistance && dist < light.Radius) + { + vec3 dir = normalize(light.RelativeOrigin - origin); + + float distAttenuation = max(1.0 - (dist / light.Radius), 0.0); + float angleAttenuation = 1.0f; + if (surfaceIndex >= 0) + { + angleAttenuation = max(dot(normal, dir), 0.0); + } + float spotAttenuation = 1.0; + if (light.OuterAngleCos > -1.0) + { + float cosDir = dot(dir, light.SpotDir); + spotAttenuation = smoothstep(light.OuterAngleCos, light.InnerAngleCos, cosDir); + spotAttenuation = max(spotAttenuation, 0.0); + } + + float attenuation = distAttenuation * angleAttenuation * spotAttenuation; + if (attenuation > 0.0) + { +#if defined(USE_SOFTSHADOWS) + + vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 xdir = normalize(cross(dir, v)); + vec3 ydir = cross(dir, xdir); + + float lightsize = 10; + int step_count = 10; + for (int i = 0; i < step_count; i++) + { + vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; + vec3 pos = light.Origin + xdir * gridoffset.x + ydir * gridoffset.y; + + rayColor = vec4(light.Color.rgb, 1.0); + if (TracePoint(origin, pos, minDistance, normalize(pos - origin), distance(origin, pos))) + { + incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity) / float(step_count); + } + } + +#else + rayColor = vec4(light.Color.rgb, 1.0); + if(TracePoint(origin, light.Origin, minDistance, dir, dist)) + { + incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity); + } +#endif + } + } + + return incoming; +} + +vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi) +{ + const float goldenAngle = radians(180.0) * (3.0 - sqrt(5.0)); + float sampleIndexF = float(sampleIndex); + float sampleCountF = float(sampleCount); + + float r = sqrt((sampleIndexF + 0.5) / sampleCountF); // Assuming index and count are positive + float theta = sampleIndexF * goldenAngle + phi; + + float sine = sin(theta); + float cosine = cos(theta); + + return vec2(cosine, sine) * r; +} + +)glsl"; diff --git a/src/lightmapper/glsl/trace_sunlight.glsl.h b/src/lightmapper/glsl/trace_sunlight.glsl.h new file mode 100644 index 0000000..60083b7 --- /dev/null +++ b/src/lightmapper/glsl/trace_sunlight.glsl.h @@ -0,0 +1,60 @@ +static const char* trace_sunlight_glsl = R"glsl( + +vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); + +vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) +{ + float angleAttenuation = 1.0f; + if (surfaceIndex >= 0) + { + angleAttenuation = max(dot(normal, SunDir), 0.0); + if (angleAttenuation == 0.0) + return vec3(0.0); + } + + const float minDistance = 0.01; + vec3 incoming = vec3(0.0); + const float dist = 32768.0; + +#if defined(USE_SOFTSHADOWS) + + vec3 target = origin + SunDir * dist; + vec3 dir = SunDir; + vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 xdir = normalize(cross(dir, v)); + vec3 ydir = cross(dir, xdir); + + float lightsize = 100; + int step_count = 10; + for (int i = 0; i < step_count; i++) + { + vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; + vec3 pos = target + xdir * gridoffset.x + ydir * gridoffset.y; + + rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); + + int primitiveID = TraceFirstHitTriangle(origin, minDistance, normalize(pos - origin), dist); + if (primitiveID != -1) + { + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + incoming.rgb += rayColor.rgb * rayColor.w * surface.Sky / float(step_count); + } + } + +#else + + rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); + + int primitiveID = TraceFirstHitTriangle(origin, minDistance, SunDir, dist); + if (primitiveID != -1) + { + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + incoming.rgb = rayColor.rgb * rayColor.w * surface.Sky; + } + +#endif + + return incoming * angleAttenuation; +} + +)glsl"; diff --git a/src/lightmapper/glsl/vert_copy.glsl.h b/src/lightmapper/glsl/vert_copy.glsl.h new file mode 100644 index 0000000..1302f8d --- /dev/null +++ b/src/lightmapper/glsl/vert_copy.glsl.h @@ -0,0 +1,44 @@ +static const char* vert_copy_glsl = R"glsl( + +layout(push_constant) uniform PushConstants +{ + int SrcTexSize; + int DestTexSize; + int Padding1; + int Padding2; +}; + +struct TileCopy +{ + ivec2 SrcPos; + ivec2 DestPos; + ivec2 TileSize; + int Padding1, Padding2; +}; + +layout(std430, set = 0, binding = 1) buffer CopyBuffer +{ + TileCopy tiles[]; +}; + +layout(location = 0) out vec2 TexCoord; + +vec2 positions[4] = vec2[]( + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0) +); + +void main() +{ + TileCopy tile = tiles[gl_InstanceIndex]; + vec2 uv = positions[gl_VertexIndex]; + vec2 src = (vec2(tile.SrcPos) + uv * vec2(tile.TileSize)) / float(SrcTexSize); + vec2 dest = (vec2(tile.DestPos) + uv * vec2(tile.TileSize)) / float(DestTexSize); + + gl_Position = vec4(dest * 2.0 - 1.0, 0.0, 1.0); + TexCoord = src; +} + +)glsl"; diff --git a/src/lightmapper/glsl/vert_raytrace.glsl.h b/src/lightmapper/glsl/vert_raytrace.glsl.h new file mode 100644 index 0000000..04e889e --- /dev/null +++ b/src/lightmapper/glsl/vert_raytrace.glsl.h @@ -0,0 +1,44 @@ +static const char* vert_raytrace_glsl = R"glsl( + +#include + +layout(location = 0) in vec3 aPosition; +layout(location = 0) out vec3 worldpos; + +#if defined(USE_DRAWINDIRECT) +layout(location = 1) out flat int InstanceIndex; +#endif + +void main() +{ +#if defined(USE_DRAWINDIRECT) + vec3 WorldToLocal = constants[gl_InstanceIndex].WorldToLocal; + float TextureSize = constants[gl_InstanceIndex].TextureSize; + vec3 ProjLocalToU = constants[gl_InstanceIndex].ProjLocalToU; + vec3 ProjLocalToV = constants[gl_InstanceIndex].ProjLocalToV; + float TileX = constants[gl_InstanceIndex].TileX; + float TileY = constants[gl_InstanceIndex].TileY; + float TileWidth = constants[gl_InstanceIndex].TileWidth; + float TileHeight = constants[gl_InstanceIndex].TileHeight; + InstanceIndex = gl_InstanceIndex; +#endif + + worldpos = aPosition; + + // Project to position relative to tile + vec3 localPos = aPosition - WorldToLocal; + float x = dot(localPos, ProjLocalToU); + float y = dot(localPos, ProjLocalToV); + + // Find the position in the output texture + gl_Position = vec4(vec2(TileX + x, TileY + y) / TextureSize * 2.0 - 1.0, 0.0, 1.0); + + // Clip all surfaces to the edge of the tile (effectly we are applying a viewport/scissor to the tile) + // Note: the tile has a 1px border around it that we also draw into + gl_ClipDistance[0] = x + 1.0; + gl_ClipDistance[1] = y + 1.0; + gl_ClipDistance[2] = TileWidth + 1.0 - x; + gl_ClipDistance[3] = TileHeight + 1.0 - y; +} + +)glsl"; diff --git a/src/lightmapper/glsl/vert_screenquad.glsl.h b/src/lightmapper/glsl/vert_screenquad.glsl.h new file mode 100644 index 0000000..d53e709 --- /dev/null +++ b/src/lightmapper/glsl/vert_screenquad.glsl.h @@ -0,0 +1,23 @@ +static const char* vert_screenquad_glsl = R"glsl( + +layout(location = 0) out vec2 TexCoord; + +vec2 positions[3] = vec2[]( + vec2(-1.0, -1.0), + vec2( 3.0, -1.0), + vec2(-1.0, 3.0) +); + +vec2 uvs[3] = vec2[]( + vec2(0.0, 0.0), + vec2(2.0, 0.0), + vec2(0.0, 2.0) +); + +void main() +{ + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + TexCoord = uvs[gl_VertexIndex]; +} + +)glsl"; diff --git a/src/lightmapper/vk_lightmap.cpp b/src/lightmapper/vk_lightmap.cpp index 91f300e..714e15c 100644 --- a/src/lightmapper/vk_lightmap.cpp +++ b/src/lightmapper/vk_lightmap.cpp @@ -5,6 +5,23 @@ #include "zvulkan/vulkanbuilders.h" #include "Framework/halffloat.h" #include "framework/zstring.h" +#include + +#include "glsl/binding_lightmapper.glsl.h" +#include "glsl/binding_raytrace.glsl.h" +#include "glsl/binding_textures.glsl.h" +#include "glsl/frag_blur.glsl.h" +#include "glsl/frag_copy.glsl.h" +#include "glsl/frag_raytrace.glsl.h" +#include "glsl/frag_resolve.glsl.h" +#include "glsl/polyfill_rayquery.glsl.h" +#include "glsl/trace_ambient_occlusion.glsl.h" +#include "glsl/trace_levelmesh.glsl.h" +#include "glsl/trace_light.glsl.h" +#include "glsl/trace_sunlight.glsl.h" +#include "glsl/vert_copy.glsl.h" +#include "glsl/vert_raytrace.glsl.h" +#include "glsl/vert_screenquad.glsl.h" #define USE_DRAWINDIRECT @@ -626,9 +643,30 @@ int VkLightmap::GetRaytracePipelineIndex() FString VkLightmap::LoadPrivateShaderLump(const char* lumpname) { - // To do: load the shader + static std::map sources = + { + { "shaders/lightmap/binding_lightmapper.glsl", binding_lightmapper_glsl }, + { "shaders/lightmap/binding_raytrace.glsl", binding_raytrace_glsl }, + { "shaders/lightmap/binding_textures.glsl", binding_textures_glsl }, + { "shaders/lightmap/frag_blur.glsl", frag_blur_glsl }, + { "shaders/lightmap/frag_copy.glsl", frag_copy_glsl }, + { "shaders/lightmap/frag_raytrace.glsl", frag_raytrace_glsl }, + { "shaders/lightmap/frag_resolve.glsl", frag_resolve_glsl }, + { "shaders/lightmap/polyfill_rayquery.glsl", polyfill_rayquery_glsl }, + { "shaders/lightmap/trace_ambient_occlusion.glsl", trace_ambient_occlusion_glsl }, + { "shaders/lightmap/trace_levelmesh.glsl", trace_levelmesh_glsl }, + { "shaders/lightmap/trace_light.glsl", trace_light_glsl }, + { "shaders/lightmap/trace_sunlight.glsl", trace_sunlight_glsl }, + { "shaders/lightmap/vert_copy.glsl", vert_copy_glsl }, + { "shaders/lightmap/vert_raytrace.glsl", vert_raytrace_glsl }, + { "shaders/lightmap/vert_screenquad.glsl", vert_screenquad_glsl } + }; - return FString(); + auto it = sources.find(lumpname); + if (it != sources.end()) + return it->second; + else + return FString(); } ShaderIncludeResult VkLightmap::OnInclude(FString headerName, FString includerName, size_t depth, bool system)