Skip to content

Project 5: Srinath Rajagopalan #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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
74 changes: 68 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,73 @@
DirectX Ray Tracing
======================

**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)
* Srinath Rajagopalan
* [LinkedIn](https://www.linkedin.com/in/srinath-rajagopalan-07a43155)
* Tested on: Windows 10, i7-6700 @ 3.4GHz 16GB, Nvidia GTX 1070 (SIG Lab)

<p align='center'>
<img src="data/final_fresnel.gif">
</p>

# Ray Tracing
In this project, we implement ray tracing for procedural geometries using DirectX Ray Tracing API. Conceptually, ray tracing is similar to path tracing with a few differences. To render a 3D scene, we project it onto a 2D image representing the plane it would be captured if we took a picture of it from the camera. Each pixel on the image has a ray associated with it. This ray is responsible for shading the pixel with a color. Like a path tracer, we trace the ray and find which objects it intersects but after that we don't recursively follow the path of the ray. As soon as it hits an object, we directly reflect it towards a light source. If the ray hits any other obstacle before the light source it is treated as a shadow ray. This is different from a path tracer where we did not explicitly handle shadows. We traced the natural course of the ray and the shadows were generated as an emergent property. In ray tracing, we have only a single pass over the scene. It's deterministic unlike the probabilistic model we had in the path tracer.

<p align='center'>
<img src="images/raytrace.jpg" width=500">
</p>

The ray tracing pipeline using the DirectX API is structured as follows,

<p align='center'>
<img src="images/pipeline.png" width=500">
<p>

We trace a ray, and for each we traverse the acceleration structures to determine the objects of intersection. In the path tracer object, we naively tested each ray with ALL the objects in the scene. This will not scale when we are dealing with a complex scene with many objects. Acceleration structures dramatically reduce the time taken for intersection tests by eliminating a large group of objects the ray is never likely to intersect. Fortunately we don't have to implement the acceleration structures ourselves as DXR handles this. We define the AABBs (which dramatically simplifies the ray-intersection-test logic) and the corresponding transformations and the geometry equation for each object. If the ray intersects an object, we keep going till it doesn't intersect anything else. The intersection shader is responsible for this and it computes the point of intersection and the surface normal (if there is a hit). Finally, if there is a hit, the closest hit shader is responsible for assigning a color to this ray. We only evaluate the intersection of the closest object from the camera. Using the point of intersection and the surface normal, the closest-hit shader generates a reflected ray to the light source. If the ray is intersects another object before the light source, this ray is shadowed. If it hits the light source, it is shaded by factoring both the color of the material and the color of the light source.

## Output

Without factoring any lighting and from just the intersection tests and shadow rays, we get the following output,

<p align='center'>
<img src="data/no_phong.gif" width=700">
<p>

In this project, we color the objects using the [Phong Reflection Model](https://en.wikipedia.org/wiki/Phong_reflection_model) which factors ambient, diffuse, and specular lighting.

<p align='center'>
<img src="data/final.gif" width=700">
<p>

If don't factor in shadows,

<p align='center'>
<img src="data/final_no_shadow.gif" width=700">
<p>

With the camera and lighting moving, we get the following,

<p align='center'>
<img src="data/final_camera.gif" width=700">
<p>

<p align='center'>
<img src="data/final_lighting.gif" width=700">
<p>

With just 1 recursion depth we get the following,

<p align='center'>
<img src="data/depth1.png" width=700>
<p>

## Performance Analysis

<p align='center'>
<img src="data/fps_depth.png" width=700>
<p>

### (TODO: Your README)
Tracking the performance across multiple recursion depths. Unsurprisingly we see huge dips as we go from 1 to 3 but after that the performance is stable. In our particular scene, most rays hit a light source or are shadowed after 3-4 bounces, so there is no additional cost for extra recursion depth as the rays terminate way earlier.

Include screenshots, analysis, etc. (Remember, this is public, so don't put
anything here that you don't want to share with the world.)
Binary file added data/depth1.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 data/final.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 data/final_camera.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 data/final_fresnel.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 data/final_lighting.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 data/final_no_shadow.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 data/fps_depth.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 data/no_phong.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 data/rtx_data.xlsx
Binary file not shown.
49 changes: 39 additions & 10 deletions src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,48 @@ 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)
{
// Define the spheres in local space (within the aabb)
float3 center = float3(-0.2, 0, -0.2);
float radius = 0.7f;

thit = RayTCurrent();
//float3 center = float3(-0.2, 0, -0.2);
//float radius = 0.7f;

float tmax;
if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius))
{
return true;
}
//thit = RayTCurrent();

return false;
//float tmax;
//if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius))
//{
// return true;
//}

//return false;

float3 center[3] = { float3(-0.2, 0, -0.2), float3(0.4, 0.7, 0.3), float3(0.6, -0.3, 0.6)};
float radius[3] = { 0.7f, 0.2f, 0.3f };

thit = RayTCurrent();

float tmax;
float tmin = thit;
bool hit = false;

ProceduralPrimitiveAttributes attrMin;

for (int i = 0; i < 3; i++) {
ProceduralPrimitiveAttributes tempAttr;
if (RaySphereIntersectionTest(ray, thit, tmax, tempAttr, center[i], radius[i]))
{
if (thit < tmin) {
tmin = thit;
attrMin = tempAttr;
}

hit = true;
}
}

thit = tmin;
attr = attrMin;

return hit;
}

