Split shaders into three types (light, sun, bounce)

This commit is contained in:
Magnus Norddahl 2021-11-07 04:05:19 +01:00
parent 9ce4c027bd
commit 8df36944eb
16 changed files with 915 additions and 595 deletions

View file

@ -170,9 +170,15 @@ set( SOURCES
src/lightmap/stacktrace.h
src/lightmap/gpuraytracer.cpp
src/lightmap/gpuraytracer.h
src/lightmap/glsl_closesthit.h
src/lightmap/glsl_miss.h
src/lightmap/glsl_raygen.h
src/lightmap/glsl_rchit_bounce.h
src/lightmap/glsl_rchit_light.h
src/lightmap/glsl_rchit_sun.h
src/lightmap/glsl_rgen_bounce.h
src/lightmap/glsl_rgen_light.h
src/lightmap/glsl_rgen_sun.h
src/lightmap/glsl_rmiss_bounce.h
src/lightmap/glsl_rmiss_light.h
src/lightmap/glsl_rmiss_sun.h
src/math/angle.cpp
src/math/bounds.cpp
src/math/mathlib.cpp

View file

@ -1,139 +0,0 @@
static const char* glsl_raygen = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
bool isSkyRay;
};
layout(location = 0) rayPayloadEXT hitPayload payload;
layout(set = 0, binding = 0) uniform accelerationStructureEXT acc;
layout(set = 0, binding = 1, rgba32f) uniform image2D positions;
layout(set = 0, binding = 2, rgba32f) uniform image2D normals;
layout(set = 0, binding = 3, rgba32f) uniform image2D outputs;
layout(set = 0, binding = 4) uniform Uniforms
{
vec3 LightOrigin;
float PassType;
float LightRadius;
float LightIntensity;
float LightInnerAngleCos;
float LightOuterAngleCos;
vec3 LightSpotDir;
float SampleDistance;
vec3 LightColor;
float Padding;
};
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
}
vec2 Hammersley(uint i, uint N)
{
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
void main()
{
ivec2 texelPos = ivec2(gl_LaunchIDEXT.xy);
vec4 data0 = imageLoad(positions, texelPos);
vec4 data1 = imageLoad(normals, texelPos);
if (data1 == vec4(0))
return;
vec3 origin = data0.xyz;
vec3 normal = data1.xyz;
vec4 emittance = vec4(0.0);
if (PassType == 1.0)
emittance = imageLoad(outputs, texelPos);
const float minDistance = 0.01;
const uint sample_count = 1024;
payload.isSkyRay = (PassType == 0.0);
if (!payload.isSkyRay)
{
float dist = distance(LightOrigin, origin);
if (dist > minDistance && dist < LightRadius)
{
vec3 dir = normalize(LightOrigin - origin);
float distAttenuation = max(1.0 - (dist / LightRadius), 0.0);
float angleAttenuation = max(dot(normal, dir), 0.0);
float spotAttenuation = 1.0;
if (LightOuterAngleCos > -1.0)
{
float cosDir = dot(dir, LightSpotDir);
spotAttenuation = smoothstep(LightOuterAngleCos, LightInnerAngleCos, cosDir);
spotAttenuation = max(spotAttenuation, 0.0);
}
float attenuation = distAttenuation * angleAttenuation * spotAttenuation;
if (attenuation > 0.0)
{
float shadowAttenuation = 0.0;
vec3 e0 = cross(normal, abs(normal.x) < abs(normal.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0));
vec3 e1 = cross(normal, e0);
e0 = cross(normal, e1);
for (uint i = 0; i < sample_count; i++)
{
vec2 offset = (Hammersley(i, sample_count) - 0.5) * SampleDistance;
vec3 origin2 = origin + offset.x * e0 + offset.y * e1;
float dist2 = distance(LightOrigin, origin2);
vec3 dir2 = normalize(LightOrigin - origin2);
traceRayEXT(acc, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, origin2, minDistance, dir2, dist2, 0);
shadowAttenuation += payload.hitAttenuation;
}
shadowAttenuation *= 1.0 / float(sample_count);
attenuation *= shadowAttenuation;
emittance.rgb += LightColor * (attenuation * LightIntensity);
}
}
}
else
{
vec3 e0 = cross(normal, abs(normal.x) < abs(normal.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0));
vec3 e1 = cross(normal, e0);
e0 = cross(normal, e1);
origin += normal * 0.1;
float attenuation = 0.0;
for (uint i = 0; i < sample_count; i++)
{
vec2 offset = (Hammersley(i, sample_count) - 0.5) * SampleDistance;
vec3 origin2 = origin + offset.x * e0 + offset.y * e1;
float dist2 = 32768.0;
vec3 dir2 = LightSpotDir;
traceRayEXT(acc, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, origin2, minDistance, dir2, dist2, 0);
attenuation += payload.hitAttenuation;
}
attenuation *= 1.0 / float(sample_count);
emittance.rgb += LightColor * (attenuation * LightIntensity);
}
emittance.w += 1.0;
imageStore(outputs, texelPos, emittance);
}
)glsl";

View file

@ -0,0 +1,33 @@
static const char* glsl_rchit_bounce = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
struct SurfaceInfo
{
vec3 Normal;
float EmissiveDistance;
vec3 EmissiveColor;
float EmissiveIntensity;
float Sky;
float Padding0, Padding1, Padding2;
};
layout(location = 0) rayPayloadInEXT hitPayload payload;
layout(set = 0, binding = 5) buffer SurfaceIndexBuffer { int surfaceIndices[]; };
layout(set = 0, binding = 6) buffer SurfaceBuffer { SurfaceInfo surfaces[]; };
void main()
{
// SurfaceInfo surface = surfaces[surfaceIndices[gl_PrimitiveID]];
payload.hitAttenuation = 0.0;
}
)glsl";

View file

@ -0,0 +1,33 @@
static const char* glsl_rchit_light = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
struct SurfaceInfo
{
vec3 Normal;
float EmissiveDistance;
vec3 EmissiveColor;
float EmissiveIntensity;
float Sky;
float Padding0, Padding1, Padding2;
};
layout(location = 0) rayPayloadInEXT hitPayload payload;
layout(set = 0, binding = 5) buffer SurfaceIndexBuffer { int surfaceIndices[]; };
layout(set = 0, binding = 6) buffer SurfaceBuffer { SurfaceInfo surfaces[]; };
void main()
{
//SurfaceInfo surface = surfaces[surfaceIndices[gl_PrimitiveID]];
payload.hitAttenuation = 0.0;
}
)glsl";

View file

@ -1,4 +1,4 @@
static const char* glsl_closesthit = R"glsl(
static const char* glsl_rchit_sun = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
@ -6,7 +6,6 @@ static const char* glsl_closesthit = R"glsl(
struct hitPayload
{
float hitAttenuation;
bool isSkyRay;
};
struct SurfaceInfo
@ -27,15 +26,7 @@ layout(set = 0, binding = 6) buffer SurfaceBuffer { SurfaceInfo surfaces[]; };
void main()
{
SurfaceInfo surface = surfaces[surfaceIndices[gl_PrimitiveID]];
if (!payload.isSkyRay)
{
payload.hitAttenuation = 0.0;
}
else
{
payload.hitAttenuation = surface.Sky;
}
}
)glsl";

View file

