Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
# DirectX Procedural Raytracing
**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)
* Zheyuan Xie
* Tested on: Windows 10 Pro, i7-7700HQ @ 2.80GHz, 16GB, GTX 1050 2GB (Dell XPS 15 9560)

### (TODO: Your README)
## Introduction

Include screenshots, analysis, etc. (Remember, this is public, so don't put
anything here that you don't want to share with the world.)
![](images/dxr.gif)

This project implemented ray tracing for procedural geometry using Direct X 12 Raytracing API.

Keyboard Shortcut:

- `C` - enable/disable camera animation.
- `G` - enable/disable geometry animation.
- `L` - enable/disable light animation.

## Features

### Supported Geometry
1. Triangles

Triangles are supported directly in DXR.

2. Axis-aligned Bounding Box

Axis-aligned Bounding Boxs (AABBs) are the simplest type of analytic geometry primitive. The shape renders into boxes that takes all the volume of a procedural AABB. One thing to note is the box has to be axis-aligned (rotation not supported).

3. Spheres

Spheres are another very simple analytic geometry primitive defined by center and radius. The project supports multiple spheres in a single procedural AABB.

4. Metaballs
Metaballs (a.k.a blobs) are special spheres that have this potential that causes adjacent spheres to merge together. This process is rendered with a special technique using potential thresholding.


## Performance Analysis
The chart below shows how FPS changes with maximum ray depth. As maximum ray depth increases, the FPS drops slightly influenced by reflective materials in the scene.

![](images/performance.png)

## Reference
- [Microsoft Direct3D 12 programming guide](https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide)
- [3D Game Engine Programming - Learning DirectX 12](https://www.3dgep.com/learning-directx-12-2/#Introduction)
- [Microsoft DirectX-Graphics-Samples](https://github.com/microsoft/DirectX-Graphics-Samples)
- Recitation Slides
45 changes: 45 additions & 0 deletions cq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Conceptual Questions
## Question 1: Generate Ray from Pixels
In ray tracing, a ray is generated for each pixel P'<sub>raster</sub> represented in raster space. The original and direction of the ray can be calculated by the following steps:

1. Convert from raster space to NDC space. Note that the y coordinate is inverted since the origin of NDC space is located on the lower left corner.


> P'<sub>NDC</sub>.x = (P'<sub>raster</sub>.x + 0.5) / PixelWidth

> P'<sub>NDC</sub>.y = 1 - (P'<sub>raster</sub>.y + 0.5) / PixelHeight

2. Convert from 2D NDC space to 2D screen space.

> P'<sub>screen</sub>.x = (1 - 2 * P'<sub>NDC</sub>.x) * screenWidth

> P'<sub>screen</sub>.y = (1 - 2 * P'<sub>NDC</sub>.y) * screenHeight

3. Convert 2D screen space coordinate to 3D camera space gives the ray origin.

> RayOrigin.x = P'<sub>screen</sub>.x

> RayOrigin.y = P'<sub>screen</sub>.y

> RayOrigin.z = - CameraToScreenDistance

4. Calculate the direction of the ray. The camera orgin is (0,0,0) in the camera space.

> RayDirection = normalize(RayOrigin - CameraOrigin)

## Question 2: Rendering Procedual Geometry

The procedural geometry is defined by:
- An Axis-Aligned Bounding Box (AABB).
- Geometry type.
- Geometry equation.

The ray is first checked against the AABB. If there's an intersection (the ray enters the AABB at P0), it is then checked against the geomtry defined by its associated equation. We can use ray marching to advance the ray after it enters the AABB.

Calculate the samllest distance from P0 to all procedure shapes inside the AABB (using the equation), say it's d. If it is negative (or smaller than some threshold), we've found the intersection of the ray with the geometry. If not we advanced the ray by distance d to P1, calculate the smallest distance from P1 to the all procedure shapes in AABB, check if the distance is smaller than the threshold. We repeat this process until we hit or some maximum number of steps is reached.