#endif // ANALYTICPRIMITIVES_H
68 changes: 53 additions & 15 deletions src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(array<vector<D3D12_
// * We filled in the format of the buffers to avoid confusion.
auto& geometryDesc = geometryDescs[BottomLevelASType::Triangle][0];
geometryDesc = {};
geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R16_UINT;
geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT;

geometryDesc.Triangles.IndexCount = m_indexBuffer.resource->GetDesc().Width / sizeof(Index);
geometryDesc.Triangles.VertexCount = m_vertexBuffer.resource->GetDesc().Width / sizeof(Vertex);
geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress();
geometryDesc.Triangles.VertexBuffer = {m_vertexBuffer.resource->GetGPUVirtualAddress(), sizeof(Vertex) };

}

{
Expand All @@ -51,6 +58,10 @@ 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 pIndex = 0; pIndex < IntersectionShaderType::TotalPrimitiveCount; pIndex++) {
UINT64 size = aabbDescTemplate.AABBs.AABBs.StrideInBytes;
geometryDescs[BottomLevelASType::AABB][pIndex].AABBs.AABBs.StartAddress = m_aabbBuffer.resource->GetGPUVirtualAddress() + pIndex * size;
}

}
}
Expand All @@ -70,6 +81,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.NumDescs = geometryDescs.size();
bottomLevelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
bottomLevelInputs.pGeometryDescs = geometryDescs.data();


// Query the driver for resource requirements to build an acceleration structure. We've done this for you.
Expand Down Expand Up @@ -110,7 +126,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)
Expand All @@ -129,7 +146,7 @@ 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{};
return AccelerationStructureBuffers{ scratch, bottomLevelAS, nullptr, bottomLevelPrebuildInfo.ResultDataMaxSizeInBytes};
}

// TODO-2.6: Build the instance descriptor for each bottom-level AS you built before.
Expand Down Expand Up @@ -181,7 +198,18 @@ 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;
instanceDesc.InstanceContributionToHitGroupIndex = 2;
instanceDesc.AccelerationStructure = bottomLevelASaddresses[BottomLevelASType::AABB];

const XMVECTOR vBasePosition = c_aabbWidth * XMLoadFloat3(&XMFLOAT3(0.0f, 0.5f, 0.0f));

XMMATRIX mTranslation = XMMatrixTranslationFromVector(vBasePosition);
//XMMATRIX mScale = XMMatrixScaling(c_aabbWidth, c_aabbWidth, c_aabbWidth);
XMMATRIX mTransform = mTranslation;
XMStoreFloat3x4(reinterpret_cast<XMFLOAT3X4*>(instanceDesc.Transform), mTransform);
}

// Upload all these instances to the GPU, and make sure the resouce is set to instanceDescsResource.
Expand All @@ -204,7 +232,11 @@ 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.InstanceDescs = BottomLevelASType::Count;
topLevelInputs.NumDescs = BottomLevelASType::Count;
topLevelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;

D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO topLevelPrebuildInfo = {};
if (m_raytracingAPI == RaytracingAPI::FallbackLayer)
Expand All @@ -218,7 +250,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"ScratchResourceTop");

// Allocate space for the top-level AS.
{
Expand All @@ -233,7 +265,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"TopLevelAS");
}

// Note on Emulated GPU pointers (AKA Wrapped pointers) requirement in Fallback Layer:
Expand Down Expand Up @@ -261,7 +293,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
};

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

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

// TODO-2.6: Call the DXR-templated version of BuildBottomLevelASInstanceDescs() you completed above.
BuildBottomLevelASInstanceDescs<D3D12_RAYTRACING_INSTANCE_DESC, D3D12_GPU_VIRTUAL_ADDRESS>(bottomLevelASaddresses, &instanceDescsResource);

}

Expand All @@ -285,7 +318,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)
Expand All @@ -294,17 +329,15 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt
ID3D12DescriptorHeap *pDescriptorHeaps[] = { m_descriptorHeap.Get() };
m_fallbackCommandList->SetDescriptorHeaps(ARRAYSIZE(pDescriptorHeaps), pDescriptorHeaps);
m_fallbackCommandList->BuildRaytracingAccelerationStructure(&topLevelBuildDesc, 0, nullptr);
}
else // DirectX Raytracing
{
} else {
m_dxrCommandList->BuildRaytracingAccelerationStructure(&topLevelBuildDesc, 0, nullptr);
}

// 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.
// Make sure to return the topLevelASBuffers before you exit the function.
return AccelerationStructureBuffers{};
return AccelerationStructureBuffers{scratch, topLevelAS, instanceDescsResource, topLevelPrebuildInfo.ResultDataMaxSizeInBytes };
}

// TODO-2.6: This will wrap building the Acceleration Structure! This is what we will call when building our scene.
Expand All @@ -320,12 +353,13 @@ 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 @@ -338,7 +372,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 @@ -349,5 +383,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;

}
13 changes: 7 additions & 6 deletions src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

// Bind the descriptor heaps.
if (m_raytracingAPI == RaytracingAPI::FallbackLayer)
Expand All @@ -49,24 +50,24 @@ 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->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
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

dispatchDesc->HitGroupTable = { m_hitGroupShaderTable->GetGPUVirtualAddress(), m_hitGroupShaderTable->GetDesc().Width ,m_hitGroupShaderTableStrideInBytes };

// TODO-2.8: now fill in dispatchDesc->MissShaderTable

dispatchDesc->MissShaderTable = { m_missShaderTable->GetGPUVirtualAddress(), m_missShaderTable->GetDesc().Width , m_missShaderTableStrideInBytes };

// TODO-2.8: now fill in dispatchDesc->RayGenerationShaderRecord

dispatchDesc->RayGenerationShaderRecord = { m_rayGenShaderTable->GetGPUVirtualAddress(), m_rayGenShaderTable->GetDesc().Width };

// We do this for you. This will define how many threads will be dispatched. Basically like a blockDims in CUDA!
dispatchDesc->Width = m_width;
Expand Down
Loading