diff --git a/shaders/slang/_rename.py b/shaders/slang/_rename.py index 1a5d3845..dfd09a4f 100644 --- a/shaders/slang/_rename.py +++ b/shaders/slang/_rename.py @@ -18,6 +18,13 @@ def checkRenameFiles(samplename): "raytracingbasic.rmiss.spv": "miss.rmiss.spv", "raytracingbasic.rgen.spv": "raygen.rgen.spv", } + case "raytracinggltf": + mappings = { + "raytracinggltf.rchit.spv": "closesthit.rchit.spv", + "raytracinggltf.rmiss.spv": "miss.rmiss.spv", + "raytracinggltf.rgen.spv": "raygen.rgen.spv", + "raytracinggltf.rahit.spv": "anyhit.rahit.spv", + } case "raytracingreflections": mappings = { "raytracingreflections.rchit.spv": "closesthit.rchit.spv", diff --git a/shaders/slang/compileshaders.py b/shaders/slang/compileshaders.py index 3f7f39ab..c2e20149 100644 --- a/shaders/slang/compileshaders.py +++ b/shaders/slang/compileshaders.py @@ -47,6 +47,10 @@ def getShaderStages(filename): stages.append("closesthit") if '[shader("callable")]' in filecontent: stages.append("callable") + if '[shader("intersection")]' in filecontent: + stages.append("intersection") + if '[shader("anyhit")]' in filecontent: + stages.append("anyhit") if '[shader("compute")]' in filecontent: stages.append("compute") if '[shader("amplification")]' in filecontent: @@ -55,6 +59,10 @@ def getShaderStages(filename): stages.append("mesh") if '[shader("geometry")]' in filecontent: stages.append("geometry") + if '[shader("hull")]' in filecontent: + stages.append("hull") + if '[shader("domain")]' in filecontent: + stages.append("domain") f.close() return stages @@ -83,10 +91,7 @@ for root, dirs, files in os.walk(dir_path): print("Compiling %s" % input_file) output_base_file_name = input_file for stage in stages: - if (len(stages) > 0): - entry_point = stage + "Main" - else: - entry_point = "main" + entry_point = stage + "Main" output_ext = "" match stage: case "vertex": @@ -101,6 +106,10 @@ for root, dirs, files in os.walk(dir_path): output_ext = ".rchit" case "callable": output_ext = ".rcall" + case "intersection": + output_ext = ".rint" + case "anyhit": + output_ext = ".rahit" case "compute": output_ext = ".comp" case "mesh": @@ -109,9 +118,15 @@ for root, dirs, files in os.walk(dir_path): output_ext = ".task" case "geometry": output_ext = ".geom" + case "hull": + output_ext = ".tesc" + case "domain": + output_ext = ".tese" output_file = output_base_file_name + output_ext + ".spv" output_file = output_file.replace(".slang", "") + print(output_file) res = subprocess.call("%s %s -profile spirv_1_4 -matrix-layout-column-major -target spirv -o %s -entry %s -stage %s -warnings-disable 39001" % (compiler_path, input_file, output_file, entry_point, stage), shell=True) if res != 0: + print("Error %s", res) sys.exit(res) checkRenameFiles(folder_name) \ No newline at end of file diff --git a/shaders/slang/raytracinggltf/raytracinggltf.slang b/shaders/slang/raytracinggltf/raytracinggltf.slang new file mode 100644 index 00000000..76f2a832 --- /dev/null +++ b/shaders/slang/raytracinggltf/raytracinggltf.slang @@ -0,0 +1,219 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct Payload +{ + float3 hitValue; + uint payloadSeed; + bool shadowed; +}; + +struct GeometryNode { + ConstBufferPointer vertices; + ConstBufferPointer indices; + int textureIndexBaseColor; + int textureIndexOcclusion; +}; + +struct UBOCameraProperties { + float4x4 viewInverse; + float4x4 projInverse; + uint frame; +} + +[[vk::binding(0, 0)]] RaytracingAccelerationStructure accelStruct; +[[vk::binding(1, 0)]] RWTexture2D image; +[[vk::binding(2, 0)]] ConstantBuffer cam; +[[vk::binding(4, 0)]] StructuredBuffer geometryNodes; +[[vk::binding(5, 0)]] Sampler2D textures[]; + +struct Vertex +{ + float3 pos; + float3 normal; + float2 uv; +}; + +struct Triangle { + Vertex vertices[3]; + float3 normal; + float2 uv; +}; + +struct Attributes +{ + float2 bary; +}; + +// Tiny Encryption Algorithm +// By Fahad Zafar, Marc Olano and Aaron Curtis, see https://www.highperformancegraphics.org/previous/www_2010/media/GPUAlgorithms/HPG2010_GPUAlgorithms_Zafar.pdf +uint tea(uint val0, uint val1) +{ + uint sum = 0; + uint v0 = val0; + uint v1 = val1; + for (uint n = 0; n < 16; n++) + { + sum += 0x9E3779B9; + v0 += ((v1 << 4) + 0xA341316C) ^ (v1 + sum) ^ ((v1 >> 5) + 0xC8013EA4); + v1 += ((v0 << 4) + 0xAD90777D) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7E95761E); + } + return v0; +} + +// Linear congruential generator based on the previous RNG state +// See https://en.wikipedia.org/wiki/Linear_congruential_generator +uint lcg(inout uint previous) +{ + const uint multiplier = 1664525u; + const uint increment = 1013904223u; + previous = (multiplier * previous + increment); + return previous & 0x00FFFFFF; +} + +// Generate a random float in [0, 1) given the previous RNG state +float rnd(inout uint previous) +{ + return (float(lcg(previous)) / float(0x01000000)); +} + +// This function will unpack our vertex buffer data into a single triangle and calculates uv coordinates +Triangle unpackTriangle(uint index, Attributes attribs) { + Triangle tri; + const uint triIndex = index * 3; + const uint vertexsize = 112; + + GeometryNode geometryNode = geometryNodes[GeometryIndex()]; + + // Indices indices = Indices(geometryNode.indexBufferDeviceAddress); + // Vertices vertices = Vertices(geometryNode.vertexBufferDeviceAddress); + + // Unpack vertices + // Data is packed as float4 so we can map to the glTF vertex structure from the host side + // We match vkglTF::Vertex: pos.xyz+normal.x, normalyz+uv.xy + // glm::float3 pos; + // glm::float3 normal; + // glm::float2 uv; + // ... + for (uint i = 0; i < 3; i++) { + const uint offset = geometryNode.indices[triIndex + i] * 6; + float4 d0 = geometryNode.vertices[offset + 0]; // pos.xyz, n.x + float4 d1 = geometryNode.vertices[offset + 1]; // n.yz, uv.xy + tri.vertices[i].pos = d0.xyz; + tri.vertices[i].normal = float3(d0.w, d1.xy); + tri.vertices[i].uv = float2(d1.z, d1.w); + } + // Calculate values at barycentric coordinates + float3 barycentricCoords = float3(1.0f - attribs.bary.x - attribs.bary.y, attribs.bary.x, attribs.bary.y); + tri.uv = tri.vertices[0].uv * barycentricCoords.x + tri.vertices[1].uv * barycentricCoords.y + tri.vertices[2].uv * barycentricCoords.z; + tri.normal = tri.vertices[0].normal * barycentricCoords.x + tri.vertices[1].normal * barycentricCoords.y + tri.vertices[2].normal * barycentricCoords.z; + return tri; +} + +[shader("raygeneration")] +void raygenerationMain() +{ + uint3 LaunchID = DispatchRaysIndex(); + uint3 LaunchSize = DispatchRaysDimensions(); + + uint seed = tea(LaunchID.y * LaunchSize.x + LaunchID.x, cam.frame); + + float r1 = rnd(seed); + float r2 = rnd(seed); + + // Subpixel jitter: send the ray through a different position inside the pixel + // each time, to provide antialiasing. + float2 subpixel_jitter = cam.frame == 0 ? float2(0.5f, 0.5f) : float2(r1, r2); + const float2 pixelCenter = float2(LaunchID.xy) + subpixel_jitter; + const float2 inUV = pixelCenter / float2(LaunchSize.xy); + float2 d = inUV * 2.0 - 1.0; + + float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1)); + + RayDesc rayDesc; + rayDesc.Origin = mul(cam.viewInverse, float4(0, 0, 0, 1)).xyz; + rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz; + rayDesc.TMin = 0.001; + rayDesc.TMax = 10000.0; + + Payload payload; + payload.hitValue = float3(0.0); + float3 hitValues = float3(0); + + const int samples = 4; + + // Trace multiple rays for e.g. transparency + for (int smpl = 0; smpl < samples; smpl++) { + payload.payloadSeed = tea(LaunchID.y * LaunchSize.x + LaunchID.x, cam.frame); + TraceRay(accelStruct, RAY_FLAG_NONE, 0xff, 0, 0, 0, rayDesc, payload); + hitValues += payload.hitValue; + } + + float3 hitVal = hitValues / float(samples); + + if (cam.frame > 0) + { + float a = 1.0f / float(cam.frame + 1); + float3 old_color = image[int2(LaunchID.xy)].xyz; + image[int2(LaunchID.xy)] = float4(lerp(old_color, hitVal, a), 1.0f); + } + else + { + // First frame, replace the value in the buffer + image[int2(LaunchID.xy)] = float4(hitVal, 1.0f); + } +} + +[shader("closesthit")] +void closesthitMain(inout Payload payload, in Attributes attribs) +{ + Triangle tri = unpackTriangle(PrimitiveIndex(), attribs); + payload.hitValue = float3(tri.normal); + + GeometryNode geometryNode = geometryNodes[GeometryIndex()]; + + float3 color = textures[NonUniformResourceIndex(geometryNode.textureIndexBaseColor)].SampleLevel(tri.uv, 0.0).rgb; + if (geometryNode.textureIndexOcclusion > -1) { + float occlusion = textures[NonUniformResourceIndex(geometryNode.textureIndexOcclusion)].SampleLevel(tri.uv, 0.0).r; + color *= occlusion; + } + + payload.hitValue = color; + + // Shadow casting + float tmin = 0.001; + float tmax = 10000.0; + float epsilon = 0.001; + float3 origin = WorldRayOrigin() + WorldRayDirection() * RayTCurrent() + tri.normal * epsilon; + payload.shadowed = true; + float3 lightVector = float3(-5.0, -2.5, -5.0); + // Trace shadow ray and offset indices to match shadow hit/miss shader group indices + // traceRayEXT(topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT, 0xFF, 0, 0, 1, origin, tmin, lightVector, tmax, 2); + // if (shadowed) { + // hitValue *= 0.7; + // } +} + +[shader("anyhit")] +void anyhitMain(inout Payload payload, in Attributes attribs) +{ + Triangle tri = unpackTriangle(PrimitiveIndex(), attribs); + GeometryNode geometryNode = geometryNodes[GeometryIndex()]; + float4 color = textures[NonUniformResourceIndex(geometryNode.textureIndexBaseColor)].SampleLevel(tri.uv, 0.0); + // If the alpha value of the texture at the current UV coordinates is below a given threshold, we'll ignore this intersection + // That way ray traversal will be stopped and the miss shader will be invoked + if (color.a < 0.9) { + if (rnd(payload.payloadSeed) > color.a) { + IgnoreHit(); + } + } +} + +[shader("miss")] +void missMain(inout Payload payload) +{ + payload.hitValue = float3(1.0); +} \ No newline at end of file diff --git a/shaders/slang/raytracinggltf/shadow.slang b/shaders/slang/raytracinggltf/shadow.slang new file mode 100644 index 00000000..481816ad --- /dev/null +++ b/shaders/slang/raytracinggltf/shadow.slang @@ -0,0 +1,18 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct Payload +{ + float3 hitValue; + uint payloadSeed; + bool shadowed; +}; + +[shader("miss")] +void missMain(inout Payload payload) +{ + payload.shadowed = false; +} \ No newline at end of file