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