diff --git a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj index 0d72eb0..4e36582 100644 --- a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj +++ b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj @@ -28,7 +28,7 @@ StaticLibrary Unicode - v141 + v142 diff --git a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters index cc2db39..52aef00 100644 --- a/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters +++ b/Libraries/D3D12RaytracingFallback/src/FallbackLayer.vcxproj.filters @@ -201,6 +201,18 @@ + + + + + + + + + + + + diff --git a/README.md b/README.md index b0189d0..2e7c1b1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,98 @@ **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5 - DirectX Procedural Raytracing** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Gangzheng Tong + * www.gtong.me +* Tested on: Windows 10, i7-8750H @ 2.20GHz 16GB, RTX 2070 8GB (personal laptop) -### (TODO: Your README) +### Overview -Include screenshots, analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +This project implements a raytracing based scene renderer and demonstrates the usage of DirectX 12 Raytracing API. It utilizes the built-in acceleration structure, analytic and procedural geometry construction and shadow ray testing. + + + +![](./img/main.gif) + + + +### Contents + +- Ray Tracing Concept +- Acceleration Structure +- Output +- Performance Analysis +- Bloopers + + + +### Ray Tracing Concept + + + + + +![](img\raytrace.jpg) + + + +Unlike path tracer, ray tracing based renderer simples the the rendering and calculation significantly by only keeping a few rays for each pixel and shading point rather than spawning multiply rays based on BSDF. In this project, we only consider: + +- **Primary ray**: the ray shot from camera to each pixel. We need to apply the project_to_world transformation in order to bring the ray to the world coordinate, for further intersection testing with the scene. + +- **Shadow ray**: from the closest_hit point, spawn a shadow ray to the light source to find out if this point is in shadow. It's still cheap for scenes with only a few light sources. + +- **Reflected ray**: calculating the reflected ray based on the incident ray direction and normal of hit point. Perform another trace like the primary ray but with the depth incremented. + + + +### Acceleration Structure + +##### Diagram of DXR Top-Level/Bottom-Level Acceleration Structures + + + +![scene](images/scene.png) + +![Acceleration Structures](img/as.png) + +### Output + +![4 meta balls](img/output1.gif) + +- Static light and static camera, 4 metaballs and 3 spheres + + + +![4 meta balls](img/output_light.gif) + +- Animating light source + + + +![4 meta balls](img/output_camera.gif) + +- Animating camera + + + +### Performance Analysis + +![FPS_DEPTH](img/performance.png) + +- With the max recursion depth increased from 3 to 10, initially there is a drop in the FPS from 3 to 5, but it quickly stables at around 280 FPS. +- It makes sense since the scene doesn't have too many reflected materials and most of rays terminated after 5 bounces. + + + +### Bloopers + +![blooper](img/blooper.PNG) + +When I used **pow(x, 5)** to calculate **x * x * x * x * x** + + + +### References + +- https://github.com/Microsoft/DirectX-Graphics-Samples +- https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide \ No newline at end of file diff --git a/img/as.png b/img/as.png new file mode 100644 index 0000000..2e6f254 Binary files /dev/null and b/img/as.png differ diff --git a/img/blooper.PNG b/img/blooper.PNG new file mode 100644 index 0000000..4ba4eb1 Binary files /dev/null and b/img/blooper.PNG differ diff --git a/img/camera_space.png b/img/camera_space.png new file mode 100644 index 0000000..309138a Binary files /dev/null and b/img/camera_space.png differ diff --git a/img/main.gif b/img/main.gif new file mode 100644 index 0000000..a26da8c Binary files /dev/null and b/img/main.gif differ diff --git a/img/ndc_1.png b/img/ndc_1.png new file mode 100644 index 0000000..259b6fb Binary files /dev/null and b/img/ndc_1.png differ diff --git a/img/output1.gif b/img/output1.gif new file mode 100644 index 0000000..be8735f Binary files /dev/null and b/img/output1.gif differ diff --git a/img/output_camera.gif b/img/output_camera.gif new file mode 100644 index 0000000..3e2e109 Binary files /dev/null and b/img/output_camera.gif differ diff --git a/img/output_light.gif b/img/output_light.gif new file mode 100644 index 0000000..aeef2c8 Binary files /dev/null and b/img/output_light.gif differ diff --git a/img/performance.png b/img/performance.png new file mode 100644 index 0000000..d747a39 Binary files /dev/null and b/img/performance.png differ diff --git a/img/pipeline.png b/img/pipeline.png new file mode 100644 index 0000000..0802502 Binary files /dev/null and b/img/pipeline.png differ diff --git a/img/raytrace.jpg b/img/raytrace.jpg new file mode 100644 index 0000000..6c0fe18 Binary files /dev/null and b/img/raytrace.jpg differ diff --git a/img/screen_space.png b/img/screen_space.png new file mode 100644 index 0000000..6f12e1f Binary files /dev/null and b/img/screen_space.png differ diff --git a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli index c6ccebb..03a80ba 100644 --- a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli @@ -169,6 +169,12 @@ bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out Procedur float3 center = float3(-0.2, 0, -0.2); float radius = 0.7f; + float3 center2 = float3(-0.3, 0.5, -0.2); + float radius2 = 0.5f; + + float3 center3 = float3(-0.1, -0.5, 0.2); + float radius3 = 0.4f; + thit = RayTCurrent(); float tmax; @@ -176,6 +182,14 @@ bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out Procedur { return true; } + if (RaySphereIntersectionTest(ray, thit, tmax, attr, center2, radius2)) + { + return true; + } + if (RaySphereIntersectionTest(ray, thit, tmax, attr, center3, radius3)) + { + return true; + } return false; } diff --git a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj index 4a8b9ab..f59a973 100644 --- a/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj +++ b/src/D3D12RaytracingProceduralGeometry/D3D12RaytracingProceduralGeometry.vcxproj @@ -21,13 +21,13 @@ Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp index 084077a..94a2412 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp @@ -30,8 +30,16 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetGPUVirtualAddress()) // 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 = { }; + geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress(); + geometryDesc.Triangles.IndexCount = static_cast(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(m_vertexBuffer.resource->GetDesc().Width) / sizeof(Vertex); + geometryDesc.Triangles.VertexBuffer.StartAddress = m_vertexBuffer.resource->GetGPUVirtualAddress(); + geometryDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(Vertex); + geometryDesc.Flags = geometryFlags; } { @@ -49,7 +57,12 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetGPUVirtualAddress() + i * sizeof(D3D12_RAYTRACING_AABB); + } } } @@ -68,7 +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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + bottomLevelInputs.Flags = buildFlags; + bottomLevelInputs.NumDescs = static_cast(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 = {}; @@ -108,8 +125,9 @@ AccelerationStructureBuffers DXProceduralProject::BuildBottomLevelAS(const vecto // TODO-2.6: Now that you have the scratch and actual bottom-level AS desc, pass their GPU addresses to the bottomLevelBuildDesc. // Consider reading about D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC. // This should be as easy as passing the GPU addresses to the struct using GetGPUVirtualAddress() calls. - - + bottomLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress(); + bottomLevelBuildDesc.DestAccelerationStructureData = bottomLevelAS->GetGPUVirtualAddress(); + // Fill up the command list with a command that tells the GPU how to build the bottom-level AS. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) { @@ -127,7 +145,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 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. @@ -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, 0))); + XMStoreFloat3x4(reinterpret_cast(instanceDesc.Transform), mTranslation); } // Upload all these instances to the GPU, and make sure the resouce is set to instanceDescsResource. @@ -202,7 +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 = {}; if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -216,7 +252,8 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt ThrowIfFalse(topLevelPrebuildInfo.ResultDataMaxSizeInBytes > 0); // TODO-2.6: Allocate a UAV buffer for the scracth/temporary top-level AS data. - + AllocateUAVBuffer(device, topLevelPrebuildInfo.ScratchDataSizeInBytes, &scratch, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, L"ScratchResource"); + // Allocate space for the top-level AS. { @@ -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: @@ -259,7 +296,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 +308,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 +320,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. - + topLevelBuildDesc.DestAccelerationStructureData = topLevelAS->GetGPUVirtualAddress(); + topLevelInputs.InstanceDescs = instanceDescsResource->GetGPUVirtualAddress(); + topLevelBuildDesc.ScratchAccelerationStructureData = scratch->GetGPUVirtualAddress(); // Build acceleration structure. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -302,7 +341,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. @@ -318,12 +362,15 @@ void DXProceduralProject::BuildAccelerationStructures() // TODO-2.6: Build the geometry descriptors. Hint: you filled in a function that does this. array, BottomLevelASType::Count> geometryDescs; - + BuildGeometryDescsForBottomLevelAS(geometryDescs); // TODO-2.6: For each bottom-level object (triangle, procedural), build a bottom-level AS. // Hint: you filled in a function that does this. AccelerationStructureBuffers bottomLevelAS[BottomLevelASType::Count]; - + for (UINT 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. @@ -336,6 +383,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. @@ -347,5 +395,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; } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp index 03a8c58..21a9798 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp @@ -22,7 +22,8 @@ void DXProceduralProject::DoRaytracing() commandList->SetComputeRootConstantBufferView(GlobalRootSignature::Slot::SceneConstant, m_sceneCB.GpuVirtualAddress(frameIndex)); // TODO-2.8: do a very similar operation for the m_aabbPrimitiveAttributeBuffer - + m_aabbPrimitiveAttributeBuffer.CopyStagingToGpu(frameIndex); + commandList->SetComputeRootShaderResourceView(GlobalRootSignature::Slot::AABBattributeBuffer, m_aabbPrimitiveAttributeBuffer.GpuVirtualAddress(frameIndex)); // Bind the descriptor heaps. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -49,10 +50,11 @@ void DXProceduralProject::DoRaytracing() // This should be done by telling the commandList to SetComputeRoot*(). You just have to figure out what * is. // Example: in the case of GlobalRootSignature::Slot::SceneConstant above, we used SetComputeRootConstantBufferView() // Hint: look at CreateRootSignatures() in DXR-Pipeline.cpp. - + commandList->SetComputeRootDescriptorTable(GlobalRootSignature::Slot::VertexBuffers, m_indexBuffer.gpuDescriptorHandle); // TODO-2.8: Bind the OutputView (basically m_raytracingOutputResourceUAVGpuDescriptor). Very similar to the Index/Vertex buffer. - + commandList->SetComputeRootDescriptorTable(GlobalRootSignature::Slot::OutputView, m_raytracingOutputResourceUAVGpuDescriptor); + // This will define a `DispatchRays` function that takes in a command list, a pipeline state, and a descriptor // This will set the hooks using the shader tables built before and call DispatchRays on the command list @@ -60,13 +62,18 @@ void DXProceduralProject::DoRaytracing() { // You will fill in a D3D12_DISPATCH_RAYS_DESC (which is dispatchDesc). // TODO-2.8: fill in dispatchDesc->HitGroupTable. Look up the struct D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE - + dispatchDesc->HitGroupTable.StartAddress = m_hitGroupShaderTable->GetGPUVirtualAddress(); + dispatchDesc->HitGroupTable.SizeInBytes = m_hitGroupShaderTable->GetDesc().Width; + dispatchDesc->HitGroupTable.StrideInBytes = m_hitGroupShaderTableStrideInBytes; // TODO-2.8: now fill in dispatchDesc->MissShaderTable - + 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 - + 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! dispatchDesc->Width = m_width; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp index e3ff63c..4c3146e 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(); + // TONG TODO + int numElements = AnalyticPrimitive::Count + VolumetricPrimitive::Count; + m_aabbPrimitiveAttributeBuffer.Create(device, numElements, frameCount, L"Scene AABB Primitive Attributes Buffer"); } // LOOKAT-2.1: Update camera matrices stored in m_sceneCB. @@ -164,6 +168,10 @@ void DXProceduralProject::UpdateAABBPrimitiveAttributes(float animationTime) // You can infer what the bottom level AS space to local space transform should be. // The intersection shader tests in this project work with local space, but the geometries are provided in bottom level // AS space. So this data will be used to convert back and forth from these spaces. + XMMATRIX transformMat = mScale * mRotation * mTranslation; + m_aabbPrimitiveAttributeBuffer[primitiveIndex].localSpaceToBottomLevelAS = transformMat; + m_aabbPrimitiveAttributeBuffer[primitiveIndex].bottomLevelASToLocalSpace = XMMatrixInverse(nullptr, transformMat); + }; UINT offset = 0; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp index 9d93504..d3f8de7 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp @@ -86,7 +86,13 @@ void DXProceduralProject::BuildProceduralGeometryAABBs() // This should take into account the basePosition and the stride defined above. auto InitializeAABB = [&](auto& offsetIndex, auto& size) { - D3D12_RAYTRACING_AABB aabb{}; + D3D12_RAYTRACING_AABB aabb{ + basePosition.x + offsetIndex.x * stride.x, + basePosition.y + offsetIndex.y * stride.y, + basePosition.z + offsetIndex.z * stride.z, + basePosition.x + offsetIndex.x * stride.x + size.x, + basePosition.y + offsetIndex.y * stride.y + size.y, + basePosition.z + offsetIndex.z * stride.z + size.z, }; return aabb; }; m_aabbs.resize(IntersectionShaderType::TotalPrimitiveCount); @@ -110,6 +116,9 @@ 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(m_aabbs[0]), &m_aabbBuffer.resource); + } } @@ -117,5 +126,6 @@ void DXProceduralProject::BuildProceduralGeometryAABBs() // TODO-2.5: Build geometry used in the project. As easy as calling both functions above :) void DXProceduralProject::BuildGeometry() { - + BuildPlaneGeometry(); + BuildProceduralGeometryAABBs(); } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp index 33899bd..549a23d 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp @@ -29,7 +29,19 @@ 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. { - + // Create hit groups for each intersection shader. + for (UINT t = 0; t < IntersectionShaderType::Count; t++) + for (UINT rayType = 0; rayType < RayType::Count; rayType++) + { + auto hitGroup = raytracingPipeline->CreateSubobject(); + hitGroup->SetIntersectionShaderImport(c_intersectionShaderNames[t]); + if (rayType == RayType::Radiance) + { + hitGroup->SetClosestHitShaderImport(c_closestHitShaderNames[GeometryType::AABB]); + } + hitGroup->SetHitGroupExport(c_hitGroupNames_AABBGeometry[t][rayType]); + hitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE); + } } } @@ -54,6 +66,14 @@ 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(); + localRootSignature->SetRootSignature(m_raytracingLocalRootSignature[LocalRootSignature::Type::AABB].Get()); + // Shader association + auto rootSignatureAssociation = raytracingPipeline->CreateSubobject(); + rootSignatureAssociation->SetSubobjectToAssociate(*localRootSignature); + for (auto& hitGroupsForIntersectionShaderType : c_hitGroupNames_AABBGeometry) + { + rootSignatureAssociation->AddExports(hitGroupsForIntersectionShaderType); + } } } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp index 2dff8b5..0f77f3b 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp @@ -21,7 +21,7 @@ void DXProceduralProject::CreateRootSignatures() // 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. - + 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. @@ -39,8 +39,12 @@ void DXProceduralProject::CreateRootSignatures() // t registers --> SRV // b registers --> CBV CD3DX12_ROOT_PARAMETER rootParameters[GlobalRootSignature::Slot::Count]; + rootParameters[GlobalRootSignature::Slot::OutputView].InitAsDescriptorTable(1, &ranges[0]); + rootParameters[GlobalRootSignature::Slot::AccelerationStructure].InitAsShaderResourceView(0); // occupy slot 0 + rootParameters[GlobalRootSignature::Slot::SceneConstant].InitAsConstantBufferView(0); + rootParameters[GlobalRootSignature::Slot::AABBattributeBuffer].InitAsShaderResourceView(3); // occupy slot 3 + rootParameters[GlobalRootSignature::Slot::VertexBuffers].InitAsDescriptorTable(1, &ranges[1]); // occupy slot 1 and 2 - // 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); } @@ -67,7 +71,14 @@ void DXProceduralProject::CreateRootSignatures() // 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]; + 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..dde2865 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-ShaderTable.cpp @@ -32,8 +32,8 @@ 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[RayType::Radiance] = stateObjectProperties->GetShaderIdentifier(c_missShaderNames[RayType::Radiance]); + missShaderIDs[RayType::Shadow] = stateObjectProperties->GetShaderIdentifier(c_missShaderNames[RayType::Shadow]); // 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 +43,13 @@ void DXProceduralProject::BuildShaderTables() } // TODO-2.7: Hitgroup shaders for the AABBs. We have 2 for each AABB. - + for (UINT i = 0; i < RayType::Count; i++) + { + for (UINT t = 0; t < IntersectionShaderType::Count; t++){ + hitGroupShaderIDs_AABBGeometry[t][i] = stateObjectProperties->GetShaderIdentifier(c_hitGroupNames_AABBGeometry[t][i]); + shaderIdToStringMap[hitGroupShaderIDs_AABBGeometry[t][i]] = c_hitGroupNames_AABBGeometry[t][i]; + } + } }; // Get shader identifiers using the lambda function defined above. @@ -95,7 +101,20 @@ void DXProceduralProject::BuildShaderTables() // TODO-2.7: Miss shader table. Very similar to the RayGen table except now we push_back() 2 shader records // 1 for the radiance ray, 1 for the shadow ray. Don't forget to call DebugPrint() on the table for your sanity! { - + UINT numShaderRecords = RayType::Count; + UINT shaderRecordSize = shaderIDSize; // No root arguments + + // The miss shader table contains 2 ShaderRecords + ShaderTable missShaderTable(device, numShaderRecords, shaderRecordSize, L"MissShaderTable"); + + // Push back the shader record, which does not need any root signatures. + missShaderTable.push_back(ShaderRecord(missShaderIDs[RayType::Radiance], shaderRecordSize, nullptr, 0)); + missShaderTable.push_back(ShaderRecord(missShaderIDs[RayType::Shadow], 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_missShaderTableStrideInBytes = missShaderTable.GetShaderRecordSize(); + m_missShaderTable = missShaderTable.GetResource(); } // Hit group shader table. This one is slightly different given that a hit group requires its own custom root signature. diff --git a/src/D3D12RaytracingProceduralGeometry/Main.cpp b/src/D3D12RaytracingProceduralGeometry/Main.cpp index 7f70bc6..6bbb443 100644 --- a/src/D3D12RaytracingProceduralGeometry/Main.cpp +++ b/src/D3D12RaytracingProceduralGeometry/Main.cpp @@ -16,7 +16,7 @@ #include "stdafx.h" #include "DXProceduralProject.h" -#define CPU_CODE_COMPLETE 0 +#define CPU_CODE_COMPLETE 1 _Use_decl_annotations_ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) diff --git a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl index d066933..b7e3a21 100644 --- a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl +++ b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl @@ -41,7 +41,7 @@ ConstantBuffer l_aabbCB: register(b2); // other // Remember to clamp the dot product term! float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal) { - return 0.0f; + return saturate(dot(-incidentLightRay, normal)); } // TODO-3.6: Phong lighting specular component. @@ -51,7 +51,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); + // DONE + float3 reflectedLightRay = normalize(reflect(incidentLightRay, normal)); + float clmaped = saturate( dot(reflectedLightRay, normalize(-WorldRayDirection())) ); + return pow(clmaped, specularPower ); } // TODO-3.6: Phong lighting model = ambient + diffuse + specular components. @@ -68,6 +71,26 @@ 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) { + float3 hitPosition = HitWorldPosition(); + float3 lightPosition = g_sceneCB.lightPosition.xyz; + float shadowCoef = isInShadow ? InShadowRadiance : 1.0; + float3 incidentLightRay = normalize(hitPosition - lightPosition); + + // Diffuse component. + float4 lightDiffuseColor = g_sceneCB.lightDiffuseColor; + float Kd = CalculateDiffuseCoefficient(incidentLightRay, normal); + float4 diffuseColor = shadowCoef * diffuseCoef * Kd * lightDiffuseColor * albedo; + + // Specular component. + float4 specularColor = float4(0, 0, 0, 0); + if (!isInShadow) + { + float4 lightSpecularColor = float4(1, 1, 1, 1); + float4 Ks = CalculateSpecularCoefficient(incidentLightRay, normal, specularPower); + specularColor = specularCoef * Ks * lightSpecularColor; + } + // DONE + // Ambient component // Fake AO: Darken faces with normal facing downwards/away from the sky a little bit float4 ambientColor = g_sceneCB.lightAmbientColor; @@ -76,7 +99,7 @@ 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; + return ambientColor + diffuseColor + specularColor; } //*************************************************************************** @@ -135,7 +158,38 @@ 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; + } + // TODO + + // Set the ray's extents. + RayDesc rayDesc; + rayDesc.Origin = ray.origin; + rayDesc.Direction = ray.direction; + // Set TMin to a zero value to avoid aliasing artifcats along contact areas. + // Note: make sure to enable back-face culling so as to avoid surface face fighting. + rayDesc.TMin = 0; + rayDesc.TMax = 10000; + + // Initialize shadow ray payload. + // Set the initial value to true since closest and any hit shaders are skipped. + // Shadow miss shader, if called, will set it to false. + ShadowRayPayload shadowPayload = { true }; + TraceRay(g_scene, + RAY_FLAG_CULL_BACK_FACING_TRIANGLES + | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH + | RAY_FLAG_FORCE_OPAQUE // skip any hit shaders + | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, // skip closest hit shaders, + TraceRayParameters::InstanceMask, + TraceRayParameters::HitGroup::Offset[RayType::Shadow], + TraceRayParameters::HitGroup::GeometryStride, + TraceRayParameters::MissShader::Offset[RayType::Shadow], + rayDesc, shadowPayload); + + // DONE + return shadowPayload.hit; } //*************************************************************************** @@ -149,9 +203,14 @@ bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth) [shader("raygeneration")] void MyRaygenShader() { + // Generate a ray for a camera pixel corresponding to an index from the dispatched 2D grid. + Ray ray = GenerateCameraRay(DispatchRaysIndex().xy, g_sceneCB.cameraPosition.xyz, g_sceneCB.projectionToWorld); - // Write the color to the render target - g_renderTarget[DispatchRaysIndex().xy] = float4(0.0f, 0.0f, 0.0f, 0.0f); + // Cast a ray into the scene and retrieve a shaded color. + float4 color = TraceRadianceRay(ray, 0); + + // Write the raytraced color to the output texture. + g_renderTarget[DispatchRaysIndex().xy] = color; } //*************************************************************************** @@ -210,6 +269,10 @@ void MyClosestHitShader_Triangle(inout RayPayload rayPayload, in BuiltInTriangle // 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. + float t = RayTCurrent(); + float val = exp(-0.0001 * pow(t, 3.0)); + color = lerp(color, BackgroundColor, 1.0 -val); + rayPayload.color = color; } @@ -227,7 +290,32 @@ void MyClosestHitShader_Triangle(inout RayPayload rayPayload, in BuiltInTriangle [shader("closesthit")] void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitiveAttributes attr) { - + float3 hitPosition = HitWorldPosition(); + Ray shadowRay = { hitPosition, normalize(g_sceneCB.lightPosition.xyz - hitPosition) }; + bool shadowRayHit = TraceShadowRayAndReportIfHit(shadowRay, rayPayload.recursionDepth); + + // Reflected component. + float4 reflectedColor = float4(0, 0, 0, 0); + if (l_materialCB.reflectanceCoef > 0.001) + { + // Trace a reflection ray. + Ray reflectionRay = { HitWorldPosition(), reflect(WorldRayDirection(), attr.normal) }; + float4 reflectionColor = TraceRadianceRay(reflectionRay, rayPayload.recursionDepth); + + float3 fresnelR = FresnelReflectanceSchlick(WorldRayDirection(), attr.normal, l_materialCB.albedo.xyz); + reflectedColor = l_materialCB.reflectanceCoef * float4(fresnelR, 1) * reflectionColor; + } + + // Calculate final color. + float4 phongColor = CalculatePhongLighting(l_materialCB.albedo, attr.normal, shadowRayHit, l_materialCB.diffuseCoef, l_materialCB.specularCoef, l_materialCB.specularPower); + float4 color = phongColor + reflectedColor; + + // Apply visibility falloff. + float t = RayTCurrent(); + float val = exp(-0.0001 * pow(t, 3.0)); + color = lerp(color, BackgroundColor, 1.0 - val); + + rayPayload.color = color; } //*************************************************************************** @@ -240,14 +328,15 @@ void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitive [shader("miss")] void MyMissShader(inout RayPayload rayPayload) { - + float4 backgroundColor = float4(BackgroundColor); + rayPayload.color = backgroundColor; } // TODO-3.3: Complete the Shadow ray miss shader. Is this ray a shadow ray if it hit nothing? [shader("miss")] void MyMissShader_ShadowRay(inout ShadowRayPayload rayPayload) { - + rayPayload.hit = false; } //*************************************************************************** @@ -299,6 +388,18 @@ void MyIntersectionShader_AnalyticPrimitive() [shader("intersection")] void MyIntersectionShader_VolumetricPrimitive() { + Ray localRay = GetRayInAABBPrimitiveLocalSpace(); + VolumetricPrimitive::Enum primitiveType = (VolumetricPrimitive::Enum) l_aabbCB.primitiveType; + + float thit; + ProceduralPrimitiveAttributes attr; + if (RayVolumetricGeometryIntersectionTest(localRay, primitiveType, thit, attr, g_sceneCB.elapsedTime)) + { + PrimitiveInstancePerFrameBuffer aabbAttribute = g_AABBPrimitiveAttributes[l_aabbCB.instanceIndex]; + attr.normal = mul(attr.normal, (float3x3) aabbAttribute.localSpaceToBottomLevelAS); + attr.normal = normalize(mul((float3x3) ObjectToWorld3x4(), attr.normal)); + ReportHit(thit, /*hitKind*/ 0, attr); + } } #endif // RAYTRACING_HLSL \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h b/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h index 6e10f0d..4c26fb3 100644 --- a/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h +++ b/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h @@ -24,7 +24,7 @@ typedef UINT16 Index; #endif // Number of metaballs to use within an AABB. Limit to 3 unless you are attempting the dynamic looping extra-credit. -#define N_METABALLS 3 +#define N_METABALLS 4 #define N_FRACTAL_ITERATIONS 5 // = <1,...> diff --git a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli index 94bf5cc..20b073f 100644 --- a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli @@ -68,7 +68,9 @@ 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 t = fmod(elapsedTime, cycleDuration) / cycleDuration; + t = (t <= 0.5f) ? 2 * t : 1 - 2 * (t - 0.5f); + return smoothstep(0, 1, t); } // Load three 2-byte indices from a ByteAddressBuffer. @@ -129,9 +131,21 @@ 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) { + float2 xy = index + 0.5f; // center in the middle of the pixel. + float2 screenPos = xy / DispatchRaysDimensions().xy * 2.0 - 1.0; + + // Invert Y for DirectX-style coordinates. + screenPos.y = -screenPos.y; + + // Apply projectionToWorld to bring the point to world space + float4 world = mul(float4(screenPos, 0, 1), projectionToWorld); + // Normalize + world.xyz /= world.w; + + // Create a ray with the origin and dir above Ray ray; - ray.origin = float3(0.0f, 0.0f, 0.0f); - ray.direction = normalize(float3(0.0f, 0.0f, 0.0f)); + ray.origin = cameraPosition; + ray.direction = normalize(world.xyz - ray.origin); return ray; } @@ -141,7 +155,8 @@ inline Ray GenerateCameraRay(uint2 index, in float3 cameraPosition, in float4x4 // f0 is usually the albedo of the material assuming the outside environment is air. float3 FresnelReflectanceSchlick(in float3 I, in float3 N, in float3 f0) { - return f0; + float cosi = saturate(dot(-I, N)); + return f0 + (1 - f0) * pow(1 - cosi, 5); } #endif // RAYTRACINGSHADERHELPER_H \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli index 31a9444..4a357ee 100644 --- a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli @@ -22,7 +22,20 @@ struct Metaball // of the distance from the center to the radius. float CalculateMetaballPotential(in float3 position, in Metaball blob) { - return 0.0f; + float distance = length(position - blob.center); + + if (distance <= blob.radius) + { + float d = blob.radius - distance; + float r = blob.radius; + float x = d / r; + + // Note: pow has limited precision! + return 6 * x * x * x * x * x + - 15 * x * x * x * x + + 10 * x * x * x; + } + return 0; } // LOOKAT-1.9.4: Calculates field potential from all active metaballs. This is just the sum of all potentials. @@ -62,11 +75,12 @@ void InitializeAnimatedMetaballs(out Metaball blobs[N_METABALLS], in float elaps { { float3(-0.3, -0.3, -0.4),float3(0.3,-0.3,-0.0) }, // begin center --> end center { float3(0.0, -0.2, 0.5), float3(0.0, 0.4, 0.5) }, - { float3(0.4,0.4, 0.4), float3(-0.4, 0.2, -0.4) } + { float3(0.4,0.4, 0.4), float3(-0.4, 0.2, -0.4) }, + { float3(0.4,-0.4, 0.4), float3(-0.4, 0.2, -0.4) } }; // Metaball field radii of max influence - float radii[N_METABALLS] = { 0.45, 0.55, 0.45 }; + float radii[N_METABALLS] = { 0.45, 0.55, 0.45, 0.2 }; // Calculate animated metaball center positions. float tAnimate = CalculateAnimationInterpolant(elapsedTime, cycleDuration); @@ -82,7 +96,19 @@ void InitializeAnimatedMetaballs(out Metaball blobs[N_METABALLS], in float elaps void TestMetaballsIntersection(in Ray ray, out float tmin, out float tmax, inout Metaball blobs[N_METABALLS]) { tmin = INFINITY; - tmax = -INFINITY; + tmax = -INFINITY; + + for (UINT i = 0; i < N_METABALLS; i++) + { + float cur_thit, cur_tmax; + if (RaySolidSphereIntersectionTest(ray, cur_thit, cur_tmax, blobs[i].center, blobs[i].radius)) + { + tmin = min(cur_thit, tmin); + tmax = max(cur_tmax, tmax); + } + } + tmin = max(tmin, RayTMin()); + tmax = min(tmax, RayTCurrent()); } // TODO-3.4.2: Test if a ray with RayFlags and segment intersects metaball field. @@ -100,8 +126,46 @@ 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) { - thit = 0.0f; - attr.normal = float3(0.0f, 0.0f, 0.0f); + Metaball blobs[N_METABALLS]; + InitializeAnimatedMetaballs(blobs, elapsedTime, 10.0f); + + float tmin, tmax; + TestMetaballsIntersection(ray, tmin, tmax, blobs); + + UINT MAX_STEPS = 128; + float t = tmin; + float minTStep = (tmax - tmin) / MAX_STEPS; + UINT i = 0; + + while (i < MAX_STEPS) + { + float3 pos = ray.origin + t * ray.direction; + float potentials[N_METABALLS]; // All metaballs potentials + float totalPotential = 0; // Sum of all metaball field potentials. + + for (UINT j = 0; j < N_METABALLS; j++) + { + potentials[j] = CalculateMetaballPotential(pos, blobs[j]); + totalPotential += potentials[j]; + } + + float Threshold = 0.55f; + + // continue if totalPotential not cross the threshold + // found a shading point otherwise + if (totalPotential >= Threshold) + { + float3 normal = CalculateMetaballsNormal(pos, blobs); + if (is_a_valid_hit(ray, t, normal)) + { + thit = t; + attr.normal = normal; + return true; + } + } + t += minTStep; + i++; + } return false; }