diff --git a/README.md b/README.md index b0189d0..2fd4e54 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ **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) +* Joshua Nadel + * https://www.linkedin.com/in/joshua-nadel-379382136/, http://www.joshnadel.com/ +* Tested on: Windows 10, i7-6700HQ @ 2.60GHz 16GB, GTX 970M (Personal laptop) -### (TODO: Your README) +![](images/showcase.png) -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/showcase.gif) + +This project implements a GPU-parallelized raytracer using the DirectX Raytracing API. The raytracer can render triangle, box, sphere, and metaball primitives. It uses axis-aligned bounding boxes around primitives to avoid evaluating primitive collisions on rays that miss each object's bounding volume. It supports a phong shading model with a raytraced reflective component, atmospheric perspective fog, and raytraced shadows. + +![](images/raydepthfps.png) + +As the number of bounces each ray is allowed to make increases, the FPS of the raytracer decreases. The severity of this decrease lessens as more bounces are allowed. This is because after each bounce, some of the remaining rays miss and are no longer available for tracing. So, the cost of allowing rays to continue bouncing decreases as more and more exit the scene. \ No newline at end of file diff --git a/images/raydepthfps.png b/images/raydepthfps.png new file mode 100644 index 0000000..448411e Binary files /dev/null and b/images/raydepthfps.png differ diff --git a/images/showcase.gif b/images/showcase.gif new file mode 100644 index 0000000..f32291a Binary files /dev/null and b/images/showcase.gif differ diff --git a/images/showcase.png b/images/showcase.png new file mode 100644 index 0000000..436dae4 Binary files /dev/null and b/images/showcase.png differ diff --git a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli index c6ccebb..1549d4d 100644 --- a/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/AnalyticPrimitives.hlsli @@ -166,18 +166,31 @@ bool RaySolidSphereIntersectionTest(in Ray ray, out float thit, out float tmax, bool RayMultipleSpheresIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr) { // Define the spheres in local space (within the aabb) - float3 center = float3(-0.2, 0, -0.2); - float radius = 0.7f; - - thit = RayTCurrent(); - - float tmax; - if (RaySphereIntersectionTest(ray, thit, tmax, attr, center, radius)) - { - return true; + float3 centers[] = { float3(-0.2, 0, -0.2), float3(0, 0.4, 0.6), float3(-0.5, -0.3, -0.3) }; + float radii[] = { 0.7f, 0.3f, 0.45f }; + + thit = INFINITY; + bool hit = false; + + for (int i = 0; i < 3; i++) { + float3 center = centers[i]; + float radius = radii[i]; + + float thitNew = RayTCurrent(); + + float tmax; + ProceduralPrimitiveAttributes attrNew; + if (RaySphereIntersectionTest(ray, thitNew, tmax, attrNew, center, radius)) + { + if (thitNew < thit) { + thit = thitNew; + attr = attrNew; + } + 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..f878bd9 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-AccelerationStructure.cpp @@ -30,8 +30,13 @@ 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 = {D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES, geometryFlags}; + geometryDesc.Triangles.IndexBuffer = m_indexBuffer.resource->GetGPUVirtualAddress(); + geometryDesc.Triangles.IndexCount = m_indexBuffer.resource->GetDesc().Width / sizeof(Index); + geometryDesc.Triangles.VertexBuffer = { m_vertexBuffer.resource->GetGPUVirtualAddress(), sizeof(Vertex) }; + geometryDesc.Triangles.VertexCount = m_vertexBuffer.resource->GetDesc().Width / sizeof(Vertex); + geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R16_UINT; + geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; } { @@ -49,7 +54,10 @@ void DXProceduralProject::BuildGeometryDescsForBottomLevelAS(arrayGetGPUVirtualAddress() + i * sizeof(D3D12_RAYTRACING_AABB); + } } } @@ -68,7 +76,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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + bottomLevelInputs.Flags = buildFlags; + bottomLevelInputs.pGeometryDescs = geometryDescs.data(); + bottomLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + bottomLevelInputs.NumDescs = geometryDescs.size(); // 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,7 +120,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. if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -127,7 +140,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. @@ -179,7 +192,22 @@ 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;// BottomLevelASType::AABB * RayType::Count; + instanceDesc.AccelerationStructure = bottomLevelASaddresses[BottomLevelASType::AABB]; + + // Calculate transformation matrix. + const XMVECTOR vBasePosition = XMLoadFloat3(&XMFLOAT3(0, c_aabbWidth / 2.f, 0)); + + // Scale in XZ dimensions. + XMMATRIX mScale = XMMatrixScaling(1, 1, 1); + XMMATRIX mTranslation = XMMatrixTranslationFromVector(vBasePosition); + XMMATRIX mTransform = mScale * mTranslation; + // Store the transform in the instanceDesc + XMStoreFloat3x4(reinterpret_cast(instanceDesc.Transform), mTransform); } // Upload all these instances to the GPU, and make sure the resouce is set to instanceDescsResource. @@ -202,7 +230,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.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + topLevelInputs.Flags = buildFlags; + topLevelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + topLevelInputs.NumDescs = NUM_BLAS; D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO topLevelPrebuildInfo = {}; if (m_raytracingAPI == RaytracingAPI::FallbackLayer) @@ -216,7 +247,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); // Allocate space for the top-level AS. { @@ -231,7 +262,7 @@ AccelerationStructureBuffers DXProceduralProject::BuildTopLevelAS(AccelerationSt } // TODO-2.6: Allocate a UAV buffer for the actual top-level AS. - + AllocateUAVBuffer(device, topLevelPrebuildInfo.ResultDataMaxSizeInBytes, &topLevelAS); } // Note on Emulated GPU pointers (AKA Wrapped pointers) requirement in Fallback Layer: @@ -259,6 +290,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 +303,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 +315,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) @@ -302,7 +336,7 @@ 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{}; + 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. @@ -318,12 +352,14 @@ void DXProceduralProject::BuildAccelerationStructures() // TODO-2.6: Build the geometry descriptors. Hint: you filled in a function that does this. array, BottomLevelASType::Count> geometryDescs; - + BuildGeometryDescsForBottomLevelAS(geometryDescs); // TODO-2.6: For each bottom-level object (triangle, procedural), build a bottom-level AS. // Hint: you filled in a function that does this. AccelerationStructureBuffers bottomLevelAS[BottomLevelASType::Count]; - + for (int 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,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(); @@ -347,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. - + m_topLevelAS = topLevelAS.accelerationStructure; + for (UINT i = 0; i < BottomLevelASType::Count; i++) + { + m_bottomLevelAS[i] = bottomLevelAS[i].accelerationStructure; + } } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DoRaytracing.cpp index 03a8c58..57bb3df 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,13 @@ 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 = {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; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp index e3ff63c..64167d5 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-DynamicBuffers.cpp @@ -67,7 +67,7 @@ void DXProceduralProject::InitializeScene() m_up = XMVector3Normalize(XMVector3Cross(direction, right)); // Rotate camera around Y axis. - XMMATRIX rotate = XMMatrixRotationY(XMConvertToRadians(45.0f)); + XMMATRIX rotate = DirectX::XMMatrixRotationY(XMConvertToRadians(45.0f)); m_eye = XMVector3Transform(m_eye, rotate); m_up = XMVector3Transform(m_up, rotate); @@ -82,14 +82,14 @@ void DXProceduralProject::InitializeScene() XMFLOAT4 lightDiffuseColor; lightPosition = XMFLOAT4(0.0f, 18.0f, -20.0f, 0.0f); - m_sceneCB->lightPosition = XMLoadFloat4(&lightPosition); + m_sceneCB->lightPosition = DirectX::XMLoadFloat4(&lightPosition); lightAmbientColor = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f); - m_sceneCB->lightAmbientColor = XMLoadFloat4(&lightAmbientColor); + m_sceneCB->lightAmbientColor = DirectX::XMLoadFloat4(&lightAmbientColor); float d = 0.8f; lightDiffuseColor = XMFLOAT4(d, d, d, 1.0f); - m_sceneCB->lightDiffuseColor = XMLoadFloat4(&lightDiffuseColor); + m_sceneCB->lightDiffuseColor = DirectX::XMLoadFloat4(&lightDiffuseColor); } } @@ -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 instanceCount = IntersectionShaderType::TotalPrimitiveCount; + m_aabbPrimitiveAttributeBuffer.Create(device, instanceCount, frameCount, L"Scene AABB Primitive Attribute Buffer"); } // LOOKAT-2.1: Update camera matrices stored in m_sceneCB. @@ -121,10 +125,10 @@ void DXProceduralProject::UpdateCameraMatrices() m_sceneCB->cameraPosition = m_eye; float fovAngleY = 45.0f; - XMMATRIX view = XMMatrixLookAtLH(m_eye, m_at, m_up); - XMMATRIX proj = XMMatrixPerspectiveFovLH(XMConvertToRadians(fovAngleY), m_aspectRatio, 0.01f, 125.0f); + XMMATRIX view = DirectX::XMMatrixLookAtLH(m_eye, m_at, m_up); + XMMATRIX proj = DirectX::XMMatrixPerspectiveFovLH(XMConvertToRadians(fovAngleY), m_aspectRatio, 0.01f, 125.0f); XMMATRIX viewProj = view * proj; - m_sceneCB->projectionToWorld = XMMatrixInverse(nullptr, viewProj); + m_sceneCB->projectionToWorld = DirectX::XMMatrixInverse(nullptr, viewProj); } // TODO-2.1: Update the PrimitiveInstancePerFrameBuffer for every AABB stored in m_aabbPrimitiveAttributeBuffer[]. @@ -140,23 +144,23 @@ void DXProceduralProject::UpdateAABBPrimitiveAttributes(float animationTime) { auto frameIndex = m_deviceResources->GetCurrentFrameIndex(); - XMMATRIX mIdentity = XMMatrixIdentity(); + XMMATRIX mIdentity = DirectX::XMMatrixIdentity(); // Different scale matrices - XMMATRIX mScale15y = XMMatrixScaling(1, 1.5, 1); - XMMATRIX mScale15 = XMMatrixScaling(1.5, 1.5, 1.5); - XMMATRIX mScale2 = XMMatrixScaling(2, 2, 2); + XMMATRIX mScale15y = DirectX::XMMatrixScaling(1, 1.5, 1); + XMMATRIX mScale15 = DirectX::XMMatrixScaling(1.5, 1.5, 1.5); + XMMATRIX mScale2 = DirectX::XMMatrixScaling(2, 2, 2); // Rotation matrix that changes over time - XMMATRIX mRotation = XMMatrixRotationY(-2 * animationTime); + XMMATRIX mRotation = DirectX::XMMatrixRotationY(-2 * animationTime); auto SetTransformForAABB = [&](UINT primitiveIndex, XMMATRIX& mScale, XMMATRIX& mRotation) { XMVECTOR vTranslation = - 0.5f * (XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MinX)) - + XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MaxX))); // i.e middle of AABB. - XMMATRIX mTranslation = XMMatrixTranslationFromVector(vTranslation); + 0.5f * (DirectX::XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MinX)) + + DirectX::XMLoadFloat3(reinterpret_cast(&m_aabbs[primitiveIndex].MaxX))); // i.e middle of AABB. + XMMATRIX mTranslation = DirectX::XMMatrixTranslationFromVector(vTranslation); // TODO-2.1: Fill in this lambda function. // It should create a transform matrix that is equal to scale * rotation * translation. @@ -164,6 +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. + auto localToAS = mScale * mRotation * mTranslation; + m_aabbPrimitiveAttributeBuffer[primitiveIndex].localSpaceToBottomLevelAS = localToAS; + m_aabbPrimitiveAttributeBuffer[primitiveIndex].bottomLevelASToLocalSpace = DirectX::XMMatrixInverse(nullptr, localToAS); + }; UINT offset = 0; diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp index 9d93504..a7aae70 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-Geometry.cpp @@ -86,7 +86,8 @@ 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 + size.x + offsetIndex.x * stride.x, basePosition.y + size.y + offsetIndex.y * stride.y, basePosition.z + size.z + offsetIndex.z * stride.z}; return aabb; }; m_aabbs.resize(IntersectionShaderType::TotalPrimitiveCount); @@ -110,12 +111,13 @@ 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(), sizeof(m_aabbs[0]) * m_aabbs.size(), &m_aabbBuffer.resource, L"Axis Aligned Bounding Box Buffers"); } } // 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..60d7d61 100644 --- a/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp +++ b/src/D3D12RaytracingProceduralGeometry/DXR-HitGroup.cpp @@ -29,7 +29,23 @@ void DXProceduralProject::CreateHitGroupSubobjects(CD3D12_STATE_OBJECT_DESC* ray // TODO-2.3: AABB geometry hit groups. Very similar to triangles, except now you have to *also* loop over the primitive types. { + for (UINT intersectionType = 0; intersectionType < IntersectionShaderType::Count; intersectionType++) { + for (UINT rayType = 0; rayType < RayType::Count; rayType++) + { + auto hitGroup = raytracingPipeline->CreateSubobject(); + if (rayType == RayType::Radiance) + { + // We import the closest hit shader name + hitGroup->SetClosestHitShaderImport(c_closestHitShaderNames[GeometryType::AABB]); + } + hitGroup->SetIntersectionShaderImport(c_intersectionShaderNames[intersectionType]); + + // We tell the hitgroup that it should export into the correct shader hit group name, with the correct type + hitGroup->SetHitGroupExport(c_hitGroupNames_AABBGeometry[intersectionType][rayType]); + hitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE); + } + } } } @@ -54,6 +70,16 @@ void DXProceduralProject::CreateLocalRootSignatureSubobjects(CD3D12_STATE_OBJECT // TODO-2.3: AABB geometry hitgroup/local root signature association. // Very similar to triangles, except now one for each primitive type. { - + auto localRootSignature = raytracingPipeline->CreateSubobject(); + + // This is the AABB local root signature you already filled in before. + localRootSignature->SetRootSignature(m_raytracingLocalRootSignature[LocalRootSignature::Type::AABB].Get()); + + // Shader association + auto rootSignatureAssociation = raytracingPipeline->CreateSubobject(); + rootSignatureAssociation->SetSubobjectToAssociate(*localRootSignature); + for (UINT intersectionType = 0; intersectionType < IntersectionShaderType::Count; intersectionType++) { + rootSignatureAssociation->AddExports(c_hitGroupNames_AABBGeometry[intersectionType]); + } } } \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp b/src/D3D12RaytracingProceduralGeometry/DXR-RootSignature.cpp index 2dff8b5..d79bc51 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,6 +39,11 @@ 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::VertexBuffers].InitAsDescriptorTable(1, &ranges[1]); + rootParameters[GlobalRootSignature::Slot::AccelerationStructure].InitAsShaderResourceView(0); + rootParameters[GlobalRootSignature::Slot::SceneConstant].InitAsConstantBufferView(0); + rootParameters[GlobalRootSignature::Slot::AABBattributeBuffer].InitAsShaderResourceView(3); // 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); @@ -67,7 +72,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(PrimitiveConstantBuffer), 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..612d10e 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,14 @@ 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 +104,20 @@ void DXProceduralProject::BuildShaderTables() // TODO-2.7: Miss shader table. Very similar to the RayGen table except now we push_back() 2 shader records // 1 for the radiance ray, 1 for the shadow ray. Don't forget to call DebugPrint() on the table for your sanity! { - + UINT numShaderRecords = 2; + UINT shaderRecordSize = shaderIDSize; // No root arguments + + // The RayGen shader table contains a single ShaderRecord: the one single raygen shader! + ShaderTable missShaderTable(device, numShaderRecords, shaderRecordSize, L"RayMissShaderTable"); + + // Push back the shader record, which does not need any root signatures. + missShaderTable.push_back(ShaderRecord(missShaderIDs[0], shaderRecordSize, nullptr, 0)); + missShaderTable.push_back(ShaderRecord(missShaderIDs[1], shaderRecordSize, nullptr, 0)); + + // Save the uploaded resource (remember that the uploaded resource is created when we call Allocate() on a GpuUploadBuffer + missShaderTable.DebugPrint(shaderIdToStringMap); + m_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..45feb1a 100644 --- a/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl +++ b/src/D3D12RaytracingProceduralGeometry/Raytracing.hlsl @@ -41,7 +41,8 @@ ConstantBuffer l_aabbCB: register(b2); // other // Remember to clamp the dot product term! float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal) { - return 0.0f; + float clampdot = max(0, dot(incidentLightRay, normal)); + return clampdot; } // TODO-3.6: Phong lighting specular component. @@ -49,9 +50,13 @@ float CalculateDiffuseCoefficient(in float3 incidentLightRay, in float3 normal) // HINT: Consider using built-in DirectX functions to find the reflected ray. Remember that a reflected ray is reflected // with respect to the normal of the hit position. // Remember to normalize the reflected ray, and to clamp the dot product term -float4 CalculateSpecularCoefficient(in float3 incidentLightRay, in float3 normal, in float specularPower) +float CalculateSpecularCoefficient(in float3 incidentLightRay, in float3 normal, in float specularPower) { - return float4(0.0f, 0.0f, 0.0f, 0.0f); + float3 reflectedRay = normalize(reflect(incidentLightRay, normal)); + if (dot(normal, -incidentLightRay) < 0) return 0; + float clampdot = max(dot(reflectedRay, -incidentLightRay), 0); + float coefficient = pow(clampdot, specularPower); + return coefficient; } // TODO-3.6: Phong lighting model = ambient + diffuse + specular components. @@ -76,7 +81,18 @@ 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; + float3 lightDirection = normalize(g_sceneCB.lightPosition - HitWorldPosition()); + + float4 diffuseColor = diffuseCoef * albedo * CalculateDiffuseCoefficient(lightDirection, normal); + if (isInShadow) { + diffuseColor *= InShadowRadiance; + } + float4 specularColor = float4(0, 0, 0, 0); + if (!isInShadow) { + specularColor = float4(1, 1, 1, 1) * specularCoef * CalculateSpecularCoefficient(-lightDirection, normal, specularPower); + } + + return ambientColor + diffuseColor + specularColor; } //*************************************************************************** @@ -135,7 +151,44 @@ float4 TraceRadianceRay(in Ray ray, in UINT currentRayRecursionDepth) // Hint 2: remember what the ShadowRay payload looks like. See RaytracingHlslCompat.h bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth) { - return false; + if (currentRayRecursionDepth >= MAX_RAY_RECURSION_DEPTH) + { + return false; + } + // Set the ray's extents. + RayDesc rayDesc; + rayDesc.Origin = ray.origin; + rayDesc.Direction = ray.direction; + // Set TMin to a zero value to avoid aliasing artifacts along contact areas. + // Note: make sure to enable face culling so as to avoid surface face fighting. + rayDesc.TMin = 0; + rayDesc.TMax = 10000; + + ShadowRayPayload rayPayload = { true }; + + // TraceRay() is a built-in DXR function. Lookup its documentation to see what it does. + // To understand how a ray "finds out" what type of object it hit and therefore call the correct shader hitgroup, it indexes into the shader + // table as follows: + // hitgroup to choose = ptr to shader table + size of a shader record + (ray contribution + (geometry index * geometry stride) + instance contribution) + // * `ray contribution` and `geometry stride` are given here. + // * `ptr to shader table` + `size of a shader record` are given in BuildShaderTables() in DXR-ShaderTable.cpp + // * `geometry index` is implicitly given when you build your bottom-level AS in BuildGeometryDescsForBottomLevelAS() in DXR-AccelerationStructure.cpp + // * `instance contribution` is given in BuildBottomLevelASInstanceDescs() in DXR-AccelerationStructure.cpp + // Essentially, when a ray hits a Geometry in a Bottom-Level AS Instance contained within a Top Level AS, it does the following: + // (1) Identify which Instance of a BLAS it hit (Triangle or AABB) --> this gives it the `instance contribution` + // (2) Identify which Geometry *inside* this Instance it hit + // (If hit Triangle instance, then Triangle geom!, if hit AABB instance, then might be Sphere, Metaball, Fractal, etc..) + // --> this gives it the `geometry index` + // (3) Identify what type of Ray it is --> this is given right here, say a Radiance ray + // (4) Combines all of these inputs to index into the correct shader into the hitgroup shader table. + TraceRay(g_scene, + RAY_FLAG_CULL_BACK_FACING_TRIANGLES | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE, + TraceRayParameters::InstanceMask, + TraceRayParameters::HitGroup::Offset[RayType::Shadow], + TraceRayParameters::HitGroup::GeometryStride, + TraceRayParameters::MissShader::Offset[RayType::Shadow], + rayDesc, rayPayload); + return rayPayload.hit; } //*************************************************************************** @@ -149,9 +202,10 @@ bool TraceShadowRayAndReportIfHit(in Ray ray, in UINT currentRayRecursionDepth) [shader("raygeneration")] void MyRaygenShader() { - + Ray r = GenerateCameraRay(DispatchRaysIndex().xy, g_sceneCB.cameraPosition, g_sceneCB.projectionToWorld); + float4 color = TraceRadianceRay(r, 0); // Write the color to the render target - g_renderTarget[DispatchRaysIndex().xy] = float4(0.0f, 0.0f, 0.0f, 0.0f); + g_renderTarget[DispatchRaysIndex().xy] = color; } //*************************************************************************** @@ -210,7 +264,8 @@ 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. - rayPayload.color = color; + float alpha = max(min(RayTCurrent() / 100, 1), 0); + rayPayload.color = lerp(color, BackgroundColor, alpha); } // TODO: Write the closest hit shader for a procedural geometry. @@ -228,6 +283,42 @@ void MyClosestHitShader_Triangle(inout RayPayload rayPayload, in BuiltInTriangle void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitiveAttributes attr) { + // Retrieve corresponding vertex normals for the triangle vertices. + float3 primNormal = attr.normal; + + // This is the intersection point on the primitive. + float3 hitPosition = HitWorldPosition(); + + // Trace a ray from the hit position towards the single light source we have. If on our way to the light we hit something, then we have a shadow! + Ray shadowRay = { hitPosition, normalize(g_sceneCB.lightPosition.xyz - hitPosition) }; + bool shadowRayHit = TraceShadowRayAndReportIfHit(shadowRay, rayPayload.recursionDepth); + + // Reflected component ray. + float4 reflectedColor = float4(0, 0, 0, 0); + if (l_materialCB.reflectanceCoef > 0.001) + { + // Trace a reflection ray from the intersection points using Snell's law. The reflect() HLSL built-in function does this for you! + // See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-intrinsic-functions + Ray reflectionRay = { hitPosition, reflect(WorldRayDirection(), primNormal) }; + float4 reflectionColor = TraceRadianceRay(reflectionRay, rayPayload.recursionDepth); + + float3 fresnelR = FresnelReflectanceSchlick(WorldRayDirection(), primNormal, l_materialCB.albedo.xyz); + reflectedColor = l_materialCB.reflectanceCoef * float4(fresnelR, 1) * reflectionColor; + } + + // Calculate final color. + float4 phongColor = CalculatePhongLighting(l_materialCB.albedo, primNormal, shadowRayHit, l_materialCB.diffuseCoef, l_materialCB.specularCoef, l_materialCB.specularPower); + float4 color = (phongColor + reflectedColor); + + // TODO: Apply a visibility falloff. + // If the ray is very very very far away, tends to sample the background color rather than the color you computed. + // This is to mimic some form of distance fog where farther objects appear to blend with the background. + // 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. + + float alpha = max(min(RayTCurrent() / 100, 1), 0); + rayPayload.color = lerp(color, BackgroundColor, alpha); } //*************************************************************************** @@ -240,14 +331,14 @@ void MyClosestHitShader_AABB(inout RayPayload rayPayload, in ProceduralPrimitive [shader("miss")] void MyMissShader(inout RayPayload rayPayload) { - + 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 +390,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/RaytracingHlslCompat.h b/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h index 6e10f0d..42f064b 100644 --- a/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h +++ b/src/D3D12RaytracingProceduralGeometry/RaytracingHlslCompat.h @@ -28,7 +28,7 @@ typedef UINT16 Index; #define N_FRACTAL_ITERATIONS 5 // = <1,...> -#define MAX_RAY_RECURSION_DEPTH 3 // ~ primary rays + reflections + shadow rays from reflected geometry. +#define MAX_RAY_RECURSION_DEPTH 3 // ~ primary rays + reflections + shadow rays from reflected geometry. /**************** Scene *****************/ static const XMFLOAT4 ChromiumReflectance = XMFLOAT4(0.549f, 0.556f, 0.554f, 1.0f); diff --git a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli index 94bf5cc..57619ae 100644 --- a/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/RaytracingShaderHelper.hlsli @@ -59,7 +59,7 @@ bool is_a_valid_hit(in Ray ray, in float thit, in float3 hitSurfaceNormal) // TODO-3.4.2: Return a cycling <0 -> 1 -> 0> animation interpolant // Given the total elapsed time, and the duration of a cycle, do the following: // (1) Find out how far in the current cycle the time is. E.g if total time is 5 seconds, a cycle is 2 seconds, then we are 50% through the current cycle. -// Call this valye `interpolant` +// Call this value `interpolant` // (2) We want the interpolant to cycle from 0 to 1 to 0 ALL in one single cycle. // So if we are < 50% through the cycle, we want to make sure that this interpolant hits 1 at 50%. // if we are > 50% through the cycle, we want to make sure that this interpolant hits 0 at 100%. @@ -68,7 +68,14 @@ 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 interpolant = (elapsedTime % cycleDuration) / cycleDuration; + if (interpolant < 0.5) { + interpolant = interpolant * 2; + } + else { + interpolant = 2 - 2 * interpolant; + } + return smoothstep(0, 1, interpolant); } // Load three 2-byte indices from a ByteAddressBuffer. @@ -129,9 +136,14 @@ 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) { + uint3 dimensions = DispatchRaysDimensions(); + float2 normalized = float2(((index.x + 0.5) / dimensions.x) * 2.0 - 1.0, 1.0 - ((index.y + 0.5) / dimensions.y) * 2.0); + float4 pixelLoc = mul(float4(normalized, 0, 1), projectionToWorld); + pixelLoc = pixelLoc / pixelLoc.w; + Ray ray; - ray.origin = float3(0.0f, 0.0f, 0.0f); - ray.direction = normalize(float3(0.0f, 0.0f, 0.0f)); + ray.origin = cameraPosition; + ray.direction = normalize(pixelLoc.xyz - cameraPosition); return ray; } @@ -141,7 +153,11 @@ 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 n1 = 1; + float n2 = 0.1; + float R0 = pow((n1 - n2) / (n1 + n2), 2); + float coefficient = R0 + (1 - R0) * pow(1 - abs(dot(I, N)), 5); + return f0 * coefficient; } #endif // RAYTRACINGSHADERHELPER_H \ No newline at end of file diff --git a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli index 31a9444..8a5e978 100644 --- a/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli +++ b/src/D3D12RaytracingProceduralGeometry/VolumetricPrimitives.hlsli @@ -22,6 +22,11 @@ struct Metaball // of the distance from the center to the radius. float CalculateMetaballPotential(in float3 position, in Metaball blob) { + float dist = length(position - blob.center); + if (dist < blob.radius) { + float ratio = 1 - (dist / blob.radius); + return 6 * pow(ratio, 5) - 15 * pow(ratio, 4) + 10 * pow(ratio, 3); + } return 0.0f; } @@ -83,6 +88,21 @@ void TestMetaballsIntersection(in Ray ray, out float tmin, out float tmax, inout { tmin = INFINITY; tmax = -INFINITY; + + for (int i = 0; i < N_METABALLS; i++) { + float3 center = blobs[i].center; + float radius = blobs[i].radius; + + float thit; + + float newtmax; + ProceduralPrimitiveAttributes attr; + if (RaySphereIntersectionTest(ray, thit, newtmax, attr, center, radius)) + { + if (thit < tmin) tmin = thit; + if (newtmax > tmax) tmax = newtmax; + } + } } // TODO-3.4.2: Test if a ray with RayFlags and segment intersects metaball field. @@ -100,6 +120,25 @@ 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) { + Metaball blobs[N_METABALLS]; + InitializeAnimatedMetaballs(blobs, elapsedTime, 10); + float tmin, tmax; + TestMetaballsIntersection(ray, tmin, tmax, blobs); + if (tmax < tmin) { + return false; + } + float inc = (tmax - tmin) / 128.f; + for (int i = 0; i < 128; i++) { + thit = tmin + inc * i; + float3 p = ray.origin + ray.direction * thit; + float potential = CalculateMetaballsPotential(p, blobs); + if (potential > 0.3) { + attr.normal = CalculateMetaballsNormal(p, blobs); + if (is_a_valid_hit(ray, thit, attr.normal)) { + return true; + } + } + } thit = 0.0f; attr.normal = float3(0.0f, 0.0f, 0.0f); return false;