diff options
| author | hachem <im@hachem.wtf> | 2026-08-22 21:40:20 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-08-22 21:40:20 +0200 |
| commit | 2b0661b7d6edacbc4a4ad87cdb625d559e08a5f9 (patch) | |
| tree | 1044422fff48753066753b979984aadadc22eff6 | |
| parent | 80239c37f1c7a2d579f6967af1f27890efb61e70 (diff) | |
feat: mip sampling
| -rw-r--r-- | assets/shaders/geodesic.slang | 66 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_renderer.cpp | 47 |
2 files changed, 90 insertions, 23 deletions
diff --git a/assets/shaders/geodesic.slang b/assets/shaders/geodesic.slang index db450a8..a1b0f46 100644 --- a/assets/shaders/geodesic.slang +++ b/assets/shaders/geodesic.slang @@ -78,11 +78,6 @@ struct Hit float hitRadius; }; -float3 SampleHDRI(float3 direction) -{ - return u_HDRIEnvironment.Sample(direction).rgb; -} - float hash(float3 p) { p = frac(p * float3(0.1031, 0.1030, 0.0973)); @@ -280,6 +275,11 @@ void RK4Step(inout Ray ray, float dL) float CalculateAdaptiveStepSize(Ray ray, float baseStepSize) { float step = 0.02 * max(ray.r - R_PHOTON, 0.0); + + float rc = length(float2(ray.x, ray.z)); + if (rc < disk.disk_r2 * 3.0 && abs(ray.y) < disk.thickness * 8.0) + step = min(step, disk.thickness); + return clamp(step, MIN_STEP_SIZE, MAX_STEP_SIZE); } @@ -288,11 +288,10 @@ float3 ACESFilm(float3 x) return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); } -[shader("fragment")] -float4 fragmentMain(VSOutput input) : SV_Target +float3 TracePixel(float2 texCoord) { - float u = (2.0 * input.texCoord.x - 1.0) * cam.aspect * cam.tanHalfFov; - float v = (1.0 - 2.0 * input.texCoord.y) * cam.tanHalfFov; + float u = (2.0 * texCoord.x - 1.0) * cam.aspect * cam.tanHalfFov; + float v = (1.0 - 2.0 * texCoord.y) * cam.tanHalfFov; float3 dir = normalize(u * cam.camRight - v * cam.camUp + cam.camForward); Ray ray = InitRay(cam.camPos, dir); @@ -323,16 +322,22 @@ float4 fragmentMain(VSOutput input) : SV_Target RK4Step(ray, stepSize); float3 newPos = float3(ray.x, ray.y, ray.z); - if (prevPos.y * newPos.y < 0.0) { - float t = prevPos.y / (prevPos.y - newPos.y); - float3 cross = lerp(prevPos, newPos, t); - float rc = length(float2(cross.x, cross.z)); - if (rc >= max(disk.disk_r1, R_ISCO) && rc <= disk.disk_r2) + float H = disk.thickness; + bool crossed = prevPos.y * newPos.y < 0.0; + bool inSlab = abs(newPos.y) <= H; + if (crossed || inSlab) { - diskColor = DiskEmission(cross, newPos - prevPos); - hitDisk = true; - break; + float3 hitP = crossed + ? lerp(prevPos, newPos, prevPos.y / (prevPos.y - newPos.y)) + : newPos; + float rc = length(float2(hitP.x, hitP.z)); + if (rc >= max(disk.disk_r1, R_ISCO) && rc <= disk.disk_r2) + { + diskColor = DiskEmission(hitP, newPos - prevPos); + hitDisk = true; + break; + } } } @@ -341,6 +346,10 @@ float4 fragmentMain(VSOutput input) : SV_Target if (ray.dr > 0.0 && ray.r > 50.0 * SagA_rs) break; } + float3 rayDir = normalize(float3(ray.x, ray.y, ray.z) - cam.camPos); + float footprint = max(length(ddx(rayDir)), length(ddy(rayDir))); + float envLod = clamp(log2(max(footprint / 0.0015, 1.0)), 0.0, 10.0); + float3 shade; if (hitDisk) { @@ -360,9 +369,26 @@ float4 fragmentMain(VSOutput input) : SV_Target } else { - float3 rayDir = normalize(float3(ray.x, ray.y, ray.z) - cam.camPos); - shade = SampleHDRI(rayDir); + shade = u_HDRIEnvironment.SampleLevel(rayDir, envLod).rgb; } - return float4(ACESFilm(shade), 1.0); + return shade; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) : SV_Target +{ + if (cam.moving) + return float4(ACESFilm(TracePixel(input.texCoord)), 1.0); + + float2 dUV = float2(ddx(input.texCoord.x), ddy(input.texCoord.y)); + float2 offs[4] = { + float2( 0.125, 0.375), float2( 0.375, -0.125), + float2(-0.125, -0.375), float2(-0.375, 0.125), + }; + float3 sum = float3(0.0); + for (int i = 0; i < 4; ++i) + sum += TracePixel(input.texCoord + offs[i] * dUV); + + return float4(ACESFilm(sum * 0.25), 1.0); } diff --git a/src/platform/vulkan/vulkan_renderer.cpp b/src/platform/vulkan/vulkan_renderer.cpp index 5b882ea..1a9f21b 100644 --- a/src/platform/vulkan/vulkan_renderer.cpp +++ b/src/platform/vulkan/vulkan_renderer.cpp @@ -731,13 +731,15 @@ namespace Donut const VkMemoryPropertyFlags host_vis = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; const uint32_t FACE = 1024; const VkFormat cube_fmt = VK_FORMAT_R16G16B16A16_SFLOAT; + uint32_t CUBE_MIPS = 1; for (uint32_t s = FACE; s > 1; s >>= 1) ++CUBE_MIPS; VkImageCreateInfo cci{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; cci.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; cci.imageType = VK_IMAGE_TYPE_2D; cci.format = cube_fmt; cci.extent = { FACE, FACE, 1 }; - cci.mipLevels = 1; cci.arrayLayers = 6; cci.samples = VK_SAMPLE_COUNT_1_BIT; + cci.mipLevels = CUBE_MIPS; cci.arrayLayers = 6; cci.samples = VK_SAMPLE_COUNT_1_BIT; cci.tiling = VK_IMAGE_TILING_OPTIMAL; - cci.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + cci.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; VK_CHECK(vkCreateImage(device, &cci, nullptr, &cube_image)); VkMemoryRequirements creq{}; vkGetImageMemoryRequirements(device, cube_image, &creq); VkMemoryAllocateInfo cai{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; @@ -746,10 +748,11 @@ namespace Donut VK_CHECK(vkBindImageMemory(device, cube_image, cube_mem, 0)); VkImageViewCreateInfo cvci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; cvci.image = cube_image; cvci.viewType = VK_IMAGE_VIEW_TYPE_CUBE; cvci.format = cube_fmt; - cvci.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6 }; + cvci.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, CUBE_MIPS, 0, 6 }; VK_CHECK(vkCreateImageView(device, &cvci, nullptr, &cube_view)); VkSamplerCreateInfo csm{ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; csm.magFilter = VK_FILTER_LINEAR; csm.minFilter = VK_FILTER_LINEAR; + csm.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; csm.minLod = 0.0f; csm.maxLod = (float)CUBE_MIPS; csm.addressModeU = csm.addressModeV = csm.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; VK_CHECK(vkCreateSampler(device, &csm, nullptr, &cube_sampler)); @@ -952,6 +955,44 @@ namespace Donut vkCmdDraw(cmd, 36, 1, 0, 0); vkCmdEndRenderPass(cmd); } + + { + VkImageMemoryBarrier src0{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + src0.image = cube_image; src0.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6 }; + src0.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; src0.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + src0.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; src0.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &src0); + + int32_t mipW = (int32_t)FACE, mipH = (int32_t)FACE; + for (uint32_t m = 1; m < CUBE_MIPS; ++m) + { + int32_t nW = mipW > 1 ? mipW / 2 : 1, nH = mipH > 1 ? mipH / 2 : 1; + VkImageMemoryBarrier bd{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + bd.image = cube_image; bd.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, m, 1, 0, 6 }; + bd.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; bd.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + bd.srcAccessMask = 0; bd.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &bd); + + VkImageBlit blit{}; + blit.srcOffsets[1] = { mipW, mipH, 1 }; blit.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, m - 1, 0, 6 }; + blit.dstOffsets[1] = { nW, nH, 1 }; blit.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, m, 0, 6 }; + vkCmdBlitImage(cmd, cube_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + cube_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); + + VkImageMemoryBarrier bs{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + bs.image = cube_image; bs.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, m, 1, 0, 6 }; + bs.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; bs.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + bs.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; bs.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &bs); + mipW = nW; mipH = nH; + } + VkImageMemoryBarrier fin{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + fin.image = cube_image; fin.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, CUBE_MIPS, 0, 6 }; + fin.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; fin.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + fin.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; fin.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &fin); + } + VK_CHECK(vkEndCommandBuffer(cmd)); VkSubmitInfo si{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.commandBufferCount = 1; si.pCommandBuffers = &cmd; VK_CHECK(vkQueueSubmit(graphics_queue, 1, &si, VK_NULL_HANDLE)); |
