diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 7d96eb4ce..757be35cb 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -40,6 +40,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -59,6 +60,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -108,6 +110,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -127,6 +130,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -175,6 +179,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -194,6 +199,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 05b3b5803..5e8144704 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -39,6 +39,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -58,6 +59,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -108,6 +110,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -127,6 +130,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -175,6 +179,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -194,6 +199,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b9a26bf5..25f1bcb71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -59,6 +60,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -109,6 +111,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -128,6 +131,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf @@ -176,6 +180,7 @@ jobs: other_licenses.txt features.txt Editor/images/ + Editor/terrain/ Editor/sound/ Editor/*.ini Editor/*.ico @@ -195,6 +200,7 @@ jobs: Tests/images/ Tests/sound/ Tests/*.ini + Tests/*.txt Tests/*.ico Tests/*.lua Tests/*.ttf diff --git a/Content/scripts/character_controller_tps.lua b/Content/scripts/character_controller_tps.lua index 007f32448..6944e49a0 100644 --- a/Content/scripts/character_controller_tps.lua +++ b/Content/scripts/character_controller_tps.lua @@ -360,8 +360,10 @@ ThirdPersonCamera = { local corner = vector.TransformCoord(coord, unproj) local target_to_corner = vector.Subtract(corner, targetPos) local corner_to_campos = vector.Subtract(camPos, corner) + local TMin = 0 + local TMax = target_to_corner.Length() -- optimization: limit the ray tracing distance - local ray = Ray(targetPos, target_to_corner.Normalize()) + local ray = Ray(targetPos, target_to_corner.Normalize(), TMin, TMax) local collObj,collPos,collNor = Pick(ray, PICK_OPAQUE, ~self.character.layerMask) if(collObj ~= INVALID_ENTITY) then @@ -425,7 +427,6 @@ runProcess(function() while true do player:Update() - camera:Update() fixedupdate() if(input.Press(KEYBOARD_BUTTON_ESCAPE)) then @@ -451,6 +452,7 @@ end) runProcess(function() while true do + camera:Update() player:Input() update() diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index 9a5ccd3d0..aa2f03e83 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -28,6 +28,7 @@ set (SOURCE_FILES TransformWindow.cpp Translator.cpp WeatherWindow.cpp + TerrainGenerator.cpp xatlas.cpp ) diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp index 8abd65520..86f303914 100644 --- a/Editor/CameraWindow.cpp +++ b/Editor/CameraWindow.cpp @@ -41,7 +41,7 @@ void CameraWindow::Create(EditorComponent* editor) float hei = 18; float step = hei + 2; - farPlaneSlider.Create(1, 5000, 1000, 100000, "Far Plane: "); + farPlaneSlider.Create(0.1f, 10000, 5000, 100000, "Far Plane: "); farPlaneSlider.SetTooltip("Controls the camera's far clip plane, geometry farther than this will be clipped."); farPlaneSlider.SetSize(XMFLOAT2(100, hei)); farPlaneSlider.SetPos(XMFLOAT2(x, y += step)); diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index befe438c8..185d861fb 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -202,6 +202,9 @@ void EditorComponent::ResizeLayout() paintToolWnd_Toggle.SetPos(XMFLOAT2(x += hstep, y)); paintToolWnd_Toggle.SetSize(option_size); + terrainWnd_Toggle.SetPos(XMFLOAT2(x += hstep, y)); + terrainWnd_Toggle.SetSize(option_size); + ///////////////////////// option_size.y = 16; @@ -364,6 +367,135 @@ void EditorComponent::Load() }); GetGUI().AddWidget(&paintToolWnd_Toggle); + terrainWnd_Toggle.Create("Terrain"); + terrainWnd_Toggle.SetTooltip("Terrain Generator"); + terrainWnd_Toggle.OnClick([&](wi::gui::EventArgs args) { + + if (terragen.terrainEntity == INVALID_ENTITY) + { + // Customize terrain generator before it's initialized: + terragen.material_Base.SetUseVertexColors(true); + terragen.material_Base.SetRoughness(1); + terragen.material_Slope.SetRoughness(0.5f); + terragen.material_LowAltitude.SetRoughness(1); + terragen.material_HighAltitude.SetRoughness(1); + terragen.material_Base.textures[MaterialComponent::BASECOLORMAP].name = "terrain/base.jpg"; + terragen.material_Base.textures[MaterialComponent::NORMALMAP].name = "terrain/base_nor.jpg"; + terragen.material_Slope.textures[MaterialComponent::BASECOLORMAP].name = "terrain/slope.jpg"; + terragen.material_Slope.textures[MaterialComponent::NORMALMAP].name = "terrain/slope_nor.jpg"; + terragen.material_LowAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/low_altitude.jpg"; + terragen.material_LowAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/low_altitude_nor.jpg"; + terragen.material_HighAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/high_altitude.jpg"; + terragen.material_HighAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/high_altitude_nor.jpg"; + terragen.material_GrassParticle.textures[MaterialComponent::BASECOLORMAP].name = "terrain/grassparticle.png"; + terragen.material_GrassParticle.alphaRef = 0.75f; + terragen.material_Base.CreateRenderData(); + terragen.material_Slope.CreateRenderData(); + terragen.material_LowAltitude.CreateRenderData(); + terragen.material_HighAltitude.CreateRenderData(); + terragen.material_GrassParticle.CreateRenderData(); + // Tree prop: + { + Scene props_scene; + wi::scene::LoadModel(props_scene, "terrain/tree.wiscene"); + TerrainGenerator::Prop& prop = terragen.props.emplace_back(); + prop.name = "tree"; + prop.min_count_per_chunk = 0; + prop.max_count_per_chunk = 10; + prop.region = 0; + prop.region_power = 2; + prop.noise_frequency = 0.1f; + prop.noise_power = 1; + prop.threshold = 0.4f; + prop.min_size = 2.0f; + prop.max_size = 8.0f; + prop.min_y_offset = -0.5f; + prop.max_y_offset = -0.5f; + prop.mesh_entity = props_scene.Entity_FindByName("tree_mesh"); + Entity object_entity = props_scene.Entity_FindByName("tree_object"); + ObjectComponent* object = props_scene.objects.GetComponent(object_entity); + if (object != nullptr) + { + prop.object = *object; + prop.object.lod_distance_multiplier = 0.05f; + //prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade + } + props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore + wi::scene::GetScene().Merge(props_scene); + } + // Rock prop: + { + Scene props_scene; + wi::scene::LoadModel(props_scene, "terrain/rock.wiscene"); + TerrainGenerator::Prop& prop = terragen.props.emplace_back(); + prop.name = "rock"; + prop.min_count_per_chunk = 0; + prop.max_count_per_chunk = 8; + prop.region = 0; + prop.region_power = 1; + prop.noise_frequency = 0.005f; + prop.noise_power = 2; + prop.threshold = 0.5f; + prop.min_size = 0.02f; + prop.max_size = 4.0f; + prop.min_y_offset = -2; + prop.max_y_offset = 0.5f; + prop.mesh_entity = props_scene.Entity_FindByName("rock_mesh"); + Entity object_entity = props_scene.Entity_FindByName("rock_object"); + ObjectComponent* object = props_scene.objects.GetComponent(object_entity); + if (object != nullptr) + { + prop.object = *object; + prop.object.lod_distance_multiplier = 0.02f; + prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade + } + props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore + wi::scene::GetScene().Merge(props_scene); + } + // Bush prop: + { + Scene props_scene; + wi::scene::LoadModel(props_scene, "terrain/bush.wiscene"); + TerrainGenerator::Prop& prop = terragen.props.emplace_back(); + prop.name = "bush"; + prop.min_count_per_chunk = 0; + prop.max_count_per_chunk = 10; + prop.region = 0; + prop.region_power = 4; + prop.noise_frequency = 0.01f; + prop.noise_power = 4; + prop.threshold = 0.1f; + prop.min_size = 0.1f; + prop.max_size = 1.5f; + prop.min_y_offset = -1; + prop.max_y_offset = 0; + prop.mesh_entity = props_scene.Entity_FindByName("bush_mesh"); + Entity object_entity = props_scene.Entity_FindByName("bush_object"); + ObjectComponent* object = props_scene.objects.GetComponent(object_entity); + if (object != nullptr) + { + prop.object = *object; + prop.object.lod_distance_multiplier = 0.05f; + prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade + } + props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore + wi::scene::GetScene().Merge(props_scene); + } + terragen.init(); + RefreshSceneGraphView(); + } + + terragen.SetVisible(!terragen.IsVisible()); + if (terragen.IsVisible() && !wi::scene::GetScene().transforms.Contains(terragen.terrainEntity)) + { + terragen.Generation_Restart(); + RefreshSceneGraphView(); + } + + }); + GetGUI().AddWidget(&terrainWnd_Toggle); + GetGUI().AddWidget(&terragen); + /////////////////////// wi::Color option_color_idle = wi::Color(255, 145, 145, 100); @@ -736,8 +868,15 @@ void EditorComponent::Load() clearButton.SetColor(wi::Color(255, 173, 43, 180), wi::gui::WIDGETSTATE::IDLE); clearButton.SetColor(wi::Color(255, 235, 173, 255), wi::gui::WIDGETSTATE::FOCUS); clearButton.OnClick([&](wi::gui::EventArgs args) { + + terragen.Generation_Cancel(); + // This is to recreate the terragen from scratch, but it has implicitly deleted copy ctor so it's weird: + terragen.~TerrainGenerator(); + new (&terragen) TerrainGenerator; + translator.selected.clear(); - wi::renderer::ClearWorld(wi::scene::GetScene()); + wi::scene::Scene& scene = wi::scene::GetScene(); + wi::renderer::ClearWorld(scene); objectWnd.SetEntity(INVALID_ENTITY); meshWnd.SetEntity(INVALID_ENTITY, -1); lightWnd.SetEntity(INVALID_ENTITY); @@ -813,6 +952,7 @@ void EditorComponent::Load() exitButton.SetColor(wi::Color(190, 0, 0, 180), wi::gui::WIDGETSTATE::IDLE); exitButton.SetColor(wi::Color(255, 0, 0, 255), wi::gui::WIDGETSTATE::FOCUS); exitButton.OnClick([this](wi::gui::EventArgs args) { + terragen.Generation_Cancel(); wi::platform::Exit(); }); GetGUI().AddWidget(&exitButton); @@ -1314,7 +1454,7 @@ void EditorComponent::Update(float dt) } } - if (pickMask & PICK_OBJECT && hovered.entity == INVALID_ENTITY) + if ((pickMask & PICK_OBJECT) && hovered.entity == INVALID_ENTITY) { // Object picking only when mouse button down, because it can be slow with high polycount if ( @@ -1519,12 +1659,22 @@ void EditorComponent::Update(float dt) for (size_t i = 0; i < count; ++i) { Entity entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE | Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES); + const HierarchyComponent* hier = scene.hierarchy.GetComponent(entity); + if (hier != nullptr) + { + scene.Component_Detach(entity); + } TransformComponent* transform = scene.transforms.GetComponent(entity); if (transform != nullptr) { transform->translation_local = {}; //transform->MatrixTransform(hovered.orientation); transform->Translate(hovered.position); + transform->UpdateTransform(); + } + if (hier != nullptr) + { + scene.Component_Attach(entity, hier->parentID); } addedEntities.push_back(entity); } @@ -1729,6 +1879,8 @@ void EditorComponent::Update(float dt) pathTraceStatisticsLabel.SetText(ss); } + terragen.Generation_Update(); + wi::profiler::EndRange(profrange); RenderPath2D::Update(dt); diff --git a/Editor/Editor.h b/Editor/Editor.h index 84af0bcc3..8326fad9c 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -1,6 +1,7 @@ #pragma once #include "WickedEngine.h" #include "Translator.h" +#include "TerrainGenerator.h" #include "MaterialWindow.h" #include "PostprocessWindow.h" @@ -61,12 +62,14 @@ public: TransformWindow transformWnd; LayerWindow layerWnd; NameWindow nameWnd; + TerrainGenerator terragen; Editor* main = nullptr; wi::gui::Button rendererWnd_Toggle; wi::gui::Button postprocessWnd_Toggle; wi::gui::Button paintToolWnd_Toggle; + wi::gui::Button terrainWnd_Toggle; wi::gui::Button weatherWnd_Toggle; wi::gui::Button objectWnd_Toggle; wi::gui::Button meshWnd_Toggle; diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems index 2057bf38f..ffc0758de 100644 --- a/Editor/Editor_SOURCE.vcxitems +++ b/Editor/Editor_SOURCE.vcxitems @@ -106,6 +106,7 @@ Create Create + @@ -145,6 +146,7 @@ + @@ -163,6 +165,18 @@ true true + + true + true + + + true + true + + + true + true + @@ -222,5 +236,71 @@ true true + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + \ No newline at end of file diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters index 5cd26018f..7f48632cb 100644 --- a/Editor/Editor_SOURCE.vcxitems.filters +++ b/Editor/Editor_SOURCE.vcxitems.filters @@ -73,6 +73,7 @@ meshoptimizer + @@ -107,9 +108,19 @@ meshoptimizer + + + terrain + + + terrain + + + terrain + @@ -118,6 +129,9 @@ {49c521c0-5dca-4fce-b8c5-a334d1746d98} + + {9a5b997d-f07e-45c5-849a-717f15cba0ab} + @@ -165,5 +179,55 @@ images + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + + + terrain + \ No newline at end of file diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 9f4edc7ed..141327fad 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -11,73 +11,10 @@ using namespace wi::ecs; using namespace wi::scene; -struct TerraGen : public wi::gui::Window -{ - wi::gui::Slider dimXSlider; - wi::gui::Slider dimYSlider; - wi::gui::Slider dimZSlider; - wi::gui::Button heightmapButton; - - // heightmap texture: - unsigned char* rgb = nullptr; - const int channelCount = 4; - int width = 0, height = 0; - - TerraGen() - { - wi::gui::Window::Create("TerraGen"); - SetSize(XMFLOAT2(260, 130)); - - float xx = 20; - float yy = 0; - float stepstep = 25; - float heihei = 20; - - dimXSlider.Create(16, 1024, 128, 1024 - 16, "X: "); - dimXSlider.SetTooltip("Terrain mesh grid resolution on X axis"); - dimXSlider.SetSize(XMFLOAT2(200, heihei)); - dimXSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); - AddWidget(&dimXSlider); - - dimYSlider.Create(0, 1, 0.5f, 10000, "Y: "); - dimYSlider.SetTooltip("Terrain mesh grid heightmap scale on Y axis"); - dimYSlider.SetSize(XMFLOAT2(200, heihei)); - dimYSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); - AddWidget(&dimYSlider); - - dimZSlider.Create(16, 1024, 128, 1024 - 16, "Z: "); - dimZSlider.SetTooltip("Terrain mesh grid resolution on Z axis"); - dimZSlider.SetSize(XMFLOAT2(200, heihei)); - dimZSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); - AddWidget(&dimZSlider); - - - heightmapButton.Create("Load Heightmap..."); - heightmapButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions"); - heightmapButton.SetSize(XMFLOAT2(200, heihei)); - heightmapButton.SetPos(XMFLOAT2(xx, yy += stepstep)); - - AddWidget(&heightmapButton); - } - ~TerraGen() - { - Cleanup(); - } - - void Cleanup() - { - if (rgb != nullptr) - { - stbi_image_free(rgb); - rgb = nullptr; - } - } -} terragen; - void MeshWindow::Create(EditorComponent* editor) { wi::gui::Window::Create("Mesh Window"); - SetSize(XMFLOAT2(580, 580)); + SetSize(XMFLOAT2(580, 600)); float x = 150; float y = 0; @@ -225,7 +162,7 @@ void MeshWindow::Create(EditorComponent* editor) impostorCreateButton.Create("Create Impostor"); impostorCreateButton.SetTooltip("Create an impostor image of the mesh. The mesh will be replaced by this image when far away, to render faster."); - impostorCreateButton.SetSize(XMFLOAT2(240, hei)); + impostorCreateButton.SetSize(XMFLOAT2(200, hei)); impostorCreateButton.SetPos(XMFLOAT2(x - 50, y += step)); impostorCreateButton.OnClick([&](wi::gui::EventArgs args) { Scene& scene = wi::scene::GetScene(); @@ -271,7 +208,7 @@ void MeshWindow::Create(EditorComponent* editor) flipCullingButton.Create("Flip Culling"); flipCullingButton.SetTooltip("Flip faces to reverse triangle culling order."); - flipCullingButton.SetSize(XMFLOAT2(240, hei)); + flipCullingButton.SetSize(XMFLOAT2(200, hei)); flipCullingButton.SetPos(XMFLOAT2(x - 50, y += step)); flipCullingButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -285,7 +222,7 @@ void MeshWindow::Create(EditorComponent* editor) flipNormalsButton.Create("Flip Normals"); flipNormalsButton.SetTooltip("Flip surface normals."); - flipNormalsButton.SetSize(XMFLOAT2(240, hei)); + flipNormalsButton.SetSize(XMFLOAT2(200, hei)); flipNormalsButton.SetPos(XMFLOAT2(x - 50, y += step)); flipNormalsButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -299,7 +236,7 @@ void MeshWindow::Create(EditorComponent* editor) computeNormalsSmoothButton.Create("Compute Normals [SMOOTH]"); computeNormalsSmoothButton.SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per vertex. This can reduce vertex count, but is slow."); - computeNormalsSmoothButton.SetSize(XMFLOAT2(240, hei)); + computeNormalsSmoothButton.SetSize(XMFLOAT2(200, hei)); computeNormalsSmoothButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsSmoothButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -313,7 +250,7 @@ void MeshWindow::Create(EditorComponent* editor) computeNormalsHardButton.Create("Compute Normals [HARD]"); computeNormalsHardButton.SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per face. This can increase vertex count."); - computeNormalsHardButton.SetSize(XMFLOAT2(240, hei)); + computeNormalsHardButton.SetSize(XMFLOAT2(200, hei)); computeNormalsHardButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsHardButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -327,7 +264,7 @@ void MeshWindow::Create(EditorComponent* editor) recenterButton.Create("Recenter"); recenterButton.SetTooltip("Recenter mesh to AABB center."); - recenterButton.SetSize(XMFLOAT2(240, hei)); + recenterButton.SetSize(XMFLOAT2(200, hei)); recenterButton.SetPos(XMFLOAT2(x - 50, y += step)); recenterButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -341,7 +278,7 @@ void MeshWindow::Create(EditorComponent* editor) recenterToBottomButton.Create("RecenterToBottom"); recenterToBottomButton.SetTooltip("Recenter mesh to AABB bottom."); - recenterToBottomButton.SetSize(XMFLOAT2(240, hei)); + recenterToBottomButton.SetSize(XMFLOAT2(200, hei)); recenterToBottomButton.SetPos(XMFLOAT2(x - 50, y += step)); recenterToBottomButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -353,9 +290,191 @@ void MeshWindow::Create(EditorComponent* editor) }); AddWidget(&recenterToBottomButton); + mergeButton.Create("Merge Selected"); + mergeButton.SetTooltip("Merges selected objects/meshes into one."); + mergeButton.SetSize(XMFLOAT2(200, hei)); + mergeButton.SetPos(XMFLOAT2(x - 50, y += step)); + mergeButton.OnClick([=](wi::gui::EventArgs args) { + Scene& scene = wi::scene::GetScene(); + MeshComponent merged_mesh; + bool valid_normals = false; + bool valid_uvset_0 = false; + bool valid_uvset_1 = false; + bool valid_atlas = false; + bool valid_boneindices = false; + bool valid_boneweights = false; + bool valid_colors = false; + bool valid_windweights = false; + wi::unordered_set entities_to_remove; + Entity prev_subset_material = INVALID_ENTITY; + for (auto& picked : editor->translator.selected) + { + ObjectComponent* object = scene.objects.GetComponent(picked.entity); + if (object == nullptr) + continue; + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + continue; + const TransformComponent* transform = scene.transforms.GetComponent(picked.entity); + XMMATRIX W = XMLoadFloat4x4(&transform->world); + uint32_t vertexOffset = (uint32_t)merged_mesh.vertex_positions.size(); + uint32_t indexOffset = (uint32_t)merged_mesh.indices.size(); + for (auto& ind : mesh->indices) + { + merged_mesh.indices.push_back(vertexOffset + ind); + } + 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; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + if (subset.materialID != prev_subset_material) + { + // new subset + prev_subset_material = subset.materialID; + merged_mesh.subsets.push_back(subset); + merged_mesh.subsets.back().indexOffset += indexOffset; + } + else + { + // append to previous subset + merged_mesh.subsets.back().indexCount += subset.indexCount; + } + } + for (size_t i = 0; i < mesh->vertex_positions.size(); ++i) + { + merged_mesh.vertex_positions.push_back(mesh->vertex_positions[i]); + XMStoreFloat3(&merged_mesh.vertex_positions.back(), XMVector3Transform(XMLoadFloat3(&merged_mesh.vertex_positions.back()), W)); + + if (mesh->vertex_normals.empty()) + { + merged_mesh.vertex_normals.emplace_back(); + } + else + { + valid_normals = true; + merged_mesh.vertex_normals.push_back(mesh->vertex_normals[i]); + XMStoreFloat3(&merged_mesh.vertex_normals.back(), XMVector3TransformNormal(XMLoadFloat3(&merged_mesh.vertex_normals.back()), W)); + } + + if (mesh->vertex_uvset_0.empty()) + { + merged_mesh.vertex_uvset_0.emplace_back(); + } + else + { + valid_uvset_0 = true; + merged_mesh.vertex_uvset_0.push_back(mesh->vertex_uvset_0[i]); + } + + if (mesh->vertex_uvset_1.empty()) + { + merged_mesh.vertex_uvset_1.emplace_back(); + } + else + { + valid_uvset_1 = true; + merged_mesh.vertex_uvset_1.push_back(mesh->vertex_uvset_1[i]); + } + + if (mesh->vertex_atlas.empty()) + { + merged_mesh.vertex_atlas.emplace_back(); + } + else + { + valid_atlas = true; + merged_mesh.vertex_atlas.push_back(mesh->vertex_atlas[i]); + } + + if (mesh->vertex_boneindices.empty()) + { + merged_mesh.vertex_boneindices.emplace_back(); + } + else + { + valid_boneindices = true; + merged_mesh.vertex_boneindices.push_back(mesh->vertex_boneindices[i]); + } + + if (mesh->vertex_boneweights.empty()) + { + merged_mesh.vertex_boneweights.emplace_back(); + } + else + { + valid_boneweights = true; + merged_mesh.vertex_boneweights.push_back(mesh->vertex_boneweights[i]); + } + + if (mesh->vertex_colors.empty()) + { + merged_mesh.vertex_colors.push_back(~0u); + } + else + { + valid_colors = true; + merged_mesh.vertex_colors.push_back(mesh->vertex_colors[i]); + } + + if (mesh->vertex_windweights.empty()) + { + merged_mesh.vertex_windweights.emplace_back(); + } + else + { + valid_windweights = true; + merged_mesh.vertex_windweights.push_back(mesh->vertex_windweights[i]); + } + } + if (merged_mesh.armatureID == INVALID_ENTITY) + { + merged_mesh.armatureID = mesh->armatureID; + } + entities_to_remove.insert(object->meshID); + entities_to_remove.insert(picked.entity); + } + + if (!merged_mesh.vertex_positions.empty()) + { + if (!valid_normals) + merged_mesh.vertex_normals.clear(); + if (!valid_uvset_0) + merged_mesh.vertex_uvset_0.clear(); + if (!valid_uvset_1) + merged_mesh.vertex_uvset_1.clear(); + if (!valid_atlas) + merged_mesh.vertex_atlas.clear(); + if (!valid_boneindices) + merged_mesh.vertex_boneindices.clear(); + if (!valid_boneweights) + merged_mesh.vertex_boneweights.clear(); + if (!valid_colors) + merged_mesh.vertex_colors.clear(); + if (!valid_windweights) + merged_mesh.vertex_windweights.clear(); + + Entity merged_object_entity = scene.Entity_CreateObject("mergedObject"); + Entity merged_mesh_entity = scene.Entity_CreateMesh("mergedMesh"); + ObjectComponent* object = scene.objects.GetComponent(merged_object_entity); + object->meshID = merged_mesh_entity; + MeshComponent* mesh = scene.meshes.GetComponent(merged_mesh_entity); + *mesh = std::move(merged_mesh); + mesh->CreateRenderData(); + } + + for (auto& x : entities_to_remove) + { + scene.Entity_Remove(x); + } + + }); + AddWidget(&mergeButton); + optimizeButton.Create("Optimize"); optimizeButton.SetTooltip("Run the meshoptimizer library."); - optimizeButton.SetSize(XMFLOAT2(240, hei)); + optimizeButton.SetSize(XMFLOAT2(200, hei)); optimizeButton.SetPos(XMFLOAT2(x - 50, y += step)); optimizeButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); @@ -473,147 +592,6 @@ void MeshWindow::Create(EditorComponent* editor) terrainMat3Combo.SetTooltip("Choose a sub terrain blend material. (ALPHA vertex color mask)"); AddWidget(&terrainMat3Combo); - terrainGenButton.Create("Generate Terrain..."); - terrainGenButton.SetTooltip("Generate terrain meshes."); - terrainGenButton.SetSize(XMFLOAT2(200, hei)); - terrainGenButton.SetPos(XMFLOAT2(x + 180, y += step)); - terrainGenButton.OnClick([=](wi::gui::EventArgs args) { - - terragen.Cleanup(); - terragen.SetVisible(true); - - editor->GetGUI().RemoveWidget(&terragen); - editor->GetGUI().AddWidget(&terragen); - - terragen.SetPos(XMFLOAT2( - terrainGenButton.translation.x + terrainGenButton.scale.x + 10, - terrainGenButton.translation.y) - ); - - Scene& scene = wi::scene::GetScene(); - Entity entity = scene.Entity_CreateObject("editorTerrain"); - ObjectComponent& object = *scene.objects.GetComponent(entity); - object.meshID = scene.Entity_CreateMesh("terrainMesh"); - MeshComponent* mesh = scene.meshes.GetComponent(object.meshID); - mesh->SetTerrain(true); - mesh->subsets.emplace_back(); - mesh->subsets.back().materialID = scene.Entity_CreateMaterial("terrainMaterial"); - mesh->subsets.back().indexOffset = 0; - MaterialComponent* material = scene.materials.GetComponent(mesh->subsets.back().materialID); - material->SetUseVertexColors(true); - - auto generate_mesh = [=] (int width, int height, unsigned char* rgb = nullptr, - int channelCount = 4, float heightmap_scale = 1) - { - mesh->vertex_positions.resize(width * height); - mesh->vertex_normals.resize(width * height); - mesh->vertex_colors.resize(width * height); - mesh->vertex_uvset_0.resize(width* height); - mesh->vertex_uvset_1.resize(width* height); - mesh->vertex_atlas.resize(width* height); - for (int i = 0; i < width; ++i) - { - for (int j = 0; j < height; ++j) - { - size_t index = size_t(i + j * width); - mesh->vertex_positions[index] = XMFLOAT3((float)i - (float)width * 0.5f, 0, (float)j - (float)height * 0.5f); - if (rgb != nullptr) - mesh->vertex_positions[index].y = ((float)rgb[index * channelCount] - 127.0f) * heightmap_scale; - mesh->vertex_colors[index] = wi::Color::Red().rgba; - XMFLOAT2 uv = XMFLOAT2((float)i / (float)width, (float)j / (float)height); - mesh->vertex_uvset_0[index] = uv; - mesh->vertex_uvset_1[index] = uv; - mesh->vertex_atlas[index] = uv; - } - } - mesh->indices.resize((width - 1) * (height - 1) * 6); - size_t counter = 0; - for (int x = 0; x < width - 1; x++) - { - for (int y = 0; y < height - 1; y++) - { - int lowerLeft = x + y * width; - int lowerRight = (x + 1) + y * width; - int topLeft = x + (y + 1) * width; - int topRight = (x + 1) + (y + 1) * width; - - mesh->indices[counter++] = topLeft; - mesh->indices[counter++] = lowerLeft; - mesh->indices[counter++] = lowerRight; - - mesh->indices[counter++] = topLeft; - mesh->indices[counter++] = lowerRight; - mesh->indices[counter++] = topRight; - } - } - mesh->subsets.back().indexCount = (uint32_t)mesh->indices.size(); - - mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST); - }; - generate_mesh(128, 128); - - wi::Archive& archive = editor->AdvanceHistory(); - archive << EditorComponent::HISTORYOP_ADD; - editor->RecordSelection(archive); - - editor->ClearSelected(); - wi::scene::PickResult pick; - pick.entity = entity; - pick.subsetIndex = 0; - editor->AddSelected(pick); - - editor->RecordSelection(archive); - editor->RecordAddedEntity(archive, entity); - - SetEntity(object.meshID, pick.subsetIndex); - - editor->RefreshSceneGraphView(); - - - - - - terragen.dimXSlider.OnSlide([=](wi::gui::EventArgs args) { - terragen.width = (int)terragen.dimXSlider.GetValue(); - terragen.height = (int)terragen.dimZSlider.GetValue(); - generate_mesh(terragen.width, terragen.height); - }); - terragen.dimZSlider.OnSlide([=](wi::gui::EventArgs args) { - terragen.width = (int)terragen.dimXSlider.GetValue(); - terragen.height = (int)terragen.dimZSlider.GetValue(); - generate_mesh(terragen.width, terragen.height); - }); - terragen.dimYSlider.OnSlide([=](wi::gui::EventArgs args) { - terragen.width = (int)terragen.dimXSlider.GetValue(); - terragen.height = (int)terragen.dimZSlider.GetValue(); - generate_mesh(terragen.width, terragen.height, terragen.rgb, terragen.channelCount, args.fValue); - }); - - terragen.heightmapButton.OnClick([=](wi::gui::EventArgs args) { - - wi::helper::FileDialogParams params; - params.type = wi::helper::FileDialogParams::OPEN; - params.description = "Texture"; - params.extensions = wi::resourcemanager::GetSupportedImageExtensions(); - wi::helper::FileDialog(params, [=](std::string fileName) { - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - if (terragen.rgb != nullptr) - { - stbi_image_free(terragen.rgb); - terragen.rgb = nullptr; - } - - int bpp; - terragen.rgb = stbi_load(fileName.c_str(), &terragen.width, &terragen.height, &bpp, terragen.channelCount); - - generate_mesh(terragen.width, terragen.height, terragen.rgb, terragen.channelCount, terragen.dimYSlider.GetValue()); - }); - }); - }); - - }); - AddWidget(&terrainGenButton); - morphTargetCombo.Create("Morph Target:"); morphTargetCombo.SetSize(XMFLOAT2(100, hei)); @@ -642,6 +620,142 @@ void MeshWindow::Create(EditorComponent* editor) }); AddWidget(&morphTargetSlider); + lodgenButton.Create("LOD Gen"); + lodgenButton.SetTooltip("Generate LODs (levels of detail)."); + lodgenButton.SetSize(XMFLOAT2(200, hei)); + lodgenButton.SetPos(XMFLOAT2(x + 180, y += step)); + lodgenButton.OnClick([&](wi::gui::EventArgs args) { + MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + if (mesh != nullptr) + { + if (mesh->subsets_per_lod == 0) + { + // if there were no lods before, record the subset count without lods: + mesh->subsets_per_lod = (uint32_t)mesh->subsets.size(); + } + + // https://github.com/zeux/meshoptimizer/blob/bedaaaf6e710d3b42d49260ca738c15d171b1a8f/demo/main.cpp + size_t index_count = mesh->indices.size(); + size_t vertex_count = mesh->vertex_positions.size(); + + const size_t lod_count = (size_t)lodCountSlider.GetValue(); + struct LOD + { + struct Subset + { + wi::vector indices; + }; + wi::vector subsets; + }; + wi::vector lods(lod_count); + + const float target_error = lodErrorSlider.GetValue(); + + for (size_t i = 0; i < lod_count; ++i) + { + lods[i].subsets.resize(mesh->subsets_per_lod); + for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + lods[i].subsets[subsetIndex].indices.resize(subset.indexCount); + for (uint32_t ind = 0; ind < subset.indexCount; ++ind) + { + lods[i].subsets[subsetIndex].indices[ind] = mesh->indices[subset.indexOffset + ind]; + } + } + } + + for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + + float threshold = wi::math::Lerp(0, 0.9f, wi::math::saturate(lodQualitySlider.GetValue())); + for (size_t i = 1; i < lod_count; ++i) + { + wi::vector& lod = lods[i].subsets[subsetIndex].indices; + + size_t target_index_count = size_t(mesh->indices.size() * threshold) / 3 * 3; + + // we can simplify all the way from base level or from the last result + // simplifying from the base level sometimes produces better results, but simplifying from last level is faster + //const wi::vector& source = lods[0].subsets[subsetIndex].indices; + const wi::vector& source = lods[i - 1].subsets[subsetIndex].indices; + + if (source.size() < target_index_count) + target_index_count = source.size(); + + lod.resize(source.size()); + if (lodSloppyCheckBox.GetCheck()) + { + lod.resize(meshopt_simplifySloppy(&lod[0], &source[0], source.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), target_index_count, target_error)); + } + else + { + lod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), target_index_count, target_error)); + } + + threshold *= threshold; + } + + // optimize each individual LOD for vertex cache & overdraw + for (size_t i = 0; i < lod_count; ++i) + { + wi::vector& lod = lods[i].subsets[subsetIndex].indices; + + meshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh->vertex_positions.size()); + meshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), 1.0f); + } + } + + mesh->indices.clear(); + wi::vector subsets; + for (size_t i = 0; i < lod_count; ++i) + { + for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + subsets.emplace_back(); + subsets.back() = subset; + subsets.back().indexOffset = (uint32_t)mesh->indices.size(); + subsets.back().indexCount = (uint32_t)lods[i].subsets[subsetIndex].indices.size(); + for (auto& x : lods[i].subsets[subsetIndex].indices) + { + mesh->indices.push_back(x); + } + } + } + mesh->subsets = subsets; + + mesh->CreateRenderData(); + SetEntity(entity, subset); + } + }); + AddWidget(&lodgenButton); + + lodCountSlider.Create(2, 10, 6, 8, "LOD Count: "); + lodCountSlider.SetTooltip("This is how many levels of detail will be created."); + lodCountSlider.SetSize(XMFLOAT2(100, hei)); + lodCountSlider.SetPos(XMFLOAT2(x + 280, y += step)); + AddWidget(&lodCountSlider); + + lodQualitySlider.Create(0.1f, 1.0f, 0.5f, 10000, "LOD Quality: "); + lodQualitySlider.SetTooltip("Lower values will make LODs more agressively simplified."); + lodQualitySlider.SetSize(XMFLOAT2(100, hei)); + lodQualitySlider.SetPos(XMFLOAT2(x + 280, y += step)); + AddWidget(&lodQualitySlider); + + lodErrorSlider.Create(0.01f, 0.1f, 0.03f, 10000, "LOD Error: "); + lodErrorSlider.SetTooltip("Lower values will make more precise levels of detail."); + lodErrorSlider.SetSize(XMFLOAT2(100, hei)); + lodErrorSlider.SetPos(XMFLOAT2(x + 280, y += step)); + AddWidget(&lodErrorSlider); + + lodSloppyCheckBox.Create("Sloppy LOD: "); + lodSloppyCheckBox.SetTooltip("Use the sloppy simplification algorithm, which is faster but doesn't preserve shape well."); + lodSloppyCheckBox.SetSize(XMFLOAT2(hei, hei)); + lodSloppyCheckBox.SetPos(XMFLOAT2(x + 280, y += step)); + AddWidget(&lodSloppyCheckBox); + Translate(XMFLOAT3((float)editor->GetLogicalWidth() - 1000, 80, 0)); SetVisible(false); @@ -659,12 +773,6 @@ void MeshWindow::SetEntity(Entity entity, int subset) const MeshComponent* mesh = scene.meshes.GetComponent(entity); - if (mesh == nullptr || !mesh->IsTerrain()) - { - terragen.Cleanup(); - terragen.SetVisible(false); - } - if (mesh != nullptr) { const NameComponent& name = *scene.names.GetComponent(entity); @@ -673,7 +781,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) ss += "Mesh name: " + name.name + "\n"; ss += "Vertex count: " + std::to_string(mesh->vertex_positions.size()) + "\n"; ss += "Index count: " + std::to_string(mesh->indices.size()) + "\n"; - ss += "Subset count: " + std::to_string(mesh->subsets.size()) + "\n"; + ss += "Subset count: " + std::to_string(mesh->subsets.size()) + " (" + std::to_string(mesh->GetLODCount()) + " LODs)\n"; ss += "GPU memory: " + std::to_string((mesh->generalBuffer.GetDesc().size + mesh->streamoutBuffer.GetDesc().size) / 1024.0f / 1024.0f) + " MB\n"; ss += "\nVertex buffers: "; if (mesh->vb_pos_nor_wind.IsValid()) ss += "position; "; @@ -697,7 +805,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) } if (subset >= 0) { - subsetComboBox.SetSelected(subset); + subsetComboBox.SetSelectedWithoutCallback(subset); } subsetMaterialComboBox.ClearItems(); @@ -766,6 +874,17 @@ void MeshWindow::SetEntity(Entity entity, int subset) morphTargetCombo.SetSelected(selected); } SetEnabled(true); + + if (mesh->targets.empty()) + { + morphTargetCombo.SetEnabled(false); + morphTargetSlider.SetEnabled(false); + } + else + { + morphTargetCombo.SetEnabled(true); + morphTargetSlider.SetEnabled(true); + } } else { @@ -773,5 +892,5 @@ void MeshWindow::SetEntity(Entity entity, int subset) SetEnabled(false); } - terrainGenButton.SetEnabled(true); + mergeButton.SetEnabled(true); } diff --git a/Editor/MeshWindow.h b/Editor/MeshWindow.h index 4d2b52830..07707aa80 100644 --- a/Editor/MeshWindow.h +++ b/Editor/MeshWindow.h @@ -5,6 +5,7 @@ class EditorComponent; class MeshWindow : public wi::gui::Window { + bool terragen_initialized = false; public: void Create(EditorComponent* editor); @@ -29,15 +30,21 @@ public: wi::gui::Button computeNormalsHardButton; wi::gui::Button recenterButton; wi::gui::Button recenterToBottomButton; + wi::gui::Button mergeButton; wi::gui::Button optimizeButton; wi::gui::CheckBox terrainCheckBox; wi::gui::ComboBox terrainMat1Combo; wi::gui::ComboBox terrainMat2Combo; wi::gui::ComboBox terrainMat3Combo; - wi::gui::Button terrainGenButton; wi::gui::ComboBox morphTargetCombo; wi::gui::Slider morphTargetSlider; + + wi::gui::Button lodgenButton; + wi::gui::Slider lodCountSlider; + wi::gui::Slider lodQualitySlider; + wi::gui::Slider lodErrorSlider; + wi::gui::CheckBox lodSloppyCheckBox; }; diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp index 145943f15..fd4738347 100644 --- a/Editor/ObjectWindow.cpp +++ b/Editor/ObjectWindow.cpp @@ -261,7 +261,7 @@ void ObjectWindow::Create(EditorComponent* editor) this->editor = editor; wi::gui::Window::Create("Object Window"); - SetSize(XMFLOAT2(660, 500)); + SetSize(XMFLOAT2(660, 520)); float x = 200; float y = 0; @@ -328,6 +328,19 @@ void ObjectWindow::Create(EditorComponent* editor) }); AddWidget(&cascadeMaskSlider); + lodSlider.Create(0.001f, 10, 1, 10000, "LOD Multiplier: "); + lodSlider.SetTooltip("How much the distance to camera will affect LOD selection. (If the mesh has lods)"); + lodSlider.SetSize(XMFLOAT2(100, hei)); + lodSlider.SetPos(XMFLOAT2(x, y += step)); + lodSlider.OnSlide([&](wi::gui::EventArgs args) { + ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + if (object != nullptr) + { + object->lod_distance_multiplier = args.fValue; + } + }); + AddWidget(&lodSlider); + y += step; physicsLabel.Create("PHYSICSLABEL"); @@ -819,6 +832,7 @@ void ObjectWindow::SetEntity(Entity entity) shadowCheckBox.SetCheck(object->IsCastingShadow()); cascadeMaskSlider.SetValue((float)object->cascadeMask); ditherSlider.SetValue(object->GetTransparency()); + lodSlider.SetValue(object->lod_distance_multiplier); switch (colorComboBox.GetSelected()) { diff --git a/Editor/ObjectWindow.h b/Editor/ObjectWindow.h index 206798e74..55c87a518 100644 --- a/Editor/ObjectWindow.h +++ b/Editor/ObjectWindow.h @@ -17,6 +17,7 @@ public: wi::gui::CheckBox shadowCheckBox; wi::gui::Slider ditherSlider; wi::gui::Slider cascadeMaskSlider; + wi::gui::Slider lodSlider; wi::gui::ComboBox colorComboBox; wi::gui::ColorPicker colorPicker; diff --git a/Editor/TerrainGenerator.cpp b/Editor/TerrainGenerator.cpp new file mode 100644 index 000000000..30744d5fb --- /dev/null +++ b/Editor/TerrainGenerator.cpp @@ -0,0 +1,947 @@ +#include "stdafx.h" +#include "TerrainGenerator.h" + +#include "Utility/stb_image.h" + +using namespace wi::ecs; +using namespace wi::scene; + +enum PRESET +{ + PRESET_HILLS, + PRESET_ISLANDS, + PRESET_MOUNTAINS, + PRESET_ARCTIC, +}; + +void TerrainGenerator::init() +{ + terrainEntity = CreateEntity(); + + indices.clear(); + lods.clear(); + lods.resize(max_lod); + for (int lod = 0; lod < max_lod; ++lod) + { + lods[lod].indexOffset = (uint32_t)indices.size(); + + if (lod == 0) + { + for (int x = 0; x < width - 1; x++) + { + for (int z = 0; z < width - 1; z++) + { + int lowerLeft = x + z * width; + int lowerRight = (x + 1) + z * width; + int topLeft = x + (z + 1) * width; + int topRight = (x + 1) + (z + 1) * width; + + indices.push_back(topLeft); + indices.push_back(lowerLeft); + indices.push_back(lowerRight); + + indices.push_back(topLeft); + indices.push_back(lowerRight); + indices.push_back(topRight); + } + } + } + else + { + const int step = 1 << lod; + // inner grid: + for (int x = 1; x < width - 2; x += step) + { + for (int z = 1; z < width - 2; z += step) + { + int lowerLeft = x + z * width; + int lowerRight = (x + step) + z * width; + int topLeft = x + (z + step) * width; + int topRight = (x + step) + (z + step) * width; + + indices.push_back(topLeft); + indices.push_back(lowerLeft); + indices.push_back(lowerRight); + + indices.push_back(topLeft); + indices.push_back(lowerRight); + indices.push_back(topRight); + } + } + // bottom border: + for (int x = 0; x < width - 1; ++x) + { + const int z = 0; + int current = x + z * width; + int neighbor = x + 1 + z * width; + int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z + 1) * width; + + indices.push_back(current); + indices.push_back(neighbor); + indices.push_back(connection); + + if (((x - 1) % (step)) == step / 2) // halfway fill triangle + { + int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z + 1) * width; + + indices.push_back(current); + indices.push_back(connection); + indices.push_back(connection1); + } + } + // top border: + for (int x = 0; x < width - 1; ++x) + { + const int z = width - 1; + int current = x + z * width; + int neighbor = x + 1 + z * width; + int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z - 1) * width; + + indices.push_back(current); + indices.push_back(connection); + indices.push_back(neighbor); + + if (((x - 1) % (step)) == step / 2) // halfway fill triangle + { + int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z - 1) * width; + + indices.push_back(current); + indices.push_back(connection1); + indices.push_back(connection); + } + } + // left border: + for (int z = 0; z < width - 1; ++z) + { + const int x = 0; + int current = x + z * width; + int neighbor = x + (z + 1) * width; + int connection = x + 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * width; + + indices.push_back(current); + indices.push_back(connection); + indices.push_back(neighbor); + + if (((z - 1) % (step)) == step / 2) // halfway fill triangle + { + int connection1 = x + 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * width; + + indices.push_back(current); + indices.push_back(connection1); + indices.push_back(connection); + } + } + // right border: + for (int z = 0; z < width - 1; ++z) + { + const int x = width - 1; + int current = x + z * width; + int neighbor = x + (z + 1) * width; + int connection = x - 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * width; + + indices.push_back(current); + indices.push_back(neighbor); + indices.push_back(connection); + + if (((z - 1) % (step)) == step / 2) // halfway fill triangle + { + int connection1 = x - 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * width; + + indices.push_back(current); + indices.push_back(connection); + indices.push_back(connection1); + } + } + } + + lods[lod].indexCount = (uint32_t)indices.size() - lods[lod].indexOffset; + } + + RemoveWidgets(); + ClearTransform(); + + wi::gui::Window::Create("TerraGen (Preview version)"); + SetSize(XMFLOAT2(410, 560)); + + float xx = 150; + float yy = 0; + float stepstep = 25; + float heihei = 20; + + centerToCamCheckBox.Create("Center to Cam: "); + centerToCamCheckBox.SetTooltip("Automatically generate chunks around camera. This sets the center chunk to camera position."); + centerToCamCheckBox.SetSize(XMFLOAT2(heihei, heihei)); + centerToCamCheckBox.SetPos(XMFLOAT2(xx, yy += stepstep)); + centerToCamCheckBox.SetCheck(true); + AddWidget(¢erToCamCheckBox); + + removalCheckBox.Create("Removal: "); + removalCheckBox.SetTooltip("Automatically remove chunks that are farther than generation distance around center chunk."); + removalCheckBox.SetSize(XMFLOAT2(heihei, heihei)); + removalCheckBox.SetPos(XMFLOAT2(xx + 100, yy)); + removalCheckBox.SetCheck(true); + AddWidget(&removalCheckBox); + + lodSlider.Create(0.0001f, 0.01f, 0.005f, 10000, "LOD Distance: "); + lodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier.\nLow values increase LOD detail in distance"); + lodSlider.SetSize(XMFLOAT2(200, heihei)); + lodSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + lodSlider.OnSlide([this](wi::gui::EventArgs args) { + for (auto& it : chunks) + { + const ChunkData& chunk_data = it.second; + if (chunk_data.entity != INVALID_ENTITY) + { + ObjectComponent* object = scene->objects.GetComponent(chunk_data.entity); + if (object != nullptr) + { + object->lod_distance_multiplier = args.fValue; + } + } + } + }); + AddWidget(&lodSlider); + + generationSlider.Create(0, 16, 12, 16, "Generation Distance: "); + generationSlider.SetTooltip("How far out chunks will be generated (value is in number of chunks)"); + generationSlider.SetSize(XMFLOAT2(200, heihei)); + generationSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&generationSlider); + + presetCombo.Create("Preset: "); + presetCombo.SetTooltip("Select a terrain preset"); + presetCombo.SetSize(XMFLOAT2(200, heihei)); + presetCombo.SetPos(XMFLOAT2(xx, yy += stepstep)); + presetCombo.AddItem("Hills", PRESET_HILLS); + presetCombo.AddItem("Islands", PRESET_ISLANDS); + presetCombo.AddItem("Mountains", PRESET_MOUNTAINS); + presetCombo.AddItem("Arctic", PRESET_ARCTIC); + presetCombo.OnSelect([=](wi::gui::EventArgs args) { + switch (args.userdata) + { + default: + case PRESET_HILLS: + seedSlider.SetValue(5333); + bottomLevelSlider.SetValue(-60); + topLevelSlider.SetValue(380); + perlinBlendSlider.SetValue(0.5f); + perlinFrequencySlider.SetValue(0.0008f); + perlinOctavesSlider.SetValue(6); + voronoiBlendSlider.SetValue(0.5f); + voronoiFrequencySlider.SetValue(0.001f); + voronoiFadeSlider.SetValue(2.59f); + voronoiShapeSlider.SetValue(0.7f); + voronoiFalloffSlider.SetValue(6); + voronoiPerturbationSlider.SetValue(0.1f); + region1Slider.SetValue(1); + region2Slider.SetValue(2); + region3Slider.SetValue(8); + break; + case PRESET_ISLANDS: + seedSlider.SetValue(4691); + bottomLevelSlider.SetValue(-79); + topLevelSlider.SetValue(520); + perlinBlendSlider.SetValue(0.5f); + perlinFrequencySlider.SetValue(0.000991f); + perlinOctavesSlider.SetValue(6); + voronoiBlendSlider.SetValue(0.5f); + voronoiFrequencySlider.SetValue(0.000317f); + voronoiFadeSlider.SetValue(8.2f); + voronoiShapeSlider.SetValue(0.126f); + voronoiFalloffSlider.SetValue(1.392f); + voronoiPerturbationSlider.SetValue(0.126f); + region1Slider.SetValue(8); + region2Slider.SetValue(0.7f); + region3Slider.SetValue(8); + break; + case PRESET_MOUNTAINS: + seedSlider.SetValue(8863); + bottomLevelSlider.SetValue(0); + topLevelSlider.SetValue(2960); + perlinBlendSlider.SetValue(0.5f); + perlinFrequencySlider.SetValue(0.00279f); + perlinOctavesSlider.SetValue(8); + voronoiBlendSlider.SetValue(0.5f); + voronoiFrequencySlider.SetValue(0.000496f); + voronoiFadeSlider.SetValue(5.2f); + voronoiShapeSlider.SetValue(0.412f); + voronoiFalloffSlider.SetValue(1.456f); + voronoiPerturbationSlider.SetValue(0.092f); + region1Slider.SetValue(1); + region2Slider.SetValue(1); + region3Slider.SetValue(0.8f); + break; + case PRESET_ARCTIC: + seedSlider.SetValue(2124); + bottomLevelSlider.SetValue(-50); + topLevelSlider.SetValue(40); + perlinBlendSlider.SetValue(1); + perlinFrequencySlider.SetValue(0.002f); + perlinOctavesSlider.SetValue(4); + voronoiBlendSlider.SetValue(1); + voronoiFrequencySlider.SetValue(0.004f); + voronoiFadeSlider.SetValue(1.8f); + voronoiShapeSlider.SetValue(0.518f); + voronoiFalloffSlider.SetValue(0.2f); + voronoiPerturbationSlider.SetValue(0.298f); + region1Slider.SetValue(8); + region2Slider.SetValue(8); + region3Slider.SetValue(0); + break; + } + Generation_Restart(); + }); + AddWidget(&presetCombo); + + seedSlider.Create(1, 12345, 3926, 12344, "Seed: "); + seedSlider.SetTooltip("Seed for terrain randomness"); + seedSlider.SetSize(XMFLOAT2(200, heihei)); + seedSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&seedSlider); + + bottomLevelSlider.Create(-100, 0, -60, 10000, "Bottom Level: "); + bottomLevelSlider.SetTooltip("Terrain mesh grid lowest level"); + bottomLevelSlider.SetSize(XMFLOAT2(200, heihei)); + bottomLevelSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&bottomLevelSlider); + + topLevelSlider.Create(0, 5000, 380, 10000, "Top Level: "); + topLevelSlider.SetTooltip("Terrain mesh grid topmost level"); + topLevelSlider.SetSize(XMFLOAT2(200, heihei)); + topLevelSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&topLevelSlider); + + perlinBlendSlider.Create(0, 1, 0.5f, 10000, "Perlin Blend: "); + perlinBlendSlider.SetTooltip("Amount of perlin noise to use"); + perlinBlendSlider.SetSize(XMFLOAT2(200, heihei)); + perlinBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&perlinBlendSlider); + + perlinFrequencySlider.Create(0.0001f, 0.01f, 0.0008f, 10000, "Perlin Frequency: "); + perlinFrequencySlider.SetTooltip("Frequency for the perlin noise"); + perlinFrequencySlider.SetSize(XMFLOAT2(200, heihei)); + perlinFrequencySlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&perlinFrequencySlider); + + perlinOctavesSlider.Create(1, 8, 6, 7, "Perlin Octaves: "); + perlinOctavesSlider.SetTooltip("Octave count for the perlin noise"); + perlinOctavesSlider.SetSize(XMFLOAT2(200, heihei)); + perlinOctavesSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&perlinOctavesSlider); + + voronoiBlendSlider.Create(0, 1, 0.5f, 10000, "Voronoi Blend: "); + voronoiBlendSlider.SetTooltip("Amount of voronoi to use for elevation"); + voronoiBlendSlider.SetSize(XMFLOAT2(200, heihei)); + voronoiBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiBlendSlider); + + voronoiFrequencySlider.Create(0.0001f, 0.01f, 0.001f, 10000, "Voronoi Frequency: "); + voronoiFrequencySlider.SetTooltip("Voronoi can create distinctly elevated areas, the more cells there are, smaller the consecutive areas"); + voronoiFrequencySlider.SetSize(XMFLOAT2(200, heihei)); + voronoiFrequencySlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiFrequencySlider); + + voronoiFadeSlider.Create(0, 100, 2.59f, 10000, "Voronoi Fade: "); + voronoiFadeSlider.SetTooltip("Fade out voronoi regions by distance from cell's center"); + voronoiFadeSlider.SetSize(XMFLOAT2(200, heihei)); + voronoiFadeSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiFadeSlider); + + voronoiShapeSlider.Create(0, 1, 0.7f, 10000, "Voronoi Shape: "); + voronoiShapeSlider.SetTooltip("How much the voronoi shape will be kept"); + voronoiShapeSlider.SetSize(XMFLOAT2(200, heihei)); + voronoiShapeSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiShapeSlider); + + voronoiFalloffSlider.Create(0, 8, 6, 10000, "Voronoi Falloff: "); + voronoiFalloffSlider.SetTooltip("Controls the falloff of the voronoi distance fade effect"); + voronoiFalloffSlider.SetSize(XMFLOAT2(200, heihei)); + voronoiFalloffSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiFalloffSlider); + + voronoiPerturbationSlider.Create(0, 1, 0.1f, 10000, "Voronoi Perturbation: "); + voronoiPerturbationSlider.SetTooltip("Controls the random look of voronoi region edges"); + voronoiPerturbationSlider.SetSize(XMFLOAT2(200, heihei)); + voronoiPerturbationSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&voronoiPerturbationSlider); + + + heightmapButton.Create("Load Heightmap..."); + heightmapButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center."); + heightmapButton.SetSize(XMFLOAT2(200, heihei)); + heightmapButton.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&heightmapButton); + + heightmapBlendSlider.Create(0, 1, 1, 10000, "Heightmap Blend: "); + heightmapBlendSlider.SetTooltip("Amount of displacement coming from the heightmap texture"); + heightmapBlendSlider.SetSize(XMFLOAT2(200, heihei)); + heightmapBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(&heightmapBlendSlider); + + region1Slider.Create(0, 8, 1, 10000, "Slope Region: "); + region1Slider.SetTooltip("The region's falloff power"); + region1Slider.SetSize(XMFLOAT2(200, heihei)); + region1Slider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(®ion1Slider); + + region2Slider.Create(0, 8, 2, 10000, "Low Altitude Region: "); + region2Slider.SetTooltip("The region's falloff power"); + region2Slider.SetSize(XMFLOAT2(200, heihei)); + region2Slider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(®ion2Slider); + + region3Slider.Create(0, 8, 8, 10000, "High Altitude Region: "); + region3Slider.SetTooltip("The region's falloff power"); + region3Slider.SetSize(XMFLOAT2(200, heihei)); + region3Slider.SetPos(XMFLOAT2(xx, yy += stepstep)); + AddWidget(®ion3Slider); + + + auto generate_callback = [=](wi::gui::EventArgs args) { + Generation_Restart(); + }; + seedSlider.OnSlide(generate_callback); + bottomLevelSlider.OnSlide(generate_callback); + topLevelSlider.OnSlide(generate_callback); + perlinFrequencySlider.OnSlide(generate_callback); + perlinBlendSlider.OnSlide(generate_callback); + perlinOctavesSlider.OnSlide(generate_callback); + voronoiBlendSlider.OnSlide(generate_callback); + voronoiFrequencySlider.OnSlide(generate_callback); + voronoiFadeSlider.OnSlide(generate_callback); + voronoiShapeSlider.OnSlide(generate_callback); + voronoiFalloffSlider.OnSlide(generate_callback); + voronoiPerturbationSlider.OnSlide(generate_callback); + heightmapBlendSlider.OnSlide(generate_callback); + region1Slider.OnSlide(generate_callback); + region2Slider.OnSlide(generate_callback); + region3Slider.OnSlide(generate_callback); + + heightmapButton.OnClick([=](wi::gui::EventArgs args) { + + wi::helper::FileDialogParams params; + params.type = wi::helper::FileDialogParams::OPEN; + params.description = "Texture"; + params.extensions = wi::resourcemanager::GetSupportedImageExtensions(); + wi::helper::FileDialog(params, [=](std::string fileName) { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + + heightmap = {}; + int bpp = 0; + stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap.width, &heightmap.height, &bpp, 1); + if (rgba != nullptr) + { + heightmap.data.resize(heightmap.width * heightmap.height); + for (int i = 0; i < heightmap.width * heightmap.height; ++i) + { + heightmap.data[i] = rgba[i]; + } + stbi_image_free(rgba); + Generation_Restart(); + } + }); + }); + }); + + heightmap = {}; + + SetPos(XMFLOAT2(50, 110)); + SetVisible(false); + SetEnabled(true); + + presetCombo.SetSelectedByUserdata(PRESET_HILLS); +} + +void TerrainGenerator::Generation_Restart() +{ + Generation_Cancel(); + generation_scene.Clear(); + generation_entities.clear(); + + // If these already exist, save them before recreating: + // (This is helpful if eg: someone edits them in the editor and then regenerates the terrain) + if (materialEntity_Base != INVALID_ENTITY) + { + MaterialComponent* material = scene->materials.GetComponent(materialEntity_Base); + if (material != nullptr) + { + material_Base = *material; + } + } + if (materialEntity_Slope != INVALID_ENTITY) + { + MaterialComponent* material = scene->materials.GetComponent(materialEntity_Slope); + if (material != nullptr) + { + material_Slope = *material; + } + } + if (materialEntity_LowAltitude != INVALID_ENTITY) + { + MaterialComponent* material = scene->materials.GetComponent(materialEntity_LowAltitude); + if (material != nullptr) + { + material_LowAltitude = *material; + } + } + if (materialEntity_HighAltitude != INVALID_ENTITY) + { + MaterialComponent* material = scene->materials.GetComponent(materialEntity_HighAltitude); + if (material != nullptr) + { + material_HighAltitude = *material; + } + } + + chunks.clear(); + + scene->Entity_Remove(terrainEntity); + scene->transforms.Create(terrainEntity); + scene->names.Create(terrainEntity) = "terrain"; + + materialEntity_Base = scene->Entity_CreateMaterial("terrainMaterial_Base"); + materialEntity_Slope = scene->Entity_CreateMaterial("terrainMaterial_Slope"); + materialEntity_LowAltitude = scene->Entity_CreateMaterial("terrainMaterial_LowAltitude"); + materialEntity_HighAltitude = scene->Entity_CreateMaterial("terrainMaterial_HighAltitude"); + scene->Component_Attach(materialEntity_Base, terrainEntity); + scene->Component_Attach(materialEntity_Slope, terrainEntity); + scene->Component_Attach(materialEntity_LowAltitude, terrainEntity); + scene->Component_Attach(materialEntity_HighAltitude, terrainEntity); + // init/restore materials: + *scene->materials.GetComponent(materialEntity_Base) = material_Base; + *scene->materials.GetComponent(materialEntity_Slope) = material_Slope; + *scene->materials.GetComponent(materialEntity_LowAltitude) = material_LowAltitude; + *scene->materials.GetComponent(materialEntity_HighAltitude) = material_HighAltitude; + + const uint32_t seed = (uint32_t)seedSlider.GetValue(); + perlin.init(seed); + + // Add some nice weather and lighting if there is none yet: + if (scene->weathers.GetCount() == 0) + { + Entity weatherEntity = CreateEntity(); + WeatherComponent& weather = scene->weathers.Create(weatherEntity); + scene->names.Create(weatherEntity) = "terrainWeather"; + scene->Component_Attach(weatherEntity, terrainEntity); + weather.ambient = XMFLOAT3(0.2f, 0.2f, 0.2f); + weather.SetRealisticSky(true); + weather.SetVolumetricClouds(true); + weather.volumetricCloudParameters.CoverageAmount = 0.4f; + weather.volumetricCloudParameters.CoverageMinimum = 1.35f; + if (presetCombo.GetItemUserData(presetCombo.GetSelected()) == PRESET_ISLANDS) + { + weather.SetOceanEnabled(true); + } + else + { + scene->ocean = {}; + } + weather.oceanParameters.waterHeight = -40; + weather.oceanParameters.wave_amplitude = 120; + weather.fogStart = 10; + weather.fogEnd = 100000; + weather.SetHeightFog(true); + weather.fogHeightStart = 0; + weather.fogHeightEnd = 100; + weather.windDirection = XMFLOAT3(0.05f, 0.05f, 0.05f); + weather.windSpeed = 4; + } + if (scene->lights.GetCount() == 0) + { + Entity sunEntity = scene->Entity_CreateLight("terrainSun"); + scene->Component_Attach(sunEntity, terrainEntity); + LightComponent& light = *scene->lights.GetComponent(sunEntity); + light.SetType(LightComponent::LightType::DIRECTIONAL); + light.energy = 6; + light.SetCastShadow(true); + //light.SetVolumetricsEnabled(true); + TransformComponent& transform = *scene->transforms.GetComponent(sunEntity); + transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV4, 0, XM_PIDIV4)); + } +} + +void TerrainGenerator::Generation_Update() +{ + // The generation task is always cancelled every frame so we are sure that generation is not running at this point + Generation_Cancel(); + + if (terrainEntity == INVALID_ENTITY || !scene->transforms.Contains(terrainEntity)) + { + chunks.clear(); + return; + } + + // What was generated, will be merged in to the main scene and parented to the terrain entity + scene->Merge(generation_scene); + for (Entity entity : generation_entities) + { + scene->Component_Attach(entity, terrainEntity); + } + generation_entities.clear(); + + if (centerToCamCheckBox.GetCheck()) + { + const CameraComponent& camera = GetCamera(); + center_chunk.x = (int)std::floor((camera.Eye.x + half_width) * width_rcp); + center_chunk.z = (int)std::floor((camera.Eye.z + half_width) * width_rcp); + } + + // Chunk removal checks: + if (removalCheckBox.GetCheck()) + { + const int removal_threshold = (int)generationSlider.GetValue() + 2; + for (auto it = chunks.begin(); it != chunks.end();) + { + const Chunk& chunk = it->first; + ChunkData& chunk_data = it->second; + const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z)); + if (dist > removal_threshold) + { + scene->Entity_Remove(it->second.entity); + it = chunks.erase(it); + continue; // don't increment iterator + } + else + { + // Grass patch removal: + if (chunk_data.grass.meshID != INVALID_ENTITY) + { + if (dist > 1) + { + wi::HairParticleSystem* grass = scene->hairs.GetComponent(chunk_data.entity); + if (grass != nullptr) + { + // remove this chunk's grass patch from the scene + scene->hairs.Remove(chunk_data.entity); + chunk_data.grass_exists = false; // grass can be generated here by generation thread... + } + } + } + } + it++; + } + } + + // Start the generation on a background thread and keep it running until the next frame + wi::jobsystem::Execute(generation_workload, [=](wi::jobsystem::JobArgs args) { + + const float lodMultiplier = lodSlider.GetValue(); + const int generation = (int)generationSlider.GetValue(); + const float bottomLevel = bottomLevelSlider.GetValue(); + const float topLevel = topLevelSlider.GetValue(); + const float heightmapBlend = heightmapBlendSlider.GetValue(); + const float perlinBlend = perlinBlendSlider.GetValue(); + const uint32_t seed = (uint32_t)seedSlider.GetValue(); + const int perlinOctaves = (int)perlinOctavesSlider.GetValue(); + const float perlinFrequency = perlinFrequencySlider.GetValue(); + const float voronoiBlend = voronoiBlendSlider.GetValue(); + const float voronoiFrequency = voronoiFrequencySlider.GetValue(); + const float voronoiFade = voronoiFadeSlider.GetValue(); + const float voronoiShape = voronoiShapeSlider.GetValue(); + const float voronoiFalloff = voronoiFalloffSlider.GetValue(); + const float voronoiPerturbation = voronoiPerturbationSlider.GetValue(); + const float region1 = region1Slider.GetValue(); + const float region2 = region2Slider.GetValue(); + const float region3 = region3Slider.GetValue(); + + auto request_chunk = [&](int offset_x, int offset_z) + { + Chunk chunk = center_chunk; + chunk.x += offset_x; + chunk.z += offset_z; + auto it = chunks.find(chunk); + if (it == chunks.end() || it->second.entity == INVALID_ENTITY) + { + // Generate a new chunk: + ChunkData& chunk_data = chunks[chunk]; + + chunk_data.entity = generation_scene.Entity_CreateObject("chunk_" + std::to_string(chunk.x) + "_" + std::to_string(chunk.z)); + ObjectComponent& object = *generation_scene.objects.GetComponent(chunk_data.entity); + object.lod_distance_multiplier = lodMultiplier; + generation_entities.push_back(chunk_data.entity); + + TransformComponent& transform = *generation_scene.transforms.GetComponent(chunk_data.entity); + transform.ClearTransform(); + const XMFLOAT3 chunk_pos = XMFLOAT3(float(chunk.x * (width - 1)), 0, float(chunk.z * (width - 1))); + transform.Translate(chunk_pos); + transform.UpdateTransform(); + + MeshComponent& mesh = generation_scene.meshes.Create(chunk_data.entity); + object.meshID = chunk_data.entity; + mesh.indices = indices; + mesh.SetTerrain(true); + mesh.terrain_material1 = materialEntity_Slope; + mesh.terrain_material2 = materialEntity_LowAltitude; + mesh.terrain_material3 = materialEntity_HighAltitude; + for (auto& lod : lods) + { + mesh.subsets.emplace_back(); + mesh.subsets.back().materialID = materialEntity_Base; + mesh.subsets.back().indexCount = lod.indexCount; + mesh.subsets.back().indexOffset = lod.indexOffset; + } + mesh.subsets_per_lod = 1; + mesh.vertex_positions.resize(vertexCount); + mesh.vertex_normals.resize(vertexCount); + mesh.vertex_colors.resize(vertexCount); + mesh.vertex_uvset_0.resize(vertexCount); + mesh.vertex_uvset_1.resize(vertexCount); + mesh.vertex_atlas.resize(vertexCount); + + wi::HairParticleSystem grass; + grass.vertex_lengths.resize(vertexCount); + std::atomic grass_valid_vertex_count{ 0 }; + + // Do a parallel for loop over all the chunk's vertices and compute their properties: + wi::jobsystem::context ctx; + wi::jobsystem::Dispatch(ctx, vertexCount, width, [&](wi::jobsystem::JobArgs args) { + uint32_t index = args.jobIndex; + const float x = float(index % width) - half_width; + const float z = float(index / width) - half_width; + XMVECTOR corners[3]; + XMFLOAT2 corner_offsets[3] = { + XMFLOAT2(0, 0), + XMFLOAT2(1, 0), + XMFLOAT2(0, 1), + }; + for (int i = 0; i < arraysize(corners); ++i) + { + float height = 0; + const XMFLOAT2 world_pos = XMFLOAT2(chunk_pos.x + x + corner_offsets[i].x, chunk_pos.z + z + corner_offsets[i].y); + if (perlinBlend > 0) + { + XMFLOAT2 p = world_pos; + p.x *= perlinFrequency; + p.y *= perlinFrequency; + height += (perlin.compute(p.x, p.y, 0, perlinOctaves) * 0.5f + 0.5f) * perlinBlend; + } + if (voronoiBlend > 0) + { + XMFLOAT2 p = world_pos; + p.x *= voronoiFrequency; + p.y *= voronoiFrequency; + if (voronoiPerturbation > 0) + { + const float angle = perlin.compute(p.x, p.y, 0, 6) * XM_2PI; + p.x += std::sin(angle) * voronoiPerturbation; + p.y += std::cos(angle) * voronoiPerturbation; + } + wi::noise::voronoi::Result res = wi::noise::voronoi::compute(p.x, p.y, (float)seed); + float weight = std::pow(1 - wi::math::saturate((res.distance - voronoiShape) * voronoiFade), std::max(0.0001f, voronoiFalloff)); + height *= weight * voronoiBlend; + } + if (!heightmap.data.empty()) + { + XMFLOAT2 pixel = XMFLOAT2(world_pos.x + heightmap.width * 0.5f, world_pos.y + heightmap.height * 0.5f); + if (pixel.x >= 0 && pixel.x < heightmap.width && pixel.y >= 0 && pixel.y < heightmap.height) + { + const int idx = int(pixel.x) + int(pixel.y) * heightmap.width; + height = ((float)heightmap.data[idx] / 255.0f) * heightmapBlend; + } + } + height = wi::math::Lerp(bottomLevel, topLevel, height); + corners[i] = XMVectorSet(world_pos.x, height, world_pos.y, 0); + } + const float height = XMVectorGetY(corners[0]); + const XMVECTOR T = XMVectorSubtract(corners[2], corners[1]); + const XMVECTOR B = XMVectorSubtract(corners[1], corners[0]); + const XMVECTOR N = XMVector3Normalize(XMVector3Cross(T, B)); + XMFLOAT3 normal; + XMStoreFloat3(&normal, N); + + const float region_base = 1; + const float region_slope = std::pow(1.0f - wi::math::saturate(normal.y), region1); + const float region_low_altitude = bottomLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, bottomLevel, height)), region2); + const float region_high_altitude = topLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, topLevel, height)), region3); + + XMFLOAT4 materialBlendWeights(region_base, 0, 0, 0); + materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 1, 0, 0), region_slope); + materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 1, 0), region_low_altitude); + materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 0, 1), region_high_altitude); + const float weight_norm = 1.0f / (materialBlendWeights.x + materialBlendWeights.y + materialBlendWeights.z + materialBlendWeights.w); + materialBlendWeights.x *= weight_norm; + materialBlendWeights.y *= weight_norm; + materialBlendWeights.z *= weight_norm; + materialBlendWeights.w *= weight_norm; + + mesh.vertex_positions[index] = XMFLOAT3(x, height, z); + mesh.vertex_normals[index] = normal; + mesh.vertex_colors[index] = wi::Color::fromFloat4(materialBlendWeights); + const XMFLOAT2 uv = XMFLOAT2(x * width_rcp, z * width_rcp); + mesh.vertex_uvset_0[index] = uv; + mesh.vertex_uvset_1[index] = uv; + mesh.vertex_atlas[index] = uv; + + XMFLOAT3 vertex_pos(chunk_pos.x + x, height, chunk_pos.z + z); + + const float grass_noise_frequency = 0.1f; + const float grass_noise = perlin.compute(vertex_pos.x * grass_noise_frequency, vertex_pos.y * grass_noise_frequency, vertex_pos.z * grass_noise_frequency) * 0.5f + 0.5f; + const float region_grass = std::pow(materialBlendWeights.x * (1 - materialBlendWeights.w), 4.0f) * grass_noise; + if (region_grass > 0.1f) + { + grass_valid_vertex_count.fetch_add(1); + grass.vertex_lengths[index] = region_grass; + } + else + { + grass.vertex_lengths[index] = 0; + } + }); + wi::jobsystem::Wait(ctx); // wait until chunk's vertex buffer is fully generated + + mesh.CreateRenderData(); + + // If there were any vertices in this chunk that could be valid for grass, store the grass particle system: + if (grass_valid_vertex_count.load() > 0) + { + grass.meshID = chunk_data.entity; + grass.length = 5; + grass.strandCount = grass_valid_vertex_count.load() * 3; + grass.viewDistance = 80; + grass.frameCount = 2; + grass.framesX = 1; + grass.framesY = 2; + grass.frameStart = 0; + generation_scene.materials.Create(chunk_data.entity) = material_GrassParticle; + chunk_data.grass = std::move(grass); // the grass will be added to the scene later, only when the chunk is close to the camera (center chunk's neighbors) + } + + // Prop placement: + chunk_data.prop_rand.seed((uint32_t)chunk.compute_hash() ^ seed); + for (auto& prop : props) + { + std::uniform_int_distribution gen_distr(prop.min_count_per_chunk, prop.max_count_per_chunk); + int gen_count = gen_distr(chunk_data.prop_rand); + for (int i = 0; i < gen_count; ++i) + { + std::uniform_real_distribution float_distr(0.0f, 1.0f); + std::uniform_int_distribution ind_distr(0, lods[0].indexCount / 3 - 1); + uint32_t tri = ind_distr(chunk_data.prop_rand); // random triangle on the chunk mesh + uint32_t ind0 = mesh.indices[tri * 3 + 0]; + uint32_t ind1 = mesh.indices[tri * 3 + 1]; + uint32_t ind2 = mesh.indices[tri * 3 + 2]; + const XMFLOAT3& pos0 = mesh.vertex_positions[ind0]; + const XMFLOAT3& pos1 = mesh.vertex_positions[ind1]; + const XMFLOAT3& pos2 = mesh.vertex_positions[ind2]; + const uint32_t& col0 = mesh.vertex_colors[ind0]; + const uint32_t& col1 = mesh.vertex_colors[ind1]; + const uint32_t& col2 = mesh.vertex_colors[ind2]; + const XMFLOAT4 region0 = wi::Color(col0).toFloat4(); + const XMFLOAT4 region1 = wi::Color(col1).toFloat4(); + const XMFLOAT4 region2 = wi::Color(col2).toFloat4(); + // random barycentric coords on the triangle: + float f = float_distr(chunk_data.prop_rand); + float g = float_distr(chunk_data.prop_rand); + if (f + g > 1) + { + f = 1 - f; + g = 1 - g; + } + XMFLOAT3 vertex_pos; + vertex_pos.x = pos0.x + f * (pos1.x - pos0.x) + g * (pos2.x - pos0.x); + vertex_pos.y = pos0.y + f * (pos1.y - pos0.y) + g * (pos2.y - pos0.y); + vertex_pos.z = pos0.z + f * (pos1.z - pos0.z) + g * (pos2.z - pos0.z); + vertex_pos.x += chunk_pos.x; + vertex_pos.z += chunk_pos.z; + XMFLOAT4 region; + region.x = region0.x + f * (region1.x - region0.x) + g * (region2.x - region0.x); + region.y = region0.y + f * (region1.y - region0.y) + g * (region2.y - region0.y); + region.z = region0.z + f * (region1.z - region0.z) + g * (region2.z - region0.z); + region.w = region0.w + f * (region1.w - region0.w) + g * (region2.w - region0.w); + + const float noise = std::pow(perlin.compute(vertex_pos.x * prop.noise_frequency, vertex_pos.y * prop.noise_frequency, vertex_pos.z * prop.noise_frequency) * 0.5f + 0.5f, prop.noise_power); + const float chance = std::pow(((float*)®ion)[prop.region], prop.region_power) * noise; + if (chance > prop.threshold) + { + Entity entity = generation_scene.Entity_CreateObject(prop.name + std::to_string(i)); + ObjectComponent* object = generation_scene.objects.GetComponent(entity); + *object = prop.object; + TransformComponent* transform = generation_scene.transforms.GetComponent(entity); + XMFLOAT3 offset = vertex_pos; + offset.y += wi::math::Lerp(prop.min_y_offset, prop.max_y_offset, float_distr(chunk_data.prop_rand)); + transform->Translate(offset); + const float scaling = wi::math::Lerp(prop.min_size, prop.max_size, float_distr(chunk_data.prop_rand)); + transform->Scale(XMFLOAT3(scaling, scaling, scaling)); + transform->RotateRollPitchYaw(XMFLOAT3(0, XM_2PI * float_distr(chunk_data.prop_rand), 0)); + transform->UpdateTransform(); + generation_scene.Component_Attach(entity, chunk_data.entity); + } + } + } + + } + + // Grass patch placement: + it = chunks.find(chunk); + if (it != chunks.end() && it->second.entity != INVALID_ENTITY) + { + ChunkData& chunk_data = it->second; + if (chunk_data.grass.meshID != INVALID_ENTITY) + { + const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z)); + if (dist <= 1) + { + if (!chunk_data.grass_exists) + { + // add patch for this chunk + wi::HairParticleSystem& grass = generation_scene.hairs.Create(chunk_data.entity); + grass = chunk_data.grass; + const MeshComponent* mesh = generation_scene.meshes.GetComponent(chunk_data.entity); + if (mesh != nullptr) + { + grass.CreateRenderData(*mesh); + } + chunk_data.grass_exists = true; // don't generate more grass here + } + } + } + } + + }; + + // generate center chunk first: + request_chunk(0, 0); + if (generation_cancelled.load()) return; + + // then generate neighbor chunks in outward spiral: + for (int growth = 0; growth < generation; ++growth) + { + const int side = 2 * (growth + 1); + int x = -growth - 1; + int z = -growth - 1; + for (int i = 0; i < side; ++i) + { + request_chunk(x, z); + if (generation_cancelled.load()) return; + x++; + } + for (int i = 0; i < side; ++i) + { + request_chunk(x, z); + if (generation_cancelled.load()) return; + z++; + } + for (int i = 0; i < side; ++i) + { + request_chunk(x, z); + if (generation_cancelled.load()) return; + x--; + } + for (int i = 0; i < side; ++i) + { + request_chunk(x, z); + if (generation_cancelled.load()) return; + z--; + } + } + + }); + +} + +void TerrainGenerator::Generation_Cancel() +{ + generation_cancelled.store(true); // tell the generation thread that work must be stopped + wi::jobsystem::Wait(generation_workload); // waits until generation thread exits + generation_cancelled.store(false); // the next generation can run +} diff --git a/Editor/TerrainGenerator.h b/Editor/TerrainGenerator.h new file mode 100644 index 000000000..25ea9d54a --- /dev/null +++ b/Editor/TerrainGenerator.h @@ -0,0 +1,136 @@ +#pragma once +#include "WickedEngine.h" + +#include +#include +#include + +struct Chunk +{ + int x, z; + constexpr bool operator==(const Chunk& other) const + { + return (x == other.x) && (z == other.z); + } + inline size_t compute_hash() const + { + return ((std::hash()(x) ^ (std::hash()(z) << 1)) >> 1); + } +}; +namespace std +{ + template <> + struct hash + { + inline size_t operator()(const Chunk& chunk) const + { + return chunk.compute_hash(); + } + }; +} + +struct ChunkData +{ + wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; + wi::HairParticleSystem grass; + bool grass_exists = false; + std::mt19937 prop_rand; +}; + +struct TerrainGenerator : public wi::gui::Window +{ + inline static const int width = 64 + 3; // + 3: filler vertices for lod apron and grid perimeter + inline static const float half_width = (width - 1) * 0.5f; + inline static const float width_rcp = 1.0f / (width - 1); + inline static const uint32_t vertexCount = width * width; + inline static const int max_lod = (int)std::log2(width - 3) + 1; + wi::scene::Scene* scene = &wi::scene::GetScene(); // by default it uses the global scene, but this can be changed + wi::ecs::Entity terrainEntity = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity materialEntity_Base = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity materialEntity_Slope = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity materialEntity_LowAltitude = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity materialEntity_HighAltitude = wi::ecs::INVALID_ENTITY; + wi::scene::MaterialComponent material_Base; + wi::scene::MaterialComponent material_Slope; + wi::scene::MaterialComponent material_LowAltitude; + wi::scene::MaterialComponent material_HighAltitude; + wi::scene::MaterialComponent material_GrassParticle; + wi::unordered_map chunks; + wi::vector indices; + struct LOD + { + uint32_t indexOffset = 0; + uint32_t indexCount = 0; + }; + wi::vector lods; + wi::noise::Perlin perlin; + Chunk center_chunk = {}; + + struct HeightmapTexture + { + wi::vector data; + int width = 0; + int height = 0; + } heightmap; + + struct Prop + { + std::string name = "prop"; + wi::ecs::Entity mesh_entity = wi::ecs::INVALID_ENTITY; + wi::scene::ObjectComponent object; + int min_count_per_chunk = 0; // a chunk will try to generate min this many props of this type + int max_count_per_chunk = 10; // a chunk will try to generate max this many props of this type + int region = 0; // region selection in range [0,3] (0: base/grass, 1: slopes, 2: low altitude (bottom level-0), 3: high altitude (0-top level)) + float region_power = 1; // region weight affection power factor + float noise_frequency = 1; // perlin noise's frequency for placement factor + float noise_power = 1; // perlin noise's power + float threshold = 0.5f; // the chance of placement (higher is less chance) + float min_size = 1; // scaling randomization range min + float max_size = 1; // scaling randomization range max + float min_y_offset = 0; // min randomized offset on Y axis + float max_y_offset = 0; // max randomized offset on Y axis + }; + wi::vector props; + + // For generating scene on a background thread: + wi::scene::Scene generation_scene; // The background generation thread can safely add things to this, it will be merged into the main scene when it is safe to do so + wi::jobsystem::context generation_workload; + std::atomic_bool generation_cancelled; + wi::vector generation_entities; // because cannot parent to main scene on the background thread, parentable entities are collected + + wi::gui::CheckBox centerToCamCheckBox; + wi::gui::CheckBox removalCheckBox; + wi::gui::Slider lodSlider; + wi::gui::Slider generationSlider; + wi::gui::ComboBox presetCombo; + wi::gui::Slider seedSlider; + wi::gui::Slider bottomLevelSlider; + wi::gui::Slider topLevelSlider; + wi::gui::Slider perlinBlendSlider; + wi::gui::Slider perlinFrequencySlider; + wi::gui::Slider perlinOctavesSlider; + wi::gui::Slider voronoiBlendSlider; + wi::gui::Slider voronoiFrequencySlider; + wi::gui::Slider voronoiFadeSlider; + wi::gui::Slider voronoiShapeSlider; + wi::gui::Slider voronoiFalloffSlider; + wi::gui::Slider voronoiPerturbationSlider; + wi::gui::Button heightmapButton; + wi::gui::Slider heightmapBlendSlider; + + wi::gui::Slider region1Slider; + wi::gui::Slider region2Slider; + wi::gui::Slider region3Slider; + + // This needs to be called at least once before using the terrain generator + void init(); + + // Restarts the terrain generation from scratch + // This will remove previously existing terrain + void Generation_Restart(); + // This will run the actual generation tasks, call it once per frame + void Generation_Update(); + // Tells the generation thread that it should be cancelled and blocks until that is confirmed + void Generation_Cancel(); + +}; diff --git a/Editor/main_Windows.cpp b/Editor/main_Windows.cpp index a99ef48d4..8076a70df 100644 --- a/Editor/main_Windows.cpp +++ b/Editor/main_Windows.cpp @@ -69,6 +69,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, } } + editor.renderComponent.terragen.Generation_Cancel(); + return (int) msg.wParam; } diff --git a/Editor/meshoptimizer/allocator.cpp b/Editor/meshoptimizer/allocator.cpp index da7cc540b..072e8e51a 100644 --- a/Editor/meshoptimizer/allocator.cpp +++ b/Editor/meshoptimizer/allocator.cpp @@ -1,7 +1,7 @@ // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details #include "meshoptimizer.h" -void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*)) +void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*)) { meshopt_Allocator::Storage::allocate = allocate; meshopt_Allocator::Storage::deallocate = deallocate; diff --git a/Editor/meshoptimizer/meshoptimizer.h b/Editor/meshoptimizer/meshoptimizer.h index fe8d34973..be4b765d9 100644 --- a/Editor/meshoptimizer/meshoptimizer.h +++ b/Editor/meshoptimizer/meshoptimizer.h @@ -1,5 +1,5 @@ /** - * meshoptimizer - version 0.16 + * meshoptimizer - version 0.17 * * Copyright (C) 2016-2021, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer @@ -12,13 +12,22 @@ #include /* Version macro; major * 1000 + minor * 10 + patch */ -#define MESHOPTIMIZER_VERSION 160 /* 0.16 */ +#define MESHOPTIMIZER_VERSION 170 /* 0.17 */ /* If no API is defined, assume default */ #ifndef MESHOPTIMIZER_API #define MESHOPTIMIZER_API #endif +/* Set the calling-convention for alloc/dealloc function pointers */ +#ifndef MESHOPTIMIZER_ALLOC_CALLCONV +#ifdef _MSC_VER +#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl +#else +#define MESHOPTIMIZER_ALLOC_CALLCONV +#endif +#endif + /* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */ #define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API @@ -108,7 +117,7 @@ MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* dest * destination must contain enough space for the resulting index buffer (index_count*2 elements) * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** * Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement @@ -124,7 +133,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateAdjacencyIndexBuffer(unsigned in * destination must contain enough space for the resulting index buffer (index_count*4 elements) * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** * Vertex transform cache optimizer @@ -201,10 +210,10 @@ MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t MESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count); /** - * Experimental: Set index encoder format version + * Set index encoder format version * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+) */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version); +MESHOPTIMIZER_API void meshopt_encodeIndexVersion(int version); /** * Index buffer decoder @@ -217,15 +226,15 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version); MESHOPTIMIZER_API int meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); /** - * Experimental: Index sequence encoder + * Index sequence encoder * Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original. * Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better. * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space * * buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size) */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count); -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count); +MESHOPTIMIZER_API size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count); +MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count); /** * Index sequence decoder @@ -235,7 +244,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_ * * destination must contain enough space for the resulting index sequence (index_count elements) */ -MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); +MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); /** * Vertex buffer encoder @@ -250,10 +259,10 @@ MESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_ MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size); /** - * Experimental: Set vertex encoder format version + * Set vertex encoder format version * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeVertexVersion(int version); +MESHOPTIMIZER_API void meshopt_encodeVertexVersion(int version); /** * Vertex buffer decoder @@ -278,9 +287,30 @@ MESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t verte * meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M. * Each 32-bit component is decoded in isolation; stride must be divisible by 4. */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size); -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size); -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size); +MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride); +MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride); +MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride); + +/** + * Vertex buffer filter encoders + * These functions can be used to encode data in a format that meshopt_decodeFilter can decode + * + * meshopt_encodeFilterOct encodes unit vectors with K-bit (K <= 16) signed X/Y as an output. + * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is. + * Input data must contain 4 floats for every vector (count*4 total). + * + * meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding. + * Each component is stored as an 16-bit integer; stride must be equal to 8. + * Input data must contain 4 floats for every quaternion (count*4 total). + * + * meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24). + * Mantissa is shared between all components of a given vector as defined by stride; stride must be divisible by 4. + * Input data must contain stride/4 floats for every vector (count*stride/4 total). + * When individual (scalar) encoding is desired, simply pass stride=4 and adjust count accordingly. + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data); +MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data); +MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data); /** * Experimental: Mesh simplifier @@ -300,7 +330,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(unsigned int* destination, co /** * Experimental: Mesh simplifier (sloppy) - * Reduces the number of triangles in the mesh, sacrificing mesh apperance for simplification performance + * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance * The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error. * Returns the number of indices after simplification, with destination containing new index data * The resulting index buffer references vertices from the original vertex buffer. @@ -327,7 +357,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destinati /** * Experimental: Returns the error scaling factor used by the simplifier to convert between absolute and relative extents - * + * * Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error * Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error. */ @@ -412,7 +442,7 @@ struct meshopt_Meshlet }; /** - * Experimental: Meshlet builder + * Meshlet builder * Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer * The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers. * When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters. @@ -425,9 +455,9 @@ struct meshopt_Meshlet * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512) * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight); -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles); +MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight); +MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); +MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles); struct meshopt_Bounds { @@ -446,7 +476,7 @@ struct meshopt_Bounds }; /** - * Experimental: Cluster bounds generator + * Cluster bounds generator * Creates bounding volumes that can be used for frustum, backface and occlusion culling. * * For backface culling with orthographic projection, use the following formula to reject backfacing clusters: @@ -466,8 +496,8 @@ struct meshopt_Bounds * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer * index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size) */ -MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); -MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** * Experimental: Spatial sorter @@ -493,7 +523,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(unsigned int* desti * Note that all algorithms only allocate memory for temporary use. * allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first. */ -MESHOPTIMIZER_API void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*)); +MESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*)); #ifdef __cplusplus } /* extern "C" */ @@ -675,8 +705,8 @@ public: template struct StorageT { - static void* (*allocate)(size_t); - static void (*deallocate)(void*); + static void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t); + static void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*); }; typedef StorageT Storage; @@ -707,8 +737,8 @@ private: }; // This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker -template void* (*meshopt_Allocator::StorageT::allocate)(size_t) = operator new; -template void (*meshopt_Allocator::StorageT::deallocate)(void*) = operator delete; +template void* (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT::allocate)(size_t) = operator new; +template void (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT::deallocate)(void*) = operator delete; #endif /* Inline implementation for C++ templated wrappers */ diff --git a/Editor/meshoptimizer/simplifier.cpp b/Editor/meshoptimizer/simplifier.cpp index 9f56cdac9..bf1431269 100644 --- a/Editor/meshoptimizer/simplifier.cpp +++ b/Editor/meshoptimizer/simplifier.cpp @@ -356,7 +356,7 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned #if TRACE printf("locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\n", - int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3])); + int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3])); #endif } @@ -952,8 +952,8 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* float error_goal_perfect = edge_collapse_goal < collapse_count ? collapses[collapse_order[edge_collapse_goal]].error : 0.f; printf("removed %d triangles, error %e (goal %e); evaluated %d/%d collapses (done %d, skipped %d, invalid %d)\n", - int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_perfect), - int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2])); + int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_perfect), + int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2])); #endif return edge_collapses; @@ -1305,7 +1305,7 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, kinds[vertex_kind[i]] += remap[i] == i; printf("kinds: manifold %d, border %d, seam %d, complex %d, locked %d\n", - int(kinds[Kind_Manifold]), int(kinds[Kind_Border]), int(kinds[Kind_Seam]), int(kinds[Kind_Complex]), int(kinds[Kind_Locked])); + int(kinds[Kind_Manifold]), int(kinds[Kind_Border]), int(kinds[Kind_Seam]), int(kinds[Kind_Complex]), int(kinds[Kind_Locked])); #endif Vector3* vertex_positions = allocator.allocate(vertex_count); @@ -1463,9 +1463,9 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind #if TRACE printf("pass %d (%s): grid size %d, triangles %d, %s\n", - pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary", - grid_size, int(triangles), - (triangles <= target_index_count / 3) ? "under" : "over"); + pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary", + grid_size, int(triangles), + (triangles <= target_index_count / 3) ? "under" : "over"); #endif float tip = interpolate(float(target_index_count / 3), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles)); @@ -1592,9 +1592,9 @@ size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_pos #if TRACE printf("pass %d (%s): grid size %d, vertices %d, %s\n", - pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary", - grid_size, int(vertices), - (vertices <= target_vertex_count) ? "under" : "over"); + pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary", + grid_size, int(vertices), + (vertices <= target_vertex_count) ? "under" : "over"); #endif float tip = interpolate(float(target_vertex_count), float(min_grid), float(min_vertices), float(grid_size), float(vertices), float(max_grid), float(max_vertices)); diff --git a/Editor/meshoptimizer/vertexcodec.cpp b/Editor/meshoptimizer/vertexcodec.cpp index 5f3ec204a..7925ea862 100644 --- a/Editor/meshoptimizer/vertexcodec.cpp +++ b/Editor/meshoptimizer/vertexcodec.cpp @@ -77,6 +77,8 @@ #endif #ifdef SIMD_WASM +#undef __DEPRECATED +#pragma clang diagnostic ignored "-Wdeprecated-declarations" #include #endif @@ -1028,7 +1030,7 @@ static unsigned int getCpuFeatures() return cpuinfo[2]; } -unsigned int cpuid = getCpuFeatures(); +static unsigned int cpuid = getCpuFeatures(); #endif } // namespace meshopt diff --git a/Editor/meshoptimizer/vertexfilter.cpp b/Editor/meshoptimizer/vertexfilter.cpp index 669eff959..606a280aa 100644 --- a/Editor/meshoptimizer/vertexfilter.cpp +++ b/Editor/meshoptimizer/vertexfilter.cpp @@ -52,6 +52,7 @@ #endif #ifdef SIMD_WASM +#undef __DEPRECATED #include #endif @@ -792,52 +793,170 @@ static void decodeFilterExpSimd(unsigned int* data, size_t count) } // namespace meshopt -void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size) +void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride) { using namespace meshopt; - assert(vertex_size == 4 || vertex_size == 8); + assert(stride == 4 || stride == 8); #if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM) - if (vertex_size == 4) - dispatchSimd(decodeFilterOctSimd, static_cast(buffer), vertex_count, 4); + if (stride == 4) + dispatchSimd(decodeFilterOctSimd, static_cast(buffer), count, 4); else - dispatchSimd(decodeFilterOctSimd, static_cast(buffer), vertex_count, 4); + dispatchSimd(decodeFilterOctSimd, static_cast(buffer), count, 4); #else - if (vertex_size == 4) - decodeFilterOct(static_cast(buffer), vertex_count); + if (stride == 4) + decodeFilterOct(static_cast(buffer), count); else - decodeFilterOct(static_cast(buffer), vertex_count); + decodeFilterOct(static_cast(buffer), count); #endif } -void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size) +void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride) { using namespace meshopt; - assert(vertex_size == 8); - (void)vertex_size; + assert(stride == 8); + (void)stride; #if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM) - dispatchSimd(decodeFilterQuatSimd, static_cast(buffer), vertex_count, 4); + dispatchSimd(decodeFilterQuatSimd, static_cast(buffer), count, 4); #else - decodeFilterQuat(static_cast(buffer), vertex_count); + decodeFilterQuat(static_cast(buffer), count); #endif } -void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size) +void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride) { using namespace meshopt; - assert(vertex_size % 4 == 0); + assert(stride > 0 && stride % 4 == 0); #if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM) - dispatchSimd(decodeFilterExpSimd, static_cast(buffer), vertex_count * (vertex_size / 4), 1); + dispatchSimd(decodeFilterExpSimd, static_cast(buffer), count * (stride / 4), 1); #else - decodeFilterExp(static_cast(buffer), vertex_count * (vertex_size / 4)); + decodeFilterExp(static_cast(buffer), count * (stride / 4)); #endif } +void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data) +{ + assert(stride == 4 || stride == 8); + assert(bits >= 1 && bits <= 16); + + signed char* d8 = static_cast(destination); + short* d16 = static_cast(destination); + + int bytebits = int(stride * 2); + + for (size_t i = 0; i < count; ++i) + { + const float* n = &data[i * 4]; + + // octahedral encoding of a unit vector + float nx = n[0], ny = n[1], nz = n[2], nw = n[3]; + float nl = fabsf(nx) + fabsf(ny) + fabsf(nz); + float ns = nl == 0.f ? 0.f : 1.f / nl; + + nx *= ns; + ny *= ns; + + float u = (nz >= 0.f) ? nx : (1 - fabsf(ny)) * (nx >= 0.f ? 1.f : -1.f); + float v = (nz >= 0.f) ? ny : (1 - fabsf(nx)) * (ny >= 0.f ? 1.f : -1.f); + + int fu = meshopt_quantizeSnorm(u, bits); + int fv = meshopt_quantizeSnorm(v, bits); + int fo = meshopt_quantizeSnorm(1.f, bits); + int fw = meshopt_quantizeSnorm(nw, bytebits); + + if (stride == 4) + { + d8[i * 4 + 0] = (signed char)(fu); + d8[i * 4 + 1] = (signed char)(fv); + d8[i * 4 + 2] = (signed char)(fo); + d8[i * 4 + 3] = (signed char)(fw); + } + else + { + d16[i * 4 + 0] = short(fu); + d16[i * 4 + 1] = short(fv); + d16[i * 4 + 2] = short(fo); + d16[i * 4 + 3] = short(fw); + } + } +} + +void meshopt_encodeFilterQuat(void* destination_, size_t count, size_t stride, int bits, const float* data) +{ + assert(stride == 8); + assert(bits >= 4 && bits <= 16); + (void)stride; + + short* destination = static_cast(destination_); + + const float scaler = sqrtf(2.f); + + for (size_t i = 0; i < count; ++i) + { + const float* q = &data[i * 4]; + short* d = &destination[i * 4]; + + // establish maximum quaternion component + int qc = 0; + qc = fabsf(q[1]) > fabsf(q[qc]) ? 1 : qc; + qc = fabsf(q[2]) > fabsf(q[qc]) ? 2 : qc; + qc = fabsf(q[3]) > fabsf(q[qc]) ? 3 : qc; + + // we use double-cover properties to discard the sign + float sign = q[qc] < 0.f ? -1.f : 1.f; + + // note: we always encode a cyclical swizzle to be able to recover the order via rotation + d[0] = short(meshopt_quantizeSnorm(q[(qc + 1) & 3] * scaler * sign, bits)); + d[1] = short(meshopt_quantizeSnorm(q[(qc + 2) & 3] * scaler * sign, bits)); + d[2] = short(meshopt_quantizeSnorm(q[(qc + 3) & 3] * scaler * sign, bits)); + d[3] = short((meshopt_quantizeSnorm(1.f, bits) & ~3) | qc); + } +} + +void meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, int bits, const float* data) +{ + assert(stride > 0 && stride % 4 == 0); + assert(bits >= 1 && bits <= 24); + + unsigned int* destination = static_cast(destination_); + size_t stride_float = stride / sizeof(float); + + for (size_t i = 0; i < count; ++i) + { + const float* v = &data[i * stride_float]; + unsigned int* d = &destination[i * stride_float]; + + // use maximum exponent to encode values; this guarantess that mantissa is [-1, 1] + int exp = -100; + + for (size_t j = 0; j < stride_float; ++j) + { + int e; + frexp(v[j], &e); + + exp = (exp < e) ? e : exp; + } + + // note that we additionally scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude) + exp -= (bits - 1); + + // compute renormalized rounded mantissa for each component + int mmask = (1 << 24) - 1; + + for (size_t j = 0; j < stride_float; ++j) + { + int m = int(ldexp(v[j], -exp) + (v[j] >= 0 ? 0.5f : -0.5f)); + + d[j] = (m & mmask) | (unsigned(exp) << 24); + } + } +} + #undef SIMD_SSE #undef SIMD_NEON #undef SIMD_WASM diff --git a/Editor/terrain/base-LICENSE.txt b/Editor/terrain/base-LICENSE.txt new file mode 100644 index 000000000..1b06d2c64 --- /dev/null +++ b/Editor/terrain/base-LICENSE.txt @@ -0,0 +1,4 @@ +ambientCG (ambientCG.com) +CC0 1.0 Public Domain Dedication (https://creativecommons.org/publicdomain/zero/1.0/) +Grass001 by ambientCG +https://ambientcg.com/a/Grass001 \ No newline at end of file diff --git a/Editor/terrain/base.jpg b/Editor/terrain/base.jpg new file mode 100644 index 000000000..89b4df241 Binary files /dev/null and b/Editor/terrain/base.jpg differ diff --git a/Editor/terrain/base_nor.jpg b/Editor/terrain/base_nor.jpg new file mode 100644 index 000000000..c35eafbf3 Binary files /dev/null and b/Editor/terrain/base_nor.jpg differ diff --git a/Editor/terrain/bush-LICENSE.txt b/Editor/terrain/bush-LICENSE.txt new file mode 100644 index 000000000..666fa1848 --- /dev/null +++ b/Editor/terrain/bush-LICENSE.txt @@ -0,0 +1 @@ +https://www.cgtrader.com/free-3d-models/plant/bush/mediteranean-bush \ No newline at end of file diff --git a/Editor/terrain/bush.wiscene b/Editor/terrain/bush.wiscene new file mode 100644 index 000000000..c43fbe9c2 Binary files /dev/null and b/Editor/terrain/bush.wiscene differ diff --git a/Editor/terrain/grassparticle.png b/Editor/terrain/grassparticle.png new file mode 100644 index 000000000..aa815a2d2 Binary files /dev/null and b/Editor/terrain/grassparticle.png differ diff --git a/Editor/terrain/high_altitude-LICENSE.txt b/Editor/terrain/high_altitude-LICENSE.txt new file mode 100644 index 000000000..1490a7370 --- /dev/null +++ b/Editor/terrain/high_altitude-LICENSE.txt @@ -0,0 +1 @@ +https://www.sharetextures.com/textures/ground/snow_ground_3/ \ No newline at end of file diff --git a/Editor/terrain/high_altitude.jpg b/Editor/terrain/high_altitude.jpg new file mode 100644 index 000000000..0267a5d58 Binary files /dev/null and b/Editor/terrain/high_altitude.jpg differ diff --git a/Editor/terrain/high_altitude_nor.jpg b/Editor/terrain/high_altitude_nor.jpg new file mode 100644 index 000000000..07dd1e9d5 Binary files /dev/null and b/Editor/terrain/high_altitude_nor.jpg differ diff --git a/Editor/terrain/low_altitude-LICENSE.txt b/Editor/terrain/low_altitude-LICENSE.txt new file mode 100644 index 000000000..a755f4192 --- /dev/null +++ b/Editor/terrain/low_altitude-LICENSE.txt @@ -0,0 +1 @@ +https://www.cgbookcase.com/textures/sandy-gravel-01/?source=3dassets.one \ No newline at end of file diff --git a/Editor/terrain/low_altitude.jpg b/Editor/terrain/low_altitude.jpg new file mode 100644 index 000000000..8e7b21576 Binary files /dev/null and b/Editor/terrain/low_altitude.jpg differ diff --git a/Editor/terrain/low_altitude_nor.jpg b/Editor/terrain/low_altitude_nor.jpg new file mode 100644 index 000000000..4b6671a2f Binary files /dev/null and b/Editor/terrain/low_altitude_nor.jpg differ diff --git a/Editor/terrain/rock-LICENSE.txt b/Editor/terrain/rock-LICENSE.txt new file mode 100644 index 000000000..5189c99e3 --- /dev/null +++ b/Editor/terrain/rock-LICENSE.txt @@ -0,0 +1 @@ +https://www.cgtrader.com/free-3d-models/plant/other/rocks-56c7a2d1-2e36-4b17-8d79-2a69ab29d860 \ No newline at end of file diff --git a/Editor/terrain/rock.wiscene b/Editor/terrain/rock.wiscene new file mode 100644 index 000000000..d193ebbe1 Binary files /dev/null and b/Editor/terrain/rock.wiscene differ diff --git a/Editor/terrain/slope-LICENSE.txt b/Editor/terrain/slope-LICENSE.txt new file mode 100644 index 000000000..42892115e --- /dev/null +++ b/Editor/terrain/slope-LICENSE.txt @@ -0,0 +1,4 @@ +ambientCG (ambientCG.com) +CC0 1.0 Public Domain Dedication (https://creativecommons.org/publicdomain/zero/1.0/) +Rock048 by ambientCG +https://ambientcg.com/a/Rock048 \ No newline at end of file diff --git a/Editor/terrain/slope.jpg b/Editor/terrain/slope.jpg new file mode 100644 index 000000000..0c0190b3c Binary files /dev/null and b/Editor/terrain/slope.jpg differ diff --git a/Editor/terrain/slope_nor.jpg b/Editor/terrain/slope_nor.jpg new file mode 100644 index 000000000..e721784f7 Binary files /dev/null and b/Editor/terrain/slope_nor.jpg differ diff --git a/Editor/terrain/tree-LICENSE.txt b/Editor/terrain/tree-LICENSE.txt new file mode 100644 index 000000000..bbc15ae1e --- /dev/null +++ b/Editor/terrain/tree-LICENSE.txt @@ -0,0 +1 @@ +https://free3d.com/3d-model/tree02-35663.html \ No newline at end of file diff --git a/Editor/terrain/tree.wiscene b/Editor/terrain/tree.wiscene new file mode 100644 index 000000000..3b55a8717 Binary files /dev/null and b/Editor/terrain/tree.wiscene differ diff --git a/README.md b/README.md index 3b5166bae..0d328d1a3 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,18 @@ If you are having trouble getting the applications to run, make sure that you sa ### Screenshots: +Procedural terrain generator: +![ProceduralTerrain](https://turanszkij.files.wordpress.com/2022/04/procedural_terrain.png) + +Path tracing: +![PathTracing](https://turanszkij.files.wordpress.com/2022/04/sanmiguel.png) + +Dynamic Diffuse Global Illumination (DDGI): +![DDGI](https://turanszkij.files.wordpress.com/2022/04/ddgi.png) + +Snow storm with particle systems: +![SnowStorm](https://turanszkij.files.wordpress.com/2022/04/snowstorm.png) + Sponza scene with voxel GI enabled: ![Sponza](https://turanszkij.files.wordpress.com/2020/08/vxgi_sponza_small.png) diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt index dfa57436a..8b8ab2082 100644 --- a/WickedEngine/ArchiveVersionHistory.txt +++ b/WickedEngine/ArchiveVersionHistory.txt @@ -1,5 +1,6 @@ This file contains changelog of wi::Archive versions +76: serialized LOD parameters 75: PreviousFrameTransformComponent is no longer serialized 74: serialized emitter restitution 73: wi::Archive no longer saves null terminator for strings diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h index 8224dca8e..eb5fe0022 100644 --- a/WickedEngine/WickedEngine.h +++ b/WickedEngine/WickedEngine.h @@ -68,6 +68,7 @@ #include "wiUnorderedMap.h" #include "wiUnorderedSet.h" #include "wiVector.h" +#include "wiNoise.h" #ifdef _WIN32 #ifdef PLATFORM_UWP diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems b/WickedEngine/WickedEngine_SOURCE.vcxitems index 98859c0b9..e48df2a0f 100644 --- a/WickedEngine/WickedEngine_SOURCE.vcxitems +++ b/WickedEngine/WickedEngine_SOURCE.vcxitems @@ -229,6 +229,7 @@ + diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters index e9b23577c..5c3e2ff0e 100644 --- a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters +++ b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters @@ -1074,6 +1074,9 @@ ENGINE\System + + ENGINE\Helpers + diff --git a/WickedEngine/shaders/brdf.hlsli b/WickedEngine/shaders/brdf.hlsli index 8bad147f5..5e58d8a6e 100644 --- a/WickedEngine/shaders/brdf.hlsli +++ b/WickedEngine/shaders/brdf.hlsli @@ -272,11 +272,11 @@ struct Surface clearcoat.roughness = clamp(clearcoat.roughness, 0.045, 1); clearcoat.roughnessBRDF = clearcoat.roughness * clearcoat.roughness; - NdotV = saturate(abs(dot(N, V)) + 1e-5); + NdotV = saturate(dot(N, V) + 1e-5); f90 = saturate(50.0 * dot(f0, 0.33)); F = F_Schlick(f0, f90, NdotV); - clearcoat.F = F_Schlick(f0, f90, saturate(abs(dot(clearcoat.N, V)) + 1e-5)); + clearcoat.F = F_Schlick(f0, f90, saturate(dot(clearcoat.N, V) + 1e-5)); clearcoat.F *= clearcoat.factor; R = -reflect(V, N); diff --git a/WickedEngine/shaders/hairparticlePS_prepass.hlsl b/WickedEngine/shaders/hairparticlePS_prepass.hlsl index 71a6368df..28db487de 100644 --- a/WickedEngine/shaders/hairparticlePS_prepass.hlsl +++ b/WickedEngine/shaders/hairparticlePS_prepass.hlsl @@ -16,10 +16,7 @@ uint2 main(VertexToPixel input, out uint coverage : SV_Coverage) : SV_Target } // Distance dithered fade: - if (dither(input.pos.xy + GetTemporalAASampleRotation()) - input.fade < 0) - { - alpha = 0; - } + clip(dither(input.pos.xy + GetTemporalAASampleRotation()) - input.fade); coverage = AlphaToCoverage(alpha, material.alphaTest, input.pos); diff --git a/WickedEngine/shaders/hairparticle_simulateCS.hlsl b/WickedEngine/shaders/hairparticle_simulateCS.hlsl index 434a7e406..ca41018bc 100644 --- a/WickedEngine/shaders/hairparticle_simulateCS.hlsl +++ b/WickedEngine/shaders/hairparticle_simulateCS.hlsl @@ -85,7 +85,13 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn target = normalize(mul((float3x3)xHairWorld, target)); const float3 root = base; + const float3 diff = root - GetCamera().position; + const float distsq = dot(diff, diff); + const bool distance_culled = dot(diff, diff) > sqr(xHairViewDistance); + float3 normal = 0; + + const float delta_time = clamp(GetFrame().delta_time, 0, 1.0 / 30.0); // clamp delta time to avoid simulation blowing up for (uint segmentID = 0; segmentID < xHairSegmentCount; ++segmentID) { @@ -140,7 +146,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn // Pull back to rest position: force += (target - normal) * xStiffness; - force *= GetFrame().delta_time; + force *= delta_time; // Simulation buffer load: float3 velocity = simulationBuffer[particleID].velocity; @@ -154,14 +160,15 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn // Apply forces: velocity += force; - normal += velocity * clamp(GetFrame().delta_time, 0, 0.1); // clamp delta time to avoid simulation blowing up + normal += velocity * delta_time; + normal = normalize(normal); // Drag: velocity *= 0.98f; // Store particle: simulationBuffer[particleID].position = base; - simulationBuffer[particleID].normal = normalize(normal); + simulationBuffer[particleID].normal = normal; // Store simulation data: simulationBuffer[particleID].velocity = velocity; @@ -205,6 +212,10 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn const float3 wind = sin(mad(GetFrame().time, GetWeather().wind.speed, waveoffset)) * wavedir; float3 position = rootposition + patchPos + wind; + if (distance_culled) + { + position = 0; + } uint4 data; data.xyz = asuint(position); @@ -218,7 +229,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn sphere.center = (base + tip) * 0.5; sphere.radius = len; - if (GetCamera().frustum.intersects(sphere)) + if (!distance_culled && GetCamera().frustum.intersects(sphere)) { uint prevCount; counterBuffer.InterlockedAdd(0, 1, prevCount); diff --git a/WickedEngine/shaders/objectHF.hlsli b/WickedEngine/shaders/objectHF.hlsli index dc2f47ff9..d60d56ae9 100644 --- a/WickedEngine/shaders/objectHF.hlsli +++ b/WickedEngine/shaders/objectHF.hlsli @@ -248,7 +248,7 @@ struct VertexSurface inline void create(in ShaderMaterial material, in VertexInput input) { position = input.GetPosition(); - color = material.baseColor * unpack_rgba(input.GetInstance().color); + color = unpack_rgba(input.GetInstance().color); color.a *= 1 - input.GetInstancePointer().GetDither(); emissiveColor = input.GetInstance().emissive; @@ -263,7 +263,6 @@ struct VertexSurface tangent.xyz = normalize(mul((float3x3)input.GetInstance().transformInverseTranspose.GetMatrix(), tangent.xyz)); uvsets = input.GetUVSets(); - uvsets.xy = mad(uvsets.xy, material.texMulAdd.xy, material.texMulAdd.zw); atlas = input.GetAtlasUV(); @@ -1077,6 +1076,11 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target const float2 ScreenCoord = pixel * GetCamera().internal_resolution_rcp; float3 bumpColor = 0; +#ifdef OBJECTSHADER_USE_UVSETS + float4 uvsets = input.uvsets; + uvsets.xy = mad(uvsets.xy, GetMaterial().texMulAdd.xy, GetMaterial().texMulAdd.zw); +#endif // OBJECTSHADER_USE_UVSETS + #ifndef DISABLE_ALPHATEST #ifndef TRANSPARENT #ifndef ENVMAPRENDERING @@ -1110,7 +1114,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target #ifdef OBJECTSHADER_USE_TANGENT #if 0 - float3x3 TBN = compute_tangent_frame(surface.N, surface.P, input.uvsets.xy); + float3x3 TBN = compute_tangent_frame(surface.N, surface.P, uvsets.xy); #else float4 tangent = input.tan; tangent.xyz = normalize(tangent.xyz); @@ -1119,7 +1123,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target #endif #ifdef POM - ParallaxOcclusionMapping(input.uvsets, surface.V, TBN); + ParallaxOcclusionMapping(uvsets, surface.V, TBN); #endif // POM #endif // OBJECTSHADER_USE_TANGENT @@ -1136,7 +1140,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target if (GetMaterial().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) #endif // PREPASS { - const float2 UV_baseColorMap = GetMaterial().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_baseColorMap = GetMaterial().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; float4 baseColorMap = texture_basecolormap.Sample(sampler_objectshader, UV_baseColorMap); baseColorMap.rgb = DEGAMMA(baseColorMap.rgb); color *= baseColorMap; @@ -1145,7 +1149,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target #ifdef OBJECTSHADER_USE_COLOR - color *= input.color; + color *= GetMaterial().baseColor * input.color; #endif // OBJECTSHADER_USE_COLOR @@ -1162,7 +1166,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target #ifndef WATER #ifdef OBJECTSHADER_USE_TANGENT - NormalMapping(input.uvsets, surface.N, TBN, bumpColor); + NormalMapping(uvsets, surface.N, TBN, bumpColor); #endif // OBJECTSHADER_USE_TANGENT #endif // WATER @@ -1173,7 +1177,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_surfaceMap >= 0) { - const float2 UV_surfaceMap = GetMaterial().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_surfaceMap = GetMaterial().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; surfaceMap = texture_surfacemap.Sample(sampler_objectshader, UV_surfaceMap); } #endif // OBJECTSHADER_USE_UVSETS @@ -1185,7 +1189,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_specularMap >= 0) { - const float2 UV_specularMap = GetMaterial().uvset_specularMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_specularMap = GetMaterial().uvset_specularMap == 0 ? uvsets.xy : uvsets.zw; specularMap = texture_specularmap.Sample(sampler_objectshader, UV_specularMap); specularMap.rgb = DEGAMMA(specularMap.rgb); } @@ -1203,7 +1207,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (any(surface.emissiveColor) && GetMaterial().uvset_emissiveMap >= 0) { - const float2 UV_emissiveMap = GetMaterial().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_emissiveMap = GetMaterial().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; float4 emissiveMap = texture_emissivemap.Sample(sampler_objectshader, UV_emissiveMap); emissiveMap.rgb = DEGAMMA(emissiveMap.rgb); surface.emissiveColor *= emissiveMap.rgb * emissiveMap.a; @@ -1213,12 +1217,14 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target #ifdef TERRAIN + color = 1; surface.N = 0; surface.albedo = 0; surface.f0 = 0; surface.roughness = 0; surface.occlusion = 0; surface.emissiveColor = 0; + surface.opacity = 1; float4 sam; float4 blend_weights = input.color; @@ -1228,15 +1234,16 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (blend_weights.x > 0) { - float4 color2 = 1; + float4 color2 = GetMaterial().baseColor; #ifdef OBJECTSHADER_USE_UVSETS [branch] if (GetMaterial().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { - float2 uv = GetMaterial().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - color2 = texture_basecolormap.Sample(sampler_objectshader, uv); - color2.rgb = DEGAMMA(color2.rgb); + float2 uv = GetMaterial().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; + float4 basecolorMap = texture_basecolormap.Sample(sampler_objectshader, uv); + basecolorMap.rgb = DEGAMMA(basecolorMap.rgb); + color2 *= basecolorMap; } #endif // OBJECTSHADER_USE_UVSETS @@ -1246,7 +1253,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_surfaceMap >= 0) { - float2 uv = GetMaterial().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; surfaceMap2 = texture_surfacemap.Sample(sampler_objectshader, uv); } #endif // OBJECTSHADER_USE_UVSETS @@ -1259,7 +1266,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().normalMapStrength > 0 && GetMaterial().uvset_normalMap >= 0) { - float2 uv = GetMaterial().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw; sam.rgb = texture_normalmap.Sample(sampler_objectshader, uv).rgb; sam.rgb = sam.rgb * 2 - 1; surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial().normalMapStrength); @@ -1272,7 +1279,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_emissiveMap >= 0 && any(surface2.emissiveColor)) { - float2 uv = GetMaterial().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; sam = texture_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); surface2.emissiveColor *= sam.rgb * sam.a; @@ -1290,15 +1297,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (blend_weights.y > 0) { - float4 color2 = 1; + float4 color2 = GetMaterial1().baseColor; + float4 uvsets = input.uvsets; + uvsets.xy = mad(uvsets.xy, GetMaterial1().texMulAdd.xy, GetMaterial1().texMulAdd.zw); #ifdef OBJECTSHADER_USE_UVSETS [branch] if (GetMaterial1().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { - float2 uv = GetMaterial1().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - color2 = texture_blend1_basecolormap.Sample(sampler_objectshader, uv); - color2.rgb = DEGAMMA(color2.rgb); + float2 uv = GetMaterial1().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; + float4 basecolorMap = texture_blend1_basecolormap.Sample(sampler_objectshader, uv); + basecolorMap.rgb = DEGAMMA(basecolorMap.rgb); + color2 *= basecolorMap; } #endif // OBJECTSHADER_USE_UVSETS @@ -1308,7 +1318,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial1().uvset_surfaceMap >= 0) { - float2 uv = GetMaterial1().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial1().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; surfaceMap2 = texture_blend1_surfacemap.Sample(sampler_objectshader, uv); } #endif // OBJECTSHADER_USE_UVSETS @@ -1321,7 +1331,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial1().normalMapStrength > 0 && GetMaterial1().uvset_normalMap >= 0) { - float2 uv = GetMaterial1().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial1().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw; sam.rgb = texture_blend1_normalmap.Sample(sampler_objectshader, uv).rgb; sam.rgb = sam.rgb * 2 - 1; surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial1().normalMapStrength); @@ -1334,7 +1344,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial1().uvset_emissiveMap >= 0 && any(surface2.emissiveColor)) { - float2 uv = GetMaterial1().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial1().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; sam = texture_blend1_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); surface2.emissiveColor *= sam.rgb * sam.a; @@ -1352,15 +1362,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (blend_weights.z > 0) { - float4 color2 = 1; + float4 color2 = GetMaterial2().baseColor; + float4 uvsets = input.uvsets; + uvsets.xy = mad(uvsets.xy, GetMaterial2().texMulAdd.xy, GetMaterial2().texMulAdd.zw); #ifdef OBJECTSHADER_USE_UVSETS [branch] if (GetMaterial2().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { - float2 uv = GetMaterial2().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - color2 = texture_blend2_basecolormap.Sample(sampler_objectshader, uv); - color2.rgb = DEGAMMA(color2.rgb); + float2 uv = GetMaterial2().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; + float4 basecolorMap = texture_blend2_basecolormap.Sample(sampler_objectshader, uv); + basecolorMap.rgb = DEGAMMA(basecolorMap.rgb); + color2 *= basecolorMap; } #endif // OBJECTSHADER_USE_UVSETS @@ -1370,7 +1383,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial2().uvset_surfaceMap >= 0) { - float2 uv = GetMaterial2().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial2().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; surfaceMap2 = texture_blend2_surfacemap.Sample(sampler_objectshader, uv); } #endif // OBJECTSHADER_USE_UVSETS @@ -1383,7 +1396,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial2().normalMapStrength > 0 && GetMaterial2().uvset_normalMap >= 0) { - float2 uv = GetMaterial2().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial2().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw; sam.rgb = texture_blend2_normalmap.Sample(sampler_objectshader, uv).rgb; sam.rgb = sam.rgb * 2 - 1; surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial2().normalMapStrength); @@ -1396,7 +1409,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial2().uvset_emissiveMap >= 0 && any(surface2.emissiveColor)) { - float2 uv = GetMaterial2().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial2().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; sam = texture_blend2_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); surface2.emissiveColor *= sam.rgb * sam.a; @@ -1414,15 +1427,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (blend_weights.w > 0) { - float4 color2 = 1; + float4 color2 = GetMaterial3().baseColor; + float4 uvsets = input.uvsets; + uvsets.xy = mad(uvsets.xy, GetMaterial3().texMulAdd.xy, GetMaterial3().texMulAdd.zw); #ifdef OBJECTSHADER_USE_UVSETS [branch] if (GetMaterial3().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { - float2 uv = GetMaterial3().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - color2 = texture_blend3_basecolormap.Sample(sampler_objectshader, uv); - color2.rgb = DEGAMMA(color2.rgb); + float2 uv = GetMaterial3().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw; + float4 basecolorMap = texture_blend3_basecolormap.Sample(sampler_objectshader, uv); + basecolorMap.rgb = DEGAMMA(basecolorMap.rgb); + color2 *= basecolorMap; } #endif // OBJECTSHADER_USE_UVSETS @@ -1432,7 +1448,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial3().uvset_surfaceMap >= 0) { - float2 uv = GetMaterial3().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial3().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; surfaceMap2 = texture_blend3_surfacemap.Sample(sampler_objectshader, uv); } #endif // OBJECTSHADER_USE_UVSETS @@ -1445,7 +1461,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial3().normalMapStrength > 0 && GetMaterial3().uvset_normalMap >= 0) { - float2 uv = GetMaterial3().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial3().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw; sam.rgb = texture_blend3_normalmap.Sample(sampler_objectshader, uv).rgb; sam.rgb = sam.rgb * 2 - 1; surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial3().normalMapStrength); @@ -1458,7 +1474,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial3().uvset_emissiveMap >= 0 && any(surface2.emissiveColor)) { - float2 uv = GetMaterial3().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; + float2 uv = GetMaterial3().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; sam = texture_blend3_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); surface2.emissiveColor *= sam.rgb * sam.a; @@ -1489,7 +1505,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().IsOcclusionEnabled_Secondary() && GetMaterial().uvset_occlusionMap >= 0) { - const float2 UV_occlusionMap = GetMaterial().uvset_occlusionMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_occlusionMap = GetMaterial().uvset_occlusionMap == 0 ? uvsets.xy : uvsets.zw; surface.occlusion *= texture_occlusionmap.Sample(sampler_objectshader, UV_occlusionMap).r; } #endif // OBJECTSHADER_USE_UVSETS @@ -1523,13 +1539,13 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_sheenColorMap >= 0) { - const float2 UV_sheenColorMap = GetMaterial().uvset_sheenColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_sheenColorMap = GetMaterial().uvset_sheenColorMap == 0 ? uvsets.xy : uvsets.zw; surface.sheen.color *= DEGAMMA(texture_sheencolormap.Sample(sampler_objectshader, UV_sheenColorMap).rgb); } [branch] if (GetMaterial().uvset_sheenRoughnessMap >= 0) { - const float2 uvset_sheenRoughnessMap = GetMaterial().uvset_sheenRoughnessMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 uvset_sheenRoughnessMap = GetMaterial().uvset_sheenRoughnessMap == 0 ? uvsets.xy : uvsets.zw; surface.sheen.roughness *= texture_sheenroughnessmap.Sample(sampler_objectshader, uvset_sheenRoughnessMap).a; } #endif // OBJECTSHADER_USE_UVSETS @@ -1545,20 +1561,20 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_clearcoatMap >= 0) { - const float2 UV_clearcoatMap = GetMaterial().uvset_clearcoatMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_clearcoatMap = GetMaterial().uvset_clearcoatMap == 0 ? uvsets.xy : uvsets.zw; surface.clearcoat.factor *= texture_clearcoatmap.Sample(sampler_objectshader, UV_clearcoatMap).r; } [branch] if (GetMaterial().uvset_clearcoatRoughnessMap >= 0) { - const float2 uvset_clearcoatRoughnessMap = GetMaterial().uvset_clearcoatRoughnessMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 uvset_clearcoatRoughnessMap = GetMaterial().uvset_clearcoatRoughnessMap == 0 ? uvsets.xy : uvsets.zw; surface.clearcoat.roughness *= texture_clearcoatroughnessmap.Sample(sampler_objectshader, uvset_clearcoatRoughnessMap).g; } #ifdef OBJECTSHADER_USE_TANGENT [branch] if (GetMaterial().uvset_clearcoatNormalMap >= 0) { - const float2 uvset_clearcoatNormalMap = GetMaterial().uvset_clearcoatNormalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 uvset_clearcoatNormalMap = GetMaterial().uvset_clearcoatNormalMap == 0 ? uvsets.xy : uvsets.zw; float3 clearcoatNormalMap = float3(texture_clearcoatnormalmap.Sample(sampler_objectshader, uvset_clearcoatNormalMap).rg, 1); clearcoatNormalMap = clearcoatNormalMap * 2 - 1; surface.clearcoat.N = mul(clearcoatNormalMap, TBN); @@ -1598,7 +1614,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_normalMap >= 0) { - const float2 UV_normalMap = GetMaterial().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_normalMap = GetMaterial().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw; bumpColor0 = 2 * texture_normalmap.Sample(sampler_objectshader, UV_normalMap - GetMaterial().texMulAdd.ww).rg - 1; bumpColor1 = 2 * texture_normalmap.Sample(sampler_objectshader, UV_normalMap + GetMaterial().texMulAdd.zw).rg - 1; } @@ -1634,7 +1650,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target [branch] if (GetMaterial().uvset_transmissionMap >= 0) { - const float2 UV_transmissionMap = GetMaterial().uvset_transmissionMap == 0 ? input.uvsets.xy : input.uvsets.zw; + const float2 UV_transmissionMap = GetMaterial().uvset_transmissionMap == 0 ? uvsets.xy : uvsets.zw; float transmissionMap = texture_transmissionmap.Sample(sampler_objectshader, UV_transmissionMap).r; surface.transmission *= transmissionMap; } diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp index 685c44043..e218b382e 100644 --- a/WickedEngine/wiArchive.cpp +++ b/WickedEngine/wiArchive.cpp @@ -5,7 +5,7 @@ namespace wi { // this should always be only INCREMENTED and only if a new serialization is implemeted somewhere! - static constexpr uint64_t __archiveVersion = 75; + static constexpr uint64_t __archiveVersion = 76; // this is the version number of which below the archive is not compatible with the current version static constexpr uint64_t __archiveVersionBarrier = 22; diff --git a/WickedEngine/wiECS.h b/WickedEngine/wiECS.h index eec045da1..772bf4d32 100644 --- a/WickedEngine/wiECS.h +++ b/WickedEngine/wiECS.h @@ -46,7 +46,7 @@ namespace wi::ecs uint64_t mem; archive >> mem; - if (seri.allow_remap) + if (mem != INVALID_ENTITY && seri.allow_remap) { auto it = seri.remap.find(mem); if (it == seri.remap.end()) diff --git a/WickedEngine/wiHairParticle.cpp b/WickedEngine/wiHairParticle.cpp index ca42d6c9e..9cbf9870a 100644 --- a/WickedEngine/wiHairParticle.cpp +++ b/WickedEngine/wiHairParticle.cpp @@ -31,6 +31,174 @@ namespace wi static PipelineState PSO[RENDERPASS_COUNT]; static PipelineState PSO_wire; + void HairParticleSystem::CreateRenderData(const wi::scene::MeshComponent& mesh) + { + GraphicsDevice* device = wi::graphics::GetDevice(); + + _flags &= ~REBUILD_BUFFERS; + regenerate_frame = true; + render_data_updated = false; + + GPUBufferDesc bd; + bd.usage = Usage::DEFAULT; + bd.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; + bd.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED; + + const uint32_t particleCount = strandCount * segmentCount; + if (particleCount > 0) + { + bd.stride = sizeof(PatchSimulationData); + bd.size = bd.stride * particleCount; + device->CreateBuffer(&bd, nullptr, &simulationBuffer); + device->SetName(&simulationBuffer, "HairParticleSystem::simulationBuffer"); + + bd.misc_flags = ResourceMiscFlag::BUFFER_RAW; + if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) + { + bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; + } + bd.stride = sizeof(MeshComponent::Vertex_POS); + bd.size = bd.stride * 4 * particleCount; + device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[0]); + device->SetName(&vertexBuffer_POS[0], "HairParticleSystem::vertexBuffer_POS[0]"); + device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[1]); + device->SetName(&vertexBuffer_POS[1], "HairParticleSystem::vertexBuffer_POS[1]"); + + bd.misc_flags = ResourceMiscFlag::BUFFER_RAW; + bd.stride = sizeof(MeshComponent::Vertex_UVS); + bd.size = bd.stride * 4 * particleCount; + device->CreateBuffer(&bd, nullptr, &vertexBuffer_UVS); + device->SetName(&vertexBuffer_UVS, "HairParticleSystem::vertexBuffer_UVS"); + + bd.bind_flags = BindFlag::SHADER_RESOURCE; + bd.misc_flags = ResourceMiscFlag::NONE; + if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) + { + bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; + } + bd.format = Format::R32_UINT; + bd.stride = sizeof(uint); + bd.size = bd.stride * 6 * particleCount; + wi::vector primitiveData(6 * particleCount); + for (uint particleID = 0; particleID < particleCount; ++particleID) + { + uint v0 = particleID * 4; + uint i0 = particleID * 6; + primitiveData[i0 + 0] = v0 + 0; + primitiveData[i0 + 1] = v0 + 1; + primitiveData[i0 + 2] = v0 + 2; + primitiveData[i0 + 3] = v0 + 2; + primitiveData[i0 + 4] = v0 + 1; + primitiveData[i0 + 5] = v0 + 3; + } + device->CreateBuffer(&bd, primitiveData.data(), &primitiveBuffer); + device->SetName(&primitiveBuffer, "HairParticleSystem::primitiveBuffer"); + + bd.bind_flags = BindFlag::INDEX_BUFFER | BindFlag::UNORDERED_ACCESS; + bd.misc_flags = ResourceMiscFlag::NONE; + bd.format = Format::R32_UINT; + bd.stride = sizeof(uint); + bd.size = bd.stride * 6 * particleCount; + device->CreateBuffer(&bd, nullptr, &culledIndexBuffer); + device->SetName(&culledIndexBuffer, "HairParticleSystem::culledIndexBuffer"); + } + + bd.usage = Usage::DEFAULT; + bd.size = sizeof(HairParticleCB); + bd.bind_flags = BindFlag::CONSTANT_BUFFER; + bd.misc_flags = ResourceMiscFlag::NONE; + device->CreateBuffer(&bd, nullptr, &constantBuffer); + device->SetName(&constantBuffer, "HairParticleSystem::constantBuffer"); + + if (vertex_lengths.size() != mesh.vertex_positions.size()) + { + vertex_lengths.resize(mesh.vertex_positions.size()); + std::fill(vertex_lengths.begin(), vertex_lengths.end(), 1.0f); + } + + indices.clear(); + 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; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; + for (size_t i = 0; i < subset.indexCount; i += 3) + { + const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0]; + const uint32_t i1 = mesh.indices[subset.indexOffset + i + 1]; + const uint32_t i2 = mesh.indices[subset.indexOffset + i + 2]; + if (vertex_lengths[i0] > 0 || vertex_lengths[i1] > 0 || vertex_lengths[i2] > 0) + { + indices.push_back(i0); + indices.push_back(i1); + indices.push_back(i2); + } + } + } + + if (!vertex_lengths.empty()) + { + wi::vector ulengths; + ulengths.reserve(vertex_lengths.size()); + for (auto& x : vertex_lengths) + { + ulengths.push_back(uint8_t(wi::math::Clamp(x, 0, 1) * 255.0f)); + } + + bd.misc_flags = ResourceMiscFlag::NONE; + bd.bind_flags = BindFlag::SHADER_RESOURCE; + bd.format = Format::R8_UNORM; + bd.stride = sizeof(uint8_t); + bd.size = bd.stride * (uint32_t)ulengths.size(); + device->CreateBuffer(&bd, ulengths.data(), &vertexBuffer_length); + device->SetName(&vertexBuffer_length, "HairParticleSystem::vertexBuffer_length"); + } + if (!indices.empty()) + { + bd.misc_flags = ResourceMiscFlag::NONE; + bd.bind_flags = BindFlag::SHADER_RESOURCE; + bd.format = Format::R32_UINT; + bd.stride = sizeof(uint32_t); + bd.size = bd.stride * (uint32_t)indices.size(); + device->CreateBuffer(&bd, indices.data(), &indexBuffer); + device->SetName(&indexBuffer, "HairParticleSystem::indexBuffer"); + } + + if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING) && primitiveBuffer.IsValid()) + { + RaytracingAccelerationStructureDesc desc; + desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL; + desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE; + desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD; + + desc.bottom_level.geometries.emplace_back(); + auto& geometry = desc.bottom_level.geometries.back(); + geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES; + geometry.triangles.vertex_buffer = vertexBuffer_POS[0]; + geometry.triangles.index_buffer = primitiveBuffer; + geometry.triangles.index_format = IndexBufferFormat::UINT32; + geometry.triangles.index_count = (uint32_t)(primitiveBuffer.desc.size / primitiveBuffer.desc.stride); + geometry.triangles.index_offset = 0; + geometry.triangles.vertex_count = (uint32_t)(vertexBuffer_POS[0].desc.size / vertexBuffer_POS[0].desc.stride); + geometry.triangles.vertex_format = Format::R32G32B32_FLOAT; + geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS); + + bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLAS); + assert(success); + device->SetName(&BLAS, "HairParticleSystem::BLAS"); + } + + if (!indirectBuffer.IsValid()) + { + GPUBufferDesc desc; + desc.size = sizeof(uint) + sizeof(IndirectDrawArgsIndexedInstanced); // counter + draw args + desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS; + desc.bind_flags = BindFlag::UNORDERED_ACCESS; + device->CreateBuffer(&desc, nullptr, &indirectBuffer); + } + } + void HairParticleSystem::UpdateCPU(const TransformComponent& transform, const MeshComponent& mesh, float dt) { world = transform.world; @@ -49,166 +217,9 @@ namespace wi aabb = AABB(_min, _max); aabb = aabb.transform(world); - GraphicsDevice* device = wi::graphics::GetDevice(); - if (_flags & REBUILD_BUFFERS || !constantBuffer.IsValid() || (strandCount * segmentCount) != simulationBuffer.GetDesc().size / sizeof(PatchSimulationData)) { - _flags &= ~REBUILD_BUFFERS; - regenerate_frame = true; - - GPUBufferDesc bd; - bd.usage = Usage::DEFAULT; - bd.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; - bd.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED; - - const uint32_t particleCount = strandCount * segmentCount; - if (particleCount > 0) - { - bd.stride = sizeof(PatchSimulationData); - bd.size = bd.stride * particleCount; - device->CreateBuffer(&bd, nullptr, &simulationBuffer); - device->SetName(&simulationBuffer, "HairParticleSystem::simulationBuffer"); - - bd.misc_flags = ResourceMiscFlag::BUFFER_RAW; - if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) - { - bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; - } - bd.stride = sizeof(MeshComponent::Vertex_POS); - bd.size = bd.stride * 4 * particleCount; - device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[0]); - device->SetName(&vertexBuffer_POS[0], "HairParticleSystem::vertexBuffer_POS[0]"); - device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[1]); - device->SetName(&vertexBuffer_POS[1], "HairParticleSystem::vertexBuffer_POS[1]"); - - bd.misc_flags = ResourceMiscFlag::BUFFER_RAW; - bd.stride = sizeof(MeshComponent::Vertex_UVS); - bd.size = bd.stride * 4 * particleCount; - device->CreateBuffer(&bd, nullptr, &vertexBuffer_UVS); - device->SetName(&vertexBuffer_UVS, "HairParticleSystem::vertexBuffer_UVS"); - - bd.bind_flags = BindFlag::SHADER_RESOURCE; - bd.misc_flags = ResourceMiscFlag::NONE; - if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) - { - bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; - } - bd.format = Format::R32_UINT; - bd.stride = sizeof(uint); - bd.size = bd.stride * 6 * particleCount; - wi::vector primitiveData(6 * particleCount); - for (uint particleID = 0; particleID < particleCount; ++particleID) - { - uint v0 = particleID * 4; - uint i0 = particleID * 6; - primitiveData[i0 + 0] = v0 + 0; - primitiveData[i0 + 1] = v0 + 1; - primitiveData[i0 + 2] = v0 + 2; - primitiveData[i0 + 3] = v0 + 2; - primitiveData[i0 + 4] = v0 + 1; - primitiveData[i0 + 5] = v0 + 3; - } - device->CreateBuffer(&bd, primitiveData.data(), &primitiveBuffer); - device->SetName(&primitiveBuffer, "HairParticleSystem::primitiveBuffer"); - - bd.bind_flags = BindFlag::INDEX_BUFFER | BindFlag::UNORDERED_ACCESS; - bd.misc_flags = ResourceMiscFlag::NONE; - bd.format = Format::R32_UINT; - bd.stride = sizeof(uint); - bd.size = bd.stride * 6 * particleCount; - device->CreateBuffer(&bd, nullptr, &culledIndexBuffer); - device->SetName(&culledIndexBuffer, "HairParticleSystem::culledIndexBuffer"); - } - - bd.usage = Usage::DEFAULT; - bd.size = sizeof(HairParticleCB); - bd.bind_flags = BindFlag::CONSTANT_BUFFER; - bd.misc_flags = ResourceMiscFlag::NONE; - device->CreateBuffer(&bd, nullptr, &constantBuffer); - device->SetName(&constantBuffer, "HairParticleSystem::constantBuffer"); - - if (vertex_lengths.size() != mesh.vertex_positions.size()) - { - vertex_lengths.resize(mesh.vertex_positions.size()); - std::fill(vertex_lengths.begin(), vertex_lengths.end(), 1.0f); - } - - indices.clear(); - for (size_t j = 0; j < mesh.indices.size(); j += 3) - { - const uint32_t triangle[] = { - mesh.indices[j + 0], - mesh.indices[j + 1], - mesh.indices[j + 2], - }; - if (vertex_lengths[triangle[0]] > 0 || vertex_lengths[triangle[1]] > 0 || vertex_lengths[triangle[2]] > 0) - { - indices.push_back(triangle[0]); - indices.push_back(triangle[1]); - indices.push_back(triangle[2]); - } - } - - if (!vertex_lengths.empty()) - { - wi::vector ulengths; - ulengths.reserve(vertex_lengths.size()); - for (auto& x : vertex_lengths) - { - ulengths.push_back(uint8_t(wi::math::Clamp(x, 0, 1) * 255.0f)); - } - - bd.misc_flags = ResourceMiscFlag::NONE; - bd.bind_flags = BindFlag::SHADER_RESOURCE; - bd.format = Format::R8_UNORM; - bd.stride = sizeof(uint8_t); - bd.size = bd.stride * (uint32_t)ulengths.size(); - device->CreateBuffer(&bd, ulengths.data(), &vertexBuffer_length); - device->SetName(&vertexBuffer_length, "HairParticleSystem::vertexBuffer_length"); - } - if (!indices.empty()) - { - bd.misc_flags = ResourceMiscFlag::NONE; - bd.bind_flags = BindFlag::SHADER_RESOURCE; - bd.format = Format::R32_UINT; - bd.stride = sizeof(uint32_t); - bd.size = bd.stride * (uint32_t)indices.size(); - device->CreateBuffer(&bd, indices.data(), &indexBuffer); - device->SetName(&indexBuffer, "HairParticleSystem::indexBuffer"); - } - - if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING) && primitiveBuffer.IsValid()) - { - RaytracingAccelerationStructureDesc desc; - desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL; - desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE; - desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD; - - desc.bottom_level.geometries.emplace_back(); - auto& geometry = desc.bottom_level.geometries.back(); - geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES; - geometry.triangles.vertex_buffer = vertexBuffer_POS[0]; - geometry.triangles.index_buffer = primitiveBuffer; - geometry.triangles.index_format = IndexBufferFormat::UINT32; - geometry.triangles.index_count = (uint32_t)(primitiveBuffer.desc.size / primitiveBuffer.desc.stride); - geometry.triangles.index_offset = 0; - geometry.triangles.vertex_count = (uint32_t)(vertexBuffer_POS[0].desc.size / vertexBuffer_POS[0].desc.stride); - geometry.triangles.vertex_format = Format::R32G32B32_FLOAT; - geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS); - - bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLAS); - assert(success); - device->SetName(&BLAS, "HairParticleSystem::BLAS"); - } - } - - if (!indirectBuffer.IsValid()) - { - GPUBufferDesc desc; - desc.size = sizeof(uint) + sizeof(IndirectDrawArgsIndexedInstanced); // counter + draw args - desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS; - desc.bind_flags = BindFlag::UNORDERED_ACCESS; - device->CreateBuffer(&desc, nullptr, &indirectBuffer); + CreateRenderData(mesh); } std::swap(vertexBuffer_POS[0], vertexBuffer_POS[1]); @@ -217,7 +228,7 @@ namespace wi { BLAS.desc.bottom_level.geometries.back().triangles.vertex_buffer = vertexBuffer_POS[0]; } - + render_data_updated = false; } void HairParticleSystem::UpdateGPU(uint32_t instanceIndex, const MeshComponent& mesh, const MaterialComponent& material, CommandList cmd) const { @@ -245,7 +256,7 @@ namespace wi hcb.xHairParticleCount = hcb.xHairStrandCount * hcb.xHairSegmentCount; hcb.xHairRandomSeed = randomSeed; hcb.xHairViewDistance = viewDistance; - hcb.xHairBaseMeshIndexCount = (indices.empty() ? (uint)mesh.indices.size() : (uint)indices.size()); + hcb.xHairBaseMeshIndexCount = (uint)indices.size(); hcb.xHairBaseMeshVertexPositionStride = sizeof(MeshComponent::Vertex_POS); // segmentCount will be loop in the shader, not a threadgroup so we don't need it here: hcb.xHairNumDispatchGroups = (hcb.xHairParticleCount + THREADCOUNT_SIMULATEHAIR - 1) / THREADCOUNT_SIMULATEHAIR; @@ -265,6 +276,22 @@ namespace wi device->Barrier(barriers, arraysize(barriers), cmd); } + if (regenerate_frame) + { + device->ClearUAV(&simulationBuffer, 0, cmd); + device->ClearUAV(&vertexBuffer_POS[0], 0, cmd); + device->ClearUAV(&vertexBuffer_POS[1], 0, cmd); + device->ClearUAV(&vertexBuffer_UVS, 0, cmd); + device->ClearUAV(&culledIndexBuffer, 0, cmd); + device->ClearUAV(&indirectBuffer, 0, cmd); + { + GPUBarrier barriers[] = { + GPUBarrier::Memory(), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + } + // Simulate: { device->BindComputeShader(&cs_simulate, cmd); @@ -331,6 +358,7 @@ namespace wi device->EventEnd(cmd); regenerate_frame = false; + render_data_updated = true; } void HairParticleSystem::Draw(const MaterialComponent& material, wi::enums::RENDERPASS renderPass, CommandList cmd) const @@ -352,7 +380,6 @@ namespace wi return; } device->BindPipelineState(&PSO_wire, cmd); - device->BindResource(wi::texturehelper::getWhite(), 0, cmd); } else { diff --git a/WickedEngine/wiHairParticle.h b/WickedEngine/wiHairParticle.h index 82dca0cf1..ef689a3b4 100644 --- a/WickedEngine/wiHairParticle.h +++ b/WickedEngine/wiHairParticle.h @@ -31,6 +31,8 @@ namespace wi wi::graphics::RaytracingAccelerationStructure BLAS; + void CreateRenderData(const wi::scene::MeshComponent& mesh); + void UpdateCPU( const wi::scene::TransformComponent& transform, const wi::scene::MeshComponent& mesh, @@ -80,6 +82,7 @@ namespace wi wi::vector indices; // it is dependent on vertex_lengths and contains triangles with non-zero lengths uint32_t layerMask = ~0u; mutable bool regenerate_frame = true; + mutable bool render_data_updated = false; void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h index 33aace5ce..a871a3e47 100644 --- a/WickedEngine/wiMath.h +++ b/WickedEngine/wiMath.h @@ -3,6 +3,7 @@ #include #include +#include #if __has_include("DirectXMath.h") // In this case, DirectXMath is coming from Windows SDK. @@ -76,12 +77,24 @@ namespace wi::math XMVECTOR vector2 = XMLoadFloat3(&v2); return Distance(vector1, vector2); } + inline float DistanceSquared(const XMFLOAT2& v1, const XMFLOAT2& v2) + { + XMVECTOR vector1 = XMLoadFloat2(&v1); + XMVECTOR vector2 = XMLoadFloat2(&v2); + return XMVectorGetX(XMVector2LengthSq(vector2 - vector1)); + } inline float DistanceSquared(const XMFLOAT3& v1, const XMFLOAT3& v2) { XMVECTOR vector1 = XMLoadFloat3(&v1); XMVECTOR vector2 = XMLoadFloat3(&v2); return DistanceSquared(vector1, vector2); } + inline float DistanceEstimated(const XMFLOAT2& v1, const XMFLOAT2& v2) + { + XMVECTOR vector1 = XMLoadFloat2(&v1); + XMVECTOR vector2 = XMLoadFloat2(&v2); + return XMVectorGetX(XMVector2LengthEst(vector2 - vector1)); + } inline float DistanceEstimated(const XMFLOAT3& v1, const XMFLOAT3& v2) { XMVECTOR vector1 = XMLoadFloat3(&v1); @@ -267,10 +280,20 @@ namespace wi::math // Ray-Triangle Intersection", Journal of Graphics Tools, vol. 2, no. 1, // pp 21-28, 1997. // - // Modified for WickedEngine to return barycentrics + // Modified for WickedEngine to return barycentrics and support TMin, TMax //----------------------------------------------------------------------------- _Use_decl_annotations_ - inline bool XM_CALLCONV RayTriangleIntersects(FXMVECTOR Origin, FXMVECTOR Direction, FXMVECTOR V0, GXMVECTOR V1, HXMVECTOR V2, float& Dist, XMFLOAT2& bary) + inline bool XM_CALLCONV RayTriangleIntersects( + FXMVECTOR Origin, + FXMVECTOR Direction, + FXMVECTOR V0, + GXMVECTOR V1, + HXMVECTOR V2, + float& Dist, + XMFLOAT2& bary, + float TMin = 0, + float TMax = std::numeric_limits::max() + ) { const XMVECTOR g_RayEpsilon = XMVectorSet(1e-20f, 1e-20f, 1e-20f, 1e-20f); const XMVECTOR g_RayNegEpsilon = XMVectorSet(-1e-20f, -1e-20f, -1e-20f, -1e-20f); @@ -366,6 +389,9 @@ namespace wi::math // Store the x-component to *pDist XMStoreFloat(&Dist, t); + if (Dist > TMax || Dist < TMin) + return false; + return true; } }; diff --git a/WickedEngine/wiNoise.h b/WickedEngine/wiNoise.h new file mode 100644 index 000000000..a22a0584d --- /dev/null +++ b/WickedEngine/wiNoise.h @@ -0,0 +1,155 @@ +#pragma once +#include "CommonInclude.h" +#include "wiMath.h" + +#include + +namespace wi::noise +{ + // Based on: https://github.com/Reputeless/PerlinNoise + struct Perlin + { + uint8_t state[256]; + + void init(uint32_t seed) + { + std::mt19937 perlin_rand(seed); + std::uniform_int_distribution perlin_distr(0, 255); + for (int i = 0; i < arraysize(state); ++i) + { + state[i] = perlin_distr(perlin_rand); + } + } + constexpr float fade(float t) const + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + constexpr float lerp(float a, float b, float t) const + { + return (a + (b - a) * t); + } + constexpr float grad(uint8_t hash, float x, float y, float z) const + { + const uint8_t h = hash & 15; + const float u = h < 8 ? x : y; + const float v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + // returns noise in range [-1, 1] + inline float compute(float x, float y, float z) const + { + const float _x = std::floor(x); + const float _y = std::floor(y); + const float _z = std::floor(z); + + const int ix = int(_x) & 255; + const int iy = int(_y) & 255; + const int iz = int(_z) & 255; + + const float fx = (x - _x); + const float fy = (y - _y); + const float fz = (z - _z); + + const float u = fade(fx); + const float v = fade(fy); + const float w = fade(fz); + + const uint8_t A = (state[ix & 255] + iy) & 255; + const uint8_t B = (state[(ix + 1) & 255] + iy) & 255; + + const uint8_t AA = (state[A] + iz) & 255; + const uint8_t AB = (state[(A + 1) & 255] + iz) & 255; + + const uint8_t BA = (state[B] + iz) & 255; + const uint8_t BB = (state[(B + 1) & 255] + iz) & 255; + + const float p0 = grad(state[AA], fx, fy, fz); + const float p1 = grad(state[BA], fx - 1, fy, fz); + const float p2 = grad(state[AB], fx, fy - 1, fz); + const float p3 = grad(state[BB], fx - 1, fy - 1, fz); + const float p4 = grad(state[(AA + 1) & 255], fx, fy, fz - 1); + const float p5 = grad(state[(BA + 1) & 255], fx - 1, fy, fz - 1); + const float p6 = grad(state[(AB + 1) & 255], fx, fy - 1, fz - 1); + const float p7 = grad(state[(BB + 1) & 255], fx - 1, fy - 1, fz - 1); + + const float q0 = lerp(p0, p1, u); + const float q1 = lerp(p2, p3, u); + const float q2 = lerp(p4, p5, u); + const float q3 = lerp(p6, p7, u); + + const float r0 = lerp(q0, q1, v); + const float r1 = lerp(q2, q3, v); + + return lerp(r0, r1, w); + } + // returns noise in range [-1, 1] + constexpr float compute(float x, float y, float z, int octaves, float persistence = 0.5f) const + { + float result = 0; + float amplitude = 1; + for (int i = 0; i < octaves; ++i) + { + result += (compute(x, y, z) * amplitude); + x *= 2; + y *= 2; + z *= 2; + amplitude *= persistence; + } + return result; + } + }; + + // Based on: https://www.shadertoy.com/view/MslGD8 + namespace voronoi + { + inline XMVECTOR fract(XMVECTOR p) + { + return p - XMVectorFloor(p); + } + inline XMVECTOR hash(XMVECTOR p) + { + p = XMVectorSet( + XMVectorGetX(XMVector2Dot(p, XMVectorSet(127.1f, 311.7f, 0, 0))), + XMVectorGetX(XMVector2Dot(p, XMVectorSet(269.5f, 183.3f, 0, 0))), + 0, + 0 + ); + return fract(XMVectorSin(p) * 18.5453f); + } + struct Result + { + float distance; + float cell_id; + }; + inline Result compute(float x, float y, float seed) + { + Result result = {}; + + XMVECTOR p = XMVectorSet(x, y, 0, 0); + XMVECTOR n = XMVectorFloor(p); + XMVECTOR f = fract(p); + + XMVECTOR m = XMVectorSet(8, 0, 0, 0); + for (int j = -1; j <= 1; j++) + { + for (int i = -1; i <= 1; i++) + { + XMVECTOR g = XMVectorSet(float(i), float(j), 0, 0); + XMVECTOR o = hash(n + g); + //XMVECTOR r = g - f + o; + XMVECTOR r = g - f + (XMVectorReplicate(0.5f) + 0.5f * XMVectorSin(seed * o)); + float d = XMVectorGetX(XMVector2Dot(r, r)); + if (d < XMVectorGetX(m)) + { + m = XMVectorSet(d, XMVectorGetX(o), XMVectorGetY(o), 0); + } + } + } + + result.distance = XMVectorGetX(XMVectorSqrt(m)); + result.cell_id = XMVectorGetY(m) + XMVectorGetZ(m); + + return result; + } + }; +} diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index 40f75d89d..005152968 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -49,6 +49,18 @@ namespace wi bool IsValid() const { return displacementMap.IsValid(); } + // occlusion result history bitfield (32 bit->32 frame history) + mutable uint32_t occlusionHistory = ~0u; + mutable int occlusionQueries[wi::graphics::GraphicsDevice::GetBufferCount() + 1]; + inline bool IsOccluded() const + { + // Perform a conservative occlusion test: + // If it is visible in any frames in the history, it is determined visible in this frame + // But if all queries failed in the history, it is occluded. + // If it pops up for a frame after occluded, it is visible again for some frames + return occlusionHistory == 0; + } + protected: wi::graphics::Texture displacementMap; // (RGBA32F) wi::graphics::Texture gradientMap; // (RGBA16F) diff --git a/WickedEngine/wiPrimitive.cpp b/WickedEngine/wiPrimitive.cpp index c4ad80818..09a7f15f2 100644 --- a/WickedEngine/wiPrimitive.cpp +++ b/WickedEngine/wiPrimitive.cpp @@ -136,18 +136,24 @@ namespace wi::primitive float tmin = std::min(tx1, tx2); float tmax = std::max(tx1, tx2); + if (ray.TMax < tmin || ray.TMin > tmax) + return false; float ty1 = (MIN.y - ray.origin.y) * ray.direction_inverse.y; float ty2 = (MAX.y - ray.origin.y) * ray.direction_inverse.y; tmin = std::max(tmin, std::min(ty1, ty2)); tmax = std::min(tmax, std::max(ty1, ty2)); + if (ray.TMax < tmin || ray.TMin > tmax) + return false; float tz1 = (MIN.z - ray.origin.z) * ray.direction_inverse.z; float tz2 = (MAX.z - ray.origin.z) * ray.direction_inverse.z; tmin = std::max(tmin, std::min(tz1, tz2)); tmax = std::min(tmax, std::max(tz1, tz2)); + if (ray.TMax < tmin || ray.TMin > tmax) + return false; return tmax >= tmin; } diff --git a/WickedEngine/wiPrimitive.h b/WickedEngine/wiPrimitive.h index f14f5b6c0..87856e1b7 100644 --- a/WickedEngine/wiPrimitive.h +++ b/WickedEngine/wiPrimitive.h @@ -103,13 +103,19 @@ namespace wi::primitive }; struct Ray { - XMFLOAT3 origin, direction, direction_inverse; + XMFLOAT3 origin; + float TMin = 0; + XMFLOAT3 direction; + float TMax = std::numeric_limits::max(); + XMFLOAT3 direction_inverse; - Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1)) : Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection)) {} - Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection) { + Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1), float newTMin = 0, float newTMax = std::numeric_limits::max()) : Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection), TMin, TMax) {} + Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection, float newTMin = 0, float newTMax = std::numeric_limits::max()) { XMStoreFloat3(&origin, newOrigin); XMStoreFloat3(&direction, newDirection); XMStoreFloat3(&direction_inverse, XMVectorDivide(XMVectorReplicate(1.0f), newDirection)); + TMin = newTMin; + TMax = newTMax; } bool intersects(const AABB& b) const; bool intersects(const Sphere& b) const; diff --git a/WickedEngine/wiPrimitive_BindLua.cpp b/WickedEngine/wiPrimitive_BindLua.cpp index 015e1e69f..359de7822 100644 --- a/WickedEngine/wiPrimitive_BindLua.cpp +++ b/WickedEngine/wiPrimitive_BindLua.cpp @@ -46,6 +46,14 @@ namespace wi::lua::primitive if (o && d) { ray = Ray(XMLoadFloat4(o), XMLoadFloat4(d)); + if (argc > 2) + { + ray.TMin = wi::lua::SGetFloat(L, 3); + } + if (argc > 3) + { + ray.TMax = wi::lua::SGetFloat(L, 4); + } } else { diff --git a/WickedEngine/wiRenderPath3D.cpp b/WickedEngine/wiRenderPath3D.cpp index 6706d978a..31763b84a 100644 --- a/WickedEngine/wiRenderPath3D.cpp +++ b/WickedEngine/wiRenderPath3D.cpp @@ -519,6 +519,7 @@ void RenderPath3D::Update(float dt) } scene->Update(dt * wi::renderer::GetGameSpeed()); + scene->UpdateLODsForCamera(*camera); } // Frustum culling for main camera: diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 2d59a2e03..303661558 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1854,9 +1854,9 @@ void SetUpStates() rs.fill_mode = FillMode::SOLID; rs.cull_mode = CullMode::NONE; rs.front_counter_clockwise = true; - rs.depth_bias = 2; + rs.depth_bias = 0; rs.depth_bias_clamp = 0; - rs.slope_scaled_depth_bias = 2; + rs.slope_scaled_depth_bias = 0; rs.depth_clip_enable = false; rs.multisample_enable = false; rs.antialiased_line_enable = false; @@ -2402,6 +2402,7 @@ void RenderMeshes( uint8_t userStencilRefOverride = 0; bool forceAlphatestForDithering = false; AABB aabb; + uint32_t lod = 0; } instancedBatch = {}; @@ -2426,7 +2427,10 @@ void RenderMeshes( device->BindIndexBuffer(&mesh.generalBuffer, mesh.GetIndexFormat(), mesh.ib.offset, cmd); - for (size_t subsetIndex = 0; subsetIndex < mesh.subsets.size(); ++subsetIndex) + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh.GetLODSubsetRange(instancedBatch.lod, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) { const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; if (subset.indexCount == 0) @@ -2539,7 +2543,10 @@ void RenderMeshes( const uint8_t userStencilRefOverride = instance.userStencilRef; // When we encounter a new mesh inside the global instance array, we begin a new RenderBatch: - if (meshIndex != instancedBatch.meshIndex || userStencilRefOverride != instancedBatch.userStencilRefOverride) + if (meshIndex != instancedBatch.meshIndex || + userStencilRefOverride != instancedBatch.userStencilRefOverride || + instance.lod != instancedBatch.lod + ) { batch_flush(); @@ -2550,6 +2557,7 @@ void RenderMeshes( instancedBatch.userStencilRefOverride = userStencilRefOverride; instancedBatch.forceAlphatestForDithering = 0; instancedBatch.aabb = AABB(); + instancedBatch.lod = instance.lod; } float dither = instance.GetTransparency(); @@ -2941,6 +2949,10 @@ void UpdateVisibility(Visibility& vis) { continue; } + const float dist = wi::math::Distance(vis.camera->Eye, hair.aabb.getCenter()); + const float radius = hair.aabb.getRadius(); + if (dist - radius > hair.viewDistance) + continue; if (hair.meshID == INVALID_ENTITY || !vis.frustum.CheckBoxFast(hair.aabb)) { continue; @@ -2964,12 +2976,25 @@ void UpdateVisibility(Visibility& vis) vis.visibleObjects.resize((size_t)vis.object_counter.load()); vis.visibleDecals.resize((size_t)vis.decal_counter.load()); - if ((vis.flags & Visibility::ALLOW_REQUEST_REFLECTION) && vis.scene->weather.IsOceanEnabled()) + if (vis.scene->weather.IsOceanEnabled()) { - // Ocean will override any current reflectors - vis.planar_reflection_visible = true; - XMVECTOR _refPlane = XMPlaneFromPointNormal(XMVectorSet(0, vis.scene->weather.oceanParameters.waterHeight, 0, 0), XMVectorSet(0, 1, 0, 0)); - XMStoreFloat4(&vis.reflectionPlane, _refPlane); + bool occluded = false; + if (vis.flags & Visibility::ALLOW_OCCLUSION_CULLING) + { + vis.scene->ocean.occlusionQueries[vis.scene->queryheap_idx] = vis.scene->queryAllocator.fetch_add(1); // allocate new occlusion query from heap + if (vis.scene->ocean.IsOccluded()) + { + occluded = true; + } + } + + if ((vis.flags & Visibility::ALLOW_REQUEST_REFLECTION) && !occluded) + { + // Ocean will override any current reflectors + vis.planar_reflection_visible = true; + XMVECTOR _refPlane = XMPlaneFromPointNormal(XMVectorSet(0, vis.scene->weather.oceanParameters.waterHeight, 0, 0), XMVectorSet(0, 1, 0, 0)); + XMStoreFloat4(&vis.reflectionPlane, _refPlane); + } } wi::profiler::EndRange(range); // Frustum Culling @@ -3853,9 +3878,12 @@ void UpdateRenderDataAsync( // Compute water simulation: if (vis.scene->weather.IsOceanEnabled()) { - auto range = wi::profiler::BeginRangeGPU("Ocean - Simulate", cmd); - vis.scene->ocean.UpdateDisplacementMap(vis.scene->weather.oceanParameters, cmd); - wi::profiler::EndRange(range); + if (!GetOcclusionCullingEnabled() || !vis.scene->ocean.IsOccluded()) + { + auto range = wi::profiler::BeginRangeGPU("Ocean - Simulate", cmd); + vis.scene->ocean.UpdateDisplacementMap(vis.scene->weather.oceanParameters, cmd); + wi::profiler::EndRange(range); + } } device->EventEnd(cmd); @@ -3908,7 +3936,7 @@ void UpdateRaytracingAccelerationStructures(const Scene& scene, CommandList cmd) { const wi::HairParticleSystem& hair = scene.hairs[i]; - if (hair.meshID != INVALID_ENTITY && hair.BLAS.IsValid()) + if (hair.meshID != INVALID_ENTITY && hair.BLAS.IsValid() && hair.render_data_updated) { device->BuildRaytracingAccelerationStructure(&hair.BLAS, cmd, nullptr); } @@ -4006,11 +4034,11 @@ void OcclusionCulling_Render(const CameraComponent& camera, const Visibility& vi XMMATRIX VP = camera.GetViewProjection(); const GPUQueryHeap& queryHeap = vis.scene->queryHeap; + int query_write = vis.scene->queryheap_idx; if (!vis.visibleObjects.empty()) { device->EventBegin("Occlusion Culling Objects", cmd); - int query_write = vis.scene->queryheap_idx; for (uint32_t instanceIndex : vis.visibleObjects) { @@ -4057,6 +4085,29 @@ void OcclusionCulling_Render(const CameraComponent& camera, const Visibility& vi device->EventEnd(cmd); } + if (vis.scene->weather.IsOceanEnabled()) + { + int queryIndex = vis.scene->ocean.occlusionQueries[query_write]; + if (queryIndex >= 0) + { + device->EventBegin("Occlusion Culling Ocean", cmd); + + AABB aabb; + aabb.createFromHalfWidth( + XMFLOAT3(vis.camera->Eye.x, vis.scene->weather.oceanParameters.waterHeight, vis.camera->Eye.z), + XMFLOAT3(vis.camera->zFarP, 1, vis.camera->zFarP) + ); + const XMMATRIX transform = aabb.getAsBoxMatrix() * VP; + device->PushConstants(&transform, sizeof(transform), cmd); + + device->QueryBegin(&queryHeap, queryIndex, cmd); + device->Draw(14, 0, cmd); + device->QueryEnd(&queryHeap, queryIndex, cmd); + + device->EventEnd(cmd); + } + } + wi::profiler::EndRange(range); // Occlusion Culling Render } void OcclusionCulling_Resolve(const Visibility& vis, CommandList cmd) @@ -4871,7 +4922,10 @@ void DrawScene( if (transparent && vis.scene->weather.IsOceanEnabled()) { - vis.scene->ocean.Render(*vis.camera, vis.scene->weather.oceanParameters, cmd); + if (!occlusion || !vis.scene->ocean.IsOccluded()) + { + vis.scene->ocean.Render(*vis.camera, vis.scene->weather.oceanParameters, cmd); + } } if (IsWireRender() && !transparent) @@ -4927,7 +4981,7 @@ void DrawScene( if (hairparticle) { - if (!transparent) + if (IsWireRender() || !transparent) { for (uint32_t hairIndex : vis.visibleHairs) { @@ -6452,7 +6506,10 @@ void RefreshImpostors(const Scene& scene, CommandList cmd) viewport.width = (float)scene.impostorTextureDim; device->BindViewports(1, &viewport, cmd); - for (size_t subsetIndex = 0; subsetIndex < mesh.subsets.size(); ++subsetIndex) + 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; ++subsetIndex) { const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; if (subset.indexCount == 0) diff --git a/WickedEngine/wiResourceManager.cpp b/WickedEngine/wiResourceManager.cpp index 06f91369c..73ca18c89 100644 --- a/WickedEngine/wiResourceManager.cpp +++ b/WickedEngine/wiResourceManager.cpp @@ -416,6 +416,7 @@ namespace wi case tinyddsloader::DDSFile::DXGIFormat::R10G10B10A2_UNorm: desc.format = Format::R10G10B10A2_UNORM; break; case tinyddsloader::DDSFile::DXGIFormat::R10G10B10A2_UInt: desc.format = Format::R10G10B10A2_UINT; break; case tinyddsloader::DDSFile::DXGIFormat::R11G11B10_Float: desc.format = Format::R11G11B10_FLOAT; break; + case tinyddsloader::DDSFile::DXGIFormat::B8G8R8X8_UNorm: desc.format = Format::B8G8R8A8_UNORM; break; case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm: desc.format = Format::B8G8R8A8_UNORM; break; case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm_SRGB: desc.format = Format::B8G8R8A8_UNORM_SRGB; break; case tinyddsloader::DDSFile::DXGIFormat::R8G8B8A8_UNorm: desc.format = Format::R8G8B8A8_UNORM; break; diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 843c88ef3..affdbcb9b 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -404,82 +404,93 @@ namespace wi::scene // Generate tangents if not found: vertex_tangents.resize(vertex_positions.size()); - for (size_t i = 0; i < indices.size(); i += 3) + uint32_t first_subset = 0; + uint32_t last_subset = 0; + GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) { - const uint32_t i0 = indices[i + 0]; - const uint32_t i1 = indices[i + 1]; - const uint32_t i2 = indices[i + 2]; + const MeshComponent::MeshSubset& subset = subsets[subsetIndex]; + for (size_t i = 0; i < subset.indexCount; i += 3) + { + const uint32_t i0 = indices[subset.indexOffset + i + 0]; + const uint32_t i1 = indices[subset.indexOffset + i + 1]; + const uint32_t i2 = indices[subset.indexOffset + i + 2]; - const XMFLOAT3 v0 = vertex_positions[i0]; - const XMFLOAT3 v1 = vertex_positions[i1]; - const XMFLOAT3 v2 = vertex_positions[i2]; + const XMFLOAT3 v0 = vertex_positions[i0]; + const XMFLOAT3 v1 = vertex_positions[i1]; + const XMFLOAT3 v2 = vertex_positions[i2]; - const XMFLOAT2 u0 = vertex_uvset_0[i0]; - const XMFLOAT2 u1 = vertex_uvset_0[i1]; - const XMFLOAT2 u2 = vertex_uvset_0[i2]; + const XMFLOAT2 u0 = vertex_uvset_0[i0]; + const XMFLOAT2 u1 = vertex_uvset_0[i1]; + const XMFLOAT2 u2 = vertex_uvset_0[i2]; - const XMFLOAT3 n0 = vertex_normals[i0]; - const XMFLOAT3 n1 = vertex_normals[i1]; - const XMFLOAT3 n2 = vertex_normals[i2]; + const XMFLOAT3 n0 = vertex_normals[i0]; + const XMFLOAT3 n1 = vertex_normals[i1]; + const XMFLOAT3 n2 = vertex_normals[i2]; - const XMVECTOR nor0 = XMLoadFloat3(&n0); - const XMVECTOR nor1 = XMLoadFloat3(&n1); - const XMVECTOR nor2 = XMLoadFloat3(&n2); + const XMVECTOR nor0 = XMLoadFloat3(&n0); + const XMVECTOR nor1 = XMLoadFloat3(&n1); + const XMVECTOR nor2 = XMLoadFloat3(&n2); - const XMVECTOR facenormal = XMVector3Normalize(nor0 + nor1 + nor2); + const XMVECTOR facenormal = XMVector3Normalize(nor0 + nor1 + nor2); - const float x1 = v1.x - v0.x; - const float x2 = v2.x - v0.x; - const float y1 = v1.y - v0.y; - const float y2 = v2.y - v0.y; - const float z1 = v1.z - v0.z; - const float z2 = v2.z - v0.z; + const float x1 = v1.x - v0.x; + const float x2 = v2.x - v0.x; + const float y1 = v1.y - v0.y; + const float y2 = v2.y - v0.y; + const float z1 = v1.z - v0.z; + const float z2 = v2.z - v0.z; - const float s1 = u1.x - u0.x; - const float s2 = u2.x - u0.x; - const float t1 = u1.y - u0.y; - const float t2 = u2.y - u0.y; + const float s1 = u1.x - u0.x; + const float s2 = u2.x - u0.x; + const float t1 = u1.y - u0.y; + const float t2 = u2.y - u0.y; - const float r = 1.0f / (s1 * t2 - s2 * t1); - const XMVECTOR sdir = XMVectorSet((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, - (t2 * z1 - t1 * z2) * r, 0); - const XMVECTOR tdir = XMVectorSet((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, - (s1 * z2 - s2 * z1) * r, 0); + const float r = 1.0f / (s1 * t2 - s2 * t1); + const XMVECTOR sdir = XMVectorSet((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r, 0); + const XMVECTOR tdir = XMVectorSet((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r, 0); - XMVECTOR tangent; - tangent = XMVector3Normalize(sdir - facenormal * XMVector3Dot(facenormal, sdir)); - float sign = XMVectorGetX(XMVector3Dot(XMVector3Cross(tangent, facenormal), tdir)) < 0.0f ? -1.0f : 1.0f; + XMVECTOR tangent; + tangent = XMVector3Normalize(sdir - facenormal * XMVector3Dot(facenormal, sdir)); + float sign = XMVectorGetX(XMVector3Dot(XMVector3Cross(tangent, facenormal), tdir)) < 0.0f ? -1.0f : 1.0f; - XMFLOAT3 t; - XMStoreFloat3(&t, tangent); + XMFLOAT3 t; + XMStoreFloat3(&t, tangent); - vertex_tangents[i0].x += t.x; - vertex_tangents[i0].y += t.y; - vertex_tangents[i0].z += t.z; - vertex_tangents[i0].w = sign; + vertex_tangents[i0].x += t.x; + vertex_tangents[i0].y += t.y; + vertex_tangents[i0].z += t.z; + vertex_tangents[i0].w = sign; - vertex_tangents[i1].x += t.x; - vertex_tangents[i1].y += t.y; - vertex_tangents[i1].z += t.z; - vertex_tangents[i1].w = sign; + vertex_tangents[i1].x += t.x; + vertex_tangents[i1].y += t.y; + vertex_tangents[i1].z += t.z; + vertex_tangents[i1].w = sign; - vertex_tangents[i2].x += t.x; - vertex_tangents[i2].y += t.y; - vertex_tangents[i2].z += t.z; - vertex_tangents[i2].w = sign; + vertex_tangents[i2].x += t.x; + vertex_tangents[i2].y += t.y; + vertex_tangents[i2].z += t.z; + vertex_tangents[i2].w = sign; + } } } - vertex_subsets.resize(vertex_positions.size()); - uint32_t subsetCounter = 0; - for (auto& subset : subsets) { - for (uint32_t i = 0; i < subset.indexCount; ++i) + vertex_subsets.resize(vertex_positions.size()); + uint32_t first_subset = 0; + uint32_t last_subset = 0; + GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) { - uint32_t index = indices[subset.indexOffset + i]; - vertex_subsets[index] = subsetCounter; + const MeshComponent::MeshSubset& subset = subsets[subsetIndex]; + for (uint32_t i = 0; i < subset.indexCount; ++i) + { + uint32_t index = indices[subset.indexOffset + i]; + vertex_subsets[index] = subsetIndex; + } } - subsetCounter++; } const size_t uv_count = std::max(vertex_uvset_0.size(), vertex_uvset_1.size()); @@ -698,8 +709,12 @@ namespace wi::scene desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_TRACE; } - for (auto& subset : subsets) + uint32_t first_subset = 0; + uint32_t last_subset = 0; + GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) { + const MeshComponent::MeshSubset& subset = subsets[subsetIndex]; desc.bottom_level.geometries.emplace_back(); auto& geometry = desc.bottom_level.geometries.back(); geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES; @@ -1523,7 +1538,7 @@ namespace wi::scene // Occlusion culling read: if(wi::renderer::GetOcclusionCullingEnabled() && !wi::renderer::GetFreezeCullingCameraEnabled()) { - uint32_t minQueryCount = uint32_t(objects.GetCount() + lights.GetCount()); + uint32_t minQueryCount = uint32_t(objects.GetCount() + lights.GetCount() + 1); // +1 : ocean if (queryHeap.desc.query_count < minQueryCount) { GPUQueryHeapDesc desc; @@ -3075,7 +3090,8 @@ namespace wi::scene if (material != nullptr) { subset.materialIndex = (uint32_t)materials.GetIndex(subset.materialID); - if (mesh.BLAS.IsValid()) + const uint32_t lod_index = mesh.subsets_per_lod > 0 ? subsetIndex / mesh.subsets_per_lod : 0; + if (lod_index == 0 && mesh.BLAS.IsValid()) { auto& geometry = mesh.BLAS.desc.bottom_level.geometries[subsetIndex]; uint32_t flags = geometry.flags; @@ -3278,7 +3294,7 @@ namespace wi::scene uint32_t transform_index = (uint32_t)transforms.GetIndex(entity); const TransformComponent& transform = transforms[transform_index]; - if (object.mesh_index >= 0) + if (object.mesh_index != ~0u) { const MeshComponent& mesh = meshes[object.mesh_index]; @@ -3302,8 +3318,12 @@ namespace wi::scene } } - for (auto& subset : mesh.subsets) + 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; ++subsetIndex) { + const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; const MaterialComponent* material = materials.GetComponent(subset.materialID); if (material != nullptr) @@ -3926,6 +3946,26 @@ namespace wi::scene { OceanRegenerate(); } + + // Ocean occlusion status: + if (!wi::renderer::GetFreezeCullingCameraEnabled() && weather.IsOceanEnabled()) + { + ocean.occlusionHistory <<= 1u; // advance history by 1 frame + int query_id = ocean.occlusionQueries[queryheap_idx]; + if (queryResultBuffer[queryheap_idx].mapped_data != nullptr && query_id >= 0) + { + uint64_t visible = ((uint64_t*)queryResultBuffer[queryheap_idx].mapped_data)[query_id]; + if (visible) + { + ocean.occlusionHistory |= 1; // visible + } + } + else + { + ocean.occlusionHistory |= 1; // visible + } + } + ocean.occlusionQueries[queryheap_idx] = -1; // invalidate query } } void Scene::RunSoundUpdateSystem(wi::jobsystem::context& ctx) @@ -3966,6 +4006,37 @@ namespace wi::scene } } + void Scene::UpdateLODsForCamera(const CameraComponent& camera) + { + wi::jobsystem::context ctx; + wi::jobsystem::Dispatch(ctx, (uint32_t)objects.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) { + ObjectComponent& object = objects[args.jobIndex]; + if (object.meshID == INVALID_ENTITY) + return; + const AABB& aabb = aabb_objects[args.jobIndex]; + const float distsq = wi::math::DistanceSquared(camera.Eye, aabb.getCenter()); + const float radius = aabb.getRadius(); + const float radiussq = radius * radius; + if (distsq < radiussq) + { + object.lod = 0; + } + else + { + const MeshComponent* mesh = meshes.GetComponent(object.meshID); + if (mesh != nullptr && mesh->subsets_per_lod > 0) + { + const float dist = std::sqrt(distsq); + const float dist_to_sphere = dist - radius; + object.lod = uint32_t(dist_to_sphere * object.lod_distance_multiplier); + object.lod = std::min(object.lod, mesh->GetLODCount() - 1); + } + } + + }); + wi::jobsystem::Wait(ctx); + } + void Scene::PutWaterRipple(const std::string& image, const XMFLOAT3& pos) { wi::Sprite img(image); @@ -4133,9 +4204,12 @@ namespace wi::scene const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr; - int subsetCounter = 0; - for (auto& subset : mesh.subsets) + 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; ++subsetIndex) { + const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; for (size_t i = 0; i < subset.indexCount; i += 3) { const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0]; @@ -4179,7 +4253,7 @@ namespace wi::scene float distance; XMFLOAT2 bary; - if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary)) + if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary, ray.TMin, ray.TMax)) { const XMVECTOR pos = XMVector3Transform(XMVectorAdd(rayOrigin_local, rayDirection_local*distance), objectMat); distance = wi::math::Distance(pos, rayOrigin); @@ -4192,7 +4266,7 @@ namespace wi::scene XMStoreFloat3(&result.position, pos); XMStoreFloat3(&result.normal, nor); result.distance = distance; - result.subsetIndex = subsetCounter; + result.subsetIndex = (int)subsetIndex; result.vertexID0 = (int)i0; result.vertexID1 = (int)i1; result.vertexID2 = (int)i2; @@ -4200,7 +4274,6 @@ namespace wi::scene } } } - subsetCounter++; } } @@ -4261,9 +4334,12 @@ namespace wi::scene const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr; - int subsetCounter = 0; - for (auto& subset : mesh.subsets) + 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; ++subsetIndex) { + const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; for (size_t i = 0; i < subset.indexCount; i += 3) { const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0]; @@ -4411,7 +4487,6 @@ namespace wi::scene return result; } } - subsetCounter++; } } @@ -4467,9 +4542,12 @@ namespace wi::scene const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr; - int subsetCounter = 0; - for (auto& subset : mesh.subsets) + 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; ++subsetIndex) { + const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex]; for (size_t i = 0; i < subset.indexCount; i += 3) { const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0]; @@ -4688,7 +4766,6 @@ namespace wi::scene return result; } } - subsetCounter++; } } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 647cd5f39..05b46c938 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -365,6 +365,8 @@ namespace wi::scene }; wi::vector targets; + uint32_t subsets_per_lod = 0; // this needs to be specified if there are multiple LOD levels + // Non-serialized attributes: wi::primitive::AABB aabb; wi::graphics::GPUBuffer generalBuffer; // index buffer + all static vertex buffers @@ -426,6 +428,18 @@ namespace wi::scene inline wi::graphics::IndexBufferFormat GetIndexFormat() const { return vertex_positions.size() > 65536 ? wi::graphics::IndexBufferFormat::UINT32 : wi::graphics::IndexBufferFormat::UINT16; } inline size_t GetIndexStride() const { return GetIndexFormat() == wi::graphics::IndexBufferFormat::UINT32 ? sizeof(uint32_t) : sizeof(uint16_t); } inline bool IsSkinned() const { return armatureID != wi::ecs::INVALID_ENTITY; } + inline uint32_t GetLODCount() const { return subsets_per_lod == 0 ? 1 : ((uint32_t)subsets.size() / subsets_per_lod); } + inline void GetLODSubsetRange(uint32_t lod, uint32_t& first_subset, uint32_t& last_subset) const + { + first_subset = 0; + last_subset = (uint32_t)subsets.size(); + if (subsets_per_lod > 0) + { + lod = std::min(lod, GetLODCount() - 1); + first_subset = subsets_per_lod * lod; + last_subset = first_subset + subsets_per_lod; + } + } // Recreates GPU resources for index/vertex buffers void CreateRenderData(); @@ -641,6 +655,7 @@ namespace wi::scene wi::vector lightmapTextureData; uint8_t userStencilRef = 0; + float lod_distance_multiplier = 1; // Non-serialized attributes: @@ -653,6 +668,8 @@ namespace wi::scene float impostorFadeThresholdRadius; float impostorSwapDistance; + uint32_t lod = 0; + // these will only be valid for a single frame: uint32_t mesh_index = ~0u; XMFLOAT4X4 worldMatrix = wi::math::IDENTITY_MATRIX; @@ -888,7 +905,7 @@ namespace wi::scene float width = 0.0f; float height = 0.0f; float zNearP = 0.1f; - float zFarP = 800.0f; + float zFarP = 5000.0f; float fov = XM_PI / 3.0f; float focal_length = 1; float aperture_size = 0; @@ -1494,6 +1511,8 @@ namespace wi::scene void RunParticleUpdateSystem(wi::jobsystem::context& ctx); void RunWeatherUpdateSystem(wi::jobsystem::context& ctx); void RunSoundUpdateSystem(wi::jobsystem::context& ctx); + + void UpdateLODsForCamera(const CameraComponent& camera); }; // Returns skinned vertex position in armature local space @@ -1545,7 +1564,7 @@ namespace wi::scene XMFLOAT2 bary = XMFLOAT2(0, 0); XMFLOAT4X4 orientation = wi::math::IDENTITY_MATRIX; - bool operator==(const PickResult& other) + constexpr bool operator==(const PickResult& other) const { return entity == other.entity; } diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index d254dc36f..e9c2c1eb1 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -403,6 +403,11 @@ namespace wi::scene } } + if (archive.GetVersion() >= 76) + { + archive >> subsets_per_lod; + } + wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) { CreateRenderData(); }); @@ -463,6 +468,11 @@ namespace wi::scene } } + if (archive.GetVersion() >= 76) + { + archive << subsets_per_lod; + } + } } void ImpostorComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) @@ -516,6 +526,10 @@ namespace wi::scene { archive >> emissiveColor; } + if (archive.GetVersion() >= 76) + { + archive >> lod_distance_multiplier; + } } else { @@ -539,6 +553,10 @@ namespace wi::scene { archive << emissiveColor; } + if (archive.GetVersion() >= 76) + { + archive << lod_distance_multiplier; + } } } void RigidBodyPhysicsComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) diff --git a/WickedEngine/wiUnorderedMap.h b/WickedEngine/wiUnorderedMap.h index 8c5a6dc04..8083fac77 100644 --- a/WickedEngine/wiUnorderedMap.h +++ b/WickedEngine/wiUnorderedMap.h @@ -14,11 +14,11 @@ namespace wi { - template + template, typename E = std::equal_to, typename A = std::allocator > > #if WI_UNORDERED_MAP_TYPE == 1 - using unordered_map = ska::flat_hash_map; + using unordered_map = ska::flat_hash_map; #else - using unordered_map = std::unordered_map; + using unordered_map = std::unordered_map; #endif // WI_UNORDERED_MAP_TYPE } diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 6a24c16c2..1dcd30416 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 = 60; // minor bug fixes, alterations, refactors, updates - const int revision = 49; + const int revision = 50; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); diff --git a/appveyor.yml b/appveyor.yml index 1e7e09eee..5e7a09671 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,11 +23,11 @@ after_build: #Editor: - cmd: move %APPVEYOR_BUILD_FOLDER%\BUILD\%PLATFORM%\Release\Editor_Windows\Editor_Windows.exe %APPVEYOR_BUILD_FOLDER%\Editor - cmd: xcopy C:\projects\wickedengine\WickedEngine\*.dll %APPVEYOR_BUILD_FOLDER%\Editor - - cmd: 7z a WickedEngineEditor.zip Content\ features.txt other_licenses.txt *.md Editor\*.exe Editor\images\ Editor\sound\ Editor\*.ini Editor\*.ico Editor\*.lua + - cmd: 7z a WickedEngineEditor.zip Content\ features.txt other_licenses.txt *.md Editor\*.exe Editor\images\ Editor\terrain\ Editor\sound\ Editor\*.ini Editor\*.ico Editor\*.lua #Tests: - cmd: move %APPVEYOR_BUILD_FOLDER%\BUILD\%PLATFORM%\Release\Tests\Tests.exe %APPVEYOR_BUILD_FOLDER%\Tests - cmd: xcopy C:\projects\wickedengine\WickedEngine\*.dll %APPVEYOR_BUILD_FOLDER%\Tests - - cmd: 7z a WickedEngineTests.zip Content\ features.txt other_licenses.txt *.md Tests\*.exe Tests\images\ Tests\sound\ Tests\*.ini Tests\*.ico Tests\*.lua Tests\*.ttf + - cmd: 7z a WickedEngineTests.zip Content\ features.txt other_licenses.txt *.md Tests\*.exe Tests\images\ Tests\sound\ Tests\*.ini Tests\*.txt Tests\*.ico Tests\*.lua Tests\*.ttf artifacts: - path: WickedEngineEditor.zip diff --git a/features.txt b/features.txt index 4267549cd..ec574ef20 100644 --- a/features.txt +++ b/features.txt @@ -83,6 +83,7 @@ Stochastic alphatest transparency Surfel GI HDR display output Dynamic Diffuse Global Illumination (DDGI) +Procedural terrain generator GLTF 2.0 extensions supported: KHR_materials_unlit