diff --git a/Editor/MaterialWindow.cpp b/Editor/MaterialWindow.cpp index 7ed200b35..c41a407f8 100644 --- a/Editor/MaterialWindow.cpp +++ b/Editor/MaterialWindow.cpp @@ -9,7 +9,7 @@ void MaterialWindow::Create(EditorComponent* _editor) { editor = _editor; wi::gui::Window::Create(ICON_MATERIAL " Material", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); - SetSize(XMFLOAT2(300, 1340)); + SetSize(XMFLOAT2(300, 1360)); closeButton.SetTooltip("Delete MaterialComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -97,6 +97,17 @@ void MaterialWindow::Create(EditorComponent* _editor) }); AddWidget(&occlusionSecondaryCheckBox); + vertexAOCheckBox.Create("Vertex AO: "); + vertexAOCheckBox.SetTooltip("If enabled, vertex ambient occlusion will be enabled (if it exists)"); + vertexAOCheckBox.SetPos(XMFLOAT2(x, y += step)); + vertexAOCheckBox.SetSize(XMFLOAT2(hei, hei)); + vertexAOCheckBox.OnClick([&](wi::gui::EventArgs args) { + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); + if (material != nullptr) + material->SetVertexAODisabled(!args.bValue); + }); + AddWidget(&vertexAOCheckBox); + windCheckBox.Create("Wind: "); windCheckBox.SetTooltip("If enabled, vertex wind weights will affect how much wind offset affects the subset."); windCheckBox.SetPos(XMFLOAT2(x, y += step)); @@ -772,6 +783,7 @@ void MaterialWindow::SetEntity(Entity entity) specularGlossinessCheckBox.SetCheck(material->IsUsingSpecularGlossinessWorkflow()); occlusionPrimaryCheckBox.SetCheck(material->IsOcclusionEnabled_Primary()); occlusionSecondaryCheckBox.SetCheck(material->IsOcclusionEnabled_Secondary()); + vertexAOCheckBox.SetCheck(!material->IsVertexAODisabled()); windCheckBox.SetCheck(material->IsUsingWind()); doubleSidedCheckBox.SetCheck(material->IsDoubleSided()); outlineCheckBox.SetCheck(material->IsOutlineEnabled()); @@ -942,6 +954,7 @@ void MaterialWindow::ResizeLayout() add_right(specularGlossinessCheckBox); add_right(occlusionPrimaryCheckBox); add_right(occlusionSecondaryCheckBox); + add_right(vertexAOCheckBox); add_right(windCheckBox); add_right(doubleSidedCheckBox); add_right(outlineCheckBox); diff --git a/Editor/MaterialWindow.h b/Editor/MaterialWindow.h index 1099569fa..419099f36 100644 --- a/Editor/MaterialWindow.h +++ b/Editor/MaterialWindow.h @@ -17,6 +17,7 @@ public: wi::gui::CheckBox specularGlossinessCheckBox; wi::gui::CheckBox occlusionPrimaryCheckBox; wi::gui::CheckBox occlusionSecondaryCheckBox; + wi::gui::CheckBox vertexAOCheckBox; wi::gui::CheckBox windCheckBox; wi::gui::CheckBox doubleSidedCheckBox; wi::gui::CheckBox outlineCheckBox; diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 1a75307fc..859e8823b 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -649,7 +649,6 @@ void MeshWindow::Create(EditorComponent* _editor) AddWidget(&exportHeaderButton); - subsetMaterialComboBox.Create("Material: "); subsetMaterialComboBox.SetSize(XMFLOAT2(wid, hei)); subsetMaterialComboBox.SetPos(XMFLOAT2(x, y += step)); diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp index 63eb12c40..2dd043a97 100644 --- a/Editor/ObjectWindow.cpp +++ b/Editor/ObjectWindow.cpp @@ -257,7 +257,7 @@ void ObjectWindow::Create(EditorComponent* _editor) editor = _editor; wi::gui::Window::Create(ICON_OBJECT " Object", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); - SetSize(XMFLOAT2(670, 740)); + SetSize(XMFLOAT2(670, 820)); closeButton.SetTooltip("Delete ObjectComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -648,6 +648,216 @@ void ObjectWindow::Create(EditorComponent* _editor) }); AddWidget(&clearLightmapButton); + + y += step; + + vertexAOButton.Create("Vertex AO"); + vertexAOButton.SetTooltip("Create or delete per vertex Ambient Occlusion."); + vertexAOButton.SetPos(XMFLOAT2(x, y += step)); + vertexAOButton.SetSize(XMFLOAT2(wid, hei)); + vertexAOButton.SetLocalizationEnabled(false); + vertexAOButton.OnClick([&](wi::gui::EventArgs args) { + const Scene& scene = editor->GetCurrentScene(); + + // Build BVHs for everything selected: + if (!deleteAOMode) + { + wi::Timer timer; + for (auto& x : this->editor->translator.selected) + { + ObjectComponent* objectcomponent = scene.objects.GetComponent(x.entity); + if (objectcomponent != nullptr) + { + const size_t objectcomponentIndex = scene.objects.GetIndex(x.entity); + MeshComponent* meshcomponent = scene.meshes.GetComponent(objectcomponent->meshID); + if (meshcomponent == nullptr) + continue; + if (!meshcomponent->bvh.IsValid()) + { + meshcomponent->BuildBVH(); + } + } + } + wi::backlog::post("Building BVHs for vertex AO took " + wi::helper::GetTimerDurationText((float)timer.elapsed_seconds())); + } + + for (auto& x : this->editor->translator.selected) + { + ObjectComponent* objectcomponent = scene.objects.GetComponent(x.entity); + if (objectcomponent != nullptr) + { + if (deleteAOMode) + { + objectcomponent->vertex_ao.clear(); + } + else + { + const MeshComponent* meshcomponent = scene.meshes.GetComponent(objectcomponent->meshID); + if (meshcomponent == nullptr) + continue; + if (meshcomponent->vertex_normals.size() != meshcomponent->vertex_positions.size()) + continue; + wi::Timer timer; + using namespace wi::primitive; + objectcomponent->vertex_ao.resize(meshcomponent->vertex_positions.size()); + const size_t objectcomponentIndex = scene.objects.GetIndex(x.entity); + + uint32_t groupSizePerCore = wi::jobsystem::DispatchGroupCount((uint32_t)objectcomponent->vertex_ao.size(), wi::jobsystem::GetThreadCount()); + + wi::jobsystem::context ctx; + wi::jobsystem::Dispatch(ctx, (uint32_t)objectcomponent->vertex_ao.size(), groupSizePerCore, [&](wi::jobsystem::JobArgs args) { + XMFLOAT3 position = meshcomponent->vertex_positions[args.jobIndex]; + XMFLOAT3 normal = meshcomponent->vertex_normals[args.jobIndex]; + const XMMATRIX W = XMLoadFloat4x4(&scene.matrix_objects[objectcomponentIndex]); + XMStoreFloat3(&position, XMVector3Transform(XMLoadFloat3(&position), W)); + XMStoreFloat3(&normal, XMVector3Normalize(XMVector3TransformNormal(XMLoadFloat3(&normal), XMMatrixTranspose(XMMatrixInverse(nullptr, W))))); + const XMMATRIX TBN = wi::math::GetTangentSpace(normal); + float accum = 0; + const uint32_t samplecount = (uint32_t)vertexAORayCountSlider.GetValue(); + const float raylength = vertexAORayLengthSlider.GetValue(); + const XMVECTOR rayOrigin = XMLoadFloat3(&position); + for (uint32_t sam = 0; sam < samplecount; ++sam) + { + XMFLOAT2 hamm = wi::math::Hammersley2D(sam, samplecount); + XMFLOAT3 hemi = wi::math::HemispherePoint_Cos(hamm.x, hamm.y); + XMVECTOR rayDirection = XMLoadFloat3(&hemi); + rayDirection = XMVector3TransformNormal(rayDirection, TBN); + rayDirection = XMVector3Normalize(rayDirection); + XMStoreFloat3(&hemi, rayDirection); + + wi::primitive::Ray ray(position, hemi, 0.0001f, raylength); + + bool hit = false; + for (size_t objectIndex = 0; (objectIndex < scene.aabb_objects.size()) && !hit; ++objectIndex) + { + const AABB& aabb = scene.aabb_objects[objectIndex]; + if (!ray.intersects(aabb)) + continue; + + const ObjectComponent& object = scene.objects[objectIndex]; + if (object.meshID == INVALID_ENTITY) + continue; + + const MeshComponent* mesh = scene.meshes.GetComponent(object.meshID); + if (mesh == nullptr) + continue; + + const Entity entity = scene.objects.GetEntity(objectIndex); + const XMMATRIX objectMat = XMLoadFloat4x4(&scene.matrix_objects[objectIndex]); + const XMMATRIX objectMatPrev = XMLoadFloat4x4(&scene.matrix_objects_prev[objectIndex]); + const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, objectMat); + const XMVECTOR rayOrigin_local = XMVector3Transform(rayOrigin, objectMat_Inverse); + const XMVECTOR rayDirection_local = XMVector3Normalize(XMVector3TransformNormal(rayDirection, objectMat_Inverse)); + + auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) + { + const uint32_t i0 = mesh->indices[indexOffset + triangleIndex * 3 + 0]; + const uint32_t i1 = mesh->indices[indexOffset + triangleIndex * 3 + 1]; + const uint32_t i2 = mesh->indices[indexOffset + triangleIndex * 3 + 2]; + + const XMVECTOR p0 = XMLoadFloat3(&mesh->vertex_positions[i0]); + const XMVECTOR p1 = XMLoadFloat3(&mesh->vertex_positions[i1]); + const XMVECTOR p2 = XMLoadFloat3(&mesh->vertex_positions[i2]); + + float distance; + XMFLOAT2 bary; + if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary, ray.TMin, ray.TMax)) + return true; + return false; + }; + + if (mesh->bvh.IsValid()) + { + Ray ray_local = Ray(rayOrigin_local, rayDirection_local); + + hit = mesh->bvh.IntersectsFirst(ray_local, [&](uint32_t index) { + const uint32_t userdata = mesh->bvh_leaf_aabbs[index].userdata; + const uint32_t triangleIndex = userdata & 0xFFFFFF; + const uint32_t subsetIndex = userdata >> 24u; + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + return false; + const MaterialComponent* material = scene.materials.GetComponent(subset.materialID); + if (material != nullptr && material->GetBlendMode() != wi::enums::BLENDMODE_OPAQUE) + return false; + const uint32_t indexOffset = subset.indexOffset; + if (intersect_triangle(subsetIndex, indexOffset, triangleIndex)) + return true; + return false; + }); + } + else + { + // Brute-force intersection test: + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; (subsetIndex < last_subset) && !hit; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.indexCount == 0) + continue; + const MaterialComponent* material = scene.materials.GetComponent(subset.materialID); + if (material != nullptr && material->GetBlendMode() != wi::enums::BLENDMODE_OPAQUE) + continue; + const uint32_t indexOffset = subset.indexOffset; + const uint32_t triangleCount = subset.indexCount / 3; + + for (uint32_t triangleIndex = 0; (triangleIndex < triangleCount) && !hit; ++triangleIndex) + { + hit |= intersect_triangle(subsetIndex, indexOffset, triangleIndex); + } + } + } + } + + if (!hit) + accum += 1.0f; + } + accum /= float(samplecount); + objectcomponent->vertex_ao[args.jobIndex] = uint8_t(accum * 255); + }); + wi::jobsystem::Wait(ctx); + wi::backlog::post("Vertex AO baking took " + wi::helper::GetTimerDurationText((float)timer.elapsed_seconds())); + } + objectcomponent->CreateRenderData(); + } + } + + // Delete BVHs that are not really needed any more: + for (auto& x : this->editor->translator.selected) + { + ObjectComponent* objectcomponent = scene.objects.GetComponent(x.entity); + if (objectcomponent != nullptr) + { + const size_t objectcomponentIndex = scene.objects.GetIndex(x.entity); + MeshComponent* meshcomponent = scene.meshes.GetComponent(objectcomponent->meshID); + if (meshcomponent == nullptr) + continue; + if (!meshcomponent->IsBVHEnabled()) + { + meshcomponent->bvh = {}; + meshcomponent->bvh_leaf_aabbs = {}; + } + } + } + + SetEntity(entity); + }); + AddWidget(&vertexAOButton); + + vertexAORayCountSlider.Create(8, 1024, 256, 1024 - 8, "Ray count: "); + vertexAORayCountSlider.SetTooltip("Set the ray count per vertex for vertex AO baking.\nThe larger this value is, the longer the baking will take, and the better the quality will get."); + vertexAORayCountSlider.SetSize(XMFLOAT2(wid, hei)); + vertexAORayCountSlider.SetPos(XMFLOAT2(x, y += step)); + AddWidget(&vertexAORayCountSlider); + + vertexAORayLengthSlider.Create(0, 1000, 100, 1000, "Ray length: "); + vertexAORayLengthSlider.SetTooltip("Set the ray length for vertex AO baking.\nSmaller ray length can reduce ambient occlusion from larger features."); + vertexAORayLengthSlider.SetSize(XMFLOAT2(wid, hei)); + vertexAORayLengthSlider.SetPos(XMFLOAT2(x, y += step)); + AddWidget(&vertexAORayLengthSlider); + y += step; colorComboBox.Create("Color picker mode: "); @@ -698,6 +908,20 @@ void ObjectWindow::SetEntity(Entity entity) if (object == nullptr) entity = INVALID_ENTITY; + if (object != nullptr) + { + if (object->vertex_ao.empty()) + { + vertexAOButton.SetText("Compute Vertex AO"); + deleteAOMode = false; + } + else + { + vertexAOButton.SetText("Delete Vertex AO"); + deleteAOMode = true; + } + } + if (this->entity == entity) return; @@ -827,4 +1051,9 @@ void ObjectWindow::ResizeLayout() add(stopLightmapGenButton); add(clearLightmapButton); + y += jump; + add_fullwidth(vertexAOButton); + add(vertexAORayCountSlider); + add(vertexAORayLengthSlider); + } diff --git a/Editor/ObjectWindow.h b/Editor/ObjectWindow.h index 224a29856..d82916c2f 100644 --- a/Editor/ObjectWindow.h +++ b/Editor/ObjectWindow.h @@ -33,6 +33,11 @@ public: wi::gui::Button stopLightmapGenButton; wi::gui::Button clearLightmapButton; + wi::gui::Button vertexAOButton; + bool deleteAOMode = false; + wi::gui::Slider vertexAORayCountSlider; + wi::gui::Slider vertexAORayLengthSlider; + void ResizeLayout() override; }; diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index ceb1edd02..b9ffb2ae0 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -72,6 +72,7 @@ static const uint SHADERMATERIAL_OPTION_BIT_DOUBLE_SIDED = 1 << 7; static const uint SHADERMATERIAL_OPTION_BIT_TRANSPARENT = 1 << 8; static const uint SHADERMATERIAL_OPTION_BIT_ADDITIVE = 1 << 9; static const uint SHADERMATERIAL_OPTION_BIT_UNLIT = 1 << 10; +static const uint SHADERMATERIAL_OPTION_BIT_USE_VERTEXAO = 1 << 11; // Same as MaterialComponent::TEXTURESLOT enum TEXTURESLOT @@ -397,6 +398,7 @@ struct ShaderMaterial #endif // __cplusplus inline bool IsUsingVertexColors() { return options & SHADERMATERIAL_OPTION_BIT_USE_VERTEXCOLORS; } + inline bool IsUsingVertexAO() { return options & SHADERMATERIAL_OPTION_BIT_USE_VERTEXAO; } inline bool IsUsingSpecularGlossinessWorkflow() { return options & SHADERMATERIAL_OPTION_BIT_SPECULARGLOSSINESS_WORKFLOW; } inline bool IsOcclusionEnabled_Primary() { return options & SHADERMATERIAL_OPTION_BIT_OCCLUSION_PRIMARY; } inline bool IsOcclusionEnabled_Secondary() { return options & SHADERMATERIAL_OPTION_BIT_OCCLUSION_SECONDARY; } @@ -477,12 +479,10 @@ struct ShaderGeometry ib = -1; vb_pos_wind = -1; vb_uvs = -1; - vb_nor = -1; vb_tan = -1; vb_col = -1; vb_atl = -1; - vb_pre = -1; materialIndex = 0; meshletOffset = 0; @@ -568,6 +568,11 @@ struct ShaderMeshInstance float3 center; float radius; + int vb_ao; + int padding0; + int padding1; + int padding2; + ShaderTransform transform; ShaderTransform transformInverseTranspose; // This correctly handles non uniform scaling for normals ShaderTransform transformPrev; @@ -588,6 +593,7 @@ struct ShaderMeshInstance fadeDistance = 0; center = float3(0, 0, 0); radius = 0; + vb_ao = -1; transform.init(); transformInverseTranspose.init(); transformPrev.init(); diff --git a/WickedEngine/shaders/envMapGS_emulation.hlsl b/WickedEngine/shaders/envMapGS_emulation.hlsl index 1cb48f279..69edda0f0 100644 --- a/WickedEngine/shaders/envMapGS_emulation.hlsl +++ b/WickedEngine/shaders/envMapGS_emulation.hlsl @@ -10,6 +10,7 @@ struct GSInput min16float4 color : COLOR; min16float4 tan : TANGENT; min16float3 nor : NORMAL; + min16float ao : AMBIENT_OCCLUSION; min16float2 atl : ATLAS; float3 pos3D : WORLDPOSITION; uint RTIndex : RTINDEX; @@ -23,6 +24,7 @@ struct GSOutput min16float4 color : COLOR; min16float4 tan : TANGENT; min16float3 nor : NORMAL; + min16float ao : AMBIENT_OCCLUSION; min16float2 atl : ATLAS; float3 pos3D : WORLDPOSITION; uint RTIndex : SV_RenderTargetArrayIndex; @@ -43,6 +45,7 @@ void main( element.uvsets = input[i].uvsets; element.atl = input[i].atl; element.nor = input[i].nor; + element.ao = input[i].ao; element.tan = input[i].tan; element.pos3D = input[i].pos3D; element.RTIndex = input[i].RTIndex; diff --git a/WickedEngine/shaders/envMapPS.hlsl b/WickedEngine/shaders/envMapPS.hlsl index 505fa7a04..100ab6645 100644 --- a/WickedEngine/shaders/envMapPS.hlsl +++ b/WickedEngine/shaders/envMapPS.hlsl @@ -9,6 +9,7 @@ #define OBJECTSHADER_USE_COLOR #define OBJECTSHADER_USE_NORMAL #define OBJECTSHADER_USE_TANGENT +#define OBJECTSHADER_USE_AO #define OBJECTSHADER_USE_POSITION3D #define OBJECTSHADER_USE_EMISSIVE #define OBJECTSHADER_USE_INSTANCEINDEX diff --git a/WickedEngine/shaders/envMapVS.hlsl b/WickedEngine/shaders/envMapVS.hlsl index d49e4481e..bca6f3426 100644 --- a/WickedEngine/shaders/envMapVS.hlsl +++ b/WickedEngine/shaders/envMapVS.hlsl @@ -9,6 +9,7 @@ #define OBJECTSHADER_USE_COLOR #define OBJECTSHADER_USE_NORMAL #define OBJECTSHADER_USE_TANGENT +#define OBJECTSHADER_USE_AO #define OBJECTSHADER_USE_POSITION3D #define OBJECTSHADER_USE_EMISSIVE #define OBJECTSHADER_USE_INSTANCEINDEX diff --git a/WickedEngine/shaders/objectHF.hlsli b/WickedEngine/shaders/objectHF.hlsli index 2c8ef4adb..10533af25 100644 --- a/WickedEngine/shaders/objectHF.hlsli +++ b/WickedEngine/shaders/objectHF.hlsli @@ -76,6 +76,7 @@ inline ShaderMaterial GetMaterial() //#define OBJECTSHADER_USE_UVSETS - shader will sample textures with uv sets //#define OBJECTSHADER_USE_ATLAS - shader will use atlas //#define OBJECTSHADER_USE_NORMAL - shader will use normals +//#define OBJECTSHADER_USE_AO - shader will use ambient occlusion //#define OBJECTSHADER_USE_TANGENT - shader will use tangents, normal mapping //#define OBJECTSHADER_USE_POSITION3D - shader will use world space positions //#define OBJECTSHADER_USE_EMISSIVE - shader will use emissive @@ -110,6 +111,7 @@ inline ShaderMaterial GetMaterial() #define OBJECTSHADER_USE_ATLAS #define OBJECTSHADER_USE_COLOR #define OBJECTSHADER_USE_NORMAL +#define OBJECTSHADER_USE_AO #define OBJECTSHADER_USE_TANGENT #define OBJECTSHADER_USE_POSITION3D #define OBJECTSHADER_USE_EMISSIVE @@ -185,6 +187,14 @@ struct VertexInput inst.init(); return inst; } + + min16float GetVertexAO() + { + [branch] + if (GetInstance().vb_ao < 0) + return 1; + return (min16float)bindless_buffers_float[GetInstance().vb_ao][vertexID]; + } }; @@ -196,6 +206,7 @@ struct VertexSurface min16float4 color; min16float3 normal; min16float4 tangent; + min16float ao; inline void create(in ShaderMaterial material, in VertexInput input) { @@ -211,6 +222,16 @@ struct VertexSurface color *= input.GetVertexColor(); } + [branch] + if (material.IsUsingVertexAO()) + { + ao = input.GetVertexAO(); + } + else + { + ao = 1; + } + normal = mul((min16float3x3)input.GetInstance().transformInverseTranspose.GetMatrix(), normal); tangent = input.GetTangent(); @@ -264,6 +285,10 @@ struct PixelInput min16float3 nor : NORMAL; #endif // OBJECTSHADER_USE_NORMAL +#ifdef OBJECTSHADER_USE_AO + min16float ao : AMBIENT_OCCLUSION; +#endif // OBJECTSHADER_USE_AO + #ifdef OBJECTSHADER_USE_ATLAS min16float2 atl : ATLAS; #endif // OBJECTSHADER_USE_ATLAS @@ -366,6 +391,10 @@ PixelInput main(VertexInput input) Out.nor = surface.normal; #endif // OBJECTSHADER_USE_NORMAL +#ifdef OBJECTSHADER_USE_AO + Out.ao = surface.ao; +#endif // OBJECTSHADER_USE_AO + #ifdef OBJECTSHADER_USE_TANGENT Out.tan = surface.tangent; #endif // OBJECTSHADER_USE_TANGENT @@ -457,6 +486,10 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target surface.N = normalize(input.nor); #endif // OBJECTSHADER_USE_NORMAL +#ifdef OBJECTSHADER_USE_AO + surface.occlusion = input.ao; +#endif // OBJECTSHADER_USE_AO + #ifdef OBJECTSHADER_USE_POSITION3D surface.P = input.pos3D; surface.V = GetCamera().position - surface.P; diff --git a/WickedEngine/shaders/objectHF_tessellation.hlsli b/WickedEngine/shaders/objectHF_tessellation.hlsli index c45dbe525..a3f491e67 100644 --- a/WickedEngine/shaders/objectHF_tessellation.hlsli +++ b/WickedEngine/shaders/objectHF_tessellation.hlsli @@ -134,6 +134,10 @@ PixelInput main(ConstantOutput input, float3 uvw : SV_DomainLocation, const Outp output.nor = min16float3(normalize(w * patch[0].nor + u * patch[1].nor + v * patch[2].nor)); #endif // OBJECTSHADER_USE_NORMAL +#ifdef OBJECTSHADER_USE_AO + output.ao = min16float(w * patch[0].ao + u * patch[1].ao + v * patch[2].ao); +#endif // OBJECTSHADER_USE_NORMAL + #ifdef OBJECTSHADER_USE_TANGENT output.tan = min16float4(normalize(w * patch[0].tan + u * patch[1].tan + v * patch[2].tan)); #endif // OBJECTSHADER_USE_TANGENT diff --git a/WickedEngine/shaders/surfaceHF.hlsli b/WickedEngine/shaders/surfaceHF.hlsli index 69d90a0a3..a451752a3 100644 --- a/WickedEngine/shaders/surfaceHF.hlsli +++ b/WickedEngine/shaders/surfaceHF.hlsli @@ -540,6 +540,17 @@ struct Surface baseColor *= vertexColor; } + [branch] + if (inst.vb_ao >= 0 && material.IsUsingVertexAO()) + { + Buffer buf = bindless_buffers_float[NonUniformResourceIndex(inst.vb_ao)]; + const float ao0 = buf[i0]; + const float ao1 = buf[i1]; + const float ao2 = buf[i2]; + float ao = attribute_at_bary(ao0, ao1, ao2, bary); + occlusion = ao; + } + [branch] if (inst.lightmap >= 0 && geometry.vb_atl >= 0) { diff --git a/WickedEngine/wiBVH.h b/WickedEngine/wiBVH.h index fed1aedf6..b2b54e3bb 100644 --- a/WickedEngine/wiBVH.h +++ b/WickedEngine/wiBVH.h @@ -140,5 +140,38 @@ namespace wi Intersects(primitive, node.left + 1, callback); } } + + // Returning true from callback will immediately exit the whole search + template + bool IntersectsFirst( + const T& primitive, + const std::function& callback + ) const + { + uint32_t stack[64]; + uint32_t count = 0; + stack[count++] = 0; // push node 0 + while (count > 0) + { + const uint32_t nodeIndex = stack[--count]; + Node& node = nodes[nodeIndex]; + if (!node.aabb.intersects(primitive)) + continue; + if (node.isLeaf()) + { + for (uint32_t i = 0; i < node.count; ++i) + { + if (callback(leaf_indices[node.offset + i])) + return true; + } + } + else + { + stack[count++] = node.left; + stack[count++] = node.left + 1; + } + } + return false; + } }; } diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index d291ff091..0c0d75f47 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -1702,4 +1702,27 @@ namespace wi::helper } return ss.str(); } + + std::string GetTimerDurationText(float timerSeconds) + { + std::stringstream ss; + ss << std::fixed << std::setprecision(1); + if (timerSeconds < 1) + { + ss << timerSeconds * 1000 << " ms"; + } + else if (timerSeconds < 60) + { + ss << timerSeconds << " sec"; + } + else if (timerSeconds < 60 * 60) + { + ss << timerSeconds * 60 << " min"; + } + else + { + ss << timerSeconds * 60 * 60 << " hours"; + } + return ss.str(); + } } diff --git a/WickedEngine/wiHelper.h b/WickedEngine/wiHelper.h index 4e45b78d7..685bb15e8 100644 --- a/WickedEngine/wiHelper.h +++ b/WickedEngine/wiHelper.h @@ -170,4 +170,7 @@ namespace wi::helper // Returns a good looking memory size string as either bytes, KB, MB or GB std::string GetMemorySizeText(size_t sizeInBytes); + + // Returns a good looking timer duration text as either milliseconds, seconds, minutes or hours + std::string GetTimerDurationText(float timerSeconds); }; diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h index a960166f6..40688a2d1 100644 --- a/WickedEngine/wiMath.h +++ b/WickedEngine/wiMath.h @@ -274,6 +274,46 @@ namespace wi::math return ++x; } + // A uniform 2D random generator for hemisphere sampling: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + // idx : iteration index + // num : number of iterations in total + constexpr XMFLOAT2 Hammersley2D(uint32_t idx, uint32_t num) { + uint32_t bits = idx; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + const float radicalInverse_VdC = float(bits) * 2.3283064365386963e-10f; // / 0x100000000 + + return XMFLOAT2(float(idx) / float(num), radicalInverse_VdC); + } + inline XMMATRIX GetTangentSpace(const XMFLOAT3& N) + { + // Choose a helper vector for the cross product + XMVECTOR helper = std::abs(N.x) > 0.99 ? XMVectorSet(0, 0, 1, 0) : XMVectorSet(1, 0, 0, 0); + + // Generate vectors + XMVECTOR normal = XMLoadFloat3(&N); + XMVECTOR tangent = XMVector3Normalize(XMVector3Cross(normal, helper)); + XMVECTOR binormal = XMVector3Normalize(XMVector3Cross(normal, tangent)); + return XMMATRIX(tangent, binormal, normal, XMVectorSet(0,0,0,1)); + } + inline XMFLOAT3 HemispherePoint_Uniform(float u, float v) + { + float phi = v * 2 * PI; + float cosTheta = 1 - u; + float sinTheta = std::sqrt(1 - cosTheta * cosTheta); + return XMFLOAT3(std::cos(phi) * sinTheta, std::sin(phi) * sinTheta, cosTheta); + } + inline XMFLOAT3 HemispherePoint_Cos(float u, float v) + { + float phi = v * 2 * PI; + float cosTheta = std::sqrt(1 - u); + float sinTheta = std::sqrt(1 - cosTheta * cosTheta); + return XMFLOAT3(std::cos(phi) * sinTheta, std::sin(phi) * sinTheta, cosTheta); + } + // A, B, C: trangle vertices float TriangleArea(const XMVECTOR& A, const XMVECTOR& B, const XMVECTOR& C); // a, b, c: trangle side lengths diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 29735cc07..afa0aa44f 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -3977,6 +3977,7 @@ namespace wi::scene inst.fadeDistance = object.fadeDistance; inst.center = object.center; inst.radius = object.radius; + inst.vb_ao = object.vb_ao_srv; inst.SetUserStencilRef(object.userStencilRef); std::memcpy(instanceArrayMapped + args.jobIndex, &inst, sizeof(inst)); // memcpy whole structure into mapped pointer to avoid read from uncached memory @@ -4854,8 +4855,6 @@ namespace wi::scene const XMVECTOR rayOrigin_local = XMVector3Transform(rayOrigin, objectMat_Inverse); const XMVECTOR rayDirection_local = XMVector3Normalize(XMVector3TransformNormal(rayDirection, objectMat_Inverse)); const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; - const XMVECTOR aabb_min = XMLoadFloat3(&mesh->aabb._min); - const XMVECTOR aabb_max = XMLoadFloat3(&mesh->aabb._max); auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { @@ -4950,7 +4949,7 @@ namespace wi::scene } else { - // Brute-force interection test: + // Brute-force intersection test: uint32_t first_subset = 0; uint32_t last_subset = 0; mesh->GetLODSubsetRange(lod, first_subset, last_subset); @@ -5050,8 +5049,6 @@ namespace wi::scene const XMMATRIX objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); const XMMATRIX objectMatInverse = XMMatrixInverse(nullptr, objectMat); const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; - const XMVECTOR aabb_min = XMLoadFloat3(&mesh->aabb._min); - const XMVECTOR aabb_max = XMLoadFloat3(&mesh->aabb._max); auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { @@ -5227,7 +5224,7 @@ namespace wi::scene } else { - // Brute-force interection test: + // Brute-force intersection test: uint32_t first_subset = 0; uint32_t last_subset = 0; mesh->GetLODSubsetRange(lod, first_subset, last_subset); @@ -5334,8 +5331,6 @@ namespace wi::scene const XMMATRIX objectMatPrev = XMLoadFloat4x4(&matrix_objects_prev[objectIndex]); const ArmatureComponent* armature = mesh->IsSkinned() ? armatures.GetComponent(mesh->armatureID) : nullptr; const XMMATRIX objectMat_Inverse = XMMatrixInverse(nullptr, objectMat); - const XMVECTOR aabb_min = XMLoadFloat3(&mesh->aabb._min); - const XMVECTOR aabb_max = XMLoadFloat3(&mesh->aabb._max); auto intersect_triangle = [&](uint32_t subsetIndex, uint32_t indexOffset, uint32_t triangleIndex) { @@ -5625,7 +5620,7 @@ namespace wi::scene } else { - // Brute-force interection test: + // Brute-force intersection test: uint32_t first_subset = 0; uint32_t last_subset = 0; mesh->GetLODSubsetRange(lod, first_subset, last_subset); diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index ca40a3446..32bfa2e81 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -33,7 +33,7 @@ namespace wi::scene wi::ecs::ComponentManager& materials = componentLibrary.Register("wi::scene::Scene::materials", 2); // version = 2 wi::ecs::ComponentManager& meshes = componentLibrary.Register("wi::scene::Scene::meshes", 2); // version = 2 wi::ecs::ComponentManager& impostors = componentLibrary.Register("wi::scene::Scene::impostors"); - wi::ecs::ComponentManager& objects = componentLibrary.Register("wi::scene::Scene::objects", 2); // version = 2 + wi::ecs::ComponentManager& objects = componentLibrary.Register("wi::scene::Scene::objects", 3); // version = 3 wi::ecs::ComponentManager& rigidbodies = componentLibrary.Register("wi::scene::Scene::rigidbodies", 1); // version = 1 wi::ecs::ComponentManager& softbodies = componentLibrary.Register("wi::scene::Scene::softbodies"); wi::ecs::ComponentManager& armatures = componentLibrary.Register("wi::scene::Scene::armatures"); diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index 3c1902c26..e73a64915 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -330,6 +330,10 @@ namespace wi::scene { material.options |= SHADERMATERIAL_OPTION_BIT_UNLIT; } + if (!IsVertexAODisabled()) + { + material.options |= SHADERMATERIAL_OPTION_BIT_USE_VERTEXAO; + } GraphicsDevice* device = wi::graphics::GetDevice(); for (int i = 0; i < TEXTURESLOT_COUNT; ++i) @@ -1599,7 +1603,6 @@ namespace wi::scene lightmapTextureData.clear(); SetLightmapRenderRequest(false); } - void ObjectComponent::SaveLightmap() { if (lightmap.IsValid() && has_flag(lightmap.desc.bind_flags, BindFlag::RENDER_TARGET)) @@ -1702,6 +1705,35 @@ namespace wi::scene lightmap.desc = desc; } } + void ObjectComponent::DeleteRenderData() + { + vb_ao = {}; + vb_ao_srv = -1; + } + void ObjectComponent::CreateRenderData() + { + DeleteRenderData(); + + GraphicsDevice* device = wi::graphics::GetDevice(); + + if (!vertex_ao.empty()) + { + GPUBufferDesc desc; + desc.bind_flags = BindFlag::SHADER_RESOURCE; + desc.size = sizeof(Vertex_AO) * vertex_ao.size(); + desc.format = Vertex_AO::FORMAT; + + auto fill_ao = [&](void* data) { + std::memcpy(data, vertex_ao.data(), vertex_ao.size()); + }; + + bool success = device->CreateBuffer2(&desc, fill_ao, &vb_ao); + assert(success); + device->SetName(&vb_ao, "ObjectComponent::vb_ao"); + + vb_ao_srv = device->GetDescriptorIndex(&vb_ao, SubresourceType::SRV); + } + } void EnvironmentProbeComponent::CreateRenderData() { diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index a3daaa27b..6ed317e38 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -122,6 +122,7 @@ namespace wi::scene DOUBLE_SIDED = 1 << 11, OUTLINE = 1 << 12, PREFER_UNCOMPRESSED_TEXTURES = 1 << 13, + DISABLE_VERTEXAO = 1 << 14, }; uint32_t _flags = CAST_SHADOW; @@ -267,6 +268,7 @@ namespace wi::scene inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; } inline bool IsOutlineEnabled() const { return _flags & OUTLINE; } inline bool IsPreferUncompressedTexturesEnabled() const { return _flags & PREFER_UNCOMPRESSED_TEXTURES; } + inline bool IsVertexAODisabled() const { return _flags & DISABLE_VERTEXAO; } inline void SetBaseColor(const XMFLOAT4& value) { SetDirty(); baseColor = value; } inline void SetSpecularColor(const XMFLOAT4& value) { SetDirty(); specularColor = value; } @@ -306,6 +308,7 @@ namespace wi::scene inline void SetDoubleSided(bool value = true) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } } inline void SetOutlineEnabled(bool value = true) { if (value) { _flags |= OUTLINE; } else { _flags &= ~OUTLINE; } } inline void SetPreferUncompressedTexturesEnabled(bool value = true) { if (value) { _flags |= PREFER_UNCOMPRESSED_TEXTURES; } else { _flags &= ~PREFER_UNCOMPRESSED_TEXTURES; } CreateRenderData(true); } + inline void SetVertexAODisabled(bool value = true) { if (value) { _flags |= DISABLE_VERTEXAO; } else { _flags &= ~DISABLE_VERTEXAO; } } // The MaterialComponent will be written to ShaderMaterial (a struct that is optimized for GPU use) void WriteShaderMaterial(ShaderMaterial* dest) const; @@ -809,12 +812,15 @@ namespace wi::scene uint32_t lightmapHeight = 0; wi::vector lightmapTextureData; uint32_t sort_priority = 0; // increase to draw earlier (currently 4 bits will be used) + wi::vector vertex_ao; // Non-serialized attributes: uint32_t filterMaskDynamic = 0; wi::graphics::Texture lightmap; mutable uint32_t lightmapIterationCount = 0; + wi::graphics::GPUBuffer vb_ao; + int vb_ao_srv = -1; XMFLOAT3 center = XMFLOAT3(0, 0, 0); float radius = 0; @@ -865,6 +871,14 @@ namespace wi::scene void CompressLightmap(); // not thread safe if LIGHTMAP_BLOCK_COMPRESSION is enabled! void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); + + void CreateRenderData(); + void DeleteRenderData(); + struct Vertex_AO + { + uint8_t value = 0; + static constexpr wi::graphics::Format FORMAT = wi::graphics::Format::R8_UNORM; + }; }; struct RigidBodyPhysicsComponent diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index 414646534..b85774f88 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -373,7 +373,6 @@ namespace wi::scene } void MeshComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) { - if (archive.IsReadMode()) { archive >> _flags; @@ -594,6 +593,14 @@ namespace wi::scene { archive >> sort_priority; } + if (seri.GetVersion() >= 3) + { + archive >> vertex_ao; + } + + wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) { + CreateRenderData(); + }); } else { @@ -632,6 +639,10 @@ namespace wi::scene { archive << sort_priority; } + if (seri.GetVersion() >= 3) + { + archive << vertex_ao; + } } } void RigidBodyPhysicsComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 513a1fa1d..c0e07ec9b 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 362; + const int revision = 363; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);