diff --git a/README.md b/README.md
index b0189d0..e45e695 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,96 @@
-**University of Pennsylvania, CIS 565: GPU Programming and Architecture,
-Project 5 - DirectX Procedural Raytracing**
+DXR 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)
+**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 3**
-### (TODO: Your README)
+* John Marcao
+ * [LinkedIn](https://www.linkedin.com/in/jmarcao/)
+ * [Personal Website](https://jmarcao.github.io)
+* Tested on: Windows 10, i5-4690K @ 3.50GHz, 8GB DDR3, RTX 2080 TI 3071MB (Personal)
-Include screenshots, analysis, etc. (Remember, this is public, so don't put
-anything here that you don't want to share with the world.)
+(Artifacts in the reflection are due to GIF encoding, will try to fix that)
+
+
+
+# Project
+This project went through the steps of setting up DXR Raytracing pipeline with triangle support, as well as simple geometries (spheres), Axis-Aligned Bounding Box (AABB), as well as volumetric Metaballs that become distorted with time.
+
+The project was focused on simply understanding the API and applying the raytracing knowledge from Project 3 as well as additional geometries. I also perform some performance analysis on several parameters, including ray depth, metaball raymarching granularity, and number of elements in a metaball group.
+
+# DXR API
+
+The DXR API provides a lot of power, but also a lot of confusion. The API can handle triangles easily, but adding support for non-triangle geometries turned out to be complicated in my opinion. Below are some of the highlights from learning the API that can hopefully help somebody else out. DXR is all about structure. To automate a lot of steps, DXR just needs to be told about the layout of the data. From there, it can figure things out (such as triangle/ray intersections).
+
+### Global and Local Root Signatures
+In CUDA, arguments are defined through normal C-style function arguments. With the DXR API, arguments can be passed in through root signatures. Each GPU thread/shader will receive the same copy of the GlobalRootSignature. This makes it great for globally true values, such as lighting locations, acceleration structure data, etc. LocalRootSignatures, on the other hand, are unique for each shader. Each is structured the same, but the contents of the data can change. This allows shaders to get data on the specific geometry they are intersecting with.
+
+# Performance
+
+I collected performance metrics on the DXR project by varying ray depth and metaball raymarching and complexity.
+
+First, I increased the maximum ray depth parameter. The collected data is below.
+
+
+
+| Ray Depth | FPS |
+|-----------|--------|
+| 3 | 550.92 |
+| 4 | 510.75 |
+| 5 | 482.51 |
+| 6 | 471.74 |
+| 7 | 486.36 |
+| 8 | 467.83 |
+| 9 | 477.81 |
+| 10 | 471.27 |
+
+As ray depth increases, the FPS goes down. This is expected, since a deeper depth adds more iterations of each ray. However, it also becomes clear that after a ray depth of 5, the difference is minimal. This is because most of the rays will, by that point, be executing on no-hit shaders. Those shaders perform minimal work and set the depth of ray to the maximum value, essentially removing them from the equation.
+
+Additionally, I analyzed the performance of the Metaball Raymarching algorithm. The algorithm looks at each point from the entry of the ray in the bounding box and the exit of the ray. It then calculates a "potential" based on the distance between the point and each other sphere. By modifying the steps taken, we can see how drastic the performance hit is of rendering these metaballs. I also include what each step variant looks like. As can be seen, The performance loss is pretty huge, which is expected since each ray needs to test against each sphere in the bounding box. However, the metaballs do not render "correctly" if not enough steps are taken.
+
+
+
+| MB Steps | FPS | GIF |
+|----------|--------| ----|
+| 2 | 1726 |  |
+| 8 | 1660 |  |
+| 32 | 1267 |  |
+| 128 | 550.92 |  |
+| 512 | 135 |  |
+
+# Conceptual Questions
+
+## Question 1
+To get each pixel mapped to a ray, we need to make a couple transformations and mappings between the camera and the world. First of all, every object is mapped to the world space at first. Even the camera has a position and a normal in world space. So the first step is to take the local coordinates of the camera and transform it to world space. This is done by multiplying the camera's position by the transformation vector Mcamera-to-world.
+This will move our coordinate system so that the camera is defined with respect to the world origin. We then have to map each (relevant) point in world space to camera space. This is done by mapping each point to a place one unit away from the camera point that is, in this case, 1280 pixels wide and 720 pixels tall. We start this by converting each point to be mapped with respect to the camera's position using the equation PCamera = PWorld * MWorld-to-Camera. The 3D points can then be mapped to 2D points on the Camera's plane by the following.
+
+P'.x = Pcamera.x / -Pcamera.z
+
+P'.y = Pcamera.y / -Pcamera.z
+
+Note the negative sign, this is done because the camera is defined as looking down the negative Z-axis.
+With the point P', we have define the position of a 3d object in the 2d plane seen by the camera. This space then is trimmed to match the height and width mentioned. If P'.x is greater than the width/2, or P'/y is greater than height/2, then the object will not be rendered in the screen.
+The last step is to transform the point P' to raster space, that is such that its coordinate will align exactly with its pixel position on the screen. This is done by taking the origin to be the top left corner of the screen. Each point is normalized by the following.
+
+P'normalized.x = (P'.x + width/2) / width
+
+P'normalized.y = (P'.y + height/2) / height
+
+And then finally, it is multiplied by the width and height provided.
+
+P'raster.x = floor(P'normalized.x * Pixel Width)
+
+P'raster.y = floor((1 - P'normalized.y) * Pixel Height)
+
+Again, noting that y is inverted because the origin is the top left of the screen.
+
+## Question 2
+Each procedural geometry is defined by its Axis-Aligned Bounding Box (AABB), its Type, and its Type's associated Equation. When detecting collisions for each ray, the collision is first checked against the AABB. If the AABB is hit, then a more complicated check follows. Otherwise, the ray ignores the geometry. If a collision is detected with the AABB, then the shader calls a intersection test function depending on the Shape of the object. For example, a Sphere is defined by the function (x-center)2 = r2. The ray is checked against this internal geometry to see if the collision happens or not. The origin and direction of the ray, with respect to the AABB, is tested against the geometry inside. If a collision is detected, then the rasterizer will render the ray at the point of intersection. If no intersection occurs inside the AABB, then no point is rendered.
+
+## Question 3
+Acceleration on the GPU is done by using Top Level Acceleration Structures (TLAS) and Bottom Level Acceleration Structures (BLAS). Each TLAS holds possibly many instances of BLAS. A BLAS may be referenced by multiple TLAS. For the example shown below...
+
+
+
+... the acceleration structures can be defined like one of the two examples below. The top example assigns each instance in the TLAS to a different geometry type, while the second example joins together simple geometries together.
+
+
\ No newline at end of file
diff --git a/data.xlsx b/data.xlsx
new file mode 100644
index 0000000..b9a3006
Binary files /dev/null and b/data.xlsx differ
diff --git a/images/accel_example.png b/images/accel_example.png
new file mode 100644
index 0000000..c22c407
Binary files /dev/null and b/images/accel_example.png differ
diff --git a/images/mb_128.gif b/images/mb_128.gif
new file mode 100644
index 0000000..8ef61f9
Binary files /dev/null and b/images/mb_128.gif differ
diff --git a/images/mb_2.gif b/images/mb_2.gif
new file mode 100644
index 0000000..3bb0e83
Binary files /dev/null and b/images/mb_2.gif differ
diff --git a/images/mb_32.gif b/images/mb_32.gif
new file mode 100644
index 0000000..311f516
Binary files /dev/null and b/images/mb_32.gif differ
diff --git a/images/mb_512.gif b/images/mb_512.gif
new file mode 100644
index 0000000..f5dfeb9
Binary files /dev/null and b/images/mb_512.gif differ
diff --git a/images/mb_8.gif b/images/mb_8.gif
new file mode 100644
index 0000000..4546546
Binary files /dev/null and b/images/mb_8.gif differ
diff --git a/images/metablls.png b/images/metablls.png
new file mode 100644
index 0000000..2dca982
Binary files /dev/null and b/images/metablls.png differ
diff --git a/images/raydepth_fps.png b/images/raydepth_fps.png
new file mode 100644
index 0000000..fbf238f
Binary files /dev/null and b/images/raydepth_fps.png differ
diff --git a/images/scene.gif b/images/scene.gif
new file mode 100644
index 0000000..cbe1a80
Binary files /dev/null and b/images/scene.gif differ
diff --git a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli
index c6ccebb..720fb7e 100644
--- a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli
+++ b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli
@@ -163,21 +163,59 @@ bool RaySolidSphereIntersectionTest(in Ray ray, out float thit, out float tmax,
// TODO-3.4.1: Change this code to support intersecting multiple spheres (~3 spheres).
// You can hardcode the local centers/radii of the spheres, just try to maintain them between 1 and -1 (and > 0 for the radii).
+// This is kinda lame cause it hardcodes the spheres in the intersection test.
+// I guess the AABB is better for passing in stuff
bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr)
{
+ bool hit = false;
+
+ // Store values in here until ready to leave
+ float tmp_thit;
+ ProceduralPrimitiveAttributes tmp_attr;
+
// Define the spheres in local space (within the aabb)
- float3 center = float3(-0.2, 0, -0.2);
- float radius = 0.7f;
+ float3 center[3];
+ float radius[3];
+
+ // Sphere 1
+ center[0] = (float3(-0.2, 0, -0.2));
+ radius[0] = (0.2f);
+
+ // Sphere 2
+ center[1] = (float3(-0.3, 0.5, -0.3));
+ radius[1] = (0.2f);
- thit = RayTCurrent();
+ // Sphere 3
+ center[2] = (float3(0.5, 0, 0.5));
+ radius[2] = (0.2f);
+
+ tmp_thit = RayTCurrent();
float tmax;
- if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius))
- {
- return true;
+ float min_diff = 100; // Arbitratily large. Will never be greater than 2 due to bounding box
+
+ // thit is modified by function
+ for (int i = 0; i < 3; i++) {
+ if (RaySphereIntersectionTest(ray, tmp_thit, tmax, tmp_attr, center[i], radius[i]))
+ {
+ // We intersected a sphere. Check now if this intersection is closest.
+ // A ray is just a line, so in this local space we need to see which is closest
+ // to the entry point on that line.
+ // RayTCurrent() - Ending point of ray
+ // RayTMin() - Starting point of ray
+ // So whichever is closest to RayTMin()
+ float diff = abs(tmp_thit - RayTMin()); // Could we be coming in from above? Below?
+
+ // This is closer to the entry point, pick it
+ if (diff < min_diff) {
+ thit = tmp_thit;
+ attr = tmp_attr;
+ hit = true;
+ }
+ }
}
- return false;
+ return hit;
}
#endif // ANALYTICPRIMITIVES_H
\ No newline at end of file
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp
index 084077a..7b48ea5 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp
@@ -31,6 +31,25 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetDesc().Width)
auto& geometryDesc = geometryDescs[BottomLevelASType::Triangle][0];
geometryDesc = {};
+
+ // D3D12_RAYTRACING_GEOMETRY_DESC has a bunch of fields so take it one at a time
+ geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; // Straighforward, this is a triangle
+ geometryDesc.Flags = geometryFlags; // Same flags as above? AABB below does the same so sure.
+
+ // Assign Index Values
+ geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress(); // Get GPU addr of this resource
+ geometryDesc.Triangles.IndexCount = (UINT)(m_indexBuffer.resource->GetDesc().Width) / sizeof(Index);
+ geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R16_UINT; // Index is UINT16
+
+ // Now assign Vertex values. Just like above.
+ // Only diff is the buffer gets its data in start and stride (vertex length)
+ // Since I guess the vertex can be a complex type
+ geometryDesc.Triangles.VertexBuffer.StartAddress = m_vertexBuffer.resource->GetGPUVirtualAddress();
+ geometryDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(Vertex);
+ geometryDesc.Triangles.VertexCount = (UINT)(m_vertexBuffer.resource->GetDesc().Width) / sizeof(Vertex);
+ geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; // Each vertex is a xyz of 32bit ints
+ // But each vertex includes vertex + normals
+ // But stride accounts for that.
}
@@ -49,7 +68,16 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetGPUVirtualAddress() + offset;
+
+ // Everything else was filled and copied above, so I think thats the only thing unique to each
+ // AABB. Simpler than triangle because we smush it all into that buffer.
+ }
}
}
@@ -68,7 +96,12 @@ 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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
+ bottomLevelInputs.Flags = buildFlags; // Passed in as arg
+ bottomLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; // This is a BLAS
+ bottomLevelInputs.NumDescs = static_cast(geometryDescs.size()); // Passed in as arg
+ // AS takes a union of different types, here we have geometryDescs to work with
+ 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 = {};
@@ -108,6 +141,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.DestAccelerationStructureData = bottomLevelAS->GetGPUVirtualAddress();
+ bottomLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress();
// Fill up the command list with a command that tells the GPU how to build the bottom-level AS.
@@ -127,7 +162,12 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto
// the AccelerationStructureBuffers struct so the top-level AS can use it!
// Don't forget that this is the return value.
// Consider looking into the AccelerationStructureBuffers struct in DXR-Structs.h
- return AccelerationStructureBuffers{};
+ AccelerationStructureBuffers asb = {};
+ asb.accelerationStructure = bottomLevelAS;
+ asb.scratch = scratch;
+ asb.ResultDataMaxSizeInBytes = bottomLevelPrebuildInfo.ResultDataMaxSizeInBytes;
+ // Theres also a instanceDesc member, but that is ONLY for TLAS.
+ return asb;
}
// TODO-2.6: Build the instance descriptor for each bottom-level AS you built before.
@@ -179,7 +219,26 @@ void DXProceduralProject::BuildBottomLevelASInstanceDescs(BLASPtrType *bottomLev
// Where do you think procedural shader records would start then? Hint: right after.
// * Make each instance hover above the ground by ~ half its width
{
+ // Start off just like triangles above
+ auto& instanceDesc = instanceDescs[BottomLevelASType::AABB];
+ instanceDesc = {};
+ instanceDesc.InstanceMask = 1;
+ // 0 is triangle radiance, 1 is triangle shadow, 2 is aabb start
+ // Could probably not hardcode this
+ instanceDesc.InstanceContributionToHitGroupIndex = 2;
+
+ // Like triangle!
+ instanceDesc.AccelerationStructure = bottomLevelASaddresses[BottomLevelASType::AABB];
+
+ // Now the transform. Hover above the ground by half its width...
+ // Don't scale here or rotate, so transform matrix == translation
+ const XMVECTOR vBasePosition = c_aabbWidth * XMLoadFloat3(&XMFLOAT3(0.0f, 0.5f, 0.0f));
+ XMMATRIX mTranslation = XMMatrixTranslationFromVector(vBasePosition);
+ XMMATRIX mTransform = 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.
@@ -202,6 +261,13 @@ 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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
+ topLevelInputs.Flags = buildFlags; // Passed in as arg
+ topLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; // This is a TLAS
+ topLevelInputs.NumDescs = BottomLevelASType::Count; // Desc here are BLAS descs.
+ // How will it know size of each element in array???
+ // Actually, we do this below after vuilding the BLAS
+ //topLevelInputs.InstanceDescs = bottomLevelAS[0].instanceDesc->GetGPUVirtualAddress();
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO topLevelPrebuildInfo = {};
@@ -216,7 +282,7 @@ 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"ScratchResource");
// Allocate space for the top-level AS.
{
@@ -231,7 +297,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
}
// TODO-2.6: Allocate a UAV buffer for the actual top-level AS.
-
+ AllocateUAVBuffer(device, topLevelPrebuildInfo.ResultDataMaxSizeInBytes, &topLevelAS, initialResourceState, L"TopLevelAccelerationStructure");
}
// Note on Emulated GPU pointers (AKA Wrapped pointers) requirement in Fallback Layer:
@@ -259,7 +325,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
};
// TODO-2.6: Call the fallback-templated version of BuildBottomLevelASInstanceDescs() you completed above.
-
+ BuildBottomLevelASInstanceDescs(bottomLevelASaddresses, &instanceDescsResource);
}
else // DirectX Raytracing
{
@@ -271,7 +337,7 @@ 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.
@@ -283,7 +349,10 @@ 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.
-
+ // Similar to the triangles above
+ topLevelBuildDesc.DestAccelerationStructureData = topLevelAS->GetGPUVirtualAddress();
+ topLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress();
+ topLevelInputs.InstanceDescs = instanceDescsResource->GetGPUVirtualAddress();
// Build acceleration structure.
if (m_raytracingAPI == RaytracingAPI::FallbackLayer)
@@ -302,7 +371,12 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
// 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.
// Make sure to return the topLevelASBuffers before you exit the function.
- return AccelerationStructureBuffers{};
+ AccelerationStructureBuffers asb;
+ asb.accelerationStructure = topLevelAS;
+ asb.instanceDesc = instanceDescsResource;
+ asb.ResultDataMaxSizeInBytes = topLevelPrebuildInfo.ResultDataMaxSizeInBytes;
+ asb.scratch = scratch;
+ return asb;
}
// TODO-2.6: This will wrap building the Acceleration Structure! This is what we will call when building our scene.
@@ -318,12 +392,16 @@ 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];
-
+ // Use a none build flag, maybe revisit
+ auto buildFlag = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_NONE;
+ for (int i = 0; i < BottomLevelASType::Count; i++) {
+ bottomLevelAS[i] = BuildBottomLevelAS(geometryDescs[i], buildFlag);
+ }
// Batch all resource barriers for bottom-level AS builds.
// This will Notifies the driver that it needs to synchronize multiple accesses to resources.
@@ -336,7 +414,9 @@ void DXProceduralProject::BuildAccelerationStructures()
// TODO-2.6: Build top-level AS. Hint, you already made a function that does this.
AccelerationStructureBuffers topLevelAS;
-
+ // Use a none build flag, maybe revisit
+ buildFlag = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_NONE;
+ topLevelAS = BuildTopLevelAS(bottomLevelAS, buildFlag);
// Kick off acceleration structure construction.
m_deviceResources->ExecuteCommandList();
@@ -345,7 +425,10 @@ void DXProceduralProject::BuildAccelerationStructures()
m_deviceResources->WaitForGpu();
// 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
+ // Do this for both the bottom-level and the top-level AS. Consider re-reading the class
// to find what member variables should be set.
-
+ for (int i = 0; i < BottomLevelASType::Count; i++) {
+ m_bottomLevelAS[i] = bottomLevelAS[i].accelerationStructure;
+ }
+ m_topLevelAS = topLevelAS.accelerationStructure;
}
\ No newline at end of file
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp
index 03a8c58..74f01c7 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp
@@ -22,7 +22,11 @@ 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,26 +53,49 @@ 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.
-
+
+ // We do indicies because verticies are just one register over? So we get register 1 + register 2
+ // In CreateRootSignatures() we bound the data to m_raytracingGlobalRootSignature. That buffer
+ // has the rootSignature for each index/vertex
+ commandList->SetComputeRootSignature(m_raytracingGlobalRootSignature.Get());
// TODO-2.8: Bind the OutputView (basically m_raytracingOutputResourceUAVGpuDescriptor). Very similar to the Index/Vertex buffer.
-
+ m_raytracingOutputResourceUAVGpuDescriptor; // Its a descriptor handle, so probably root descriptor table?
+ commandList->SetComputeRootDescriptorTable(
+ GlobalRootSignature::Slot::OutputView,
+ m_raytracingOutputResourceUAVGpuDescriptor);
+
+ // Copy down the vertex buffers. Duh. I forgot this line and
+ // spent hours trying to find out what was wrong
+ commandList->SetComputeRootDescriptorTable(
+ GlobalRootSignature::Slot::VertexBuffers,
+ m_indexBuffer.gpuDescriptorHandle);
// 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
auto DispatchRays = [&](auto* raytracingCommandList, auto* stateObject, auto* dispatchDesc)
{
// 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: fill in dispatchDesc->HitGroupTable. Look up the struct D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE
+ dispatchDesc->HitGroupTable.StartAddress = m_hitGroupShaderTable->GetGPUVirtualAddress(); // Want to start here, makes sense.
+ dispatchDesc->HitGroupTable.SizeInBytes = m_hitGroupShaderTable->GetDesc().Width; // Similar to before
+ dispatchDesc->HitGroupTable.StrideInBytes = m_hitGroupShaderTableStrideInBytes; // Makes sense...
// TODO-2.8: now fill in dispatchDesc->MissShaderTable
-
+ // Similar to above? They're all very simialr structs
+ dispatchDesc->MissShaderTable.StartAddress = m_missShaderTable->GetGPUVirtualAddress();
+ dispatchDesc->MissShaderTable.SizeInBytes = m_missShaderTable->GetDesc().Width;
+ dispatchDesc->MissShaderTable.StrideInBytes = m_missShaderTableStrideInBytes;
// TODO-2.8: now fill in dispatchDesc->RayGenerationShaderRecord
-
+ // This one is different, It doesn't have a stride
+ // Which makes sense. There is only one object represented here.
+ // No other object to stride to
+ dispatchDesc->RayGenerationShaderRecord.StartAddress = m_rayGenShaderTable->GetGPUVirtualAddress();
+ dispatchDesc->RayGenerationShaderRecord.SizeInBytes = m_rayGenShaderTable->GetDesc().Width;
// We do this for you. This will define how many threads will be dispatched. Basically like a blockDims in CUDA!
+ // 2d!
dispatchDesc->Width = m_width;
dispatchDesc->Height = m_height;
dispatchDesc->Depth = 1;
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp
index e3ff63c..fa6a244 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp
@@ -111,7 +111,11 @@ 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()
{
+ auto device = m_deviceResources->GetD3DDevice();
+ auto frameCount = m_deviceResources->GetBackBufferCount();
+ auto elements = IntersectionShaderType::TotalPrimitiveCount;
+ m_aabbPrimitiveAttributeBuffer.Create(device, elements, frameCount, L"AABB Primitive Attr. Buffer");
}
// LOOKAT-2.1: Update camera matrices stored in m_sceneCB.
@@ -164,6 +168,11 @@ 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.
+
+ // Transformation matrix is Scaling * Rotation * Translation
+ auto mTransform = XMMatrixMultiply(XMMatrixMultiply(mScale, mRotation), mTranslation);
+ m_aabbPrimitiveAttributeBuffer[primitiveIndex].localSpaceToBottomLevelAS = mTransform;
+ m_aabbPrimitiveAttributeBuffer[primitiveIndex].bottomLevelASToLocalSpace = XMMatrixInverse(nullptr, mTransform);
};
UINT offset = 0;
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp
index 9d93504..39c768f 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp
@@ -33,12 +33,12 @@ void DXProceduralProject::BuildPlaneGeometry()
{
3,1,0,
2,1,3,
-
};
// Cube vertices positions and corresponding triangle normals.
Vertex vertices[] =
{
+ // Position...................Normal......................
{ XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // vertex 0: position 0, normal 0
{ XMFLOAT3(1.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // vertex 1: position 1, normal 1
{ XMFLOAT3(1.0f, 0.0f, 1.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // vertex 2..
@@ -87,6 +87,19 @@ void DXProceduralProject::BuildProceduralGeometryAABBs()
auto InitializeAABB = [&](auto& offsetIndex, auto& size)
{
D3D12_RAYTRACING_AABB aabb{};
+ // Minimum positions will be the base plus the offset of the
+ // object plus the size of the stride. This is like
+ // hopping to the right position in an array using size of
+ // the elements.
+ aabb.MinX = basePosition.x + offsetIndex.x * stride.x;
+ aabb.MinY = basePosition.y + offsetIndex.y * stride.y;
+ aabb.MinZ = basePosition.z + offsetIndex.z * stride.z;
+
+ // Max is the same as above, but also add in the size.
+ aabb.MaxX = basePosition.x + offsetIndex.x * stride.x + size.x;
+ aabb.MaxY = basePosition.y + offsetIndex.y * stride.y + size.y;
+ aabb.MaxZ = basePosition.z + offsetIndex.z * stride.z + size.z;
+
return aabb;
};
m_aabbs.resize(IntersectionShaderType::TotalPrimitiveCount);
@@ -110,12 +123,17 @@ 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).
-
+ AllocateUploadBuffer(device,
+ m_aabbs.data(), m_aabbs.size() * sizeof(D3D12_RAYTRACING_AABB), // Get the data from the CPU side buffer...
+ &m_aabbBuffer.resource, // And put in the GPU resource.
+ L"m_aabbBufferResouce"); // And give it a fun name
}
}
// TODO-2.5: Build geometry used in the project. As easy as calling both functions above :)
void DXProceduralProject::BuildGeometry()
{
-
+ // The hardest part of this project :(
+ BuildPlaneGeometry();
+ BuildProceduralGeometryAABBs();
}
\ No newline at end of file
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp
index 33899bd..2dbb413 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp
@@ -29,6 +29,24 @@ 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 primType = 0; primType < IntersectionShaderType::Count; primType++) {
+ for (UINT rayType = 0; rayType < RayType::Count; rayType++) {
+ auto hitGroup = raytracingPipeline->CreateSubobject();
+
+ // Like above, if this is a radiance ray, get the shader
+ if (rayType == RayType::Radiance) {
+ hitGroup->SetClosestHitShaderImport(c_closestHitShaderNames[GeometryType::AABB]);
+ }
+
+ hitGroup->SetHitGroupExport(c_hitGroupNames_AABBGeometry[primType][rayType]);
+ hitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE);
+
+ // Also include the intersection shader based on the primitive
+ hitGroup->SetIntersectionShaderImport(c_intersectionShaderNames[primType]);
+
+ // Anyhit shaders? Need to implement them.
+ }
+ }
}
}
@@ -54,6 +72,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();
+
+ // Get the AABB local root signature
+ localRootSignature->SetRootSignature(m_raytracingLocalRootSignature[LocalRootSignature::Type::AABB].Get());
+
+ // Associate the shaders
+ auto rootSignatureAssociation = raytracingPipeline->CreateSubobject();
+ rootSignatureAssociation->SetSubobjectToAssociate(*localRootSignature);
+
+ // Associate for each primitive type
+ for (UINT primType = 0; primType < IntersectionShaderType::Count; primType++) {
+ rootSignatureAssociation->AddExports(c_hitGroupNames_AABBGeometry[primType]);
+ }
}
}
\ No newline at end of file
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-Other.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-Other.cpp
index f889597..d551bcd 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-Other.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-Other.cpp
@@ -23,7 +23,7 @@ DXProceduralProject::DXProceduralProject(UINT width, UINT height, std::wstring n
m_forceComputeFallback(false)
{
m_forceComputeFallback = false;
- SelectRaytracingAPI(RaytracingAPI::FallbackLayer);
+ SelectRaytracingAPI(RaytracingAPI::DirectXRaytracing);
UpdateForSizeChange(width, height);
}
diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp
index 2dff8b5..9daa7fc 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp
@@ -20,26 +20,38 @@ void DXProceduralProject::CreateRootSignatures()
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.
- // This will effectively put the indices at register 1, and the vertices at register 2.
-
+ // 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.
// - 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.
- // 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.
+ // - 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.
// - The AccelerationStructure should be init as SRV bound to register 0 of the SRV registers.
- // - 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.
+ // - 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.
// - 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
+ // - 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];
+ // JOHN: https://docs.microsoft.com/en-us/windows/win32/direct3d12/creating-a-root-signature
+ // Init the output view using the UAV Descriptor above
+ rootParameters[GlobalRootSignature::Slot::OutputView].InitAsDescriptorTable(1, &ranges[0]);
+ // Init to SRV register 0
+ rootParameters[GlobalRootSignature::Slot::AccelerationStructure].InitAsShaderResourceView(0);
+ // Init to CBV register 0
+ rootParameters[GlobalRootSignature::Slot::SceneConstant].InitAsConstantBufferView(0);
+ // Init to SRV register 3
+ rootParameters[GlobalRootSignature::Slot::AABBattributeBuffer].InitAsShaderResourceView(3);
+ // Init to range above for SRV. We use 2 registers above...
+ rootParameters[GlobalRootSignature::Slot::VertexBuffers].InitAsDescriptorTable(1, &ranges[1]);
+
// 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);
SerializeAndCreateRaytracingRootSignature(globalRootSignatureDesc, &m_raytracingGlobalRootSignature);
@@ -63,11 +75,22 @@ 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
+ // - Use registers 1 and 2 of the CBVs for the AABB. Yes, although 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];
+
+ // In RayTractingDefines.h, LocalRootSignature::AABB, it defines each of the registers we need to pass in
+ // So this is starting to make sense, these will be passed to each shader uniquely, while the above are global
+ // to all shaders.... ahh?
+ rootParameters[RootSignatureSlots::MaterialConstant].InitAsConstants(SizeOfInUint32(PrimitiveConstantBuffer), 1);
+ rootParameters[RootSignatureSlots::GeometryIndex].InitAsConstants(SizeOfInUint32(PrimitiveInstanceConstantBuffer), 2);
+
+ 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..7db7b24 100644
--- a/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp
+++ b/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp
@@ -32,8 +32,10 @@ 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;
+ missShaderIDs[0] = stateObjectProperties->GetShaderIdentifier(c_missShaderNames[0]);
+ shaderIdToStringMap[missShaderIDs[0]] = c_missShaderNames[0];
+ missShaderIDs[1] = stateObjectProperties->GetShaderIdentifier(c_missShaderNames[1]);
+ shaderIdToStringMap[missShaderIDs[1]] = c_missShaderNames[1];
// 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 +45,13 @@ void DXProceduralProject::BuildShaderTables()
}
// TODO-2.7: Hitgroup shaders for the AABBs. We have 2 for each AABB.
-
+ for (UINT j = 0; j < IntersectionShaderType::Count; j++) {
+ for (UINT i = 0; i < RayType::Count; i++)
+ {
+ hitGroupShaderIDs_AABBGeometry[j][i] = stateObjectProperties->GetShaderIdentifier(c_hitGroupNames_AABBGeometry[j][i]);
+ shaderIdToStringMap[hitGroupShaderIDs_AABBGeometry[j][i]] = c_hitGroupNames_AABBGeometry[j][i];
+ }
+ }
};
// Get shader identifiers using the lambda function defined above.
@@ -95,7 +103,21 @@ 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 = RayType::Count; // one for each ray
+ UINT shaderRecordSize = shaderIDSize; // No root arguments
+
+ // The RayGen shader table contains a single ShaderRecord: the one single raygen shader!
+ ShaderTable missShaderTable(device, numShaderRecords, shaderRecordSize, L"MissShaderTable");
+
+ // Push back a shader record for each ray type
+ for (UINT i = 0; i < numShaderRecords; i++) {
+ missShaderTable.push_back(ShaderRecord(missShaderIDs[i], 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.
@@ -134,6 +156,7 @@ void DXProceduralProject::BuildShaderTables()
// the primitive type is used to tell the shader what type of procedural geometry this is.
// Remember that hitGroupShaderIDs_AABBGeometry is a 2-array indexed like so [type of geometry][ray type]
{
+ // .....I guess this is already done?
LocalRootSignature::AABB::RootArguments rootArgs;
UINT instanceIndex = 0;
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..a9c9a10 100644
--- a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl
+++ b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl
@@ -41,7 +41,16 @@ ConstantBuffer l_aabbCB: register(b2); // other
// Remember to clamp the dot product term!
float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal)
{
- return 0.0f;
+ // From wiki
+ /*
+ The reflection is calculated by taking the dot product of the surface's normal
+ vector, {\displaystyle \mathbf {N} }\mathbf {N} , and a normalized light-direction
+ vector, {\displaystyle \mathbf {L} }\mathbf {L} , pointing from the surface to
+ the light source.
+ */
+
+ // So we want to return the clamped dot of reverse light and normal
+ return clamp(dot(normalize(-incidentLightRay), normal), 0, 1);
}
// TODO-3.6: Phong lighting specular component.
@@ -51,7 +60,10 @@ 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);
+ // Use DXR builtin
+ float3 reflected = normalize(reflect(incidentLightRay, normal));
+ float4 coefficient = pow(clamp(dot(reflected, normalize(-incidentLightRay)), 0, 1), specularPower);
+ return coefficient;
}
// TODO-3.6: Phong lighting model = ambient + diffuse + specular components.
@@ -68,6 +80,9 @@ float4 CalculateSpecularCoefficient(in float3 incidentLightRay, in float3 normal
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)
{
+ // Calculate the light ray by taking the ray from the light source to our hit position
+ float3 lightRay = normalize(HitWorldPosition() - g_sceneCB.lightPosition.xyz);
+
// Ambient component
// Fake AO: Darken faces with normal facing downwards/away from the sky a little bit
float4 ambientColor = g_sceneCB.lightAmbientColor;
@@ -76,7 +91,21 @@ float4 CalculatePhongLighting(in float4 albedo, in float3 normal, in bool isInSh
float a = 1 - saturate(dot(normal, float3(0, -1, 0)));
ambientColor = albedo * lerp(ambientColorMin, ambientColorMax, a);
- return ambientColor;
+ // Diffuse part
+ float diffuse = CalculateDiffuseCoefficient(lightRay, normal);
+ float4 diffuseColor = diffuseCoef * diffuse * g_sceneCB.lightDiffuseColor * albedo;
+ if (isInShadow) {
+ diffuseColor *= InShadowRadiance;
+ }
+
+ // Specular part
+ float4 specular = CalculateSpecularCoefficient(lightRay, normal, specularPower);
+ float4 specularColor = specularCoef * specular;
+ if (isInShadow) {
+ //specularColor *= 0; // Black out if in shadow
+ }
+
+ return ambientColor + diffuseColor + specularColor;
}
//***************************************************************************
@@ -135,7 +164,40 @@ 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;
+
+ // Updated to work with shadowrays
+ ShadowRayPayload rayPayload = { true };
+
+ // https://docs.microsoft.com/en-us/windows/win32/direct3d12/ray_flag
+ RAY_FLAG flags =
+ RAY_FLAG_CULL_BACK_FACING_TRIANGLES |
+ RAY_FLAG_FORCE_OPAQUE |
+ RAY_FLAG_SKIP_CLOSEST_HIT_SHADER |
+ RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
+
+ TraceRay(g_scene,
+ flags,
+ TraceRayParameters::InstanceMask,
+ TraceRayParameters::HitGroup::Offset[RayType::Shadow],
+ TraceRayParameters::HitGroup::GeometryStride,
+ TraceRayParameters::MissShader::Offset[RayType::Shadow],
+ rayDesc, rayPayload);
+
+ // Return if hit
+ return rayPayload.hit;
}
//***************************************************************************
@@ -149,9 +211,23 @@ bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth)
[shader("raygeneration")]
void MyRaygenShader()
{
+ // Snneaky shortcut from the end of this function. Probably noot at all sneaky
+ uint2 idx = DispatchRaysIndex().xy;
+
+ // (1) Generate a ray using the function GenerateCameraRay() in RaytracingShaderHelper.hlsli
+ Ray r = GenerateCameraRay(
+ idx, // Index is uint2, though it can be up to uint3
+ g_sceneCB.cameraPosition.xyz, // Use the buffers we set up before!
+ g_sceneCB.projectionToWorld // Same, use this!
+ );
+
+ // (2) Trace a radiance ray using the generated ray to obtain a color
+ // Set recursion to 0 becuase this is our inital ray.
+ // Other shaders will not have this set to 0
+ float4 rayColor = TraceRadianceRay(r, 0); // See above function!
// Write the color to the render target
- g_renderTarget[DispatchRaysIndex().xy] = float4(0.0f, 0.0f, 0.0f, 0.0f);
+ g_renderTarget[DispatchRaysIndex().xy] = rayColor;
}
//***************************************************************************
@@ -209,6 +285,13 @@ 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.
+
+ // Interpolate between final color and background color
+ // lerp(x, y, s) --> x*(1-s) + y*s
+ // Want far to be closer to 1, near closer to 0
+ // So a power of sorts?
+ float far = RayTCurrent();
+ color = lerp(color, BackgroundColor, 1.0f - exp(-0.01 * far));
rayPayload.color = color;
}
@@ -227,7 +310,57 @@ void MyClosestHitShader_Triangle(inout RayPayload rayPayload, in BuiltInTriangle
[shader("closesthit")]
void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitiveAttributes attr)
{
-
+ float4 tmpColor;
+
+ // Relevant positions
+ float3 pos = HitWorldPosition();
+ float3 direction = WorldRayDirection();
+ float3 lightpos = g_sceneCB.lightPosition.xyz;
+
+ // (1) Trace a shadow ray to determine if this ray is a shadow ray.
+ // TraceShadowRayAndReportIfHit()
+ Ray shadowray;
+ shadowray.origin = pos;
+ shadowray.direction = normalize(lightpos - pos);
+ bool shadowHit = TraceShadowRayAndReportIfHit(shadowray, rayPayload.recursionDepth);
+
+ // (2) Trace a reflectance ray --> compute the reflected color.
+ float4 reflColor;
+ Ray reflRay;
+ reflRay.origin = pos;
+ reflRay.direction = normalize(reflect(direction, attr.normal));
+ reflColor = TraceRadianceRay(reflRay, rayPayload.recursionDepth);
+
+ // Apply fresnel
+ float3 fresnelR = FresnelReflectanceSchlick(WorldRayDirection(), attr.normal, l_materialCB.albedo.xyz);
+
+ // Update Color
+ reflColor = l_materialCB.reflectanceCoef * float4(fresnelR, 1) * reflColor;
+
+
+ // (3) Use the fact that your ray is a shadow ray or not to compute the Phong lighting.
+ float4 phongLighting = CalculatePhongLighting(
+ l_materialCB.albedo,
+ attr.normal,
+ shadowHit,
+ l_materialCB.diffuseCoef, // Bunch of stuff from constant buffers
+ l_materialCB.specularCoef,
+ l_materialCB.specularPower
+ );
+
+ // (4) Combine the reflect color and the phong color into one color.
+ tmpColor = phongLighting + reflColor;
+
+ // (5) Apply visibility falloff to select some interpolation between the computed color or the background color
+ // Interpolate between final color and background color
+ // lerp(x, y, s) --> x*(1-s) + y*s
+ // Want far to be closer to 1, near closer to 0
+ // So a power of sorts?
+ float far = RayTCurrent();
+ tmpColor = lerp(tmpColor, BackgroundColor, 1.0f - exp(-0.01 * far));
+
+ // (6) Fill the payload color with whatever final color you computed
+ rayPayload.color = tmpColor;
}
//***************************************************************************
@@ -240,14 +373,19 @@ void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitive
[shader("miss")]
void MyMissShader(inout RayPayload rayPayload)
{
+ // Hit nothing, so end the ray
+ rayPayload.recursionDepth = MAX_RAY_RECURSION_DEPTH;
+ // Set the color to the background.
+ 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)
{
-
+ // We hit nothing. That means no hit. So...
+ rayPayload.hit = false;
}
//***************************************************************************
@@ -299,6 +437,24 @@ void MyIntersectionShader_AnalyticPrimitive()
[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 procedural
+ // (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))
+ {
+ 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..78dc835 100644
--- a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli
+++ b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli
@@ -68,7 +68,20 @@ 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 cyclePos = elapsedTime;
+
+ // Subtract durations until we are under
+ while (cyclePos > cycleDuration) { cyclePos -= cycleDuration; }
+
+ // Think of it as going from 0 to 2, but if greater than 1 subtract the excess.
+ // Like folding in on itself
+ float cyclePercent = cyclePos / cycleDuration;
+ float interpolant = cyclePercent * 2;
+ if (interpolant > 1) {
+ interpolant -= interpolant - 1;
+ }
+
+ return smoothstep(0, 1, interpolant);
}
// Load three 2-byte indices from a ByteAddressBuffer.
@@ -129,9 +142,35 @@ float3 HitAttribute(float3 vertexAttribute[3], float2 barycentrics)
// as long as the direction of the ray is correct then the depth does not matter.
inline Ray GenerateCameraRay(uint2 index, in float3 cameraPosition, in float4x4 projectionToWorld)
{
+ // Index is our assigned pixel, which is also our ray index.
+ // We need to divide this by width/height to get a position beteen -1,-1 and 1,1
+ // So easy: get it to 0,0 and 1,1. Then -0.5 and double it?
+ float2 screenDim = DispatchRaysDimensions().xy;
+
+ float2 pos = index / screenDim;
+ pos -= 0.5;
+ pos *= 2;
+
+ // Now take that position and flip the y axis cause the DirectX screen geos from top to bottom
+ pos.y = -pos.y;
+
+ // Create a position vector to multiply with the projection mat
+ float4 posvec = float4(pos, 0, 1); // Depth is 0, we dont care
+
+ // Transform to world position from camera position
+ float4 worldPos = mul(posvec, projectionToWorld);
+
+ // "Each component of the vertex coordinate is divided by its
+ // w component giving smaller vertex coordinates the further
+ // away a vertex is from the viewer." - https://learnopengl.com/Getting-started/Coordinate-Systems
+ // I still don't get it, but it fixes my bugs.
+ worldPos.x /= worldPos.w;
+ worldPos.y /= worldPos.w;
+ worldPos.z /= worldPos.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; // All rays start at camera!
+ ray.direction = normalize(worldPos.xyz - ray.origin); // Direction is the vector from origin to the worldpos
return ray;
}
@@ -141,7 +180,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;
+ // Did this in Project 3, so adapt it for DXR
+ return f0 + (1 - f0)*pow((1 - abs(dot(I, N))), 5.0f);;
}
#endif // RAYTRACINGSHADERHELPER_H
\ No newline at end of file
diff --git a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli
index 31a9444..392d871 100644
--- a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli
+++ b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli
@@ -19,10 +19,24 @@ struct Metaball
// 2) If it is at the radius or beyond, the potential is 0.
// 3) In between (i.e the distance from the center is between 0 and radius), consider using the a
// quintic polynomial field function of the form 6x^5 - 15x^4 + 10x^3, such that x is the ratio
-// of the distance from the center to the radius.
+// of the distance from the center to the radius.
float CalculateMetaballPotential(in float3 position, in Metaball blob)
{
- return 0.0f;
+ float pot = 0.0f;
+
+ // How far from the center are we
+ float dist = distance(position, blob.center);
+
+ if (dist >= blob.radius) {
+ pot = 0.0f;
+ }
+ else {
+ // the ratio of the distance from the center to the radius.
+ float x = (blob.radius - dist) / blob.radius;
+ pot = (6 * pow(x, 5)) - (15 * pow(x, 4)) + (10 * pow(x, 3));
+ }
+
+ return pot;
}
// LOOKAT-1.9.4: Calculates field potential from all active metaballs. This is just the sum of all potentials.
@@ -79,10 +93,26 @@ void InitializeAnimatedMetaballs(out Metaball blobs[N_METABALLS], in float elaps
// TODO-3.4.2: Find the entry and exit points for all metaball bounding spheres combined.
// Remember that a metaball is just a solid sphere. Didn't we already do this somewhere else?
+// Hmmmm... just a sphere test?
void TestMetaballsIntersection(in Ray ray, out float tmin, out float tmax, inout Metaball blobs[N_METABALLS])
{
tmin = INFINITY;
tmax = -INFINITY;
+
+ float tmp_thit;
+ float tmp_tmax;
+
+ // Do something similar to the multiple spheres test
+ // Difference here is that we return the entry and exit through
+ // tmin and tmax
+ for (int i = 0; i < N_METABALLS; i++) {
+ Metaball m = blobs[i];
+ if (RaySolidSphereIntersectionTest(ray, tmp_thit, tmp_tmax, m.center, m.radius)) {
+ // Intersected, set values
+ tmin = min(tmp_thit, tmin);
+ tmax = max(tmp_tmax, tmax);
+ }
+ }
}
// TODO-3.4.2: Test if a ray with RayFlags and segment intersects metaball field.
@@ -100,9 +130,57 @@ void TestMetaballsIntersection(in Ray ray, out float tmin, out float tmax, inout
// If this condition fails, keep raymarching!
bool RayMetaballsIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr, in float elapsedTime)
{
+ bool hit = false;
+ float tmin;
+ float tmax;
+ Metaball blobs[N_METABALLS];
+
thit = 0.0f;
attr.normal = float3(0.0f, 0.0f, 0.0f);
- return false;
+
+ // 1) Initialize a metaball array. See InitializeAnimatedMetaballs()
+ InitializeAnimatedMetaballs(blobs, elapsedTime, 10); // duration can be whatever according to piaz
+
+ // 2) Test intersections on the metaballs to find the minimum t and the maximum t to raymarch between.
+ TestMetaballsIntersection(ray, tmin, tmax, blobs);
+
+ // 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!
+ const float THRESHOLD = 0.5;
+ const UINT STEPS = 128;
+ float marchDist = tmax - tmin;
+ float delta = marchDist / STEPS;
+ float pot = 0.0f;
+ float3 normal;
+ float3 posStart = ray.origin;
+ float posAlongRay = tmin;
+
+ // Step through...
+ for (UINT i = 0; i < STEPS; i++, posAlongRay += delta) {
+ float3 pos = posStart + posAlongRay * ray.direction;
+
+ // And calculate the potential.
+ pot = CalculateMetaballsPotential(pos, blobs);
+
+ // If the potential is greater than some threshold + is valid, render it
+ if (pot > THRESHOLD) {
+ normal = CalculateMetaballsNormal(pos, blobs);
+ if (is_a_valid_hit(ray, posAlongRay, normal)) {
+ thit = posAlongRay;
+ attr.normal = normal;
+ hit = true;
+ break;
+ }
+ }
+ }
+
+ return hit;
}
#endif // VOLUMETRICPRIMITIVESLIBRARY_H
\ No newline at end of file