## Question 3: DXR Acceleratin Structures
We can build 1 TLAS and 4 BLASs for the given scene:

![](images/accelstructs.png)
Binary file added images/accelstructs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/dxr.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/performance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 29 additions & 11 deletions src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,37 @@ bool RaySolidSphereIntersectionTest(in Ray ray, out float thit, out float tmax,
// You can hardcode the local centers/radii of the spheres, just try to maintain them between 1 and -1 (and > 0 for the radii).
bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr)
{
const int N_SPHERES = 3;
// Define the spheres in local space (within the aabb)
float3 center = float3(-0.2, 0, -0.2);
float radius = 0.7f;

thit = RayTCurrent();

float tmax;
if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius))
{
return true;
}
float3 center[N_SPHERES] = {
float3(-0.2, 0, -0.2),
float3(-0.8, 0.5, -0.8),
float3(0.5, 0, 0.5),
};
float radius[N_SPHERES] = {
0.7f,
0.1f,
0.3f,
};

thit = RayTCurrent();
bool hit = false;

for (int idx = 0; idx < N_SPHERES; idx++) {
float _thit;
float _tmax;
ProceduralPrimitiveAttributes _attr;
if (RaySphereIntersectionTest(ray, _thit, _tmax, _attr, center[idx], radius[idx]))
{
if (_thit < thit) {
thit = _thit;
attr = _attr;
hit = true;
}
}
}

return false;
return hit;
}

#endif // ANALYTICPRIMITIVES_H
85 changes: 69 additions & 16 deletions src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(array<vector<D3D12_
// The number of elements of a D3D12 resource can be accessed from GetDesc().Width (e.g m_indexBuffer.resource->GetDesc().Width)
auto& geometryDesc = geometryDescs[BottomLevelASType::Triangle][0];
geometryDesc = {};

geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress();
geometryDesc.Triangles.IndexCount = static_cast<UINT>(m_indexBuffer.resource->GetDesc().Width) / sizeof(Index);
geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R16_UINT;
geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT;
geometryDesc.Triangles.VertexCount = static_cast<UINT>(m_vertexBuffer.resource->GetDesc().Width) / sizeof(Vertex);
geometryDesc.Triangles.VertexBuffer.StartAddress = m_vertexBuffer.resource->GetGPUVirtualAddress();
geometryDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(Vertex);
geometryDesc.Flags = geometryFlags;
}

{
Expand All @@ -49,7 +57,11 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(array<vector<D3D12_
// Remember to use m_aabbBuffer to get the AABB geometry data you previously filled in.
// Note: Having separate geometries allows of separate shader record binding per geometry.
// In this project, this lets us specify custom hit groups per AABB geometry.

for (UINT i = 0; i < IntersectionShaderType::TotalPrimitiveCount; i++)
{
auto& geometryDesc = geometryDescs[BottomLevelASType::AABB][i];
geometryDesc.AABBs.AABBs.StartAddress = m_aabbBuffer.resource->GetGPUVirtualAddress() + i * sizeof(D3D12_RAYTRACING_AABB);
}
}
}

Expand All @@ -68,7 +80,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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
bottomLevelInputs.Flags = buildFlags;
bottomLevelInputs.NumDescs = static_cast<UINT>(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 = {};
Expand All @@ -87,7 +103,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.
Expand All @@ -108,7 +124,10 @@ 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)
Expand All @@ -127,7 +146,11 @@ 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 bottomLevelASBuffers;
bottomLevelASBuffers.accelerationStructure = bottomLevelAS;
bottomLevelASBuffers.scratch = scratch;
bottomLevelASBuffers.ResultDataMaxSizeInBytes = bottomLevelPrebuildInfo.ResultDataMaxSizeInBytes;
return bottomLevelASBuffers;
}

