diff --git a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters index cc2db39..5f46935 100644 --- a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters +++ b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters @@ -201,6 +201,9 @@ + + + diff --git a/README.md b/README.md index b0189d0..338e9dd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,98 @@ **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5 - DirectX Procedural Raytracing** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Alexis Ward + * [LinkedIn](https://www.linkedin.com/in/alexis-ward47/), [personal website](https://www.alexis-ward.tech/) +* Tested on: Windows 10, i7-8750H CPU @ 2.20GHz 16GB, GTX 1050 Ti -### (TODO: Your README) +![](images/oof.gif) -Include screenshots, analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +# Ray Tracing Background (Conceptual Questions) + +### How To Ray Trace + +Ray tracing begins by firing off "rays" from the camera's perspective, with 1 ray corresponding to 1 pixel. Each ray is defined by an `Origin` and a `Direction`, such that `Ray = Origin + t * Direction`, with `t` being a distance along the ray. You can convert each pixel in the display to a ray by: + +1. Setting `Origin` equal to the camera's "eye," or position. +2. Given pixel coordinates `index`, we get it's Screen Space counterpart by: + * `screenX = (index.x / ScreenXDimension) * 2.f - 1.f` and + * `screenY = 1.f - (index.y / ScreenYDimension) * 2.f` +3. Since we don't have a `z` or `w` value, we estimate the furthest possible point by using the camera's far-clip distance: `farPoint = vec4(screenX * -farClip, screenY * -farClip, farClip, farClip)`. This puts us into Unhomogenized Screen Space. +4. We now multiply this vector by the inverse of the camera's view and projection matrices: `inverse(u_View) * inverse(u_Project) * farPoint` +5. Set the `Direction` to the normalized difference between the above result and the `Origin`. + +This is essentially converting a pervieved point in Pixel Space back to World Space, and then finding the vector from the camera to said point. We can now test for the ray's intersections with geometry. + +DirectX Update: Multiplication is swapped by virtue of DirectX's wonkiness. + +### Locating Procedural Geometry + +![](images/sdf.png) + +Every procedural geometry in this project is defined using 3 things: its `Axis-Aligned Bounding Box` (shortened to AABB, it is a tight bounds that encases the whole shape), its `Type` or shape, and the `Equation` determined by its `Type` and transformation. This `Equation` will most likely be a signed distance function, which tells us how far we are from the closest point on the geometry (but not necessarily the closest point along a ray). + +Update: No SDFs in this project, but the process is similar nonetheless. + +To render procedural geometry, we check each pixel's corresponding ray for intersections with the scene. To simplify this, we can first see if the current ray passes through an `AABB` stored within our Bottom Level Acceleration Structure (BLAS). This tells us that the geometry encased within can potentially influence this pixel. We then use a Closest Hit Shader determined by the object's `Type` to trace our ray with the influence of this geometry's `Equation`. This equation should give us a `t` value, as shown in the aforementioned ray representation: `Ray = Origin + t * Direction`. We displace our ray by this value and test again. This part happens recursively until the `Equation` returns a zero (or a value within some epsilon factor), or if the ray leaves the `AABB`. + +This method greatly decreases runtime because, by working with only the intersected AABBs, we avoid having to recursively compare the ray to every geometry equation equation in the scene: we only perform this for the relevant equations. The AABB intersection check is a simple one because of how they are defined by their min and max [x, y, z] positions. + +### DXR Top-Level / Bottom-Level Example + +Say our scene looks like this: + +

+ +

+ +This is a diagram of the resulting acceleration structure: + +![](images/diagram.png) + + +# The Project + +## The CPU Side +This project would not run without a complete CPU implementation, so unfortunately I have no visuals to display here. The general setup process goes as follows: + + - Allocating and uploadng **scene data** to the GPU, which includes a camera, lights, colors for objects, as well as transforms for the objects to render. I use `GpuUploadBuffers` to accomplish this. + - Set up **root signatures** as a way to programmatically read/write to various data from the GPU. You first create a slot for the present resource and a descriptor that tells the GPU how to read said resource. The two are linked later in the process. + - Create the **HitGroup** subobjects that will be built into the pipeline. A hit group, in this project, is composed of a `Closest Hit Shader` and at least one `Intersection Shader`. + - Allocating and uploading the **geometry data**. This is done in two different ways: by triangle data and by procedural geometry data (AABBs). + - Creating an **acceleration structure** to boost performance. The scene is divided into `Top Level Acceleration Structures` (TLAS) that hold multiple instances of `Bottom Level Acceleration Structures` (BLAS0, as visualized in the example above) + - DXR's **Shader Tables**. Again, `GpuUploadBuffers`, and this version holds shaders of the same types. + - Finally, we "glue" all this data together using the many `Set...()` functions available. Then `DispatchRays()` can be calleld for each pixel, like a CUDA kernel. + +## The GPU Side + +First we have to generate the rays as described in the **How To Ray Trace** section above. This will show a dark image like the one below, that only depicts ambient lighting. + +

+ +

+ +### Trace Ray + +DXR's TraceRay function, as the name implies, traces a ray through the scene. One implementation occurs for the radiance ray and another for the shadow ray. This is what the scene looks like with and without the shadow processing: + +![](images/shadows.png) + +### Shaders + +Miss Shaders and Intersection Shaders pull the scene together. They determine what happens when a ray hits or misses geometry and calculates essential information like the hit points and normals. In this project, I allow for intersections with spheres and metaballs (left and right below). + +### Shading +This project uses the Phone Lighting Model for all objects. There is also a distance falloff that "blurs" geometry the further away they are - though, this is closer to atmospheric perspective. View the before and aafter below: + +![](images/falloff.png) + +## Interesting bugs +I sadly have some visual artifacts when it comes to the box AABB. This includes extra geometry and spotty shading. I hope to fix this in the future. + +I noticed that the calls to DirectX's `TraceRay()` function cuts my frame rate down by two thirds. I wasn't yet sure if that's just what happens with `TraceRay()`, or if it's somehow related to the aforementioned artifacts or improper buffer handling. To be determined! + +## Performance Analysis + +![](images/graph.png) + +As expected, the frame rate goes down with increased ray depth (with few exceptions). \ No newline at end of file diff --git a/images/balls.png b/images/balls.png new file mode 100644 index 0000000..7c34af0 Binary files /dev/null and b/images/balls.png differ diff --git a/images/diagram.png b/images/diagram.png new file mode 100644 index 0000000..1cd6a2a Binary files /dev/null and b/images/diagram.png differ diff --git a/images/falloff.png b/images/falloff.png new file mode 100644 index 0000000..64c17e5 Binary files /dev/null and b/images/falloff.png differ diff --git a/images/graph.png b/images/graph.png new file mode 100644 index 0000000..c7589c9 Binary files /dev/null and b/images/graph.png differ diff --git a/images/oof.gif b/images/oof.gif new file mode 100644 index 0000000..668ebeb Binary files /dev/null and b/images/oof.gif differ diff --git a/images/polyGraph.png b/images/polyGraph.png new file mode 100644 index 0000000..a6069f4 Binary files /dev/null and b/images/polyGraph.png differ diff --git a/images/post-falloff.PNG b/images/post-falloff.PNG new file mode 100644 index 0000000..ec4be3f Binary files /dev/null and b/images/post-falloff.PNG differ diff --git a/images/pre-falloff.PNG b/images/pre-falloff.PNG new file mode 100644 index 0000000..c1ff07a Binary files /dev/null and b/images/pre-falloff.PNG differ diff --git a/images/refl.png b/images/refl.png new file mode 100644 index 0000000..de5fb71 Binary files /dev/null and b/images/refl.png differ diff --git a/images/sdf.png b/images/sdf.png new file mode 100644 index 0000000..ef01322 Binary files /dev/null and b/images/sdf.png differ diff --git a/images/shadows.png b/images/shadows.png new file mode 100644 index 0000000..511c2a6 Binary files /dev/null and b/images/shadows.png differ diff --git a/images/spheres.png b/images/spheres.png new file mode 100644 index 0000000..0215169 Binary files /dev/null and b/images/spheres.png differ diff --git a/images/wOShadow.png b/images/wOShadow.png new file mode 100644 index 0000000..69f8013 Binary files /dev/null and b/images/wOShadow.png differ diff --git a/images/withShadow.png b/images/withShadow.png new file mode 100644 index 0000000..0be72de Binary files /dev/null and b/images/withShadow.png differ diff --git a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli index c6ccebb..71c010f 100644 --- a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli @@ -166,13 +166,29 @@ bool RaySolidSphereIntersectionTest(in Ray ray, out float thit, out float tmax, bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr) { // Define the spheres in local space (within the aabb) - float3 center = float3(-0.2, 0, -0.2); - float radius = 0.7f; + float3 centerS1 = float3(-0.2, 0, -0.5); + float r1 = 0.7f; + + // Sphere 2 + float3 centerS2 = float3(0, 0.2, 0); + float r2 = 0.5f; + + // Sphere3 + float3 centerS3 = float3(-0.1, -0.7, -0.2); + float r3 = 0.3f; thit = RayTCurrent(); float tmax; - if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius)) + if (RaySphereIntersectionTest(ray, thit, tmax, attr, centerS1, r1)) + { + return true; + } + else if (RaySphereIntersectionTest(ray, thit, tmax, attr, centerS2, r2)) + { + return true; + } + else if (RaySphereIntersectionTest(ray, thit, tmax, attr, centerS3, r3)) { return true; } diff --git a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj index 4a8b9ab..5b4d504 100644 --- a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj +++ b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj @@ -219,14 +219,14 @@ PrebuildCheck.bat /Zpr %(AdditionalOptions)
- - - {4be280a6-1066-41ca-acdd-6bb7e532508b} + + + diff --git a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj.filters b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj.filters index ebe7438..a524448 100644 --- a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj.filters +++ b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj.filters @@ -132,12 +132,12 @@ Source Files - - - Assets\Shaders + + + \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp index 905341d..d47e4ec 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp @@ -26,14 +26,21 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetGPUVirtualAddress()) - // * The *total size* of the buffer can be accessed from GetDesc().Width (e.g m_indexBuffer.resource->GetDesc().Width) - // * We filled in the format of the buffers to avoid confusion. auto& geometryDesc = geometryDescs[BottomLevelASType::Triangle][0]; geometryDesc = {}; + // Geometry Desc variables (in D3D12_RAYTRACING_GEOMETRY_DESC) + geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + geometryDesc.Flags = geometryFlags; + // * 565 staff filled in the format of the buffers to avoid confusion. geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R16_UINT; geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; + // * Use m_indexBuffer and m_vertexBuffer to get pointers to the following data: + // * The *total size* of the buffer can be accessed from GetDesc().Width (e.g m_indexBuffer.resource->GetDesc().Width) + geometryDesc.Triangles.IndexCount = m_indexBuffer.resource->GetDesc().Width / sizeof(Index); + geometryDesc.Triangles.VertexCount = m_vertexBuffer.resource->GetDesc().Width / sizeof(Vertex); + // * GPUVirtualAddresses can be accessed from a D3D12Resource using GetGPUVirtualAddress() (e.g m_vertexBuffer.resource->GetGPUVirtualAddress()) + geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress(); + geometryDesc.Triangles.VertexBuffer = { m_vertexBuffer.resource->GetGPUVirtualAddress(), sizeof(Vertex) };// Virtual Address and Stride } { @@ -51,12 +58,24 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetDesc().Width / sizeof(D3D12_RAYTRACING_AABB); // total size over data size, like above + geometryDescs[BottomLevelASType::AABB][aabbGeom].AABBs.AABBs = { + m_aabbBuffer.resource->GetGPUVirtualAddress() + aabbGeom * sizeof(D3D12_RAYTRACING_AABB), + sizeof(D3D12_RAYTRACING_AABB) + }; + } } } // TODO-2.6: Given the geometry and the geometry descriptors, build the bottom-level acceleration structures. -AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vector& geometryDescs, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS buildFlags) +AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vector& geometryDescs, + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS buildFlags) { auto device = m_deviceResources->GetD3DDevice(); auto commandList = m_deviceResources->GetCommandList(); @@ -70,7 +89,11 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto // Again, these tell the AS where the actual geometry data is and how it is laid out. // TODO-2.6: fill the bottom-level inputs. Consider using D3D12_ELEMENTS_LAYOUT_ARRAY as the DescsLayout. D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS &bottomLevelInputs = bottomLevelBuildDesc.Inputs; - + bottomLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + bottomLevelInputs.Flags = buildFlags; + bottomLevelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + bottomLevelInputs.NumDescs = geometryDescs.size(); + bottomLevelInputs.pGeometryDescs = geometryDescs.data(); // Query the driver for resource requirements to build an acceleration structure. We've done this for you. D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO bottomLevelPrebuildInfo = {}; @@ -89,7 +112,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto // Allocate resources for acceleration structures as a UAV --> this will prepare bottomLevelAS for us. // Acceleration structures can only be placed in resources that are created in the default heap (or custom heap equivalent). - // Default heap is OK since the application doesn�t need CPU read/write access to them. + // Default heap is OK since the application doesn't need CPU read/write access to them. // The resources that will contain acceleration structures must be created in the state D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, // and must have resource flag D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS. The ALLOW_UNORDERED_ACCESS requirement simply acknowledges both: // - the system will be doing this type of access in its implementation of acceleration structure builds behind the scenes. @@ -110,7 +133,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto // TODO-2.6: Now that you have the scratch and actual bottom-level AS desc, pass their GPU addresses to the bottomLevelBuildDesc. // Consider reading about D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC. // This should be as easy as passing the GPU addresses to the struct using GetGPUVirtualAddress() calls. - + bottomLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress(); + bottomLevelBuildDesc.DestAccelerationStructureData = bottomLevelAS->GetGPUVirtualAddress(); // Fill up the command list with a command that tells the GPU how to build the bottom-level AS. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -127,9 +151,15 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto // TODO-2.6: After we finished building the bottom-level AS, save all the info in // the AccelerationStructureBuffers struct so the top-level AS can use it! + AccelerationStructureBuffers accelBuff{}; + accelBuff.scratch = scratch; + accelBuff.accelerationStructure = bottomLevelAS; + accelBuff.instanceDesc = nullptr; + accelBuff.ResultDataMaxSizeInBytes = bottomLevelPrebuildInfo.ResultDataMaxSizeInBytes; + // Don't forget that this is the return value. // Consider looking into the AccelerationStructureBuffers struct in DXR-Structs.h - return AccelerationStructureBuffers{}; + return accelBuff; } // TODO-2.6: Build the instance descriptor for each bottom-level AS you built before. @@ -178,10 +208,26 @@ void DXProceduralProject::BuildBottomLevelASInstanceDescs(BLASPtrType *bottomLev // TODO-2.6: Create instanced bottom-level AS with procedural geometry AABBs. // * Make sure to set InstanceContributionToHitGroupIndex to beyond the shader records for the triangle AABB. // For triangles, we have 1 shader record for radiance rays, and another for shadow rays. - // Where do you think procedural shader records would start then? Hint: right after. + // Where do you think procedural shader records would start then? Hint: right after - so 2! // * Make each instance hover above the ground by ~ half its width { + auto& instanceDesc = instanceDescs[BottomLevelASType::AABB]; + instanceDesc = {}; + instanceDesc.InstanceMask = 1; + instanceDesc.InstanceContributionToHitGroupIndex = 2; + instanceDesc.AccelerationStructure = bottomLevelASaddresses[BottomLevelASType::AABB]; + + // Calculate transformation matrix. + // We multiply the width by 0.5 and translate up by that ammount for each object. + const XMVECTOR widthHover = XMLoadFloat3(&XMFLOAT3(0.f, c_aabbWidth * 0.5f , 0.f)); + + // Transform + // XMMATRIX mScale = XMMatrixScaling(fWidth.x, fWidth.y, fWidth.z); // Maybe by c_aabbWidth? + XMMATRIX mTranslation = XMMatrixTranslationFromVector(widthHover); + XMMATRIX mTransform = /*mScale */ mTranslation; + // Store the transform in the instanceDesc. + XMStoreFloat3x4(reinterpret_cast(instanceDesc.Transform), mTransform); } // Upload all these instances to the GPU, and make sure the resouce is set to instanceDescsResource. @@ -204,7 +250,10 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt // TODO-2.6: fill in the topLevelInputs, read about D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS. // Consider using D3D12_ELEMENTS_LAYOUT_ARRAY as a DescsLayout since we are using an array of bottom-level AS. D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS &topLevelInputs = topLevelBuildDesc.Inputs; - + topLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + topLevelInputs.Flags = buildFlags; + topLevelInputs.NumDescs = NUM_BLAS; // 2 + topLevelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO topLevelPrebuildInfo = {}; if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -218,7 +267,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt ThrowIfFalse(topLevelPrebuildInfo.ResultDataMaxSizeInBytes > 0); // TODO-2.6: Allocate a UAV buffer for the scracth/temporary top-level AS data. - + AllocateUAVBuffer(device, topLevelPrebuildInfo.ScratchDataSizeInBytes, &scratch, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, L"Temp_top-level_AS_data"); // Need this line?? compiled without // Allocate space for the top-level AS. { @@ -233,7 +283,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt } // TODO-2.6: Allocate a UAV buffer for the actual top-level AS. - + AllocateUAVBuffer(device, topLevelPrebuildInfo.ResultDataMaxSizeInBytes, &topLevelAS, + initialResourceState, L"Top-level_AS_data"); } // Note on Emulated GPU pointers (AKA Wrapped pointers) requirement in Fallback Layer: @@ -261,7 +312,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt }; // TODO-2.6: Call the fallback-templated version of BuildBottomLevelASInstanceDescs() you completed above. - + BuildBottomLevelASInstanceDescs( + bottomLevelASaddresses, &instanceDescsResource); } else // DirectX Raytracing { @@ -273,7 +325,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt }; // TODO-2.6: Call the DXR-templated version of BuildBottomLevelASInstanceDescs() you completed above. - + BuildBottomLevelASInstanceDescs( + bottomLevelASaddresses, &instanceDescsResource); } // Create a wrapped pointer to the acceleration structure. @@ -285,7 +338,9 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt // TODO-2.6: fill in the topLevelBuildDesc. Read about D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC. // This should be as easy as passing the GPU addresses to the struct using GetGPUVirtualAddress() calls. - + topLevelInputs.InstanceDescs = instanceDescsResource->GetGPUVirtualAddress(); + topLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress(); + topLevelBuildDesc.DestAccelerationStructureData = topLevelAS->GetGPUVirtualAddress(); // Build acceleration structure. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -303,8 +358,14 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt // TODO-2.6: After we finished building the top-level AS, save all the info in the AccelerationStructureBuffers struct. // Very similar to how you did this in BuildBottomLevelAS() except now you have to worry about topLevelASBuffers.instanceDesc. // Consider looking into the AccelerationStructureBuffers struct in DXR-Structs.h. + AccelerationStructureBuffers accelBuff{}; + accelBuff.scratch = scratch; + accelBuff.accelerationStructure = topLevelAS; + accelBuff.instanceDesc = instanceDescsResource; + accelBuff.ResultDataMaxSizeInBytes = topLevelPrebuildInfo.ResultDataMaxSizeInBytes; + // Make sure to return the topLevelASBuffers before you exit the function. - return AccelerationStructureBuffers{}; + return accelBuff; } // TODO-2.6: This will wrap building the Acceleration Structure! This is what we will call when building our scene. @@ -320,12 +381,14 @@ void DXProceduralProject::BuildAccelerationStructures() // TODO-2.6: Build the geometry descriptors. Hint: you filled in a function that does this. array, BottomLevelASType::Count> geometryDescs; - + BuildGeometryDescsForBottomLevelAS(geometryDescs); // TODO-2.6: For each bottom-level object (triangle, procedural), build a bottom-level AS. // Hint: you filled in a function that does this. AccelerationStructureBuffers bottomLevelAS[BottomLevelASType::Count]; - + for (UINT bLObj = 0; bLObj < BottomLevelASType::Count; bLObj++) { + bottomLevelAS[bLObj] = BuildBottomLevelAS(geometryDescs[bLObj]); + } // Batch all resource barriers for bottom-level AS builds. // This will Notifies the driver that it needs to synchronize multiple accesses to resources. @@ -337,8 +400,7 @@ void DXProceduralProject::BuildAccelerationStructures() commandList->ResourceBarrier(BottomLevelASType::Count, resourceBarriers); // TODO-2.6: Build top-level AS. Hint, you already made a function that does this. - AccelerationStructureBuffers topLevelAS; - + AccelerationStructureBuffers topLevelAS = BuildTopLevelAS(bottomLevelAS); // Kick off acceleration structure construction. m_deviceResources->ExecuteCommandList(); @@ -349,5 +411,8 @@ void DXProceduralProject::BuildAccelerationStructures() // TODO-2.6: Store the AS buffers. The rest of the buffers will be released once we exit the function. // Do this for both the bottom-level and the top-level AS. Consider re-reading the DXProceduralProject class // to find what member variables should be set. - + m_topLevelAS = topLevelAS.accelerationStructure; + for (UINT blAS = 0; blAS < BottomLevelASType::Count; blAS++) { + m_bottomLevelAS[blAS] = bottomLevelAS[blAS].accelerationStructure; + } } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-AppLifeCyclecpp.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-AppLifeCyclecpp.cpp index 527139a..7b9f0ea 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-AppLifeCyclecpp.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-AppLifeCyclecpp.cpp @@ -22,6 +22,7 @@ void DXProceduralProject::OnInit() m_deviceResources->SetWindow(Win32Application::GetHwnd(), m_width, m_height); m_deviceResources->InitializeDXGIAdapter(); EnableDirectXRaytracing(m_deviceResources->GetAdapter()); + SelectRaytracingAPI(RaytracingAPI::DirectXRaytracing); m_deviceResources->CreateDeviceResources(); m_deviceResources->CreateWindowSizeDependentResources(); diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp index 03a8c58..61e6513 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp @@ -22,7 +22,8 @@ void DXProceduralProject::DoRaytracing() commandList->SetComputeRootConstantBufferView(GlobalRootSignature::Slot::SceneConstant, m_sceneCB.GpuVirtualAddress(frameIndex)); // TODO-2.8: do a very similar operation for the m_aabbPrimitiveAttributeBuffer - + m_aabbPrimitiveAttributeBuffer.CopyStagingToGpu(frameIndex); + commandList->SetComputeRootShaderResourceView(GlobalRootSignature::Slot::AABBattributeBuffer, m_aabbPrimitiveAttributeBuffer.GpuVirtualAddress(frameIndex)); // Bind the descriptor heaps. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -49,10 +50,11 @@ void DXProceduralProject::DoRaytracing() // This should be done by telling the commandList to SetComputeRoot*(). You just have to figure out what * is. // Example: in the case of GlobalRootSignature::Slot::SceneConstant above, we used SetComputeRootConstantBufferView() // Hint: look at CreateRootSignatures() in DXR-Pipeline.cpp. - + commandList->SetComputeRootDescriptorTable(GlobalRootSignature::Slot::VertexBuffers, m_indexBuffer.gpuDescriptorHandle); // TODO-2.8: Bind the OutputView (basically m_raytracingOutputResourceUAVGpuDescriptor). Very similar to the Index/Vertex buffer. - + //commandList->SetComputeRootUnorderedAccessView(GlobalRootSignature::Slot::OutputView, m_raytracingOutputResourceUAVGpuDescriptor.); + commandList->SetComputeRootDescriptorTable(GlobalRootSignature::Slot::OutputView, m_raytracingOutputResourceUAVGpuDescriptor); // This will define a `DispatchRays` function that takes in a command list, a pipeline state, and a descriptor // This will set the hooks using the shader tables built before and call DispatchRays on the command list @@ -60,13 +62,18 @@ void DXProceduralProject::DoRaytracing() { // You will fill in a D3D12_DISPATCH_RAYS_DESC (which is dispatchDesc). // TODO-2.8: fill in dispatchDesc->HitGroupTable. Look up the struct D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE - - - // TODO-2.8: now fill in dispatchDesc->MissShaderTable - - - // TODO-2.8: now fill in dispatchDesc->RayGenerationShaderRecord - + dispatchDesc->HitGroupTable = { m_hitGroupShaderTable->GetGPUVirtualAddress(), // StartAddress + m_hitGroupShaderTable->GetDesc().Width, // SizeInBytes + m_hitGroupShaderTableStrideInBytes }; // StrideInBytes + + // TODO-2.8: now fill in dispatchDesc->MissShaderTable, also D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE + dispatchDesc->MissShaderTable = { m_missShaderTable->GetGPUVirtualAddress(), // StartAddress + m_missShaderTable->GetDesc().Width, // SizeInBytes + m_missShaderTableStrideInBytes }; // StrideInBytes + + // TODO-2.8: now fill in dispatchDesc->RayGenerationShaderRecord, D3D12_GPU_VIRTUAL_ADDRESS_RANGE instead + dispatchDesc->RayGenerationShaderRecord = { m_rayGenShaderTable->GetGPUVirtualAddress(), // StartAddress + m_rayGenShaderTable->GetDesc().Width }; // SizeInBytes // We do this for you. This will define how many threads will be dispatched. Basically like a blockDims in CUDA! dispatchDesc->Width = m_width; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp index e3ff63c..787cfd5 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp @@ -67,9 +67,9 @@ void DXProceduralProject::InitializeScene() m_up = XMVector3Normalize(XMVector3Cross(direction, right)); // Rotate camera around Y axis. - XMMATRIX rotate = XMMatrixRotationY(XMConvertToRadians(45.0f)); - m_eye = XMVector3Transform(m_eye, rotate); - m_up = XMVector3Transform(m_up, rotate); + XMMATRIX rotate = DirectX::XMMatrixRotationY(XMConvertToRadians(45.0f)); // Got errors out of nowhere, + m_eye = XMVector3Transform(m_eye, rotate); // so added DirectX:: to all + m_up = XMVector3Transform(m_up, rotate); // (classmate's advice) UpdateCameraMatrices(); } @@ -111,7 +111,13 @@ void DXProceduralProject::CreateConstantBuffers() // structured buffers are for structs that have dynamic data (e.g lights in a scene, or AABBs in this case) void DXProceduralProject::CreateAABBPrimitiveAttributesBuffers() { + // Similar besides the "numElements" input to create - from m_aabbMaterialCB in the .h file + auto device = m_deviceResources->GetD3DDevice(); + auto frameCount = m_deviceResources->GetBackBufferCount(); + m_aabbPrimitiveAttributeBuffer.Create(device, + IntersectionShaderType::TotalPrimitiveCount, + frameCount, L"AABB Primitive Attribute Buffer"); } // LOOKAT-2.1: Update camera matrices stored in m_sceneCB. @@ -121,10 +127,10 @@ void DXProceduralProject::UpdateCameraMatrices() m_sceneCB->cameraPosition = m_eye; float fovAngleY = 45.0f; - XMMATRIX view = XMMatrixLookAtLH(m_eye, m_at, m_up); - XMMATRIX proj = XMMatrixPerspectiveFovLH(XMConvertToRadians(fovAngleY), m_aspectRatio, 0.01f, 125.0f); + XMMATRIX view = DirectX::XMMatrixLookAtLH(m_eye, m_at, m_up); + XMMATRIX proj = DirectX::XMMatrixPerspectiveFovLH(XMConvertToRadians(fovAngleY), m_aspectRatio, 0.01f, 125.0f); XMMATRIX viewProj = view * proj; - m_sceneCB->projectionToWorld = XMMatrixInverse(nullptr, viewProj); + m_sceneCB->projectionToWorld = DirectX::XMMatrixInverse(nullptr, viewProj); } // TODO-2.1: Update the PrimitiveInstancePerFrameBuffer for every AABB stored in m_aabbPrimitiveAttributeBuffer[]. @@ -140,15 +146,15 @@ void DXProceduralProject::UpdateAABBPrimitiveAttributes(float animationTime) { auto frameIndex = m_deviceResources->GetCurrentFrameIndex(); - XMMATRIX mIdentity = XMMatrixIdentity(); + XMMATRIX mIdentity = DirectX::XMMatrixIdentity(); // Different scale matrices - XMMATRIX mScale15y = XMMatrixScaling(1, 1.5, 1); - XMMATRIX mScale15 = XMMatrixScaling(1.5, 1.5, 1.5); - XMMATRIX mScale2 = XMMatrixScaling(2, 2, 2); + XMMATRIX mScale15y = DirectX::XMMatrixScaling(1, 1.5, 1); + XMMATRIX mScale15 = DirectX::XMMatrixScaling(1.5, 1.5, 1.5); + XMMATRIX mScale2 = DirectX::XMMatrixScaling(2, 2, 2); // Rotation matrix that changes over time - XMMATRIX mRotation = XMMatrixRotationY(-2 * animationTime); + XMMATRIX mRotation = DirectX::XMMatrixRotationY(-2 * animationTime); auto SetTransformForAABB = [&](UINT primitiveIndex, XMMATRIX& mScale, XMMATRIX& mRotation) @@ -156,7 +162,7 @@ void DXProceduralProject::UpdateAABBPrimitiveAttributes(float animationTime) XMVECTOR vTranslation = 0.5f * (XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MinX)) + XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MaxX))); // i.e middle of AABB. - XMMATRIX mTranslation = XMMatrixTranslationFromVector(vTranslation); + XMMATRIX mTranslation = DirectX::XMMatrixTranslationFromVector(vTranslation); // TODO-2.1: Fill in this lambda function. // It should create a transform matrix that is equal to scale * rotation * translation. @@ -164,6 +170,10 @@ void DXProceduralProject::UpdateAABBPrimitiveAttributes(float animationTime) // You can infer what the bottom level AS space to local space transform should be. // The intersection shader tests in this project work with local space, but the geometries are provided in bottom level // AS space. So this data will be used to convert back and forth from these spaces. + m_aabbPrimitiveAttributeBuffer[primitiveIndex].localSpaceToBottomLevelAS = + mScale * mRotation * mTranslation; + m_aabbPrimitiveAttributeBuffer[primitiveIndex].bottomLevelASToLocalSpace = + DirectX::XMMatrixInverse(nullptr, m_aabbPrimitiveAttributeBuffer[primitiveIndex].localSpaceToBottomLevelAS); }; UINT offset = 0; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp index 9d93504..d5a2c79 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp @@ -86,7 +86,12 @@ void DXProceduralProject::BuildProceduralGeometryAABBs() // This should take into account the basePosition and the stride defined above. auto InitializeAABB = [&](auto& offsetIndex, auto& size) { - D3D12_RAYTRACING_AABB aabb{}; + D3D12_RAYTRACING_AABB aabb{ basePosition.x + offsetIndex.x * stride.x, // MinX + basePosition.y + offsetIndex.y * stride.y, // MinY + basePosition.z + offsetIndex.z * stride.z, // MinZ + basePosition.x + offsetIndex.x * stride.x + size.x, // MaxX + basePosition.y + offsetIndex.y * stride.y + size.y, // MaxY + basePosition.z + offsetIndex.z * stride.z + size.z }; // MaxZ return aabb; }; m_aabbs.resize(IntersectionShaderType::TotalPrimitiveCount); @@ -110,12 +115,15 @@ void DXProceduralProject::BuildProceduralGeometryAABBs() // TODO-2.5: Allocate an upload buffer for this AABB data. // The base data lives in m_aabbs.data() (the stuff you filled in!), but the allocationg should be pointed // towards m_aabbBuffer.resource (the actual D3D12 resource that will hold all of our AABB data as a contiguous buffer). - + //m_aabbBuffer.resource.A + AllocateUploadBuffer(device, m_aabbs.data(), + m_aabbs.size() * sizeof(D3D12_RAYTRACING_AABB), &m_aabbBuffer.resource); } } // TODO-2.5: Build geometry used in the project. As easy as calling both functions above :) void DXProceduralProject::BuildGeometry() { - + BuildPlaneGeometry(); + BuildProceduralGeometryAABBs(); } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp index 33899bd..3a14d12 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp @@ -29,7 +29,23 @@ void DXProceduralProject::CreateHitGroupSubobjects(CD3D12_STATE_OBJECT_DESC* ray // TODO-2.3: AABB geometry hit groups. Very similar to triangles, except now you have to *also* loop over the primitive types. { + for (UINT rayType = 0; rayType < RayType::Count; rayType++) + { + for (UINT primitives = 0; primitives < IntersectionShaderType::Count; primitives++) + { + auto hitGroup = raytracingPipeline->CreateSubobject(); + if (rayType == RayType::Radiance) + { + // We import the closest hit shader name + hitGroup->SetClosestHitShaderImport(c_closestHitShaderNames[GeometryType::AABB]); + } + hitGroup->SetIntersectionShaderImport(c_intersectionShaderNames[primitives]); + // We tell the hitgroup that it should export into the correct shader hit group name, with the correct type + hitGroup->SetHitGroupExport(c_hitGroupNames_AABBGeometry[primitives][rayType]); + hitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE); + } + } } } @@ -54,6 +70,18 @@ void DXProceduralProject::CreateLocalRootSignatureSubobjects(CD3D12_STATE_OBJECT // TODO-2.3: AABB geometry hitgroup/local root signature association. // Very similar to triangles, except now one for each primitive type. { - + auto localRootSignature = raytracingPipeline->CreateSubobject(); + + // This is the AABB local root signature you already filled in before. + localRootSignature->SetRootSignature(m_raytracingLocalRootSignature[LocalRootSignature::Type::AABB].Get()); + + // Shader association + auto rootSignatureAssociation = raytracingPipeline->CreateSubobject(); + rootSignatureAssociation->SetSubobjectToAssociate(*localRootSignature); + // For each primitive type + for (UINT primitives = 0; primitives < IntersectionShaderType::Count; primitives++) + { + rootSignatureAssociation->AddExports(c_hitGroupNames_AABBGeometry[primitives]); + } } } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp index 2dff8b5..73c1155 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp @@ -19,26 +19,32 @@ void DXProceduralProject::CreateRootSignatures() CD3DX12_DESCRIPTOR_RANGE ranges[2]; ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0); // 1 output texture - // TODO-2.2: In range index 1 (the second range), initialize 2 SRV resources at register 1: indices and vertices of triangle data. + // TODO-2.2: In range index 1 (the second range), initialize 2 SRV (ShaderResourceView) resources at register 1: indices and vertices of triangle data. // This will effectively put the indices at register 1, and the vertices at register 2. - + ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 1); // TODO-2.2: Initialize all the parameters of the GlobalRootSignature in their appropriate slots. // * See GlobalRootSignature in RaytracingSceneDefines.h to understand what they are. + CD3DX12_ROOT_PARAMETER rootParameters[GlobalRootSignature::Slot::Count]; // - The OutputView should correspond to the UAV range descriptor above (descriptor table), bound to register 0 of the UAV registers. - // - The Index/Vertex Buffer should correspond to the SRV range (descriptor table) above, bound to registers 1 and 2 of the SRV registers. + rootParameters[GlobalRootSignature::Slot::OutputView].InitAsDescriptorTable(1, &ranges[0]); + // - The Index/Vertex Buffer should correspond to the SRV range (descriptor table) above, bound to registers 1 and 2 of the SRV registers. // Note that since we initialize these as a range of size 2, then you should bind the entire range to register 1. // This will automatically fill in registers 1 and 2. + rootParameters[GlobalRootSignature::Slot::VertexBuffers].InitAsDescriptorTable(1, &ranges[1]); // - The AccelerationStructure should be init as SRV bound to register 0 of the SRV registers. + rootParameters[GlobalRootSignature::Slot::AccelerationStructure].InitAsShaderResourceView(0); // - The SceneConstant should be init as a ConstantBufferView (CBV) bound to register 0 of the CBV registers. - // - The AABBAttributeBuffer should be init as SRV bound to register 3 of the SRV registers. + rootParameters[GlobalRootSignature::Slot::SceneConstant].InitAsConstantBufferView(0); + // - The AABBAttributeBuffer should be init as SRV bound to register 3 of the SRV registers. + rootParameters[GlobalRootSignature::Slot::AABBattributeBuffer].InitAsShaderResourceView(3); // - Look up InitAsDescriptorTable(), InitAsShaderResourceView(), and InitAsConstantBuffer() in the DirectX documentation // to understand what to do. // - If you're ever unsure if the register mapping is correct, look at the top of Raytracing.hlsl. // u registers --> UAV // t registers --> SRV // b registers --> CBV - CD3DX12_ROOT_PARAMETER rootParameters[GlobalRootSignature::Slot::Count]; + // Finally, we bundle up all the descriptors you filled up and tell the device to create this global root signature! CD3DX12_ROOT_SIGNATURE_DESC globalRootSignatureDesc(ARRAYSIZE(rootParameters), rootParameters); @@ -61,13 +67,21 @@ void DXProceduralProject::CreateRootSignatures() } // TODO-2.2: AABB geometry. Inspire yourself from the triangle local signature above to create an AABB local signature - // - Remember that the AABB holds 1 slot for Material Constants, and another 1 for the geometry instance. - // - See the AABB Definition in RaytracingSceneDefines.h to understand what this means. - // - Use registers 1 and 2 of the CBVs for the AABB. Yes, althought the triangle MaterialConstant *also* maps - // to register 1, this overlap is allowed since we are talking about *local* root signatures - // --> the values they hold will depend on the shader function the local signature is bound to! { - + namespace RootSignatureSlots = LocalRootSignature::AABB::Slot; + CD3DX12_ROOT_PARAMETER rootParameters[RootSignatureSlots::Count]; + // - The AABB holds 1 slot for Material Constants, and another 1 for the geometry instance. + // - See the AABB Definition in RaytracingSceneDefines.h to understand what this means. + rootParameters[RootSignatureSlots::MaterialConstant].InitAsConstants(SizeOfInUint32(PrimitiveConstantBuffer), 1); + rootParameters[RootSignatureSlots::GeometryIndex].InitAsConstants(SizeOfInUint32(PrimitiveInstanceConstantBuffer), 2); + // - The above use registers 1 and 2 of the CBVs for the AABB. Yes, althought the triangle MaterialConstant *also* maps + // to register 1, this overlap is allowed since we are talking about *local* root signatures + // --> the values they hold will depend on the shader function the local signature is bound to! + + + CD3DX12_ROOT_SIGNATURE_DESC localRootSignatureDesc(ARRAYSIZE(rootParameters), rootParameters); + localRootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; + SerializeAndCreateRaytracingRootSignature(localRootSignatureDesc, &m_raytracingLocalRootSignature[LocalRootSignature::Type::AABB]); } } } diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp index 150e92d..511768f 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp @@ -32,8 +32,11 @@ void DXProceduralProject::BuildShaderTables() // TODO-2.7: Miss shaders. // Similar to the raygen shader, but now we have 1 for each ray type (radiance, shadow) // Don't forget to update shaderIdToStringMap. - missShaderIDs[0] = nullptr; - missShaderIDs[1] = nullptr; + for (UINT i = 0; i < RayType::Count; i++) + { + missShaderIDs[i] = stateObjectProperties->GetShaderIdentifier(c_missShaderNames[i]); + shaderIdToStringMap[missShaderIDs[i]] = c_missShaderNames[i]; // Radiance = 0, Shadow = 1 - from RayType + } // Hitgroup shaders for the Triangle. We have 2: one for radiance ray, and another for the shadow ray. for (UINT i = 0; i < RayType::Count; i++) @@ -43,7 +46,14 @@ void DXProceduralProject::BuildShaderTables() } // TODO-2.7: Hitgroup shaders for the AABBs. We have 2 for each AABB. - + for (UINT i = 0; i < RayType::Count; i++) + { + for (UINT aabb = 0; aabb < IntersectionShaderType::Count; aabb++) + { + hitGroupShaderIDs_AABBGeometry[aabb][i] = stateObjectProperties->GetShaderIdentifier(c_hitGroupNames_AABBGeometry[aabb][i]); + shaderIdToStringMap[hitGroupShaderIDs_AABBGeometry[aabb][i]] = c_hitGroupNames_AABBGeometry[aabb][i]; + } + } }; // Get shader identifiers using the lambda function defined above. @@ -95,7 +105,20 @@ void DXProceduralProject::BuildShaderTables() // TODO-2.7: Miss shader table. Very similar to the RayGen table except now we push_back() 2 shader records // 1 for the radiance ray, 1 for the shadow ray. Don't forget to call DebugPrint() on the table for your sanity! { - + UINT numShaderRecords = 2; + UINT shaderRecordSize = shaderIDSize; // No root arguments + + // The miss shader table contains a two ShaderRecords: the radiance ray and the shadow ray! + ShaderTable missShaderTable(device, numShaderRecords, shaderRecordSize, L"Miss_Shader_Table"); + + // Push back the shader records, which do not need any root signatures. + missShaderTable.push_back(ShaderRecord(missShaderIDs[0], shaderRecordSize, nullptr, 0)); + missShaderTable.push_back(ShaderRecord(missShaderIDs[1], shaderRecordSize, nullptr, 0)); + + // Save the uploaded resource (remember that the uploaded resource is created when we call Allocate() on a GpuUploadBuffer + missShaderTable.DebugPrint(shaderIdToStringMap); + m_missShaderTable = missShaderTable.GetResource(); + m_missShaderTableStrideInBytes = missShaderTable.GetShaderRecordSize(); } // Hit group shader table. This one is slightly different given that a hit group requires its own custom root signature. diff --git a/src/D3D12RaytracingProceduralGeometry/Main.cpp b/src/D3D12RaytracingProceduralGeometry/Main.cpp index 7f70bc6..6bbb443 100644 --- a/src/D3D12RaytracingProceduralGeometry/Main.cpp +++ b/src/D3D12RaytracingProceduralGeometry/Main.cpp @@ -16,7 +16,7 @@ #include "stdafx.h" #include "DXProceduralProject.h" -#define CPU_CODE_COMPLETE 0 +#define CPU_CODE_COMPLETE 1 _Use_decl_annotations_ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) diff --git a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl index d066933..716e080 100644 --- a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl +++ b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl @@ -41,7 +41,7 @@ ConstantBuffer l_aabbCB: register(b2); // other // Remember to clamp the dot product term! float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal) { - return 0.0f; + return min(1.f, abs(dot(normalize(normal), normalize(incidentLightRay)))); } // TODO-3.6: Phong lighting specular component. @@ -51,20 +51,14 @@ float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal) // Remember to normalize the reflected ray, and to clamp the dot product term float4 CalculateSpecularCoefficient(in float3 incidentLightRay, in float3 normal, in float specularPower) { - return float4(0.0f, 0.0f, 0.0f, 0.0f); + float3 reflectedRay = normalize(reflect(normalize(normal), normalize(incidentLightRay))); + float3 reverseRayDirection = normalize(-incidentLightRay); //?? normalize(WorldRayDirection()); // + float coefficient = pow(min(1.f, abs(dot(reflectedRay, reverseRayDirection))), specularPower); + return coefficient * float4(1.f, 1.f, 1.f, 1.f); } // TODO-3.6: Phong lighting model = ambient + diffuse + specular components. // See https://en.wikipedia.org/wiki/Phong_reflection_model for the full simple equation. -// We have filled in the ambient color for you. -// HINT 1: remember that you can get the world position of the hitpoint using HitWorldPosition() -// from RaytracingShaderHelper.hlsli. The light position is somewhere in g_sceneCB. -// HINT 1: first you need to figure out the diffuse coefficient using CalculateDiffuseCoefficient() -// then you need to figure out the specular coefficient using CalculateSpecularCoefficient() -// then you need to combine the diffuse, specular, and ambient colors into one color. -// Remember that if the ray is a shadow ray, then the hit position should be very dim. Consider using -// InShadowRadiance from RaytracingHlslCompat.h tp dim down the diffuse color. The specular color should -// be completely black if the ray is a shadow ray. float4 CalculatePhongLighting(in float4 albedo, in float3 normal, in bool isInShadow, in float diffuseCoef = 1.0, in float specularCoef = 1.0, in float specularPower = 50) { @@ -74,9 +68,28 @@ float4 CalculatePhongLighting(in float4 albedo, in float3 normal, in bool isInSh float4 ambientColorMin = g_sceneCB.lightAmbientColor - 0.1; float4 ambientColorMax = g_sceneCB.lightAmbientColor; float a = 1 - saturate(dot(normal, float3(0, -1, 0))); + // 565 staff has filled in the ambient color in advance. ambientColor = albedo * lerp(ambientColorMin, ambientColorMax, a); - return ambientColor; + // HINT 1: remember that you can get the world position of the hitpoint using HitWorldPosition() + // from RaytracingShaderHelper.hlsli. The light position is somewhere in g_sceneCB. + float3 incidentLightRay = normalize(g_sceneCB.lightPosition - HitWorldPosition()); + + // HINT 2: first you need to figure out the diffuse coefficient using CalculateDiffuseCoefficient() + diffuseCoef = CalculateDiffuseCoefficient(incidentLightRay, normal); + // then you need to figure out the specular coefficient using CalculateSpecularCoefficient() + specularCoef = CalculateSpecularCoefficient(incidentLightRay, normal, specularPower); + + // Remember that if the ray is a shadow ray, then the hit position should be very dim. Consider using + // InShadowRadiance from RaytracingHlslCompat.h tp dim down the diffuse color. The specular color should + // be completely black if the ray is a shadow ray. + if (isInShadow) { + specularCoef = 0.f; + diffuseCoef *= InShadowRadiance; // CAUSES WEIRD SPOTS - fixed in Shadow Ray below + } + + // then you need to combine the diffuse, specular, and ambient colors into one color. + return ambientColor + g_sceneCB.lightDiffuseColor * albedo * (diffuseCoef + specularCoef); } //*************************************************************************** @@ -135,7 +148,44 @@ float4 TraceRadianceRay(in Ray ray, in UINT currentRayRecursionDepth) // Hint 2: remember what the ShadowRay payload looks like. See RaytracingHlslCompat.h bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth) { - return false; + if (currentRayRecursionDepth >= MAX_RAY_RECURSION_DEPTH) + { + return false; + } + + // Set the ray's extents. + RayDesc rayDesc; + rayDesc.Origin = ray.origin; + rayDesc.Direction = ray.direction; + // Set TMin to a zero value to avoid aliasing artifacts along contact areas. + // Note: make sure to enable face culling so as to avoid surface face fighting. + rayDesc.TMin = 0; + rayDesc.TMax = 10000; + + // From RaytracingHlslCompat.h, to be passed into TraceRay + ShadowRayPayload rayPayload = { true }; + + // TraceRay() is a built-in DXR function. Bellow are comments from it's documentation + TraceRay( + // AccelerationStructure + g_scene, + // RayFlags: used to specify the behavior upon hitting a surface, found options in "Flags per ray" documentation + RAY_FLAG_CULL_BACK_FACING_TRIANGLES | + RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | + RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | + RAY_FLAG_FORCE_OPAQUE, + // InstanceInclusionMask: can mask out geometry + TraceRayParameters::InstanceMask, + // RayContributionToHitGroupIndex: since given objects can have multiple hit groups + TraceRayParameters::HitGroup::Offset[RayType::Shadow], + // MultiplierForGeometryContributionToHitGroupIndex: orders shaders as in AS + TraceRayParameters::HitGroup::GeometryStride, + // MissShaderIndex: index to use in case several consecutive miss shaders are present + TraceRayParameters::MissShader::Offset[RayType::Shadow], + // Ray information to trace and write to + rayDesc, rayPayload); + + return rayPayload.hit; } //*************************************************************************** @@ -149,9 +199,11 @@ bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth) [shader("raygeneration")] void MyRaygenShader() { - + Ray ray = GenerateCameraRay(DispatchRaysIndex().xy, + g_sceneCB.cameraPosition, + g_sceneCB.projectionToWorld); // Write the color to the render target - g_renderTarget[DispatchRaysIndex().xy] = float4(0.0f, 0.0f, 0.0f, 0.0f); + g_renderTarget[DispatchRaysIndex().xy] = TraceRadianceRay(ray, 0); } //*************************************************************************** @@ -209,25 +261,46 @@ void MyClosestHitShader_Triangle(inout RayPayload rayPayload, in BuiltInTriangle // Hint 1: look at the intrinsic function RayTCurrent() that returns how "far away" your ray is. // Hint 2: use the built-in function lerp() to linearly interpolate between the computed color and the Background color. // When t is big, we want the background color to be more pronounced. - - rayPayload.color = color; + + rayPayload.color = lerp(color, BackgroundColor, min(max(RayTCurrent() - 20.f, 0.f) / 100.f, 1.f)); } // TODO: Write the closest hit shader for a procedural geometry. // You suppose this is called after the ray successfully iterated over all geometries and determined it intersected with some AABB. // The attributes of the AABB are in attr (basically the normal) -// You need to: -// (0) Realize that you do not need to load in indices or vertices because we're talking procedural geometry here (wohoo!) -// (1) Trace a shadow ray to determine if this ray is a shadow ray. -// (2) Trace a reflectance ray --> compute the reflected color. -// (3) Use the fact that your ray is a shadow ray or not to compute the Phong lighting. -// (4) Combine the reflect color and the phong color into one color. -// (5) Apply visibility falloff to select some interpolation between the computed color or the background color -// (6) Fill the payload color with whatever final color you computed [shader("closesthit")] void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitiveAttributes attr) { + // (0) Realize that you do not need to load in indices or vertices because we're talking procedural geometry here (wohoo!) + // This is the intersection point on the primitive. - Skip straight to this step + float3 hitPosition = HitWorldPosition(); + // (1) Trace a shadow ray to determine if this ray is a shadow ray. + // From the hit position towards the single light source we have. If on our way to the light we hit something, then we have a shadow! + Ray shadowRay = { hitPosition, normalize(g_sceneCB.lightPosition.xyz - hitPosition) }; + bool shadowRayHit = TraceShadowRayAndReportIfHit(shadowRay, rayPayload.recursionDepth); + + // (2) Trace a reflectance ray --> compute the reflected color. + float4 reflectedColor = float4(0, 0, 0, 0); + if (l_materialCB.reflectanceCoef > 0.001) + { + // Trace the ray from the intersection points using Snell's law with the built in reflect() func + Ray reflectionRay = { hitPosition, reflect(WorldRayDirection(), attr.normal) }; + float4 reflectionColor = TraceRadianceRay(reflectionRay, rayPayload.recursionDepth); + + float3 fresnelR = FresnelReflectanceSchlick(WorldRayDirection(), attr.normal, l_materialCB.albedo.xyz); + reflectedColor = l_materialCB.reflectanceCoef * float4(fresnelR, 1) * reflectionColor; + } + + // (3) Use the fact that your ray is a shadow ray or not to compute the Phong lighting. + float4 phongColor = CalculatePhongLighting(l_materialCB.albedo, attr.normal, shadowRayHit, + l_materialCB.diffuseCoef, l_materialCB.specularCoef, l_materialCB.specularPower); + // (4) Combine the reflect color and the phong color into one color. + float4 color = (phongColor + reflectedColor); + + // (5) Apply visibility falloff to select some interpolation between the computed color or the background color + // And (6) Fill the payload color with whatever final color you computed + rayPayload.color = lerp(color, BackgroundColor, min(max(RayTCurrent() - 20.f, 0.f) / 100.f, 1.f)); } //*************************************************************************** @@ -240,14 +313,15 @@ void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitive [shader("miss")] void MyMissShader(inout RayPayload rayPayload) { - + // rayPayload.color = float4(145.f / 255.f, 83.f / 255.f, 181.f / 255.f); + rayPayload.color = BackgroundColor; } // TODO-3.3: Complete the Shadow ray miss shader. Is this ray a shadow ray if it hit nothing? [shader("miss")] void MyMissShader_ShadowRay(inout ShadowRayPayload rayPayload) { - + rayPayload.hit = false; } //*************************************************************************** @@ -296,9 +370,28 @@ void MyIntersectionShader_AnalyticPrimitive() // TODO-3.4.2: Volumetric primitive intersection shader. In our case, we only have Metaballs to take care of. // The overall structure of this function is parallel to MyIntersectionShader_AnalyticPrimitive() // except you have to call the appropriate intersection test function (see RayVolumetricGeometryIntersectionTest()) +// Even more similar than I expected! [shader("intersection")] void MyIntersectionShader_VolumetricPrimitive() { + Ray localRay = GetRayInAABBPrimitiveLocalSpace(); + VolumetricPrimitive::Enum primitiveType = (VolumetricPrimitive::Enum) l_aabbCB.primitiveType; + // The point of the intersection shader is to: + // (1) find out what is the t at which the ray hits the volumetric + // (2) pass on some attributes used by the closest hit shader to do some shading (e.g: normal vector) + float thit; + ProceduralPrimitiveAttributes attr; + if (RayVolumetricGeometryIntersectionTest(localRay, primitiveType, thit, attr, g_sceneCB.elapsedTime)) // Extra variable time + { + PrimitiveInstancePerFrameBuffer aabbAttribute = g_AABBPrimitiveAttributes[l_aabbCB.instanceIndex]; + + // Make sure the normals are stored in BLAS space and not the local space + attr.normal = mul(attr.normal, (float3x3) aabbAttribute.localSpaceToBottomLevelAS); + attr.normal = normalize(mul((float3x3) ObjectToWorld3x4(), attr.normal)); + + // thit is invariant to the space transformation + ReportHit(thit, /*hitKind*/ 0, attr); + } } #endif // RAYTRACING_HLSL \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli index 94bf5cc..8fef46d 100644 --- a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli @@ -68,7 +68,11 @@ bool is_a_valid_hit(in Ray ray, in float thit, in float3 hitSurfaceNormal) // (3) Call the hlsl built-in function smoothstep() on this interpolant to smooth it out so it doesn't change abruptly. float CalculateAnimationInterpolant(in float elapsedTime, in float cycleDuration) { - return smoothstep(0, 1, 0); + float step = fmod(elapsedTime, cycleDuration) / cycleDuration; + if (step > 0.5f) { + step = 1.f - step; + } + return smoothstep(0, 1, step); } // Load three 2-byte indices from a ByteAddressBuffer. @@ -121,17 +125,30 @@ float3 HitAttribute(float3 vertexAttribute[3], float2 barycentrics) // TODO-3.1: Generate a ray in world space for a camera pixel corresponding to a dispatch index (analogous to a thread index in CUDA). // Check out https://docs.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-raytracing-hlsl-system-value-intrinsics to see interesting -// intrinsic HLSL raytracing functions you may use. +// intrinsic HLSL raytracing functions you may use. - This is where I got DispatchRaysDimensions! // Remember that you are given the pixel coordinates from index. You need to convert this to normalized-device coordinates first. // Want: bottom left corner = (-1,-1) and top right corner = (1,1). // Keep in mind that the pixel space in DirectX is top left = (0,0) and bottom right = (width, height) // Once you have the normalized-device coordinates, use the projectionToWorld matrix to find a 3D location for this pixel. The depth will be wrong but // as long as the direction of the ray is correct then the depth does not matter. +// Was having trouble getting this to work at first, so Grace told me to add that "+ 0.5" to the NDC +// calculation, and Tabatha reminded me to swap the order of multiplication from what I oiginally imagined inline Ray GenerateCameraRay(uint2 index, in float3 cameraPosition, in float4x4 projectionToWorld) { + // Converting Pixel Coordinates to Screen Space Coordinates (actually NDC) + float screenX = (float(index.x + 0.5f) / float(DispatchRaysDimensions().x)) * 2.f - 1.f; + float screenY = 1.f - (float(index.y + 0.5f) / float(DispatchRaysDimensions().y)) * 2.f; + + // Point estimate, assuming Unhomogenized Screen Space but without a proper perpective divide + float4 pEst = float4(screenX, screenY, 1.f, 1.f); + + // Get 3D location (convert to world space) + float4 dir = mul(pEst, projectionToWorld); + dir /= dir.w; + Ray ray; - ray.origin = float3(0.0f, 0.0f, 0.0f); - ray.direction = normalize(float3(0.0f, 0.0f, 0.0f)); + ray.origin = cameraPosition; + ray.direction = normalize(dir.xyz - cameraPosition); return ray; } @@ -141,7 +158,8 @@ inline Ray GenerateCameraRay(uint2 index, in float3 cameraPosition, in float4x4 // f0 is usually the albedo of the material assuming the outside environment is air. float3 FresnelReflectanceSchlick(in float3 I, in float3 N, in float3 f0) { - return f0; + float cosTheta = abs(dot(normalize(I), normalize(N))); + return f0 + (1.f - f0) * pow(1.f - cosTheta, 5.f); } #endif // RAYTRACINGSHADERHELPER_H \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli index 31a9444..52e971b 100644 --- a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli @@ -22,7 +22,13 @@ struct Metaball // of the distance from the center to the radius. float CalculateMetaballPotential(in float3 position, in Metaball blob) { - return 0.0f; + float d = distance(blob.center, position); + float x = (d <= blob.radius ? (blob.radius - d) / blob.radius : 0.f);// 1.f - max(distance(blob.center, position), blob.radius) / blob.radius; + float x3 = x * x * x; + float x4 = x3 * x; + float x5 = x4 * x; + + return 6.f * x5 - 15.f * x4 + 10 * x3; } // LOOKAT-1.9.4: Calculates field potential from all active metaballs. This is just the sum of all potentials. @@ -83,25 +89,65 @@ void TestMetaballsIntersection(in Ray ray, out float tmin, out float tmax, inout { tmin = INFINITY; tmax = -INFINITY; + + for (int blob = 0; blob < N_METABALLS; blob++) { + // Variables to pass in + float t0 = INFINITY; + float t1 = INFINITY; + // Use this previously created function, since metaballs are solid spheres + if (!RaySolidSphereIntersectionTest(ray, t0, t1, + blobs[blob].center, blobs[blob].radius)) { + continue; + } + + // Retain the minimum and t-values from all intersected blobs :) + tmin = min(t0, tmin); + tmax = max(t1, tmax); // Clamped by RayTMin() and RayTCurrent() in RSSIntersectionTest + } } // TODO-3.4.2: Test if a ray with RayFlags and segment intersects metaball field. // The test sphere traces through the metaball field until it hits a threshold isosurface. // Returns true if we found a point. False otherwise. -// 1) Initialize a metaball array. See InitializeAnimatedMetaballs() -// 2) Test intersections on the metaballs to find the minimum t and the maximum t to raymarch between. -// 3) Use some number of steps (~128 is a good number for raymarching) to do the following: -// a) Compute the total metaball potential over this point by summing ALL potentials of each metaball. -// See CalculateMetaballsPotential(). -// b) If the total potential crosses an isosurface threshold (defined on (0,1]), then we will potentially -// render this point: -// i) We compute the normal at this point (see CalculateMetaballsNormal()) -// ii) Only render this point if it is valid hit. See is_a_valid_hit(). -// If this condition fails, keep raymarching! bool RayMetaballsIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr, in float elapsedTime) { thit = 0.0f; attr.normal = float3(0.0f, 0.0f, 0.0f); + + // 1) Initialize a metaball array. See InitializeAnimatedMetaballs() + Metaball blobs[N_METABALLS]; // out variable + InitializeAnimatedMetaballs(blobs, elapsedTime, 16.f); // CycleDuration = a value of your choice that will + // define how much time it takes for an animation to restart + + // 2) Test intersections on the metaballs to find the minimum t and the maximum t to raymarch between. + float t0 = 0; + float t1 = 0; + TestMetaballsIntersection(ray, t0, t1, blobs); + + // 3) Use some number of steps (~128 is a good number for raymarching) to do the following: + float maxSteps = 128.f; + float dt = (t1 - t0) / maxSteps; + for (int steps = 0; steps < (int) maxSteps; steps++) { + // a) Compute the total metaball potential over this point by summing ALL potentials of each metaball. + // See CalculateMetaballsPotential(). + float3 position = ray.origin + t0 * ray.direction; + float sumFieldPotential = CalculateMetaballsPotential(position, blobs); + + // b) If the total potential crosses an isosurface threshold (defined on (0,1]), then we will potentially + // render this point: + if (sumFieldPotential > 0.1) { + // i) We compute the normal at this point (see CalculateMetaballsNormal()) + float3 norm = CalculateMetaballsNormal(position, blobs); + // ii) Only render this point if it is valid hit. See is_a_valid_hit(), which checks segment . + if (is_a_valid_hit(ray, t0, norm)) { + attr.normal = norm; + thit = t0; + return true; + } + } + // If this condition fails, keep raymarching! - At a constant value since no SDF (dt) + t0 += dt; + } return false; }