@ -0,0 +1,110 @@
static const char* glsl_rgen_bounce = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
layout(location = 0) rayPayloadEXT hitPayload payload;
layout(set = 0, binding = 0) uniform accelerationStructureEXT acc;
layout(set = 0, binding = 1, rgba32f) uniform image2D positions;
layout(set = 0, binding = 2, rgba32f) uniform image2D normals;
layout(set = 0, binding = 3, rgba32f) uniform image2D outputs;
layout(set = 0, binding = 4) uniform Uniforms
{
vec3 LightOrigin;
float PassType;
float LightRadius;
float LightIntensity;
float LightInnerAngleCos;
float LightOuterAngleCos;
vec3 LightSpotDir;
float SampleDistance;
vec3 LightColor;
float Padding;
};
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
}
vec2 Hammersley(uint i, uint N)
{
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
void main()
{
ivec2 texelPos = ivec2(gl_LaunchIDEXT.xy);
vec4 data0 = imageLoad(positions, texelPos);
vec4 data1 = imageLoad(normals, texelPos);
if (data1 == vec4(0))
return;
vec3 origin = data0.xyz;
vec3 normal = data1.xyz;
vec4 emittance = vec4(0.0);
if (PassType == 1.0)
emittance = imageLoad(outputs, texelPos);
const float minDistance = 0.01;
const uint sample_count = 1024;
float dist = distance(LightOrigin, origin);
if (dist > minDistance && dist < LightRadius)
{
vec3 dir = normalize(LightOrigin - origin);
float distAttenuation = max(1.0 - (dist / LightRadius), 0.0);
float angleAttenuation = max(dot(normal, dir), 0.0);
float spotAttenuation = 1.0;
if (LightOuterAngleCos > -1.0)
{
float cosDir = dot(dir, LightSpotDir);
spotAttenuation = smoothstep(LightOuterAngleCos, LightInnerAngleCos, cosDir);
spotAttenuation = max(spotAttenuation, 0.0);
}
float attenuation = distAttenuation * angleAttenuation * spotAttenuation;
if (attenuation > 0.0)
{
float shadowAttenuation = 0.0;
vec3 e0 = cross(normal, abs(normal.x) < abs(normal.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0));
vec3 e1 = cross(normal, e0);
e0 = cross(normal, e1);
for (uint i = 0; i < sample_count; i++)
{
vec2 offset = (Hammersley(i, sample_count) - 0.5) * SampleDistance;
vec3 origin2 = origin + offset.x * e0 + offset.y * e1;
float dist2 = distance(LightOrigin, origin2);
vec3 dir2 = normalize(LightOrigin - origin2);
traceRayEXT(acc, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, origin2, minDistance, dir2, dist2, 0);
shadowAttenuation += payload.hitAttenuation;
}
shadowAttenuation *= 1.0 / float(sample_count);
attenuation *= shadowAttenuation;
emittance.rgb += LightColor * (attenuation * LightIntensity);
}
}
emittance.w += 1.0;
imageStore(outputs, texelPos, emittance);
}
)glsl";

View file

@ -0,0 +1,110 @@
static const char* glsl_rgen_light = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
layout(location = 0) rayPayloadEXT hitPayload payload;
layout(set = 0, binding = 0) uniform accelerationStructureEXT acc;
layout(set = 0, binding = 1, rgba32f) uniform image2D positions;
layout(set = 0, binding = 2, rgba32f) uniform image2D normals;
layout(set = 0, binding = 3, rgba32f) uniform image2D outputs;
layout(set = 0, binding = 4) uniform Uniforms
{
vec3 LightOrigin;
float PassType;
float LightRadius;
float LightIntensity;
float LightInnerAngleCos;
float LightOuterAngleCos;
vec3 LightSpotDir;
float SampleDistance;
vec3 LightColor;
float Padding;
};
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
}
vec2 Hammersley(uint i, uint N)
{
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
void main()
{
ivec2 texelPos = ivec2(gl_LaunchIDEXT.xy);
vec4 data0 = imageLoad(positions, texelPos);
vec4 data1 = imageLoad(normals, texelPos);
if (data1 == vec4(0))
return;
vec3 origin = data0.xyz;
vec3 normal = data1.xyz;
vec4 emittance = vec4(0.0);
if (PassType == 1.0)
emittance = imageLoad(outputs, texelPos);
const float minDistance = 0.01;
const uint sample_count = 1024;
float dist = distance(LightOrigin, origin);
if (dist > minDistance && dist < LightRadius)
{
vec3 dir = normalize(LightOrigin - origin);
float distAttenuation = max(1.0 - (dist / LightRadius), 0.0);
float angleAttenuation = max(dot(normal, dir), 0.0);
float spotAttenuation = 1.0;
if (LightOuterAngleCos > -1.0)
{
float cosDir = dot(dir, LightSpotDir);
spotAttenuation = smoothstep(LightOuterAngleCos, LightInnerAngleCos, cosDir);
spotAttenuation = max(spotAttenuation, 0.0);
}
float attenuation = distAttenuation * angleAttenuation * spotAttenuation;
if (attenuation > 0.0)
{
float shadowAttenuation = 0.0;
vec3 e0 = cross(normal, abs(normal.x) < abs(normal.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0));
vec3 e1 = cross(normal, e0);
e0 = cross(normal, e1);
for (uint i = 0; i < sample_count; i++)
{
vec2 offset = (Hammersley(i, sample_count) - 0.5) * SampleDistance;
vec3 origin2 = origin + offset.x * e0 + offset.y * e1;
float dist2 = distance(LightOrigin, origin2);
vec3 dir2 = normalize(LightOrigin - origin2);
traceRayEXT(acc, gl_RayFlagsOpaqueEXT, 0xff, 1, 0, 1, origin2, minDistance, dir2, dist2, 0);
shadowAttenuation += payload.hitAttenuation;
}
shadowAttenuation *= 1.0 / float(sample_count);
attenuation *= shadowAttenuation;
emittance.rgb += LightColor * (attenuation * LightIntensity);
}
}
emittance.w += 1.0;
imageStore(outputs, texelPos, emittance);
}
)glsl";

View file

@ -0,0 +1,90 @@
static const char* glsl_rgen_sun = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
layout(location = 0) rayPayloadEXT hitPayload payload;
layout(set = 0, binding = 0) uniform accelerationStructureEXT acc;
layout(set = 0, binding = 1, rgba32f) uniform image2D positions;
layout(set = 0, binding = 2, rgba32f) uniform image2D normals;
layout(set = 0, binding = 3, rgba32f) uniform image2D outputs;
layout(set = 0, binding = 4) uniform Uniforms
{
vec3 LightOrigin;
float PassType;
float LightRadius;
float LightIntensity;
float LightInnerAngleCos;
float LightOuterAngleCos;
vec3 LightSpotDir;
float SampleDistance;
vec3 LightColor;
float Padding;
};
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
}
vec2 Hammersley(uint i, uint N)
{
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
void main()
{
ivec2 texelPos = ivec2(gl_LaunchIDEXT.xy);
vec4 data0 = imageLoad(positions, texelPos);
vec4 data1 = imageLoad(normals, texelPos);
if (data1 == vec4(0))
return;
vec3 origin = data0.xyz;
vec3 normal = data1.xyz;
vec4 emittance = vec4(0.0);
if (PassType == 1.0)
emittance = imageLoad(outputs, texelPos);
const float minDistance = 0.01;
const uint sample_count = 1024;
vec3 e0 = cross(normal, abs(normal.x) < abs(normal.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0));
vec3 e1 = cross(normal, e0);
e0 = cross(normal, e1);
origin += normal * 0.1;
float attenuation = 0.0;
for (uint i = 0; i < sample_count; i++)
{
vec2 offset = (Hammersley(i, sample_count) - 0.5) * SampleDistance;
vec3 origin2 = origin + offset.x * e0 + offset.y * e1;
float dist2 = 32768.0;
vec3 dir2 = LightSpotDir;
traceRayEXT(acc, gl_RayFlagsOpaqueEXT, 0xff, 2, 0, 2, origin2, minDistance, dir2, dist2, 0);
attenuation += payload.hitAttenuation;
}
attenuation *= 1.0 / float(sample_count);
emittance.rgb += LightColor * (attenuation * LightIntensity);
emittance.w += 1.0;
imageStore(outputs, texelPos, emittance);
}
)glsl";