// TODO-2.6: Build the instance descriptor for each bottom-level AS you built before.
Expand Down Expand Up @@ -179,7 +202,17 @@ 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
{
auto& instanceDesc = instanceDescs[BottomLevelASType::AABB];
instanceDesc = {};
instanceDesc.InstanceMask = 1;

// Set hit group offset to beyond the shader records for the triangle AABB.
instanceDesc.InstanceContributionToHitGroupIndex = BottomLevelASType::AABB * RayType::Count;
instanceDesc.AccelerationStructure = bottomLevelASaddresses[BottomLevelASType::AABB];

// Move all AABBS above the ground plane.
XMMATRIX mTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&XMFLOAT3(0, c_aabbWidth / 2, 0)));
XMStoreFloat3x4(reinterpret_cast<XMFLOAT3X4*>(instanceDesc.Transform), mTranslation);
}

// Upload all these instances to the GPU, and make sure the resouce is set to instanceDescsResource.
Expand All @@ -202,6 +235,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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
topLevelInputs.Flags = buildFlags;
topLevelInputs.NumDescs = NUM_BLAS;


D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO topLevelPrebuildInfo = {};
Expand All @@ -216,7 +253,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.
{
Expand All @@ -231,7 +268,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:
Expand Down Expand Up @@ -259,7 +296,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
};

// TODO-2.6: Call the fallback-templated version of BuildBottomLevelASInstanceDescs() you completed above.

BuildBottomLevelASInstanceDescs<D3D12_RAYTRACING_FALLBACK_INSTANCE_DESC>(bottomLevelASaddresses, &instanceDescsResource);
}
else // DirectX Raytracing
{
Expand All @@ -271,7 +308,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
};

// TODO-2.6: Call the DXR-templated version of BuildBottomLevelASInstanceDescs() you completed above.

BuildBottomLevelASInstanceDescs<D3D12_RAYTRACING_INSTANCE_DESC>(bottomLevelASaddresses, &instanceDescsResource);
}

// Create a wrapped pointer to the acceleration structure.
Expand All @@ -283,7 +320,11 @@ 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.

{
topLevelBuildDesc.DestAccelerationStructureData = topLevelAS->GetGPUVirtualAddress();
topLevelBuildDesc.Inputs.InstanceDescs = instanceDescsResource->GetGPUVirtualAddress();
topLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress();
}

// Build acceleration structure.
if (m_raytracingAPI == RaytracingAPI::FallbackLayer)
Expand All @@ -302,7 +343,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 topLevelASBuffers;
topLevelASBuffers.accelerationStructure = topLevelAS;
topLevelASBuffers.instanceDesc = instanceDescsResource;
topLevelASBuffers.scratch = scratch;
topLevelASBuffers.ResultDataMaxSizeInBytes = topLevelPrebuildInfo.ResultDataMaxSizeInBytes;
return topLevelASBuffers;
}

// TODO-2.6: This will wrap building the Acceleration Structure! This is what we will call when building our scene.
Expand All @@ -318,12 +364,15 @@ void DXProceduralProject::BuildAccelerationStructures()

// TODO-2.6: Build the geometry descriptors. Hint: you filled in a function that does this.
array<vector<D3D12_RAYTRACING_GEOMETRY_DESC>, 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 i = 0; i < BottomLevelASType::Count; i++)
{
bottomLevelAS[i] = BuildBottomLevelAS(geometryDescs[i]);
}

// Batch all resource barriers for bottom-level AS builds.
// This will Notifies the driver that it needs to synchronize multiple accesses to resources.
Expand All @@ -336,7 +385,7 @@ void DXProceduralProject::BuildAccelerationStructures()

// TODO-2.6: Build top-level AS. Hint, you already made a function that does this.
AccelerationStructureBuffers topLevelAS;

topLevelAS = BuildTopLevelAS(bottomLevelAS);

// Kick off acceleration structure construction.
m_deviceResources->ExecuteCommandList();
Expand All @@ -347,5 +396,9 @@ 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.

for (UINT i = 0; i < BottomLevelASType::Count; i++)
{
m_bottomLevelAS[i] = bottomLevelAS[i].accelerationStructure;
}
m_topLevelAS = topLevelAS.accelerationStructure;
}
Loading