View file

@ -1,4 +1,4 @@
static const char* glsl_miss = R"glsl(
static const char* glsl_rmiss_bounce = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
@ -6,21 +6,13 @@ static const char* glsl_miss = R"glsl(
struct hitPayload
{
float hitAttenuation;
bool isSkyRay;
};
layout(location = 0) rayPayloadInEXT hitPayload payload;
void main()
{
if (!payload.isSkyRay)
{
payload.hitAttenuation = 1.0;
}
else
{
payload.hitAttenuation = 0.0;
}
}
)glsl";

View file

@ -0,0 +1,18 @@
static const char* glsl_rmiss_light = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
layout(location = 0) rayPayloadInEXT hitPayload payload;
void main()
{
payload.hitAttenuation = 1.0;
}
)glsl";

View file

@ -0,0 +1,18 @@
static const char* glsl_rmiss_sun = R"glsl(
#version 460
#extension GL_EXT_ray_tracing : require
struct hitPayload
{
float hitAttenuation;
};
layout(location = 0) rayPayloadInEXT hitPayload payload;
void main()
{
payload.hitAttenuation = 0.0;
}
)glsl";

View file

@ -8,64 +8,27 @@
#include "framework/templates.h"
#include "framework/halffloat.h"
#include "vulkanbuilders.h"
#include "stacktrace.h"
#include <map>
#include <vector>
#include <algorithm>
#include <zlib.h>
#include "renderdoc_app.h"
#include "glsl_raygen.h"
#include "glsl_miss.h"
#include "glsl_closesthit.h"
#include "glsl_rgen_bounce.h"
#include "glsl_rgen_light.h"
#include "glsl_rgen_sun.h"
#include "glsl_rchit_bounce.h"
#include "glsl_rchit_light.h"
#include "glsl_rchit_sun.h"
#include "glsl_rmiss_bounce.h"
#include "glsl_rmiss_light.h"
#include "glsl_rmiss_sun.h"
extern int LightBounce;
static RENDERDOC_API_1_4_2* renderdoc = nullptr;
extern bool VKDebug;
GPURaytracer::GPURaytracer()
{
if (!renderdoc)
{
if (HMODULE mod = GetModuleHandle(TEXT("renderdoc.dll")))
{
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_2, (void**)&renderdoc);
if (ret == 1)
printf("Renderdoc detected!\n");
else
renderdoc = nullptr;
}
}
auto printLog = [](const char* typestr, const std::string& msg)
{
printf("\n[%s] %s\n", typestr, msg.c_str());
std::string callstack = CaptureStackTraceText(0);
if (!callstack.empty())
printf("%s\n", callstack.c_str());
};
device = std::make_unique<VulkanDevice>(0, true, printLog);
const auto& props = device->physicalDevice.properties;
std::string deviceType;
switch (props.deviceType)
{
case VK_PHYSICAL_DEVICE_TYPE_OTHER: deviceType = "other"; break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: deviceType = "integrated gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: deviceType = "discrete gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: deviceType = "virtual gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_CPU: deviceType = "cpu"; break;
default: deviceType = std::to_string(props.deviceType); break;
}
std::string apiVersion = std::to_string(VK_VERSION_MAJOR(props.apiVersion)) + "." + std::to_string(VK_VERSION_MINOR(props.apiVersion)) + "." + std::to_string(VK_VERSION_PATCH(props.apiVersion));
std::string driverVersion = std::to_string(VK_VERSION_MAJOR(props.driverVersion)) + "." + std::to_string(VK_VERSION_MINOR(props.driverVersion)) + "." + std::to_string(VK_VERSION_PATCH(props.driverVersion));
printf("Vulkan device: %s\n", props.deviceName);
printf("Vulkan device type: %s\n", deviceType.c_str());
printf("Vulkan version: %s (api) %s (driver)\n", apiVersion.c_str(), driverVersion.c_str());
device = std::make_unique<VulkanDevice>(0, VKDebug);
PrintVulkanInfo();
}
GPURaytracer::~GPURaytracer()
@ -76,6 +39,13 @@ void GPURaytracer::Raytrace(LevelMesh* level)
{
mesh = level;
printf("Building vulkan acceleration structures\n");
if (device->renderdoc)
device->renderdoc->StartFrameCapture(0, 0);
CreateVulkanObjects();
printf("Tracing light probes\n");
Worker::RunJob((int)mesh->lightProbes.size(), [=](int id) {
@ -84,10 +54,6 @@ void GPURaytracer::Raytrace(LevelMesh* level)
printf("Tracing surfaces (%d bounces)\n", LightBounce);
struct SurfaceTask
{
int surf, x, y;
};
std::vector<SurfaceTask> tasks;
for (size_t i = 0; i < mesh->surfaces.size(); i++)
@ -108,9 +74,48 @@ void GPURaytracer::Raytrace(LevelMesh* level)
}
}
if (renderdoc)
renderdoc->StartFrameCapture(0, 0);
UploadTasks(tasks);
// Sunlight
{
Uniforms uniforms = {};
uniforms.LightOrigin = Vec3(0.0f, 0.0f, 0.0f);
uniforms.LightRadius = -1.0f;
uniforms.LightIntensity = 1.0f;
uniforms.LightInnerAngleCos = -1.0f;
uniforms.LightOuterAngleCos = -1.0f;
uniforms.LightSpotDir = mesh->map->GetSunDirection();
uniforms.LightColor = mesh->map->GetSunColor();
uniforms.PassType = 0.0f;
uniforms.SampleDistance = (float)mesh->samples;
RunTrace(uniforms, rgenSunRegion);
}
for (ThingLight& light : mesh->map->ThingLights)
{
Uniforms uniforms = {};
uniforms.LightOrigin = light.LightOrigin();
uniforms.LightRadius = light.LightRadius();
uniforms.LightIntensity = light.intensity;
uniforms.LightInnerAngleCos = light.innerAngleCos;
uniforms.LightOuterAngleCos = light.outerAngleCos;
uniforms.LightSpotDir = light.SpotDir();
uniforms.LightColor = light.rgb;
uniforms.PassType = 1.0f;
uniforms.SampleDistance = (float)mesh->samples;
RunTrace(uniforms, rgenLightRegion);
}
DownloadTasks(tasks);
if (device->renderdoc)
device->renderdoc->EndFrameCapture(0, 0);
printf("Raytrace complete\n");
}
void GPURaytracer::CreateVulkanObjects()
{
cmdpool = std::make_unique<VulkanCommandPool>(device.get(), device->graphicsFamily);
cmdbuffer = cmdpool->createBuffer();
cmdbuffer->begin();
@ -125,7 +130,10 @@ void GPURaytracer::Raytrace(LevelMesh* level)
PipelineBarrier finishbuildbarrier;
finishbuildbarrier.addMemory(VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR);
finishbuildbarrier.execute(cmdbuffer.get(), VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR);
}
void GPURaytracer::UploadTasks(const std::vector<SurfaceTask>& tasks)
{
size_t maxTasks = (size_t)rayTraceImageSize * rayTraceImageSize;
if (tasks.size() > maxTasks)
throw std::runtime_error("Ray trace task count is too large");
@ -136,7 +144,7 @@ void GPURaytracer::Raytrace(LevelMesh* level)
Vec4* normals = (Vec4*)(imageData + imageSize);
for (size_t i = 0; i < tasks.size(); i++)
{
SurfaceTask& task = tasks[i];
const SurfaceTask& task = tasks[i];
Surface* surface = mesh->surfaces[task.surf].get();
Vec3 normal = surface->plane.Normal();
@ -181,20 +189,10 @@ void GPURaytracer::Raytrace(LevelMesh* level)
barrier2.addImage(normalsImage.get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
barrier2.addImage(outputImage.get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
barrier2.execute(cmdbuffer.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR);
}
// Sunlight
void GPURaytracer::RunTrace(const Uniforms& uniforms, const VkStridedDeviceAddressRegionKHR& rgenShader)
{
Uniforms uniforms = {};
uniforms.LightOrigin = Vec3(0.0f, 0.0f, 0.0f);
uniforms.LightRadius = -1.0f;
uniforms.LightIntensity = 1.0f;
uniforms.LightInnerAngleCos = -1.0f;
uniforms.LightOuterAngleCos = -1.0f;
uniforms.LightSpotDir = mesh->map->GetSunDirection();
uniforms.LightColor = mesh->map->GetSunColor();
uniforms.PassType = 0.0f;
uniforms.SampleDistance = (float)mesh->samples;
auto data = uniformTransferBuffer->Map(0, sizeof(Uniforms));
memcpy(data, &uniforms, sizeof(Uniforms));
uniformTransferBuffer->Unmap();
@ -207,8 +205,7 @@ void GPURaytracer::Raytrace(LevelMesh* level)
cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline.get());
cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout.get(), 0, descriptorSet.get());
cmdbuffer->traceRays(&rgenRegion, &missRegion, &hitRegion, &callRegion, rayTraceImageSize, rayTraceImageSize, 1);
cmdbuffer->traceRays(&rgenShader, &missRegion, &hitRegion, &callRegion, rayTraceImageSize, rayTraceImageSize, 1);
cmdbuffer->end();
auto submitFence = std::make_unique<VulkanFence>(device.get());
@ -227,58 +224,13 @@ void GPURaytracer::Raytrace(LevelMesh* level)
cmdbuffer->begin();
}
for (ThingLight& light : mesh->map->ThingLights)
void GPURaytracer::DownloadTasks(const std::vector<SurfaceTask>& tasks)
{
Uniforms uniforms = {};
uniforms.LightOrigin = light.LightOrigin();
uniforms.LightRadius = light.LightRadius();
uniforms.LightIntensity = light.intensity;
uniforms.LightInnerAngleCos = light.innerAngleCos;
uniforms.LightOuterAngleCos = light.outerAngleCos;
uniforms.LightSpotDir = light.SpotDir();
uniforms.LightColor = light.rgb;
uniforms.PassType = 1.0f;
uniforms.SampleDistance = (float)mesh->samples;
auto data = uniformTransferBuffer->Map(0, sizeof(Uniforms));
memcpy(data, &uniforms, sizeof(Uniforms));
uniformTransferBuffer->Unmap();
cmdbuffer->copyBuffer(uniformTransferBuffer.get(), uniformBuffer.get());
PipelineBarrier barrier3;
barrier3.addBuffer(uniformBuffer.get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
barrier3.execute(cmdbuffer.get(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR);
cmdbuffer->bindPipeline(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline.get());
cmdbuffer->bindDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout.get(), 0, descriptorSet.get());
cmdbuffer->traceRays(&rgenRegion, &missRegion, &hitRegion, &callRegion, rayTraceImageSize, rayTraceImageSize, 1);
cmdbuffer->end();
auto submitFence = std::make_unique<VulkanFence>(device.get());
QueueSubmit submit;
submit.addCommandBuffer(cmdbuffer.get());
submit.execute(device.get(), device->graphicsQueue, submitFence.get());
vkWaitForFences(device->device, 1, &submitFence->fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
vkResetFences(device->device, 1, &submitFence->fence);
printf(".");
cmdbuffer.reset();
cmdbuffer = cmdpool->createBuffer();
cmdbuffer->begin();
}
printf("\n");
PipelineBarrier barrier4;
barrier4.addImage(outputImage.get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier4.execute(cmdbuffer.get(), VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_TRANSFER_BIT);
region = {};
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.imageExtent.width = rayTraceImageSize;
region.imageExtent.height = rayTraceImageSize;
@ -297,11 +249,12 @@ void GPURaytracer::Raytrace(LevelMesh* level)
vkWaitForFences(device->device, 1, &submitFence->fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
vkResetFences(device->device, 1, &submitFence->fence);
imageData = (uint8_t*)imageTransferBuffer->Map(0, imageSize);
size_t imageSize = sizeof(Vec4) * rayTraceImageSize * rayTraceImageSize;
uint8_t* imageData = (uint8_t*)imageTransferBuffer->Map(0, imageSize);
Vec4* output = (Vec4*)imageData;
for (size_t i = 0; i < tasks.size(); i++)
{
SurfaceTask& task = tasks[i];
const SurfaceTask& task = tasks[i];
Surface* surface = mesh->surfaces[task.surf].get();
size_t sampleWidth = surface->lightmapDims[0];
@ -309,245 +262,7 @@ void GPURaytracer::Raytrace(LevelMesh* level)
}
imageTransferBuffer->Unmap();
if (renderdoc)
renderdoc->EndFrameCapture(0, 0);
printf("Raytrace complete\n");
}
void GPURaytracer::RaytraceProbeSample(LightProbeSample* probe)
{
Vec3 incoming(0.0f, 0.0f, 0.0f);
if (LightBounce > 0)
{
Vec3 directions[6] =
{
{ 1.0f, 0.0f, 0.0f },
{ -1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, -1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f, },
{ 0.0f, 0.0f, -1.0f, }
};
for (int i = 0; i < SAMPLE_COUNT; i++)
{
const Vec3& normal = directions[i % 6];
Vec2 Xi = Hammersley(i, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
incoming += TracePath(probe->Position, L, i);
}
incoming = incoming / (float)SAMPLE_COUNT / (float)LightBounce;
}
for (ThingLight& light : mesh->map->ThingLights)
{
Vec3 lightOrigin = light.LightOrigin();
float lightRadius = light.LightRadius();
if (probe->Position.DistanceSq(lightOrigin) > (lightRadius * lightRadius))
continue;
if (mesh->TraceAnyHit(lightOrigin, probe->Position))
continue; // this light is occluded by something
Vec3 dir = (lightOrigin - probe->Position);
float dist = dir.Unit();
dir.Normalize();
incoming += light.rgb * (light.SpotAttenuation(dir) * light.DistAttenuation(dist) * light.intensity);
}
const Vec3& sundir = mesh->map->GetSunDirection();
LevelTraceHit trace = mesh->Trace(probe->Position, probe->Position + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
incoming += mesh->map->GetSunColor();
probe->Color = incoming;
}
void GPURaytracer::RaytraceSurfaceSample(Surface* surface, int x, int y)
{
Vec3 normal = surface->plane.Normal();
Vec3 pos = surface->lightmapOrigin + normal + surface->lightmapSteps[0] * (float)x + surface->lightmapSteps[1] * (float)y;
Vec3 incoming(0.0f, 0.0f, 0.0f);
if (LightBounce > 0)
{
float totalWeight = 0.0f;
for (int i = 0; i < SAMPLE_COUNT; i++)
{
Vec2 Xi = Hammersley(i, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
float NdotL = std::max(Vec3::Dot(normal, L), 0.0f);
if (NdotL > 0.0f)
{
incoming += TracePath(pos, L, i) * NdotL;
totalWeight += NdotL;
}
}
incoming = incoming / totalWeight / (float)LightBounce;
}
incoming = incoming + GetSurfaceEmittance(surface, 0.0f) + GetLightEmittance(surface, pos);
const Vec3& sundir = mesh->map->GetSunDirection();
float attenuation = normal.Dot(sundir);
if (attenuation > 0.0f)
{
LevelTraceHit trace = mesh->Trace(pos, pos + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
incoming += mesh->map->GetSunColor() * attenuation;
}
size_t sampleWidth = surface->lightmapDims[0];
surface->samples[x + y * sampleWidth] = incoming;
}
Vec3 GPURaytracer::GetLightEmittance(Surface* surface, const Vec3& pos)
{
Vec3 emittance = Vec3(0.0f);
for (ThingLight& light : mesh->map->ThingLights)
{
Vec3 lightOrigin = light.LightOrigin();
float lightRadius = light.LightRadius();
if (surface->plane.Distance(lightOrigin) - surface->plane.d < 0)
continue; // completely behind the plane
if (pos.DistanceSq(lightOrigin) > (lightRadius * lightRadius))
continue; // light too far away
Vec3 dir = (lightOrigin - pos);
float dist = dir.Unit();
dir.Normalize();
float attenuation = light.SpotAttenuation(dir) * light.DistAttenuation(dist) * surface->plane.Normal().Dot(dir);
if (attenuation <= 0.0f)
continue;
if (mesh->TraceAnyHit(lightOrigin, pos))
continue; // this light is occluded by something
emittance += light.rgb * (attenuation * light.intensity);
}
return emittance;
}
Vec3 GPURaytracer::TracePath(const Vec3& pos, const Vec3& dir, int sampleIndex, int depth)
{
if (depth >= LightBounce)
return Vec3(0.0f);
LevelTraceHit hit = mesh->Trace(pos + dir * 0.1f, pos + dir * 2000.0f);
if (hit.fraction == 1.0f)
return Vec3(0.0f);
Vec3 normal = hit.hitSurface->plane.Normal();
Vec3 hitpos = hit.start * (1.0f - hit.fraction) + hit.end * hit.fraction;
Vec3 emittance = GetSurfaceEmittance(hit.hitSurface, pos.Distance(hitpos)) + GetLightEmittance(hit.hitSurface, hitpos) * 0.5f;
const Vec3& sundir = mesh->map->GetSunDirection();
float attenuation = normal.Dot(sundir);
if (attenuation > 0.0f)
{
Vec3 start = hitpos + normal * 0.1f;
LevelTraceHit trace = mesh->Trace(start, start + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
emittance += mesh->map->GetSunColor() * (attenuation * 0.5f);
}
Vec2 Xi = Hammersley(sampleIndex, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
float NdotL = Vec3::Dot(normal, L);
if (NdotL <= 0.0f)
return emittance;
const float p = 1 / (2 * M_PI);
Vec3 incoming = TracePath(hitpos, normal, (sampleIndex + depth + 1) % SAMPLE_COUNT, depth + 1);
return emittance + incoming * NdotL / p;
}
float GPURaytracer::RadicalInverse_VdC(uint32_t 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
}
Vec2 GPURaytracer::Hammersley(uint32_t i, uint32_t N)
{
return Vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
Vec3 GPURaytracer::ImportanceSampleGGX(Vec2 Xi, Vec3 N, float roughness)
{
float a = roughness * roughness;
float phi = 2.0f * M_PI * Xi.x;
float cosTheta = sqrt((1.0f - Xi.y) / (1.0f + (a * a - 1.0f) * Xi.y));
float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
// from spherical coordinates to cartesian coordinates
Vec3 H(std::cos(phi) * sinTheta, std::sin(phi) * sinTheta, cosTheta);
// from tangent-space vector to world-space sample vector
Vec3 up = std::abs(N.z) < 0.999f ? Vec3(0.0f, 0.0f, 1.0f) : Vec3(1.0f, 0.0f, 0.0f);
Vec3 tangent = Vec3::Normalize(Vec3::Cross(up, N));
Vec3 bitangent = Vec3::Cross(N, tangent);
Vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return Vec3::Normalize(sampleVec);
}
Vec3 GPURaytracer::GetSurfaceEmittance(Surface* surface, float distance)
{
SurfaceLightDef* def = nullptr;
if (surface->type >= ST_MIDDLESIDE && surface->type <= ST_LOWERSIDE)
{
int lightdefidx = mesh->map->Sides[surface->typeIndex].lightdef;
if (lightdefidx != -1)
{
def = &mesh->map->SurfaceLights[lightdefidx];
}
}
else if (surface->type == ST_FLOOR || surface->type == ST_CEILING)
{
MapSubsectorEx* sub = &mesh->map->GLSubsectors[surface->typeIndex];
IntSector* sector = mesh->map->GetSectorFromSubSector(sub);
if (sector && surface->numVerts > 0)
{
if (sector->floorlightdef != -1 && surface->type == ST_FLOOR)
{
def = &mesh->map->SurfaceLights[sector->floorlightdef];
}
else if (sector->ceilinglightdef != -1 && surface->type == ST_CEILING)
{
def = &mesh->map->SurfaceLights[sector->ceilinglightdef];
}
}
}
if (def && distance < def->distance + def->distance)
{
float radius = def->distance + def->distance;
float attenuation = std::max(1.0f - (distance / radius), 0.0f);
return def->rgb * (attenuation * def->intensity);
}
else
{
return Vec3(0.0f);
}
printf("\n");
}
void GPURaytracer::CreateVertexAndIndexBuffers()
@ -828,40 +543,62 @@ void GPURaytracer::CreateTopLevelAccelerationStructure()
void GPURaytracer::CreateShaders()
{
try
{
ShaderBuilder builder;
builder.setRayGenShader(glsl_raygen);
shaderRayGen = builder.create(device.get());
shaderRayGen->SetDebugName("shaderRayGen");
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Could not compile raygen shader: ") + e.what());
rgenBounce = CompileRayGenShader(glsl_rgen_bounce, "rgen.bounce");
rgenLight = CompileRayGenShader(glsl_rgen_light, "rgen.light");
rgenSun = CompileRayGenShader(glsl_rgen_sun, "rgen.sun");
rchitBounce = CompileClosestHitShader(glsl_rchit_bounce, "rchit.bounce");
rchitLight = CompileClosestHitShader(glsl_rchit_light, "rchit.light");
rchitSun = CompileClosestHitShader(glsl_rchit_sun, "rchit.sun");
rmissBounce = CompileMissShader(glsl_rmiss_bounce, "rmiss.bounce");
rmissLight = CompileMissShader(glsl_rmiss_light, "rmiss.light");
rmissSun = CompileMissShader(glsl_rmiss_sun, "rmiss.sun");
}
std::unique_ptr<VulkanShader> GPURaytracer::CompileRayGenShader(const char* code, const char* name)
{
try
{
ShaderBuilder builder;
builder.setMissShader(glsl_miss);
shaderMiss = builder.create(device.get());
shaderMiss->SetDebugName("shaderMiss");
builder.setRayGenShader(code);
auto shader = builder.create(device.get());
shader->SetDebugName(name);
return shader;
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Could not compile miss shader: ") + e.what());
throw std::runtime_error(std::string("Could not compile ") + name + ": " + e.what());
}
}
std::unique_ptr<VulkanShader> GPURaytracer::CompileClosestHitShader(const char* code, const char* name)
{
try
{
ShaderBuilder builder;
builder.setClosestHitShader(glsl_closesthit);
shaderClosestHit = builder.create(device.get());
shaderClosestHit->SetDebugName("shaderClosestHit");
builder.setClosestHitShader(code);
auto shader = builder.create(device.get());
shader->SetDebugName(name);
return shader;
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Could not compile closest hit shader: ") + e.what());
throw std::runtime_error(std::string("Could not compile ") + name + ": " + e.what());
}
}
std::unique_ptr<VulkanShader> GPURaytracer::CompileMissShader(const char* code, const char* name)
{
try
{
ShaderBuilder builder;
builder.setMissShader(code);
auto shader = builder.create(device.get());
shader->SetDebugName(name);
return shader;
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Could not compile ") + name + ": " + e.what());
}
}
@ -886,20 +623,27 @@ void GPURaytracer::CreatePipeline()
RayTracingPipelineBuilder builder;
builder.setLayout(pipelineLayout.get());
builder.setMaxPipelineRayRecursionDepth(1);
builder.addShader(VK_SHADER_STAGE_RAYGEN_BIT_KHR, shaderRayGen.get());
builder.addShader(VK_SHADER_STAGE_MISS_BIT_KHR, shaderMiss.get());
builder.addShader(VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, shaderClosestHit.get());
builder.addShader(VK_SHADER_STAGE_RAYGEN_BIT_KHR, rgenBounce.get());
builder.addShader(VK_SHADER_STAGE_RAYGEN_BIT_KHR, rgenLight.get());
builder.addShader(VK_SHADER_STAGE_RAYGEN_BIT_KHR, rgenSun.get());
builder.addShader(VK_SHADER_STAGE_MISS_BIT_KHR, rmissBounce.get());
builder.addShader(VK_SHADER_STAGE_MISS_BIT_KHR, rmissLight.get());
builder.addShader(VK_SHADER_STAGE_MISS_BIT_KHR, rmissSun.get());
builder.addShader(VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, rchitBounce.get());
builder.addShader(VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, rchitLight.get());
builder.addShader(VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, rchitSun.get());
builder.addRayGenGroup(0);
builder.addMissGroup(1);
builder.addTrianglesHitGroup(2);
builder.addRayGenGroup(1);
builder.addRayGenGroup(2);
builder.addMissGroup(3);
builder.addMissGroup(4);
builder.addMissGroup(5);
builder.addTrianglesHitGroup(6);
builder.addTrianglesHitGroup(7);
builder.addTrianglesHitGroup(8);
pipeline = builder.create(device.get());
pipeline->SetDebugName("pipeline");
// OK, this is by far the WORST idea I've seen in vulkan yet. And that's saying a lot.
//
// Each shader type has its own table, which needs to be aligned.
// That means we can't just copy the shader table pointers directly. Each group needs to be sized up and copied individually.
const auto& rtProperties = device->physicalDevice.rayTracingProperties;
auto align_up = [](VkDeviceSize value, VkDeviceSize alignment)
@ -910,25 +654,34 @@ void GPURaytracer::CreatePipeline()
return value;
};
VkDeviceSize missCount = 1;
VkDeviceSize hitCount = 1;
VkDeviceSize raygenCount = 3;
VkDeviceSize missCount = 3;
VkDeviceSize hitCount = 3;
VkDeviceSize handleSize = rtProperties.shaderGroupHandleSize;
VkDeviceSize handleSizeAligned = align_up(handleSize, rtProperties.shaderGroupHandleAlignment);
rgenRegion.stride = align_up(handleSizeAligned, rtProperties.shaderGroupBaseAlignment);
missRegion.stride = handleSizeAligned;
hitRegion.stride = handleSizeAligned;
VkDeviceSize rgenStride = align_up(handleSizeAligned, rtProperties.shaderGroupBaseAlignment);
VkDeviceSize rgenSize = rgenStride * raygenCount;
rgenRegion.size = align_up(handleSizeAligned, rtProperties.shaderGroupBaseAlignment);
rgenBounceRegion.stride = rgenStride;
rgenBounceRegion.size = rgenStride;
rgenLightRegion.stride = rgenStride;
rgenLightRegion.size = rgenStride;
rgenSunRegion.stride = rgenStride;
rgenSunRegion.size = rgenStride;
missRegion.stride = handleSizeAligned;
missRegion.size = align_up(missCount * handleSizeAligned, rtProperties.shaderGroupBaseAlignment);
hitRegion.stride = handleSizeAligned;
hitRegion.size = align_up(hitCount * handleSizeAligned, rtProperties.shaderGroupBaseAlignment);
VkDeviceSize rgenOffset = 0;
VkDeviceSize missOffset = rgenOffset + rgenRegion.size;
VkDeviceSize missOffset = rgenOffset + rgenSize;
VkDeviceSize hitOffset = missOffset + missRegion.size;
VkDeviceSize sbtBufferSize = rgenRegion.size + missRegion.size + hitRegion.size;
VkDeviceSize sbtBufferSize = rgenSize + missRegion.size + hitRegion.size;
BufferBuilder bufbuilder;
bufbuilder.setUsage(VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
@ -943,11 +696,21 @@ void GPURaytracer::CreatePipeline()
sbtTransferBuffer->SetDebugName("sbtTransferBuffer");
uint8_t* src = (uint8_t*)pipeline->shaderGroupHandles.data();
uint8_t* dest = (uint8_t*)sbtTransferBuffer->Map(0, sbtBufferSize);
memcpy(dest + rgenOffset, src, handleSize);
for (VkDeviceSize i = 0; i < raygenCount; i++)
{
memcpy(dest + rgenOffset + i * rgenStride, src, handleSize);
src += handleSize;
}
for (VkDeviceSize i = 0; i < missCount; i++)
memcpy(dest + missOffset + i * missRegion.stride, src + (1 + i) * handleSize, handleSize);
{
memcpy(dest + missOffset + i * missRegion.stride, src, handleSize);
src += handleSize;
}
for (VkDeviceSize i = 0; i < hitCount; i++)
memcpy(dest + hitOffset, src + (1 + missCount + i) * handleSize, handleSize);
{
memcpy(dest + hitOffset + i * hitRegion.stride, src, handleSize);
src += handleSize;
}
sbtTransferBuffer->Unmap();
cmdbuffer->copyBuffer(sbtTransferBuffer.get(), shaderBindingTable.get());
@ -956,7 +719,9 @@ void GPURaytracer::CreatePipeline()
info.buffer = shaderBindingTable->buffer;
VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(device->device, &info);
rgenRegion.deviceAddress = sbtAddress + rgenOffset;
rgenBounceRegion.deviceAddress = sbtAddress + rgenOffset;
rgenLightRegion.deviceAddress = sbtAddress + rgenOffset + rgenStride;
rgenSunRegion.deviceAddress = sbtAddress + rgenOffset + 2 * rgenStride;
missRegion.deviceAddress = sbtAddress + missOffset;
hitRegion.deviceAddress = sbtAddress + hitOffset;
}
@ -1039,3 +804,261 @@ void GPURaytracer::CreateDescriptorSet()
write.addBuffer(descriptorSet.get(), 6, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, surfaceBuffer.get());
write.updateSets(device.get());
}
void GPURaytracer::PrintVulkanInfo()
{
const auto& props = device->physicalDevice.properties;
std::string deviceType;
switch (props.deviceType)
{
case VK_PHYSICAL_DEVICE_TYPE_OTHER: deviceType = "other"; break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: deviceType = "integrated gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: deviceType = "discrete gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: deviceType = "virtual gpu"; break;
case VK_PHYSICAL_DEVICE_TYPE_CPU: deviceType = "cpu"; break;
default: deviceType = std::to_string(props.deviceType); break;
}
std::string apiVersion = std::to_string(VK_VERSION_MAJOR(props.apiVersion)) + "." + std::to_string(VK_VERSION_MINOR(props.apiVersion)) + "." + std::to_string(VK_VERSION_PATCH(props.apiVersion));
std::string driverVersion = std::to_string(VK_VERSION_MAJOR(props.driverVersion)) + "." + std::to_string(VK_VERSION_MINOR(props.driverVersion)) + "." + std::to_string(VK_VERSION_PATCH(props.driverVersion));
printf("Vulkan device: %s\n", props.deviceName);
printf("Vulkan device type: %s\n", deviceType.c_str());
printf("Vulkan version: %s (api) %s (driver)\n", apiVersion.c_str(), driverVersion.c_str());
}
void GPURaytracer::RaytraceProbeSample(LightProbeSample* probe)
{
Vec3 incoming(0.0f, 0.0f, 0.0f);
if (LightBounce > 0)
{
Vec3 directions[6] =
{
{ 1.0f, 0.0f, 0.0f },
{ -1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, -1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f, },
{ 0.0f, 0.0f, -1.0f, }
};
for (int i = 0; i < SAMPLE_COUNT; i++)
{
const Vec3& normal = directions[i % 6];
Vec2 Xi = Hammersley(i, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
incoming += TracePath(probe->Position, L, i);
}
incoming = incoming / (float)SAMPLE_COUNT / (float)LightBounce;
}
for (ThingLight& light : mesh->map->ThingLights)
{
Vec3 lightOrigin = light.LightOrigin();
float lightRadius = light.LightRadius();
if (probe->Position.DistanceSq(lightOrigin) > (lightRadius * lightRadius))
continue;
if (mesh->TraceAnyHit(lightOrigin, probe->Position))
continue; // this light is occluded by something
Vec3 dir = (lightOrigin - probe->Position);
float dist = dir.Unit();
dir.Normalize();
incoming += light.rgb * (light.SpotAttenuation(dir) * light.DistAttenuation(dist) * light.intensity);
}
const Vec3& sundir = mesh->map->GetSunDirection();
LevelTraceHit trace = mesh->Trace(probe->Position, probe->Position + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
incoming += mesh->map->GetSunColor();
probe->Color = incoming;
}
void GPURaytracer::RaytraceSurfaceSample(Surface* surface, int x, int y)
{
Vec3 normal = surface->plane.Normal();
Vec3 pos = surface->lightmapOrigin + normal + surface->lightmapSteps[0] * (float)x + surface->lightmapSteps[1] * (float)y;
Vec3 incoming(0.0f, 0.0f, 0.0f);
if (LightBounce > 0)
{
float totalWeight = 0.0f;
for (int i = 0; i < SAMPLE_COUNT; i++)
{
Vec2 Xi = Hammersley(i, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
float NdotL = std::max(Vec3::Dot(normal, L), 0.0f);
if (NdotL > 0.0f)
{
incoming += TracePath(pos, L, i) * NdotL;
totalWeight += NdotL;
}
}
incoming = incoming / totalWeight / (float)LightBounce;
}
incoming = incoming + GetSurfaceEmittance(surface, 0.0f) + GetLightEmittance(surface, pos);
const Vec3& sundir = mesh->map->GetSunDirection();
float attenuation = normal.Dot(sundir);
if (attenuation > 0.0f)
{
LevelTraceHit trace = mesh->Trace(pos, pos + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
incoming += mesh->map->GetSunColor() * attenuation;
}
size_t sampleWidth = surface->lightmapDims[0];
surface->samples[x + y * sampleWidth] = incoming;
}
Vec3 GPURaytracer::GetLightEmittance(Surface* surface, const Vec3& pos)
{
Vec3 emittance = Vec3(0.0f);
for (ThingLight& light : mesh->map->ThingLights)
{
Vec3 lightOrigin = light.LightOrigin();
float lightRadius = light.LightRadius();
if (surface->plane.Distance(lightOrigin) - surface->plane.d < 0)
continue; // completely behind the plane
if (pos.DistanceSq(lightOrigin) > (lightRadius * lightRadius))
continue; // light too far away
Vec3 dir = (lightOrigin - pos);
float dist = dir.Unit();
dir.Normalize();
float attenuation = light.SpotAttenuation(dir) * light.DistAttenuation(dist) * surface->plane.Normal().Dot(dir);
if (attenuation <= 0.0f)
continue;
if (mesh->TraceAnyHit(lightOrigin, pos))
continue; // this light is occluded by something
emittance += light.rgb * (attenuation * light.intensity);
}
return emittance;
}
Vec3 GPURaytracer::TracePath(const Vec3& pos, const Vec3& dir, int sampleIndex, int depth)
{
if (depth >= LightBounce)
return Vec3(0.0f);
LevelTraceHit hit = mesh->Trace(pos + dir * 0.1f, pos + dir * 2000.0f);
if (hit.fraction == 1.0f)
return Vec3(0.0f);
Vec3 normal = hit.hitSurface->plane.Normal();
Vec3 hitpos = hit.start * (1.0f - hit.fraction) + hit.end * hit.fraction;
Vec3 emittance = GetSurfaceEmittance(hit.hitSurface, pos.Distance(hitpos)) + GetLightEmittance(hit.hitSurface, hitpos) * 0.5f;
const Vec3& sundir = mesh->map->GetSunDirection();
float attenuation = normal.Dot(sundir);
if (attenuation > 0.0f)
{
Vec3 start = hitpos + normal * 0.1f;
LevelTraceHit trace = mesh->Trace(start, start + sundir * 32768.0f);
if (trace.fraction != 1.0f && trace.hitSurface->bSky)
emittance += mesh->map->GetSunColor() * (attenuation * 0.5f);
}
Vec2 Xi = Hammersley(sampleIndex, SAMPLE_COUNT);
Vec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
Vec3 L = Vec3::Normalize(H * (2.0f * Vec3::Dot(normal, H)) - normal);
float NdotL = Vec3::Dot(normal, L);
if (NdotL <= 0.0f)
return emittance;
const float p = 1 / (2 * M_PI);
Vec3 incoming = TracePath(hitpos, normal, (sampleIndex + depth + 1) % SAMPLE_COUNT, depth + 1);
return emittance + incoming * NdotL / p;
}
float GPURaytracer::RadicalInverse_VdC(uint32_t 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
}
Vec2 GPURaytracer::Hammersley(uint32_t i, uint32_t N)
{
return Vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
Vec3 GPURaytracer::ImportanceSampleGGX(Vec2 Xi, Vec3 N, float roughness)
{
float a = roughness * roughness;
float phi = 2.0f * M_PI * Xi.x;
float cosTheta = sqrt((1.0f - Xi.y) / (1.0f + (a * a - 1.0f) * Xi.y));
float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
// from spherical coordinates to cartesian coordinates
Vec3 H(std::cos(phi) * sinTheta, std::sin(phi) * sinTheta, cosTheta);
// from tangent-space vector to world-space sample vector
Vec3 up = std::abs(N.z) < 0.999f ? Vec3(0.0f, 0.0f, 1.0f) : Vec3(1.0f, 0.0f, 0.0f);
Vec3 tangent = Vec3::Normalize(Vec3::Cross(up, N));
Vec3 bitangent = Vec3::Cross(N, tangent);
Vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return Vec3::Normalize(sampleVec);
}
Vec3 GPURaytracer::GetSurfaceEmittance(Surface* surface, float distance)
{
SurfaceLightDef* def = nullptr;
if (surface->type >= ST_MIDDLESIDE && surface->type <= ST_LOWERSIDE)
{
int lightdefidx = mesh->map->Sides[surface->typeIndex].lightdef;
if (lightdefidx != -1)
{
def = &mesh->map->SurfaceLights[lightdefidx];
}
}
else if (surface->type == ST_FLOOR || surface->type == ST_CEILING)
{
MapSubsectorEx* sub = &mesh->map->GLSubsectors[surface->typeIndex];
IntSector* sector = mesh->map->GetSectorFromSubSector(sub);
if (sector && surface->numVerts > 0)
{
if (sector->floorlightdef != -1 && surface->type == ST_FLOOR)
{
def = &mesh->map->SurfaceLights[sector->floorlightdef];
}
else if (sector->ceilinglightdef != -1 && surface->type == ST_CEILING)
{
def = &mesh->map->SurfaceLights[sector->ceilinglightdef];
}
}
}
if (def && distance < def->distance + def->distance)
{
float radius = def->distance + def->distance;
float attenuation = std::max(1.0f - (distance / radius), 0.0f);
return def->rgb * (attenuation * def->intensity);
}
else
{
return Vec3(0.0f);
}
}

View file

@ -30,6 +30,11 @@ struct SurfaceInfo
float Padding0, Padding1, Padding2;
};
struct SurfaceTask
{
int surf, x, y;
};
class GPURaytracer
{
public:
@ -39,13 +44,23 @@ public:
void Raytrace(LevelMesh* level);
private:
void CreateVulkanObjects();
void CreateVertexAndIndexBuffers();
void CreateBottomLevelAccelerationStructure();
void CreateTopLevelAccelerationStructure();
void CreateShaders();
std::unique_ptr<VulkanShader> CompileRayGenShader(const char* code, const char* name);
std::unique_ptr<VulkanShader> CompileClosestHitShader(const char* code, const char* name);
std::unique_ptr<VulkanShader> CompileMissShader(const char* code, const char* name);
void CreatePipeline();
void CreateDescriptorSet();
void UploadTasks(const std::vector<SurfaceTask>& tasks);
void RunTrace(const Uniforms& uniforms, const VkStridedDeviceAddressRegionKHR& rgenShader);
void DownloadTasks(const std::vector<SurfaceTask>& tasks);
void PrintVulkanInfo();
void RaytraceProbeSample(LightProbeSample* probe);
void RaytraceSurfaceSample(Surface* surface, int x, int y);
Vec3 TracePath(const Vec3& pos, const Vec3& dir, int sampleIndex, int depth = 0);
@ -79,9 +94,9 @@ private:
std::unique_ptr<VulkanBuffer> tlAccelStructBuffer;
std::unique_ptr<VulkanAccelerationStructure> tlAccelStruct;
std::unique_ptr<VulkanShader> shaderRayGen;
std::unique_ptr<VulkanShader> shaderMiss;
std::unique_ptr<VulkanShader> shaderClosestHit;
std::unique_ptr<VulkanShader> rgenBounce, rgenLight, rgenSun;
std::unique_ptr<VulkanShader> rmissBounce, rmissLight, rmissSun;
std::unique_ptr<VulkanShader> rchitBounce, rchitLight, rchitSun;
std::unique_ptr<VulkanDescriptorSetLayout> descriptorSetLayout;
@ -90,7 +105,7 @@ private:
std::unique_ptr<VulkanBuffer> shaderBindingTable;
std::unique_ptr<VulkanBuffer> sbtTransferBuffer;
VkStridedDeviceAddressRegionKHR rgenRegion = {};
VkStridedDeviceAddressRegionKHR rgenBounceRegion = {}, rgenLightRegion = {}, rgenSunRegion = {};
VkStridedDeviceAddressRegionKHR missRegion = {};
VkStridedDeviceAddressRegionKHR hitRegion = {};
VkStridedDeviceAddressRegionKHR callRegion = {};

View file

@ -1,13 +1,22 @@
#include "vulkandevice.h"
#include "vulkanobjects.h"
#include "stacktrace.h"
#include <algorithm>
#include <set>
#include <string>
#include <mutex>
VulkanDevice::VulkanDevice(int vk_device, bool vk_debug, std::function<void(const char* typestr, const std::string& msg)> printLogCallback) : vk_device(vk_device), vk_debug(vk_debug), printLogCallback(printLogCallback)
VulkanDevice::VulkanDevice(int vk_device, bool vk_debug) : vk_device(vk_device), vk_debug(vk_debug)
{
if (HMODULE mod = GetModuleHandle(TEXT("renderdoc.dll")))
{
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_2, (void**)&renderdoc);
if (ret != 1)
renderdoc = nullptr;
}
try
{
initVolk();
@ -391,8 +400,11 @@ VkBool32 VulkanDevice::debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT mess
typestr = "vulkan";
}
if (device->printLogCallback)
device->printLogCallback(typestr, msg);
printf("\n[%s] %s\n", typestr, msg.c_str());
std::string callstack = CaptureStackTraceText(0);
if (!callstack.empty())
printf("%s\n", callstack.c_str());
}
}

View file

@ -8,6 +8,7 @@
#include "volk/volk.h"
#include "vk_mem_alloc/vk_mem_alloc.h"
#include "renderdoc_app.h"
#ifdef WIN32
#undef min
@ -45,7 +46,7 @@ public:
class VulkanDevice
{
public:
VulkanDevice(int vk_device = 0, bool vk_debug = false, std::function<void(const char* typestr, const std::string& msg)> printLogCallback = {});
VulkanDevice(int vk_device = 0, bool vk_debug = false);
~VulkanDevice();
void setDebugObjectName(const char *name, uint64_t handle, VkObjectType type)
@ -102,12 +103,13 @@ public:
std::vector<VulkanPhysicalDevice> availableDevices;
std::vector<VulkanCompatibleDevice> supportedDevices;
RENDERDOC_API_1_4_2* renderdoc = nullptr;
static void initVolk();
private:
int vk_device;
bool vk_debug;
std::function<void(const char* typestr, const std::string& msg)> printLogCallback;
void createInstance();
//void createSurface();

View file

@ -117,6 +117,7 @@ int NumThreads = 0;
int LMDims = 1024;
int Samples = 8;
bool CPURaytrace = false;
bool VKDebug = false;
int LightBounce = 1;
float GridSize = 32.0f;
@ -159,10 +160,11 @@ static option long_opts[] =
{"cpu-raytrace", no_argument, 0, 'C'},
{"bounce", required_argument, 0, 'B'},
{"gridsize", required_argument, 0, 'i'},
{"vkdebug", no_argument, 0, 'D'},
{0,0,0,0}
};
static const char short_opts[] = "wVgGvbNrReEm:o:f:p:s:d:PqtzZXx5cj:Q:S:C";
static const char short_opts[] = "wVgGvbNrReEm:o:f:p:s:d:PqtzZXx5cj:Q:S:CD";
// CODE --------------------------------------------------------------------
@ -452,6 +454,9 @@ static void ParseArgs(int argc, char **argv)
case 'C':
CPURaytrace = true;
break;
case 'D':
VKDebug = true;
break;
case 'B':
LightBounce = atoi(optarg);
if (LightBounce < 0) LightBounce = 0;
@ -512,6 +517,7 @@ static void ShowUsage()
" -i, --gridsize=NNN Automatic light probe grid size, floating point\n"
" Lower values increase granularity at the expense of performance\n"
" Recommended: 32.0, 64.0, 128.0, etc (default %.1f)\n"
" -D, --vkdebug Print messages from the vulkan validation layer\n"
" -w, --warn Show warning messages\n"
#if HAVE_TIMING
" -t, --no-timing Suppress timing information\n"