2387 lines
78 KiB
C++
2387 lines
78 KiB
C++
#include "wiScene_Components.h"
|
|
#include "wiTextureHelper.h"
|
|
#include "wiResourceManager.h"
|
|
#include "wiPhysics.h"
|
|
#include "wiRenderer.h"
|
|
#include "wiJobSystem.h"
|
|
#include "wiSpinLock.h"
|
|
#include "wiHelper.h"
|
|
#include "wiRenderer.h"
|
|
#include "wiBacklog.h"
|
|
#include "wiTimer.h"
|
|
#include "wiUnorderedMap.h"
|
|
#include "wiLua.h"
|
|
|
|
#include "Utility/mikktspace.h"
|
|
#include "Utility/meshoptimizer/meshoptimizer.h"
|
|
|
|
#if __has_include("OpenImageDenoise/oidn.hpp")
|
|
#include "OpenImageDenoise/oidn.hpp"
|
|
#if OIDN_VERSION_MAJOR >= 2
|
|
#define OPEN_IMAGE_DENOISE
|
|
#pragma comment(lib,"OpenImageDenoise.lib")
|
|
// Also provide the required DLL files from OpenImageDenoise release near the exe!
|
|
#endif // OIDN_VERSION_MAJOR >= 2
|
|
#endif // __has_include("OpenImageDenoise/oidn.hpp")
|
|
|
|
using namespace wi::ecs;
|
|
using namespace wi::enums;
|
|
using namespace wi::graphics;
|
|
using namespace wi::primitive;
|
|
|
|
namespace wi::scene
|
|
{
|
|
|
|
|
|
XMFLOAT3 TransformComponent::GetPosition() const
|
|
{
|
|
return *((XMFLOAT3*)&world._41);
|
|
}
|
|
XMFLOAT4 TransformComponent::GetRotation() const
|
|
{
|
|
XMFLOAT4 rotation;
|
|
XMStoreFloat4(&rotation, GetRotationV());
|
|
return rotation;
|
|
}
|
|
XMFLOAT3 TransformComponent::GetScale() const
|
|
{
|
|
XMFLOAT3 scale;
|
|
XMStoreFloat3(&scale, GetScaleV());
|
|
return scale;
|
|
}
|
|
XMVECTOR TransformComponent::GetPositionV() const
|
|
{
|
|
return XMLoadFloat3((XMFLOAT3*)&world._41);
|
|
}
|
|
XMVECTOR TransformComponent::GetRotationV() const
|
|
{
|
|
XMVECTOR S, R, T;
|
|
XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
|
|
return R;
|
|
}
|
|
XMVECTOR TransformComponent::GetScaleV() const
|
|
{
|
|
XMVECTOR S, R, T;
|
|
XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
|
|
return S;
|
|
}
|
|
XMMATRIX TransformComponent::GetLocalMatrix() const
|
|
{
|
|
XMVECTOR S_local = XMLoadFloat3(&scale_local);
|
|
XMVECTOR R_local = XMLoadFloat4(&rotation_local);
|
|
XMVECTOR T_local = XMLoadFloat3(&translation_local);
|
|
return
|
|
XMMatrixScalingFromVector(S_local) *
|
|
XMMatrixRotationQuaternion(R_local) *
|
|
XMMatrixTranslationFromVector(T_local);
|
|
}
|
|
XMFLOAT3 TransformComponent::GetForward() const
|
|
{
|
|
return wi::math::GetForward(world);
|
|
}
|
|
XMFLOAT3 TransformComponent::GetUp() const
|
|
{
|
|
return wi::math::GetUp(world);
|
|
}
|
|
XMFLOAT3 TransformComponent::GetRight() const
|
|
{
|
|
return wi::math::GetRight(world);
|
|
}
|
|
XMVECTOR TransformComponent::GetForwardV() const
|
|
{
|
|
XMFLOAT3 v = wi::math::GetForward(world);
|
|
return XMLoadFloat3(&v);
|
|
}
|
|
XMVECTOR TransformComponent::GetUpV() const
|
|
{
|
|
XMFLOAT3 v = wi::math::GetUp(world);
|
|
return XMLoadFloat3(&v);
|
|
}
|
|
XMVECTOR TransformComponent::GetRightV() const
|
|
{
|
|
XMFLOAT3 v = wi::math::GetRight(world);
|
|
return XMLoadFloat3(&v);
|
|
}
|
|
void TransformComponent::UpdateTransform()
|
|
{
|
|
if (IsDirty())
|
|
{
|
|
SetDirty(false);
|
|
|
|
XMStoreFloat4x4(&world, GetLocalMatrix());
|
|
}
|
|
}
|
|
void TransformComponent::UpdateTransform_Parented(const TransformComponent& parent)
|
|
{
|
|
XMMATRIX W = GetLocalMatrix();
|
|
XMMATRIX W_parent = XMLoadFloat4x4(&parent.world);
|
|
W = W * W_parent;
|
|
|
|
XMStoreFloat4x4(&world, W);
|
|
}
|
|
void TransformComponent::ApplyTransform()
|
|
{
|
|
SetDirty();
|
|
|
|
XMVECTOR S, R, T;
|
|
XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
|
|
XMStoreFloat3(&scale_local, S);
|
|
XMStoreFloat4(&rotation_local, R);
|
|
XMStoreFloat3(&translation_local, T);
|
|
}
|
|
void TransformComponent::ClearTransform()
|
|
{
|
|
SetDirty();
|
|
scale_local = XMFLOAT3(1, 1, 1);
|
|
rotation_local = XMFLOAT4(0, 0, 0, 1);
|
|
translation_local = XMFLOAT3(0, 0, 0);
|
|
}
|
|
void TransformComponent::Translate(const XMFLOAT3& value)
|
|
{
|
|
SetDirty();
|
|
translation_local.x += value.x;
|
|
translation_local.y += value.y;
|
|
translation_local.z += value.z;
|
|
}
|
|
void TransformComponent::Translate(const XMVECTOR& value)
|
|
{
|
|
XMFLOAT3 translation;
|
|
XMStoreFloat3(&translation, value);
|
|
Translate(translation);
|
|
}
|
|
void TransformComponent::RotateRollPitchYaw(const XMFLOAT3& value)
|
|
{
|
|
SetDirty();
|
|
|
|
// This needs to be handled a bit differently
|
|
XMVECTOR quat = XMLoadFloat4(&rotation_local);
|
|
XMVECTOR x = XMQuaternionRotationRollPitchYaw(value.x, 0, 0);
|
|
XMVECTOR y = XMQuaternionRotationRollPitchYaw(0, value.y, 0);
|
|
XMVECTOR z = XMQuaternionRotationRollPitchYaw(0, 0, value.z);
|
|
|
|
quat = XMQuaternionMultiply(x, quat);
|
|
quat = XMQuaternionMultiply(quat, y);
|
|
quat = XMQuaternionMultiply(z, quat);
|
|
quat = XMQuaternionNormalize(quat);
|
|
|
|
XMStoreFloat4(&rotation_local, quat);
|
|
}
|
|
void TransformComponent::Rotate(const XMFLOAT4& quaternion)
|
|
{
|
|
SetDirty();
|
|
|
|
XMVECTOR result = XMQuaternionMultiply(XMLoadFloat4(&rotation_local), XMLoadFloat4(&quaternion));
|
|
result = XMQuaternionNormalize(result);
|
|
XMStoreFloat4(&rotation_local, result);
|
|
}
|
|
void TransformComponent::Rotate(const XMVECTOR& quaternion)
|
|
{
|
|
XMFLOAT4 rotation;
|
|
XMStoreFloat4(&rotation, quaternion);
|
|
Rotate(rotation);
|
|
}
|
|
void TransformComponent::Scale(const XMFLOAT3& value)
|
|
{
|
|
SetDirty();
|
|
scale_local.x *= value.x;
|
|
scale_local.y *= value.y;
|
|
scale_local.z *= value.z;
|
|
}
|
|
void TransformComponent::Scale(const XMVECTOR& value)
|
|
{
|
|
XMFLOAT3 scale;
|
|
XMStoreFloat3(&scale, value);
|
|
Scale(scale);
|
|
}
|
|
void TransformComponent::MatrixTransform(const XMFLOAT4X4& matrix)
|
|
{
|
|
MatrixTransform(XMLoadFloat4x4(&matrix));
|
|
}
|
|
void TransformComponent::MatrixTransform(const XMMATRIX& matrix)
|
|
{
|
|
SetDirty();
|
|
|
|
XMVECTOR S;
|
|
XMVECTOR R;
|
|
XMVECTOR T;
|
|
XMMatrixDecompose(&S, &R, &T, GetLocalMatrix() * matrix);
|
|
|
|
XMStoreFloat3(&scale_local, S);
|
|
XMStoreFloat4(&rotation_local, R);
|
|
XMStoreFloat3(&translation_local, T);
|
|
}
|
|
void TransformComponent::Lerp(const TransformComponent& a, const TransformComponent& b, float t)
|
|
{
|
|
SetDirty();
|
|
|
|
XMVECTOR aS, aR, aT;
|
|
XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
|
|
|
|
XMVECTOR bS, bR, bT;
|
|
XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
|
|
|
|
XMVECTOR S = XMVectorLerp(aS, bS, t);
|
|
XMVECTOR R = XMQuaternionSlerp(aR, bR, t);
|
|
XMVECTOR T = XMVectorLerp(aT, bT, t);
|
|
|
|
XMStoreFloat3(&scale_local, S);
|
|
XMStoreFloat4(&rotation_local, R);
|
|
XMStoreFloat3(&translation_local, T);
|
|
}
|
|
void TransformComponent::CatmullRom(const TransformComponent& a, const TransformComponent& b, const TransformComponent& c, const TransformComponent& d, float t)
|
|
{
|
|
SetDirty();
|
|
|
|
XMVECTOR aS, aR, aT;
|
|
XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
|
|
|
|
XMVECTOR bS, bR, bT;
|
|
XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
|
|
|
|
XMVECTOR cS, cR, cT;
|
|
XMMatrixDecompose(&cS, &cR, &cT, XMLoadFloat4x4(&c.world));
|
|
|
|
XMVECTOR dS, dR, dT;
|
|
XMMatrixDecompose(&dS, &dR, &dT, XMLoadFloat4x4(&d.world));
|
|
|
|
XMVECTOR T = XMVectorCatmullRom(aT, bT, cT, dT, t);
|
|
|
|
XMVECTOR setupA;
|
|
XMVECTOR setupB;
|
|
XMVECTOR setupC;
|
|
|
|
aR = XMQuaternionNormalize(aR);
|
|
bR = XMQuaternionNormalize(bR);
|
|
cR = XMQuaternionNormalize(cR);
|
|
dR = XMQuaternionNormalize(dR);
|
|
|
|
XMQuaternionSquadSetup(&setupA, &setupB, &setupC, aR, bR, cR, dR);
|
|
XMVECTOR R = XMQuaternionSquad(bR, setupA, setupB, setupC, t);
|
|
|
|
XMVECTOR S = XMVectorCatmullRom(aS, bS, cS, dS, t);
|
|
|
|
XMStoreFloat3(&translation_local, T);
|
|
XMStoreFloat4(&rotation_local, R);
|
|
XMStoreFloat3(&scale_local, S);
|
|
}
|
|
|
|
void MaterialComponent::WriteShaderMaterial(ShaderMaterial* dest) const
|
|
{
|
|
ShaderMaterial material;
|
|
material.baseColor = baseColor;
|
|
material.emissive_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(emissiveColor.x * emissiveColor.w, emissiveColor.y * emissiveColor.w, emissiveColor.z * emissiveColor.w));
|
|
material.specular_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(specularColor.x * specularColor.w, specularColor.y * specularColor.w, specularColor.z * specularColor.w));
|
|
material.texMulAdd = texMulAdd;
|
|
material.roughness = roughness;
|
|
material.reflectance = reflectance;
|
|
material.metalness = metalness;
|
|
material.refraction = refraction;
|
|
material.normalMapStrength = (textures[NORMALMAP].resource.IsValid() ? normalMapStrength : 0);
|
|
material.parallaxOcclusionMapping = parallaxOcclusionMapping;
|
|
material.displacementMapping = displacementMapping;
|
|
XMFLOAT4 sss = subsurfaceScattering;
|
|
sss.x *= sss.w;
|
|
sss.y *= sss.w;
|
|
sss.z *= sss.w;
|
|
XMFLOAT4 sss_inv = XMFLOAT4(
|
|
sss_inv.x = 1.0f / ((1 + sss.x) * (1 + sss.x)),
|
|
sss_inv.y = 1.0f / ((1 + sss.y) * (1 + sss.y)),
|
|
sss_inv.z = 1.0f / ((1 + sss.z) * (1 + sss.z)),
|
|
sss_inv.w = 1.0f / ((1 + sss.w) * (1 + sss.w))
|
|
);
|
|
material.subsurfaceScattering = sss;
|
|
material.subsurfaceScattering_inv = sss_inv;
|
|
|
|
material.sheenColor_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(sheenColor.x, sheenColor.y, sheenColor.z));
|
|
material.sheenRoughness = sheenRoughness;
|
|
material.clearcoat = clearcoat;
|
|
material.clearcoatRoughness = clearcoatRoughness;
|
|
material.alphaTest = 1 - alphaRef;
|
|
material.layerMask = layerMask;
|
|
material.transmission = transmission;
|
|
if (shaderType == SHADERTYPE_PBR_ANISOTROPIC)
|
|
{
|
|
material.anisotropy_strength = wi::math::Clamp(anisotropy_strength, 0, 0.99f);
|
|
material.anisotropy_rotation_sin = std::sin(anisotropy_rotation);
|
|
material.anisotropy_rotation_cos = std::cos(anisotropy_rotation);
|
|
}
|
|
else
|
|
{
|
|
material.anisotropy_strength = 0;
|
|
material.anisotropy_rotation_sin = 0;
|
|
material.anisotropy_rotation_cos = 0;
|
|
}
|
|
if (blend_with_terrain_height > 0)
|
|
{
|
|
material.blend_with_terrain_height_rcp = 1.0f / blend_with_terrain_height;
|
|
}
|
|
else
|
|
{
|
|
material.blend_with_terrain_height_rcp = 0;
|
|
}
|
|
material.stencilRef = wi::renderer::CombineStencilrefs(engineStencilRef, userStencilRef);
|
|
material.shaderType = (uint)shaderType;
|
|
material.userdata = userdata;
|
|
|
|
material.options = 0;
|
|
if (IsUsingVertexColors())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_USE_VERTEXCOLORS;
|
|
}
|
|
if (IsUsingSpecularGlossinessWorkflow())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_SPECULARGLOSSINESS_WORKFLOW;
|
|
}
|
|
if (IsOcclusionEnabled_Primary())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_PRIMARY;
|
|
}
|
|
if (IsOcclusionEnabled_Secondary())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_SECONDARY;
|
|
}
|
|
if (IsUsingWind())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_USE_WIND;
|
|
}
|
|
if (IsReceiveShadow())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_RECEIVE_SHADOW;
|
|
}
|
|
if (IsCastingShadow())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_CAST_SHADOW;
|
|
}
|
|
if (IsDoubleSided())
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_DOUBLE_SIDED;
|
|
}
|
|
if (GetFilterMask() & FILTER_TRANSPARENT)
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_TRANSPARENT;
|
|
}
|
|
if (userBlendMode == BLENDMODE_ADDITIVE)
|
|
{
|
|
material.options |= SHADERMATERIAL_OPTION_BIT_ADDITIVE;
|
|
}
|
|
if (shaderType == SHADERTYPE_UNLIT)
|
|
{
|
|
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)
|
|
{
|
|
material.textures[i].uvset_lodclamp = (textures[i].uvset & 1) | (XMConvertFloatToHalf(textures[i].lod_clamp) << 1u);
|
|
if (textures[i].resource.IsValid())
|
|
{
|
|
int subresource = -1;
|
|
switch (i)
|
|
{
|
|
case BASECOLORMAP:
|
|
case EMISSIVEMAP:
|
|
case SPECULARMAP:
|
|
case SHEENCOLORMAP:
|
|
subresource = textures[i].resource.GetTextureSRGBSubresource();
|
|
break;
|
|
case SURFACEMAP:
|
|
if (IsUsingSpecularGlossinessWorkflow())
|
|
{
|
|
subresource = textures[i].resource.GetTextureSRGBSubresource();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
material.textures[i].texture_descriptor = device->GetDescriptorIndex(textures[i].GetGPUResource(), SubresourceType::SRV, subresource);
|
|
}
|
|
else
|
|
{
|
|
material.textures[i].texture_descriptor = -1;
|
|
}
|
|
material.textures[i].sparse_residencymap_descriptor = textures[i].sparse_residencymap_descriptor;
|
|
material.textures[i].sparse_feedbackmap_descriptor = textures[i].sparse_feedbackmap_descriptor;
|
|
}
|
|
|
|
if (sampler_descriptor < 0)
|
|
{
|
|
material.sampler_descriptor = device->GetDescriptorIndex(wi::renderer::GetSampler(wi::enums::SAMPLER_OBJECTSHADER));
|
|
}
|
|
else
|
|
{
|
|
material.sampler_descriptor = sampler_descriptor;
|
|
}
|
|
|
|
std::memcpy(dest, &material, sizeof(ShaderMaterial)); // memcpy whole structure into mapped pointer to avoid read from uncached memory
|
|
}
|
|
void MaterialComponent::WriteShaderTextureSlot(ShaderMaterial* dest, int slot, int descriptor)
|
|
{
|
|
std::memcpy(&dest->textures[slot].texture_descriptor, &descriptor, sizeof(descriptor)); // memcpy into mapped pointer to avoid read from uncached memory
|
|
}
|
|
void MaterialComponent::WriteTextures(const wi::graphics::GPUResource** dest, int count) const
|
|
{
|
|
count = std::min(count, (int)TEXTURESLOT_COUNT);
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
dest[i] = textures[i].GetGPUResource();
|
|
}
|
|
}
|
|
uint32_t MaterialComponent::GetFilterMask() const
|
|
{
|
|
if (IsCustomShader() && customShaderID < (int)wi::renderer::GetCustomShaders().size())
|
|
{
|
|
auto& customShader = wi::renderer::GetCustomShaders()[customShaderID];
|
|
return customShader.filterMask;
|
|
}
|
|
if (shaderType == SHADERTYPE_WATER)
|
|
{
|
|
return FILTER_TRANSPARENT | FILTER_WATER;
|
|
}
|
|
if (transmission > 0)
|
|
{
|
|
return FILTER_TRANSPARENT;
|
|
}
|
|
if (userBlendMode == BLENDMODE_OPAQUE)
|
|
{
|
|
return FILTER_OPAQUE;
|
|
}
|
|
return FILTER_TRANSPARENT;
|
|
}
|
|
wi::resourcemanager::Flags MaterialComponent::GetTextureSlotResourceFlags(TEXTURESLOT slot)
|
|
{
|
|
wi::resourcemanager::Flags flags = wi::resourcemanager::Flags::NONE;
|
|
if (!IsPreferUncompressedTexturesEnabled())
|
|
{
|
|
flags |= wi::resourcemanager::Flags::IMPORT_BLOCK_COMPRESSED;
|
|
}
|
|
if (!IsTextureStreamingDisabled())
|
|
{
|
|
flags |= wi::resourcemanager::Flags::STREAMING;
|
|
}
|
|
switch (slot)
|
|
{
|
|
case NORMALMAP:
|
|
case CLEARCOATNORMALMAP:
|
|
flags |= wi::resourcemanager::Flags::IMPORT_NORMALMAP;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return flags;
|
|
}
|
|
void MaterialComponent::CreateRenderData(bool force_recreate)
|
|
{
|
|
if (force_recreate)
|
|
{
|
|
for (uint32_t slot = 0; slot < TEXTURESLOT_COUNT; ++slot)
|
|
{
|
|
auto& textureslot = textures[slot];
|
|
if (textureslot.resource.IsValid())
|
|
{
|
|
textureslot.resource.SetOutdated();
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t slot = 0; slot < TEXTURESLOT_COUNT; ++slot)
|
|
{
|
|
auto& textureslot = textures[slot];
|
|
if (!textureslot.name.empty())
|
|
{
|
|
wi::resourcemanager::Flags flags = GetTextureSlotResourceFlags(TEXTURESLOT(slot));
|
|
textureslot.resource = wi::resourcemanager::Load(textureslot.name, flags);
|
|
}
|
|
}
|
|
}
|
|
uint32_t MaterialComponent::GetStencilRef() const
|
|
{
|
|
return wi::renderer::CombineStencilrefs(engineStencilRef, userStencilRef);
|
|
}
|
|
|
|
struct MikkTSpaceUserdata
|
|
{
|
|
MeshComponent* mesh = nullptr;
|
|
const uint32_t* indicesLOD0 = nullptr;
|
|
int faceCountLOD0 = 0;
|
|
};
|
|
int get_num_faces(const SMikkTSpaceContext* context)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
return userdata->faceCountLOD0;
|
|
}
|
|
int get_num_vertices_of_face(const SMikkTSpaceContext* context, const int iFace)
|
|
{
|
|
return 3;
|
|
}
|
|
int get_vertex_index(const SMikkTSpaceContext* context, int iFace, int iVert)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
int face_size = get_num_vertices_of_face(context, iFace);
|
|
int indices_index = iFace * face_size + iVert;
|
|
int index = int(userdata->indicesLOD0[indices_index]);
|
|
return index;
|
|
}
|
|
void get_position(const SMikkTSpaceContext* context, float* outpos, const int iFace, const int iVert)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
int index = get_vertex_index(context, iFace, iVert);
|
|
const XMFLOAT3& vert = userdata->mesh->vertex_positions[index];
|
|
outpos[0] = vert.x;
|
|
outpos[1] = vert.y;
|
|
outpos[2] = vert.z;
|
|
}
|
|
void get_normal(const SMikkTSpaceContext* context, float* outnormal, const int iFace, const int iVert)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
int index = get_vertex_index(context, iFace, iVert);
|
|
const XMFLOAT3& vert = userdata->mesh->vertex_normals[index];
|
|
outnormal[0] = vert.x;
|
|
outnormal[1] = vert.y;
|
|
outnormal[2] = vert.z;
|
|
}
|
|
void get_tex_coords(const SMikkTSpaceContext* context, float* outuv, const int iFace, const int iVert)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
int index = get_vertex_index(context, iFace, iVert);
|
|
const XMFLOAT2& vert = userdata->mesh->vertex_uvset_0[index];
|
|
outuv[0] = vert.x;
|
|
outuv[1] = vert.y;
|
|
}
|
|
void set_tspace_basic(const SMikkTSpaceContext* context, const float* tangentu, const float fSign, const int iFace, const int iVert)
|
|
{
|
|
const MikkTSpaceUserdata* userdata = static_cast<const MikkTSpaceUserdata*>(context->m_pUserData);
|
|
auto index = get_vertex_index(context, iFace, iVert);
|
|
XMFLOAT4& vert = userdata->mesh->vertex_tangents[index];
|
|
vert.x = tangentu[0];
|
|
vert.y = tangentu[1];
|
|
vert.z = tangentu[2];
|
|
vert.w = fSign;
|
|
}
|
|
|
|
void MeshComponent::DeleteRenderData()
|
|
{
|
|
generalBuffer = {};
|
|
streamoutBuffer = {};
|
|
ib = {};
|
|
vb_pos_wind = {};
|
|
vb_nor = {};
|
|
vb_tan = {};
|
|
vb_uvs = {};
|
|
vb_atl = {};
|
|
vb_col = {};
|
|
vb_bon = {};
|
|
so_pos = {};
|
|
so_nor = {};
|
|
so_tan = {};
|
|
so_pre = {};
|
|
BLASes.clear();
|
|
for (MorphTarget& morph : morph_targets)
|
|
{
|
|
morph.offset_pos = ~0ull;
|
|
morph.offset_nor = ~0ull;
|
|
}
|
|
}
|
|
void MeshComponent::CreateRenderData()
|
|
{
|
|
DeleteRenderData();
|
|
|
|
GraphicsDevice* device = wi::graphics::GetDevice();
|
|
|
|
if (vertex_tangents.empty() && !vertex_uvset_0.empty() && !vertex_normals.empty())
|
|
{
|
|
// Generate tangents if not found:
|
|
vertex_tangents.resize(vertex_positions.size());
|
|
|
|
#if 1
|
|
// MikkTSpace tangent generation:
|
|
MikkTSpaceUserdata userdata;
|
|
userdata.mesh = this;
|
|
uint32_t indexOffsetLOD0 = ~0u;
|
|
uint32_t indexCountLOD0 = 0;
|
|
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];
|
|
indexOffsetLOD0 = std::min(indexOffsetLOD0, subset.indexOffset);
|
|
indexCountLOD0 = std::max(indexCountLOD0, subset.indexCount);
|
|
}
|
|
userdata.indicesLOD0 = indices.data() + indexOffsetLOD0;
|
|
userdata.faceCountLOD0 = int(indexCountLOD0) / 3;
|
|
|
|
SMikkTSpaceInterface iface = {};
|
|
iface.m_getNumFaces = get_num_faces;
|
|
iface.m_getNumVerticesOfFace = get_num_vertices_of_face;
|
|
iface.m_getNormal = get_normal;
|
|
iface.m_getPosition = get_position;
|
|
iface.m_getTexCoord = get_tex_coords;
|
|
iface.m_setTSpaceBasic = set_tspace_basic;
|
|
SMikkTSpaceContext context = {};
|
|
context.m_pInterface = &iface;
|
|
context.m_pUserData = &userdata;
|
|
tbool mikktspace_result = genTangSpaceDefault(&context);
|
|
assert(mikktspace_result == 1);
|
|
|
|
#else
|
|
// Old tangent generation logic:
|
|
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];
|
|
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 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 XMVECTOR nor0 = XMLoadFloat3(&n0);
|
|
const XMVECTOR nor1 = XMLoadFloat3(&n1);
|
|
const XMVECTOR nor2 = XMLoadFloat3(&n2);
|
|
|
|
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 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);
|
|
|
|
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);
|
|
|
|
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[i2].x += t.x;
|
|
vertex_tangents[i2].y += t.y;
|
|
vertex_tangents[i2].z += t.z;
|
|
vertex_tangents[i2].w = sign;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const size_t uv_count = std::max(vertex_uvset_0.size(), vertex_uvset_1.size());
|
|
|
|
// Bounds computation:
|
|
XMFLOAT3 _min = XMFLOAT3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
|
|
XMFLOAT3 _max = XMFLOAT3(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
const XMFLOAT3& pos = vertex_positions[i];
|
|
_min = wi::math::Min(_min, pos);
|
|
_max = wi::math::Max(_max, pos);
|
|
}
|
|
aabb = AABB(_min, _max);
|
|
|
|
if (IsQuantizedPositionsDisabled())
|
|
{
|
|
position_format = vertex_windweights.empty() ? Vertex_POS32::FORMAT : Vertex_POS32W::FORMAT;
|
|
}
|
|
else
|
|
{
|
|
// Determine minimum precision for positions:
|
|
const float target_precision = 1.0f / 1000.0f; // millimeter
|
|
position_format = Vertex_POS10::FORMAT;
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
const XMFLOAT3& pos = vertex_positions[i];
|
|
const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
|
|
if (position_format == Vertex_POS10::FORMAT)
|
|
{
|
|
Vertex_POS10 v;
|
|
v.FromFULL(aabb, pos, wind);
|
|
XMFLOAT3 p = v.GetPOS(aabb);
|
|
if (
|
|
std::abs(p.x - pos.x) <= target_precision &&
|
|
std::abs(p.y - pos.y) <= target_precision &&
|
|
std::abs(p.z - pos.z) <= target_precision &&
|
|
wind == v.GetWind()
|
|
)
|
|
{
|
|
// success, continue to next vertex with 8 bits
|
|
continue;
|
|
}
|
|
position_format = Vertex_POS16::FORMAT; // failed, increase to 16 bits
|
|
}
|
|
if (position_format == Vertex_POS16::FORMAT)
|
|
{
|
|
Vertex_POS16 v;
|
|
v.FromFULL(aabb, pos, wind);
|
|
XMFLOAT3 p = v.GetPOS(aabb);
|
|
if (
|
|
std::abs(p.x - pos.x) <= target_precision &&
|
|
std::abs(p.y - pos.y) <= target_precision &&
|
|
std::abs(p.z - pos.z) <= target_precision &&
|
|
wind == v.GetWind()
|
|
)
|
|
{
|
|
// success, continue to next vertex with 16 bits
|
|
continue;
|
|
}
|
|
position_format = vertex_windweights.empty() ? Vertex_POS32::FORMAT : Vertex_POS32W::FORMAT; // failed, increase to 32 bits
|
|
break; // since 32 bit is the max, we can bail out
|
|
}
|
|
}
|
|
|
|
if (IsFormatUnorm(position_format))
|
|
{
|
|
// This is done to avoid 0 scaling on any axis of the UNORM remap matrix of the AABB
|
|
// It specifically solves a problem with hardware raytracing which treats AABB with zero axis as invisible
|
|
// Also there was problem with using float epsilon value, it did not enough precision for raytracing
|
|
constexpr float min_dim = 0.01f;
|
|
if (aabb._max.x - aabb._min.x < min_dim)
|
|
{
|
|
aabb._max.x += min_dim;
|
|
aabb._min.x -= min_dim;
|
|
}
|
|
if (aabb._max.y - aabb._min.y < min_dim)
|
|
{
|
|
aabb._max.y += min_dim;
|
|
aabb._min.y -= min_dim;
|
|
}
|
|
if (aabb._max.z - aabb._min.z < min_dim)
|
|
{
|
|
aabb._max.z += min_dim;
|
|
aabb._min.z -= min_dim;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine UV range for normalization:
|
|
if (!vertex_uvset_0.empty() || !vertex_uvset_1.empty())
|
|
{
|
|
const XMFLOAT2* uv0_stream = vertex_uvset_0.empty() ? vertex_uvset_1.data() : vertex_uvset_0.data();
|
|
const XMFLOAT2* uv1_stream = vertex_uvset_1.empty() ? vertex_uvset_0.data() : vertex_uvset_1.data();
|
|
|
|
uv_range_min = XMFLOAT2(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
|
|
uv_range_max = XMFLOAT2(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
|
|
for (size_t i = 0; i < uv_count; ++i)
|
|
{
|
|
uv_range_max = wi::math::Max(uv_range_max, uv0_stream[i]);
|
|
uv_range_max = wi::math::Max(uv_range_max, uv1_stream[i]);
|
|
uv_range_min = wi::math::Min(uv_range_min, uv0_stream[i]);
|
|
uv_range_min = wi::math::Min(uv_range_min, uv1_stream[i]);
|
|
}
|
|
}
|
|
|
|
const size_t position_stride = GetFormatStride(position_format);
|
|
|
|
GPUBufferDesc bd;
|
|
if (device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
|
|
{
|
|
// In UMA mode, it is better to create UPLOAD buffer, this avoids one copy from UPLOAD to DEFAULT
|
|
bd.usage = Usage::UPLOAD;
|
|
}
|
|
else
|
|
{
|
|
bd.usage = Usage::DEFAULT;
|
|
}
|
|
bd.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::INDEX_BUFFER | BindFlag::SHADER_RESOURCE;
|
|
bd.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::TYPED_FORMAT_CASTING | ResourceMiscFlag::NO_DEFAULT_DESCRIPTORS;
|
|
if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
|
|
{
|
|
bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
|
|
}
|
|
const uint64_t alignment = device->GetMinOffsetAlignment(&bd);
|
|
bd.size =
|
|
AlignTo(vertex_positions.size() * position_stride, alignment) + // position will be first to have 0 offset for flexible alignment!
|
|
AlignTo(indices.size() * GetIndexStride(), alignment) +
|
|
AlignTo(vertex_normals.size() * sizeof(Vertex_NOR), alignment) +
|
|
AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment) +
|
|
AlignTo(uv_count * sizeof(Vertex_UVS), alignment) +
|
|
AlignTo(vertex_atlas.size() * sizeof(Vertex_TEX), alignment) +
|
|
AlignTo(vertex_colors.size() * sizeof(Vertex_COL), alignment) +
|
|
AlignTo(vertex_boneindices.size() * sizeof(Vertex_BON), alignment) +
|
|
AlignTo(vertex_boneindices2.size() * sizeof(Vertex_BON), alignment)
|
|
;
|
|
|
|
constexpr Format morph_format = Format::R16G16B16A16_FLOAT;
|
|
constexpr size_t morph_stride = GetFormatStride(morph_format);
|
|
for (MorphTarget& morph : morph_targets)
|
|
{
|
|
if (!morph.vertex_positions.empty())
|
|
{
|
|
bd.size += AlignTo(vertex_positions.size() * morph_stride, alignment);
|
|
}
|
|
if (!morph.vertex_normals.empty())
|
|
{
|
|
bd.size += AlignTo(vertex_normals.size() * morph_stride, alignment);
|
|
}
|
|
}
|
|
|
|
auto init_callback = [&](void* dest) {
|
|
uint8_t* buffer_data = (uint8_t*)dest;
|
|
uint64_t buffer_offset = 0ull;
|
|
|
|
// vertexBuffer - POSITION + WIND:
|
|
switch (position_format)
|
|
{
|
|
case Vertex_POS10::FORMAT:
|
|
{
|
|
vb_pos_wind.offset = buffer_offset;
|
|
vb_pos_wind.size = vertex_positions.size() * sizeof(Vertex_POS10);
|
|
Vertex_POS10* vertices = (Vertex_POS10*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_pos_wind.size, alignment);
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
XMFLOAT3 pos = vertex_positions[i];
|
|
const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
|
|
Vertex_POS10 vert;
|
|
vert.FromFULL(aabb, pos, wind);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
break;
|
|
case Vertex_POS16::FORMAT:
|
|
{
|
|
vb_pos_wind.offset = buffer_offset;
|
|
vb_pos_wind.size = vertex_positions.size() * sizeof(Vertex_POS16);
|
|
Vertex_POS16* vertices = (Vertex_POS16*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_pos_wind.size, alignment);
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
XMFLOAT3 pos = vertex_positions[i];
|
|
const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
|
|
Vertex_POS16 vert;
|
|
vert.FromFULL(aabb, pos, wind);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
break;
|
|
case Vertex_POS32::FORMAT:
|
|
{
|
|
vb_pos_wind.offset = buffer_offset;
|
|
vb_pos_wind.size = vertex_positions.size() * sizeof(Vertex_POS32);
|
|
Vertex_POS32* vertices = (Vertex_POS32*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_pos_wind.size, alignment);
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
const XMFLOAT3& pos = vertex_positions[i];
|
|
const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
|
|
Vertex_POS32 vert;
|
|
vert.FromFULL(pos);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
break;
|
|
case Vertex_POS32W::FORMAT:
|
|
{
|
|
vb_pos_wind.offset = buffer_offset;
|
|
vb_pos_wind.size = vertex_positions.size() * sizeof(Vertex_POS32W);
|
|
Vertex_POS32W* vertices = (Vertex_POS32W*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_pos_wind.size, alignment);
|
|
for (size_t i = 0; i < vertex_positions.size(); ++i)
|
|
{
|
|
const XMFLOAT3& pos = vertex_positions[i];
|
|
const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
|
|
Vertex_POS32W vert;
|
|
vert.FromFULL(pos, wind);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
// Create index buffer GPU data:
|
|
if (GetIndexFormat() == IndexBufferFormat::UINT32)
|
|
{
|
|
ib.offset = buffer_offset;
|
|
ib.size = indices.size() * sizeof(uint32_t);
|
|
uint32_t* indexdata = (uint32_t*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(ib.size, alignment);
|
|
std::memcpy(indexdata, indices.data(), ib.size);
|
|
}
|
|
else
|
|
{
|
|
ib.offset = buffer_offset;
|
|
ib.size = indices.size() * sizeof(uint16_t);
|
|
uint16_t* indexdata = (uint16_t*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(ib.size, alignment);
|
|
for (size_t i = 0; i < indices.size(); ++i)
|
|
{
|
|
std::memcpy(indexdata + i, &indices[i], sizeof(uint16_t));
|
|
}
|
|
}
|
|
|
|
// vertexBuffer - NORMALS:
|
|
if (!vertex_normals.empty())
|
|
{
|
|
vb_nor.offset = buffer_offset;
|
|
vb_nor.size = vertex_normals.size() * sizeof(Vertex_NOR);
|
|
Vertex_NOR* vertices = (Vertex_NOR*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_nor.size, alignment);
|
|
for (size_t i = 0; i < vertex_normals.size(); ++i)
|
|
{
|
|
Vertex_NOR vert;
|
|
vert.FromFULL(vertex_normals[i]);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
|
|
// vertexBuffer - TANGENTS
|
|
if (!vertex_tangents.empty())
|
|
{
|
|
vb_tan.offset = buffer_offset;
|
|
vb_tan.size = vertex_tangents.size() * sizeof(Vertex_TAN);
|
|
Vertex_TAN* vertices = (Vertex_TAN*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_tan.size, alignment);
|
|
for (size_t i = 0; i < vertex_tangents.size(); ++i)
|
|
{
|
|
Vertex_TAN vert;
|
|
vert.FromFULL(vertex_tangents[i]);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
|
|
// vertexBuffer - UV SETS
|
|
if (!vertex_uvset_0.empty() || !vertex_uvset_1.empty())
|
|
{
|
|
const XMFLOAT2* uv0_stream = vertex_uvset_0.empty() ? vertex_uvset_1.data() : vertex_uvset_0.data();
|
|
const XMFLOAT2* uv1_stream = vertex_uvset_1.empty() ? vertex_uvset_0.data() : vertex_uvset_1.data();
|
|
|
|
vb_uvs.offset = buffer_offset;
|
|
vb_uvs.size = uv_count * sizeof(Vertex_UVS);
|
|
Vertex_UVS* vertices = (Vertex_UVS*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_uvs.size, alignment);
|
|
for (size_t i = 0; i < uv_count; ++i)
|
|
{
|
|
Vertex_UVS vert;
|
|
vert.uv0.FromFULL(uv0_stream[i], uv_range_min, uv_range_max);
|
|
vert.uv1.FromFULL(uv1_stream[i], uv_range_min, uv_range_max);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
|
|
// vertexBuffer - ATLAS
|
|
if (!vertex_atlas.empty())
|
|
{
|
|
vb_atl.offset = buffer_offset;
|
|
vb_atl.size = vertex_atlas.size() * sizeof(Vertex_TEX);
|
|
Vertex_TEX* vertices = (Vertex_TEX*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_atl.size, alignment);
|
|
for (size_t i = 0; i < vertex_atlas.size(); ++i)
|
|
{
|
|
Vertex_TEX vert;
|
|
vert.FromFULL(vertex_atlas[i]);
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
|
|
// vertexBuffer - COLORS
|
|
if (!vertex_colors.empty())
|
|
{
|
|
vb_col.offset = buffer_offset;
|
|
vb_col.size = vertex_colors.size() * sizeof(Vertex_COL);
|
|
Vertex_COL* vertices = (Vertex_COL*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_col.size, alignment);
|
|
for (size_t i = 0; i < vertex_colors.size(); ++i)
|
|
{
|
|
Vertex_COL vert;
|
|
vert.color = vertex_colors[i];
|
|
std::memcpy(vertices + i, &vert, sizeof(vert));
|
|
}
|
|
}
|
|
|
|
// bone reference buffers (skinning, soft body):
|
|
if (!vertex_boneindices.empty())
|
|
{
|
|
vb_bon.offset = buffer_offset;
|
|
const size_t influence_div4 = GetBoneInfluenceDiv4();
|
|
vb_bon.size = (vertex_boneindices.size() + vertex_boneindices2.size()) * sizeof(Vertex_BON);
|
|
Vertex_BON* vertices = (Vertex_BON*)(buffer_data + buffer_offset);
|
|
buffer_offset += AlignTo(vb_bon.size, alignment);
|
|
assert(vertex_boneindices.size() == vertex_boneweights.size()); // must have same number of indices as weights
|
|
assert(vertex_boneindices2.empty() || vertex_boneindices2.size() == vertex_boneindices.size()); // if second influence stream exists, it must be as large as the first
|
|
assert(vertex_boneindices2.size() == vertex_boneweights2.size()); // must have same number of indices as weights
|
|
for (size_t i = 0; i < vertex_boneindices.size(); ++i)
|
|
{
|
|
// Normalize weights:
|
|
// Note: if multiple influence streams are present,
|
|
// we have to normalize them together, not separately
|
|
float weights[8] = {};
|
|
weights[0] = vertex_boneweights[i].x;
|
|
weights[1] = vertex_boneweights[i].y;
|
|
weights[2] = vertex_boneweights[i].z;
|
|
weights[3] = vertex_boneweights[i].w;
|
|
if (influence_div4 > 1)
|
|
{
|
|
weights[4] = vertex_boneweights2[i].x;
|
|
weights[5] = vertex_boneweights2[i].y;
|
|
weights[6] = vertex_boneweights2[i].z;
|
|
weights[7] = vertex_boneweights2[i].w;
|
|
}
|
|
float sum = 0;
|
|
for (auto& weight : weights)
|
|
{
|
|
sum += weight;
|
|
}
|
|
if (sum > 0)
|
|
{
|
|
const float norm = 1.0f / sum;
|
|
for (auto& weight : weights)
|
|
{
|
|
weight *= norm;
|
|
}
|
|
}
|
|
// Store back normalized weights:
|
|
vertex_boneweights[i].x = weights[0];
|
|
vertex_boneweights[i].y = weights[1];
|
|
vertex_boneweights[i].z = weights[2];
|
|
vertex_boneweights[i].w = weights[3];
|
|
if (influence_div4 > 1)
|
|
{
|
|
vertex_boneweights2[i].x = weights[4];
|
|
vertex_boneweights2[i].y = weights[5];
|
|
vertex_boneweights2[i].z = weights[6];
|
|
vertex_boneweights2[i].w = weights[7];
|
|
}
|
|
|
|
Vertex_BON vert;
|
|
vert.FromFULL(vertex_boneindices[i], vertex_boneweights[i]);
|
|
std::memcpy(vertices + (i * influence_div4 + 0), &vert, sizeof(vert));
|
|
|
|
if (influence_div4 > 1)
|
|
{
|
|
vert.FromFULL(vertex_boneindices2[i], vertex_boneweights2[i]);
|
|
std::memcpy(vertices + (i * influence_div4 + 1), &vert, sizeof(vert));
|
|
}
|
|
}
|
|
}
|
|
|
|
// morph buffers:
|
|
if (!morph_targets.empty())
|
|
{
|
|
vb_mor.offset = buffer_offset;
|
|
for (MorphTarget& morph : morph_targets)
|
|
{
|
|
if (!morph.vertex_positions.empty())
|
|
{
|
|
morph.offset_pos = (buffer_offset - vb_mor.offset) / morph_stride;
|
|
XMHALF4* vertices = (XMHALF4*)(buffer_data + buffer_offset);
|
|
std::fill(vertices, vertices + vertex_positions.size(), 0);
|
|
if (morph.sparse_indices_positions.empty())
|
|
{
|
|
// flat morphs:
|
|
for (size_t i = 0; i < morph.vertex_positions.size(); ++i)
|
|
{
|
|
XMStoreHalf4(vertices + i, XMLoadFloat3(&morph.vertex_positions[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// sparse morphs will be flattened for GPU because they will be evaluated in skinning for every vertex:
|
|
for (size_t i = 0; i < morph.sparse_indices_positions.size(); ++i)
|
|
{
|
|
const uint32_t ind = morph.sparse_indices_positions[i];
|
|
XMStoreHalf4(vertices + ind, XMLoadFloat3(&morph.vertex_positions[i]));
|
|
}
|
|
}
|
|
buffer_offset += AlignTo(morph.vertex_positions.size() * sizeof(XMHALF4), alignment);
|
|
}
|
|
if (!morph.vertex_normals.empty())
|
|
{
|
|
morph.offset_nor = (buffer_offset - vb_mor.offset) / morph_stride;
|
|
XMHALF4* vertices = (XMHALF4*)(buffer_data + buffer_offset);
|
|
std::fill(vertices, vertices + vertex_normals.size(), 0);
|
|
if (morph.sparse_indices_normals.empty())
|
|
{
|
|
// flat morphs:
|
|
for (size_t i = 0; i < morph.vertex_normals.size(); ++i)
|
|
{
|
|
XMStoreHalf4(vertices + i, XMLoadFloat3(&morph.vertex_normals[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// sparse morphs will be flattened for GPU because they will be evaluated in skinning for every vertex:
|
|
for (size_t i = 0; i < morph.sparse_indices_normals.size(); ++i)
|
|
{
|
|
const uint32_t ind = morph.sparse_indices_normals[i];
|
|
XMStoreHalf4(vertices + ind, XMLoadFloat3(&morph.vertex_normals[i]));
|
|
}
|
|
}
|
|
buffer_offset += AlignTo(morph.vertex_normals.size() * sizeof(XMHALF4), alignment);
|
|
}
|
|
}
|
|
vb_mor.size = buffer_offset - vb_mor.offset;
|
|
}
|
|
};
|
|
|
|
bool success = device->CreateBuffer2(&bd, init_callback, &generalBuffer);
|
|
assert(success);
|
|
device->SetName(&generalBuffer, "MeshComponent::generalBuffer");
|
|
|
|
assert(ib.IsValid());
|
|
const Format ib_format = GetIndexFormat() == IndexBufferFormat::UINT32 ? Format::R32_UINT : Format::R16_UINT;
|
|
ib.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, ib.offset, ib.size, &ib_format);
|
|
ib.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, ib.subresource_srv);
|
|
|
|
assert(vb_pos_wind.IsValid());
|
|
vb_pos_wind.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_pos_wind.offset, vb_pos_wind.size, &position_format);
|
|
vb_pos_wind.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_pos_wind.subresource_srv);
|
|
|
|
if (vb_nor.IsValid())
|
|
{
|
|
vb_nor.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_nor.offset, vb_nor.size, &Vertex_NOR::FORMAT);
|
|
vb_nor.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_nor.subresource_srv);
|
|
}
|
|
if (vb_tan.IsValid())
|
|
{
|
|
vb_tan.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_tan.offset, vb_tan.size, &Vertex_TAN::FORMAT);
|
|
vb_tan.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_tan.subresource_srv);
|
|
}
|
|
if (vb_uvs.IsValid())
|
|
{
|
|
vb_uvs.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_uvs.offset, vb_uvs.size, &Vertex_UVS::FORMAT);
|
|
vb_uvs.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_uvs.subresource_srv);
|
|
}
|
|
if (vb_atl.IsValid())
|
|
{
|
|
vb_atl.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_atl.offset, vb_atl.size, &Vertex_TEX::FORMAT);
|
|
vb_atl.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_atl.subresource_srv);
|
|
}
|
|
if (vb_col.IsValid())
|
|
{
|
|
vb_col.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_col.offset, vb_col.size, &Vertex_COL::FORMAT);
|
|
vb_col.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_col.subresource_srv);
|
|
}
|
|
if (vb_bon.IsValid())
|
|
{
|
|
vb_bon.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_bon.offset, vb_bon.size);
|
|
vb_bon.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_bon.subresource_srv);
|
|
}
|
|
if (vb_mor.IsValid())
|
|
{
|
|
vb_mor.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_mor.offset, vb_mor.size, &morph_format);
|
|
vb_mor.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_mor.subresource_srv);
|
|
}
|
|
|
|
if (!vertex_boneindices.empty() || !morph_targets.empty())
|
|
{
|
|
CreateStreamoutRenderData();
|
|
}
|
|
}
|
|
void MeshComponent::CreateStreamoutRenderData()
|
|
{
|
|
GraphicsDevice* device = wi::graphics::GetDevice();
|
|
|
|
GPUBufferDesc desc;
|
|
desc.usage = Usage::DEFAULT;
|
|
desc.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
|
|
desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::TYPED_FORMAT_CASTING | ResourceMiscFlag::NO_DEFAULT_DESCRIPTORS;
|
|
if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
|
|
{
|
|
desc.misc_flags |= ResourceMiscFlag::RAY_TRACING;
|
|
}
|
|
|
|
const uint64_t alignment = device->GetMinOffsetAlignment(&desc) * sizeof(Vertex_POS32); // additional alignment for RGB32F
|
|
desc.size =
|
|
AlignTo(vertex_positions.size() * sizeof(Vertex_POS32), alignment) + // pos
|
|
AlignTo(vertex_positions.size() * sizeof(Vertex_POS32), alignment) + // prevpos
|
|
AlignTo(vertex_normals.size() * sizeof(Vertex_NOR), alignment) +
|
|
AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment)
|
|
;
|
|
|
|
bool success = device->CreateBuffer(&desc, nullptr, &streamoutBuffer);
|
|
assert(success);
|
|
device->SetName(&streamoutBuffer, "MeshComponent::streamoutBuffer");
|
|
|
|
uint64_t buffer_offset = 0ull;
|
|
|
|
so_pos.offset = buffer_offset;
|
|
so_pos.size = vertex_positions.size() * sizeof(Vertex_POS32);
|
|
buffer_offset += AlignTo(so_pos.size, alignment);
|
|
so_pos.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pos.offset, so_pos.size, &Vertex_POS32::FORMAT);
|
|
so_pos.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pos.offset, so_pos.size); // UAV can't have RGB32_F format!
|
|
so_pos.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pos.subresource_srv);
|
|
so_pos.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pos.subresource_uav);
|
|
|
|
so_pre.offset = buffer_offset;
|
|
so_pre.size = so_pos.size;
|
|
buffer_offset += AlignTo(so_pre.size, alignment);
|
|
so_pre.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pre.offset, so_pre.size, &Vertex_POS32::FORMAT);
|
|
so_pre.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pre.offset, so_pre.size); // UAV can't have RGB32_F format!
|
|
so_pre.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pre.subresource_srv);
|
|
so_pre.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pre.subresource_uav);
|
|
|
|
if (vb_nor.IsValid())
|
|
{
|
|
so_nor.offset = buffer_offset;
|
|
so_nor.size = vb_nor.size;
|
|
buffer_offset += AlignTo(so_nor.size, alignment);
|
|
so_nor.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_nor.offset, so_nor.size, &Vertex_NOR::FORMAT);
|
|
so_nor.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_nor.offset, so_nor.size, &Vertex_NOR::FORMAT);
|
|
so_nor.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_nor.subresource_srv);
|
|
so_nor.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_nor.subresource_uav);
|
|
}
|
|
|
|
if (vb_tan.IsValid())
|
|
{
|
|
so_tan.offset = buffer_offset;
|
|
so_tan.size = vb_tan.size;
|
|
buffer_offset += AlignTo(so_tan.size, alignment);
|
|
so_tan.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_tan.offset, so_tan.size, &Vertex_TAN::FORMAT);
|
|
so_tan.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_tan.offset, so_tan.size, &Vertex_TAN::FORMAT);
|
|
so_tan.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_tan.subresource_srv);
|
|
so_tan.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_tan.subresource_uav);
|
|
}
|
|
}
|
|
void MeshComponent::CreateRaytracingRenderData()
|
|
{
|
|
GraphicsDevice* device = wi::graphics::GetDevice();
|
|
|
|
if (!device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
|
|
return;
|
|
|
|
BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD;
|
|
|
|
const uint32_t lod_count = GetLODCount();
|
|
BLASes.resize(lod_count);
|
|
for (uint32_t lod = 0; lod < lod_count; ++lod)
|
|
{
|
|
RaytracingAccelerationStructureDesc desc;
|
|
desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL;
|
|
|
|
if (streamoutBuffer.IsValid())
|
|
{
|
|
desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE;
|
|
desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
|
|
}
|
|
else
|
|
{
|
|
desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_TRACE;
|
|
}
|
|
|
|
uint32_t first_subset = 0;
|
|
uint32_t last_subset = 0;
|
|
GetLODSubsetRange(lod, 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;
|
|
geometry.triangles.vertex_buffer = generalBuffer;
|
|
geometry.triangles.vertex_byte_offset = vb_pos_wind.offset;
|
|
geometry.triangles.index_buffer = generalBuffer;
|
|
geometry.triangles.index_format = GetIndexFormat();
|
|
geometry.triangles.index_count = subset.indexCount;
|
|
geometry.triangles.index_offset = ib.offset / GetIndexStride() + subset.indexOffset;
|
|
geometry.triangles.vertex_count = (uint32_t)vertex_positions.size();
|
|
if (so_pos.IsValid())
|
|
{
|
|
geometry.triangles.vertex_format = Vertex_POS32::FORMAT;
|
|
geometry.triangles.vertex_stride = sizeof(Vertex_POS32);
|
|
}
|
|
else
|
|
{
|
|
geometry.triangles.vertex_format = position_format == Format::R32G32B32A32_FLOAT ? Format::R32G32B32_FLOAT : position_format;
|
|
geometry.triangles.vertex_stride = GetFormatStride(position_format);
|
|
}
|
|
}
|
|
|
|
bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLASes[lod]);
|
|
assert(success);
|
|
device->SetName(&BLASes[lod], std::string("MeshComponent::BLAS[LOD" + std::to_string(lod) + "]").c_str());
|
|
}
|
|
}
|
|
void MeshComponent::BuildBVH()
|
|
{
|
|
bvh_leaf_aabbs.clear();
|
|
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];
|
|
if (subset.indexCount == 0)
|
|
continue;
|
|
const uint32_t indexOffset = subset.indexOffset;
|
|
const uint32_t triangleCount = subset.indexCount / 3;
|
|
for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex)
|
|
{
|
|
const uint32_t i0 = indices[indexOffset + triangleIndex * 3 + 0];
|
|
const uint32_t i1 = indices[indexOffset + triangleIndex * 3 + 1];
|
|
const uint32_t i2 = indices[indexOffset + triangleIndex * 3 + 2];
|
|
const XMFLOAT3& p0 = vertex_positions[i0];
|
|
const XMFLOAT3& p1 = vertex_positions[i1];
|
|
const XMFLOAT3& p2 = vertex_positions[i2];
|
|
AABB aabb = wi::primitive::AABB(wi::math::Min(p0, wi::math::Min(p1, p2)), wi::math::Max(p0, wi::math::Max(p1, p2)));
|
|
aabb.layerMask = triangleIndex;
|
|
aabb.userdata = subsetIndex;
|
|
bvh_leaf_aabbs.push_back(aabb);
|
|
}
|
|
}
|
|
bvh.Build(bvh_leaf_aabbs.data(), (uint32_t)bvh_leaf_aabbs.size());
|
|
}
|
|
void MeshComponent::ComputeNormals(COMPUTE_NORMALS compute)
|
|
{
|
|
// Start recalculating normals:
|
|
|
|
if (compute != COMPUTE_NORMALS_SMOOTH_FAST)
|
|
{
|
|
// Compute hard surface normals:
|
|
|
|
// Right now they are always computed even before smooth setting
|
|
|
|
wi::vector<uint32_t> newIndexBuffer;
|
|
wi::vector<XMFLOAT3> newPositionsBuffer;
|
|
wi::vector<XMFLOAT3> newNormalsBuffer;
|
|
wi::vector<XMFLOAT2> newUV0Buffer;
|
|
wi::vector<XMFLOAT2> newUV1Buffer;
|
|
wi::vector<XMFLOAT2> newAtlasBuffer;
|
|
wi::vector<XMUINT4> newBoneIndicesBuffer;
|
|
wi::vector<XMFLOAT4> newBoneWeightsBuffer;
|
|
wi::vector<uint32_t> newColorsBuffer;
|
|
|
|
for (size_t face = 0; face < indices.size() / 3; face++)
|
|
{
|
|
uint32_t i0 = indices[face * 3 + 0];
|
|
uint32_t i1 = indices[face * 3 + 1];
|
|
uint32_t i2 = indices[face * 3 + 2];
|
|
|
|
XMFLOAT3& p0 = vertex_positions[i0];
|
|
XMFLOAT3& p1 = vertex_positions[i1];
|
|
XMFLOAT3& p2 = vertex_positions[i2];
|
|
|
|
XMVECTOR U = XMLoadFloat3(&p2) - XMLoadFloat3(&p0);
|
|
XMVECTOR V = XMLoadFloat3(&p1) - XMLoadFloat3(&p0);
|
|
|
|
XMVECTOR N = XMVector3Cross(U, V);
|
|
N = XMVector3Normalize(N);
|
|
|
|
XMFLOAT3 normal;
|
|
XMStoreFloat3(&normal, N);
|
|
|
|
newPositionsBuffer.push_back(p0);
|
|
newPositionsBuffer.push_back(p1);
|
|
newPositionsBuffer.push_back(p2);
|
|
|
|
newNormalsBuffer.push_back(normal);
|
|
newNormalsBuffer.push_back(normal);
|
|
newNormalsBuffer.push_back(normal);
|
|
|
|
if (!vertex_uvset_0.empty())
|
|
{
|
|
newUV0Buffer.push_back(vertex_uvset_0[i0]);
|
|
newUV0Buffer.push_back(vertex_uvset_0[i1]);
|
|
newUV0Buffer.push_back(vertex_uvset_0[i2]);
|
|
}
|
|
|
|
if (!vertex_uvset_1.empty())
|
|
{
|
|
newUV1Buffer.push_back(vertex_uvset_1[i0]);
|
|
newUV1Buffer.push_back(vertex_uvset_1[i1]);
|
|
newUV1Buffer.push_back(vertex_uvset_1[i2]);
|
|
}
|
|
|
|
if (!vertex_atlas.empty())
|
|
{
|
|
newAtlasBuffer.push_back(vertex_atlas[i0]);
|
|
newAtlasBuffer.push_back(vertex_atlas[i1]);
|
|
newAtlasBuffer.push_back(vertex_atlas[i2]);
|
|
}
|
|
|
|
if (!vertex_boneindices.empty())
|
|
{
|
|
newBoneIndicesBuffer.push_back(vertex_boneindices[i0]);
|
|
newBoneIndicesBuffer.push_back(vertex_boneindices[i1]);
|
|
newBoneIndicesBuffer.push_back(vertex_boneindices[i2]);
|
|
}
|
|
|
|
if (!vertex_boneweights.empty())
|
|
{
|
|
newBoneWeightsBuffer.push_back(vertex_boneweights[i0]);
|
|
newBoneWeightsBuffer.push_back(vertex_boneweights[i1]);
|
|
newBoneWeightsBuffer.push_back(vertex_boneweights[i2]);
|
|
}
|
|
|
|
if (!vertex_colors.empty())
|
|
{
|
|
newColorsBuffer.push_back(vertex_colors[i0]);
|
|
newColorsBuffer.push_back(vertex_colors[i1]);
|
|
newColorsBuffer.push_back(vertex_colors[i2]);
|
|
}
|
|
|
|
newIndexBuffer.push_back(static_cast<uint32_t>(newIndexBuffer.size()));
|
|
newIndexBuffer.push_back(static_cast<uint32_t>(newIndexBuffer.size()));
|
|
newIndexBuffer.push_back(static_cast<uint32_t>(newIndexBuffer.size()));
|
|
}
|
|
|
|
// For hard surface normals, we created a new mesh in the previous loop through faces, so swap data:
|
|
vertex_positions = newPositionsBuffer;
|
|
vertex_normals = newNormalsBuffer;
|
|
vertex_uvset_0 = newUV0Buffer;
|
|
vertex_uvset_1 = newUV1Buffer;
|
|
vertex_atlas = newAtlasBuffer;
|
|
vertex_colors = newColorsBuffer;
|
|
if (!vertex_boneindices.empty())
|
|
{
|
|
vertex_boneindices = newBoneIndicesBuffer;
|
|
}
|
|
if (!vertex_boneweights.empty())
|
|
{
|
|
vertex_boneweights = newBoneWeightsBuffer;
|
|
}
|
|
indices = newIndexBuffer;
|
|
}
|
|
|
|
switch (compute)
|
|
{
|
|
case MeshComponent::COMPUTE_NORMALS_HARD:
|
|
break;
|
|
|
|
case MeshComponent::COMPUTE_NORMALS_SMOOTH:
|
|
{
|
|
// Compute smooth surface normals:
|
|
|
|
// 1.) Zero normals, they will be averaged later
|
|
for (size_t i = 0; i < vertex_normals.size(); i++)
|
|
{
|
|
vertex_normals[i] = XMFLOAT3(0, 0, 0);
|
|
}
|
|
|
|
// 2.) Find identical vertices by POSITION, accumulate face normals
|
|
for (size_t i = 0; i < vertex_positions.size(); i++)
|
|
{
|
|
XMFLOAT3& v_search_pos = vertex_positions[i];
|
|
|
|
for (size_t ind = 0; ind < indices.size() / 3; ++ind)
|
|
{
|
|
uint32_t i0 = indices[ind * 3 + 0];
|
|
uint32_t i1 = indices[ind * 3 + 1];
|
|
uint32_t i2 = indices[ind * 3 + 2];
|
|
|
|
XMFLOAT3& v0 = vertex_positions[i0];
|
|
XMFLOAT3& v1 = vertex_positions[i1];
|
|
XMFLOAT3& v2 = vertex_positions[i2];
|
|
|
|
|
|
bool match_pos0 =
|
|
wi::math::float_equal(v_search_pos.x, v0.x) &&
|
|
wi::math::float_equal(v_search_pos.y, v0.y) &&
|
|
wi::math::float_equal(v_search_pos.z, v0.z);
|
|
|
|
bool match_pos1 =
|
|
wi::math::float_equal(v_search_pos.x, v1.x) &&
|
|
wi::math::float_equal(v_search_pos.y, v1.y) &&
|
|
wi::math::float_equal(v_search_pos.z, v1.z);
|
|
|
|
bool match_pos2 =
|
|
wi::math::float_equal(v_search_pos.x, v2.x) &&
|
|
wi::math::float_equal(v_search_pos.y, v2.y) &&
|
|
wi::math::float_equal(v_search_pos.z, v2.z);
|
|
|
|
if (match_pos0 || match_pos1 || match_pos2)
|
|
{
|
|
XMVECTOR U = XMLoadFloat3(&v2) - XMLoadFloat3(&v0);
|
|
XMVECTOR V = XMLoadFloat3(&v1) - XMLoadFloat3(&v0);
|
|
|
|
XMVECTOR N = XMVector3Cross(U, V);
|
|
N = XMVector3Normalize(N);
|
|
|
|
XMFLOAT3 normal;
|
|
XMStoreFloat3(&normal, N);
|
|
|
|
vertex_normals[i].x += normal.x;
|
|
vertex_normals[i].y += normal.y;
|
|
vertex_normals[i].z += normal.z;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// 3.) Find duplicated vertices by POSITION and UV0 and UV1 and ATLAS and SUBSET and remove them:
|
|
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];
|
|
for (uint32_t i = 0; i < subset.indexCount - 1; i++)
|
|
{
|
|
uint32_t ind0 = indices[subset.indexOffset + (uint32_t)i];
|
|
const XMFLOAT3& p0 = vertex_positions[ind0];
|
|
const XMFLOAT2& u00 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind0];
|
|
const XMFLOAT2& u10 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind0];
|
|
const XMFLOAT2& at0 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind0];
|
|
|
|
for (uint32_t j = i + 1; j < subset.indexCount; j++)
|
|
{
|
|
uint32_t ind1 = indices[subset.indexOffset + (uint32_t)j];
|
|
|
|
if (ind1 == ind0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const XMFLOAT3& p1 = vertex_positions[ind1];
|
|
const XMFLOAT2& u01 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind1];
|
|
const XMFLOAT2& u11 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind1];
|
|
const XMFLOAT2& at1 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind1];
|
|
|
|
const bool duplicated_pos =
|
|
wi::math::float_equal(p0.x, p1.x) &&
|
|
wi::math::float_equal(p0.y, p1.y) &&
|
|
wi::math::float_equal(p0.z, p1.z);
|
|
|
|
const bool duplicated_uv0 =
|
|
wi::math::float_equal(u00.x, u01.x) &&
|
|
wi::math::float_equal(u00.y, u01.y);
|
|
|
|
const bool duplicated_uv1 =
|
|
wi::math::float_equal(u10.x, u11.x) &&
|
|
wi::math::float_equal(u10.y, u11.y);
|
|
|
|
const bool duplicated_atl =
|
|
wi::math::float_equal(at0.x, at1.x) &&
|
|
wi::math::float_equal(at0.y, at1.y);
|
|
|
|
if (duplicated_pos && duplicated_uv0 && duplicated_uv1 && duplicated_atl)
|
|
{
|
|
// Erase vertices[ind1] because it is a duplicate:
|
|
if (ind1 < vertex_positions.size())
|
|
{
|
|
vertex_positions.erase(vertex_positions.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_normals.size())
|
|
{
|
|
vertex_normals.erase(vertex_normals.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_uvset_0.size())
|
|
{
|
|
vertex_uvset_0.erase(vertex_uvset_0.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_uvset_1.size())
|
|
{
|
|
vertex_uvset_1.erase(vertex_uvset_1.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_atlas.size())
|
|
{
|
|
vertex_atlas.erase(vertex_atlas.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_boneindices.size())
|
|
{
|
|
vertex_boneindices.erase(vertex_boneindices.begin() + ind1);
|
|
}
|
|
if (ind1 < vertex_boneweights.size())
|
|
{
|
|
vertex_boneweights.erase(vertex_boneweights.begin() + ind1);
|
|
}
|
|
|
|
// The vertices[ind1] was removed, so each index after that needs to be updated:
|
|
for (auto& index : indices)
|
|
{
|
|
if (index > ind1 && index > 0)
|
|
{
|
|
index--;
|
|
}
|
|
else if (index == ind1)
|
|
{
|
|
index = ind0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST:
|
|
{
|
|
for (size_t i = 0; i < vertex_normals.size(); i++)
|
|
{
|
|
vertex_normals[i] = XMFLOAT3(0, 0, 0);
|
|
}
|
|
for (size_t i = 0; i < indices.size() / 3; ++i)
|
|
{
|
|
uint32_t index1 = indices[i * 3];
|
|
uint32_t index2 = indices[i * 3 + 1];
|
|
uint32_t index3 = indices[i * 3 + 2];
|
|
|
|
XMVECTOR side1 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index3]);
|
|
XMVECTOR side2 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index2]);
|
|
XMVECTOR N = XMVector3Normalize(XMVector3Cross(side1, side2));
|
|
XMFLOAT3 normal;
|
|
XMStoreFloat3(&normal, N);
|
|
|
|
vertex_normals[index1].x += normal.x;
|
|
vertex_normals[index1].y += normal.y;
|
|
vertex_normals[index1].z += normal.z;
|
|
|
|
vertex_normals[index2].x += normal.x;
|
|
vertex_normals[index2].y += normal.y;
|
|
vertex_normals[index2].z += normal.z;
|
|
|
|
vertex_normals[index3].x += normal.x;
|
|
vertex_normals[index3].y += normal.y;
|
|
vertex_normals[index3].z += normal.z;
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
vertex_tangents.clear(); // <- will be recomputed
|
|
|
|
CreateRenderData(); // <- normals will be normalized here!
|
|
}
|
|
void MeshComponent::FlipCulling()
|
|
{
|
|
for (size_t face = 0; face < indices.size() / 3; face++)
|
|
{
|
|
uint32_t i0 = indices[face * 3 + 0];
|
|
uint32_t i1 = indices[face * 3 + 1];
|
|
uint32_t i2 = indices[face * 3 + 2];
|
|
|
|
indices[face * 3 + 0] = i0;
|
|
indices[face * 3 + 1] = i2;
|
|
indices[face * 3 + 2] = i1;
|
|
}
|
|
|
|
CreateRenderData();
|
|
}
|
|
void MeshComponent::FlipNormals()
|
|
{
|
|
for (auto& normal : vertex_normals)
|
|
{
|
|
normal.x *= -1;
|
|
normal.y *= -1;
|
|
normal.z *= -1;
|
|
}
|
|
|
|
CreateRenderData();
|
|
}
|
|
void MeshComponent::Recenter()
|
|
{
|
|
XMFLOAT3 center = aabb.getCenter();
|
|
|
|
for (auto& pos : vertex_positions)
|
|
{
|
|
pos.x -= center.x;
|
|
pos.y -= center.y;
|
|
pos.z -= center.z;
|
|
}
|
|
|
|
CreateRenderData();
|
|
}
|
|
void MeshComponent::RecenterToBottom()
|
|
{
|
|
XMFLOAT3 center = aabb.getCenter();
|
|
center.y -= aabb.getHalfWidth().y;
|
|
|
|
for (auto& pos : vertex_positions)
|
|
{
|
|
pos.x -= center.x;
|
|
pos.y -= center.y;
|
|
pos.z -= center.z;
|
|
}
|
|
|
|
CreateRenderData();
|
|
}
|
|
Sphere MeshComponent::GetBoundingSphere() const
|
|
{
|
|
Sphere sphere;
|
|
sphere.center = aabb.getCenter();
|
|
sphere.radius = aabb.getRadius();
|
|
return sphere;
|
|
}
|
|
size_t MeshComponent::GetMemoryUsageCPU() const
|
|
{
|
|
size_t size =
|
|
vertex_positions.size() * sizeof(XMFLOAT3) +
|
|
vertex_normals.size() * sizeof(XMFLOAT3) +
|
|
vertex_tangents.size() * sizeof(XMFLOAT4) +
|
|
vertex_uvset_0.size() * sizeof(XMFLOAT2) +
|
|
vertex_uvset_1.size() * sizeof(XMFLOAT2) +
|
|
vertex_boneindices.size() * sizeof(XMUINT4) +
|
|
vertex_boneweights.size() * sizeof(XMFLOAT4) +
|
|
vertex_atlas.size() * sizeof(XMFLOAT2) +
|
|
vertex_colors.size() * sizeof(uint32_t) +
|
|
vertex_windweights.size() * sizeof(uint8_t) +
|
|
indices.size() * sizeof(uint32_t);
|
|
|
|
for (const MorphTarget& morph : morph_targets)
|
|
{
|
|
size +=
|
|
morph.vertex_positions.size() * sizeof(XMFLOAT3) +
|
|
morph.vertex_normals.size() * sizeof(XMFLOAT3) +
|
|
morph.sparse_indices_positions.size() * sizeof(uint32_t) +
|
|
morph.sparse_indices_normals.size() * sizeof(uint32_t);
|
|
}
|
|
|
|
size += GetMemoryUsageBVH();
|
|
|
|
return size;
|
|
}
|
|
size_t MeshComponent::GetMemoryUsageGPU() const
|
|
{
|
|
return generalBuffer.desc.size + streamoutBuffer.desc.size;
|
|
}
|
|
size_t MeshComponent::GetMemoryUsageBVH() const
|
|
{
|
|
return
|
|
bvh.allocation.capacity() +
|
|
bvh_leaf_aabbs.size() * sizeof(wi::primitive::AABB);
|
|
}
|
|
|
|
void ObjectComponent::ClearLightmap()
|
|
{
|
|
lightmap = Texture();
|
|
lightmapWidth = 0;
|
|
lightmapHeight = 0;
|
|
lightmapIterationCount = 0;
|
|
lightmapTextureData.clear();
|
|
SetLightmapRenderRequest(false);
|
|
}
|
|
void ObjectComponent::SaveLightmap()
|
|
{
|
|
if (lightmap.IsValid() && has_flag(lightmap.desc.bind_flags, BindFlag::RENDER_TARGET))
|
|
{
|
|
SetLightmapRenderRequest(false);
|
|
|
|
bool success = wi::helper::saveTextureToMemory(lightmap, lightmapTextureData);
|
|
assert(success);
|
|
|
|
#ifdef OPEN_IMAGE_DENOISE
|
|
if (success)
|
|
{
|
|
wi::vector<uint8_t> texturedata_dst(lightmapTextureData.size());
|
|
|
|
size_t width = (size_t)lightmapWidth;
|
|
size_t height = (size_t)lightmapHeight;
|
|
{
|
|
// https://github.com/OpenImageDenoise/oidn#c11-api-example
|
|
|
|
// Create an Intel Open Image Denoise device
|
|
static oidn::DeviceRef device = oidn::newDevice();
|
|
static bool init = false;
|
|
if (!init)
|
|
{
|
|
device.commit();
|
|
init = true;
|
|
}
|
|
|
|
oidn::BufferRef lightmapTextureData_buffer = device.newBuffer(lightmapTextureData.size());
|
|
oidn::BufferRef texturedata_dst_buffer = device.newBuffer(texturedata_dst.size());
|
|
|
|
lightmapTextureData_buffer.write(0, lightmapTextureData.size(), lightmapTextureData.data());
|
|
|
|
// Create a denoising filter
|
|
oidn::FilterRef filter = device.newFilter("RTLightmap");
|
|
filter.setImage("color", lightmapTextureData_buffer, oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
|
|
filter.setImage("output", texturedata_dst_buffer, oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
|
|
filter.commit();
|
|
|
|
// Filter the image
|
|
filter.execute();
|
|
|
|
// Check for errors
|
|
const char* errorMessage;
|
|
auto error = device.getError(errorMessage);
|
|
if (error != oidn::Error::None && error != oidn::Error::Cancelled)
|
|
{
|
|
wi::backlog::post(std::string("[OpenImageDenoise error] ") + errorMessage);
|
|
}
|
|
else
|
|
{
|
|
texturedata_dst_buffer.read(0, texturedata_dst.size(), texturedata_dst.data());
|
|
}
|
|
}
|
|
|
|
lightmapTextureData = std::move(texturedata_dst); // replace old (raw) data with denoised data
|
|
}
|
|
#endif // OPEN_IMAGE_DENOISE
|
|
|
|
CompressLightmap();
|
|
|
|
wi::texturehelper::CreateTexture(lightmap, lightmapTextureData.data(), lightmapWidth, lightmapHeight, lightmap.desc.format);
|
|
wi::graphics::GetDevice()->SetName(&lightmap, "lightmap");
|
|
}
|
|
}
|
|
void ObjectComponent::CompressLightmap()
|
|
{
|
|
if (IsLightmapDisableBlockCompression())
|
|
{
|
|
// Simple packing to R11G11B10_FLOAT format on CPU:
|
|
using namespace PackedVector;
|
|
wi::vector<uint8_t> packed_data;
|
|
packed_data.resize(sizeof(XMFLOAT3PK) * lightmapWidth * lightmapHeight);
|
|
XMFLOAT3PK* packed_ptr = (XMFLOAT3PK*)packed_data.data();
|
|
XMFLOAT4* raw_ptr = (XMFLOAT4*)lightmapTextureData.data();
|
|
|
|
uint32_t texelcount = lightmapWidth * lightmapHeight;
|
|
for (uint32_t i = 0; i < texelcount; ++i)
|
|
{
|
|
XMStoreFloat3PK(packed_ptr + i, XMLoadFloat4(raw_ptr + i));
|
|
}
|
|
|
|
lightmapTextureData = std::move(packed_data);
|
|
lightmap.desc.format = Format::R11G11B10_FLOAT;
|
|
lightmap.desc.bind_flags = BindFlag::SHADER_RESOURCE;
|
|
}
|
|
else
|
|
{
|
|
// BC6 compress on GPU:
|
|
wi::texturehelper::CreateTexture(lightmap, lightmapTextureData.data(), lightmapWidth, lightmapHeight, lightmap.desc.format);
|
|
TextureDesc desc = lightmap.desc;
|
|
desc.format = Format::BC6H_UF16;
|
|
desc.bind_flags = BindFlag::SHADER_RESOURCE;
|
|
Texture bc6tex;
|
|
GraphicsDevice* device = GetDevice();
|
|
device->CreateTexture(&desc, nullptr, &bc6tex);
|
|
CommandList cmd = device->BeginCommandList();
|
|
wi::renderer::BlockCompress(lightmap, bc6tex, cmd);
|
|
wi::helper::saveTextureToMemory(bc6tex, lightmapTextureData); // internally waits for GPU completion
|
|
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()
|
|
{
|
|
if (!textureName.empty() && !resource.IsValid())
|
|
{
|
|
resource = wi::resourcemanager::Load(textureName);
|
|
}
|
|
if (resource.IsValid())
|
|
{
|
|
texture = resource.GetTexture();
|
|
SetDirty(false);
|
|
return;
|
|
}
|
|
resolution = wi::math::GetNextPowerOfTwo(resolution);
|
|
if (texture.IsValid() && resolution == texture.desc.width)
|
|
return;
|
|
SetDirty();
|
|
|
|
GraphicsDevice* device = wi::graphics::GetDevice();
|
|
|
|
TextureDesc desc;
|
|
desc.array_size = 6;
|
|
desc.height = resolution;
|
|
desc.width = resolution;
|
|
desc.usage = Usage::DEFAULT;
|
|
desc.format = Format::BC6H_UF16;
|
|
desc.sample_count = 1; // Note that this texture is always non-MSAA, even if probe is rendered as MSAA, because this contains resolved result
|
|
desc.bind_flags = BindFlag::SHADER_RESOURCE;
|
|
desc.mip_levels = GetMipCount(resolution, resolution, 1, 16);
|
|
desc.misc_flags = ResourceMiscFlag::TEXTURECUBE;
|
|
desc.layout = ResourceState::SHADER_RESOURCE;
|
|
device->CreateTexture(&desc, nullptr, &texture);
|
|
device->SetName(&texture, "EnvironmentProbeComponent::texture");
|
|
}
|
|
void EnvironmentProbeComponent::DeleteResource()
|
|
{
|
|
if (resource.IsValid())
|
|
{
|
|
// only delete these if resource is actually valid!
|
|
resource = {};
|
|
texture = {};
|
|
textureName = {};
|
|
}
|
|
}
|
|
size_t EnvironmentProbeComponent::GetMemorySizeInBytes() const
|
|
{
|
|
return ComputeTextureMemorySizeInBytes(texture.desc);
|
|
}
|
|
|
|
AnimationComponent::AnimationChannel::PathDataType AnimationComponent::AnimationChannel::GetPathDataType() const
|
|
{
|
|
switch (path)
|
|
{
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::TRANSLATION:
|
|
return PathDataType::Float3;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::ROTATION:
|
|
return PathDataType::Float4;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SCALE:
|
|
return PathDataType::Float3;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::WEIGHTS:
|
|
return PathDataType::Weights;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_COLOR:
|
|
return PathDataType::Float3;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_RANGE:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE:
|
|
return PathDataType::Float;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_PLAY:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_STOP:
|
|
return PathDataType::Event;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_VOLUME:
|
|
return PathDataType::Float;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT:
|
|
return PathDataType::Float;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOV:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOCAL_LENGTH:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SIZE:
|
|
return PathDataType::Float;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SHAPE:
|
|
return PathDataType::Float2;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_PLAY:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_STOP:
|
|
return PathDataType::Event;
|
|
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_EMISSIVE:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_TEXMULADD:
|
|
return PathDataType::Float4;
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_ROUGHNESS:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_REFLECTANCE:
|
|
case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_METALNESS:
|
|
return PathDataType::Float;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
return PathDataType::Event;
|
|
}
|
|
|
|
void SoftBodyPhysicsComponent::CreateFromMesh(MeshComponent& mesh)
|
|
{
|
|
if (weights.size() != mesh.vertex_positions.size())
|
|
{
|
|
weights.resize(mesh.vertex_positions.size());
|
|
std::fill(weights.begin(), weights.end(), 1.0f);
|
|
}
|
|
if (physicsIndices.empty())
|
|
{
|
|
bool pinning_required = false;
|
|
wi::vector<uint32_t> source;
|
|
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 uint32_t* indices = mesh.indices.data() + subset.indexOffset;
|
|
for (uint32_t i = 0; i < subset.indexCount; ++i)
|
|
{
|
|
source.push_back(indices[i]);
|
|
pinning_required |= weights[indices[i]] == 0;
|
|
}
|
|
}
|
|
physicsIndices.resize(source.size());
|
|
|
|
if (pinning_required)
|
|
{
|
|
// If there is pinning, we need to use precise LOD to retain difference between pinned and soft vertices:
|
|
wi::vector<XMFLOAT4> vertices(mesh.vertex_positions.size());
|
|
for (size_t i = 0; i < mesh.vertex_positions.size(); ++i)
|
|
{
|
|
vertices[i].x = mesh.vertex_positions[i].x;
|
|
vertices[i].y = mesh.vertex_positions[i].y;
|
|
vertices[i].z = mesh.vertex_positions[i].z;
|
|
vertices[i].w = weights[i] == 0 ? 1.0f : 0.0f;
|
|
}
|
|
|
|
// Generate shadow indices for position+weight-only stream:
|
|
wi::vector<uint32_t> shadow_indices(source.size());
|
|
meshopt_generateShadowIndexBuffer(
|
|
shadow_indices.data(), source.data(), source.size(),
|
|
vertices.data(), vertices.size(), sizeof(XMFLOAT4), sizeof(XMFLOAT4)
|
|
);
|
|
|
|
size_t result = 0;
|
|
size_t target_index_count = size_t(shadow_indices.size() * saturate(detail)) / 3 * 3;
|
|
float target_error = 1 - saturate(detail);
|
|
int tries = 0;
|
|
while (result == 0 && tries < 100)
|
|
{
|
|
result = meshopt_simplify(
|
|
&physicsIndices[0],
|
|
&shadow_indices[0],
|
|
shadow_indices.size(),
|
|
(const float*)&mesh.vertex_positions[0],
|
|
mesh.vertex_positions.size(),
|
|
sizeof(XMFLOAT3),
|
|
target_index_count,
|
|
target_error
|
|
);
|
|
target_error *= 0.5f;
|
|
}
|
|
assert(result > 0);
|
|
physicsIndices.resize(result);
|
|
}
|
|
else
|
|
{
|
|
// Sloppy LOD can be used if no pinning is required:
|
|
size_t result = 0;
|
|
size_t target_index_count = 0;
|
|
float target_error = sqr(1 - saturate(detail));
|
|
int tries = 0;
|
|
while (result == 0 && tries < 100)
|
|
{
|
|
result = meshopt_simplifySloppy(
|
|
&physicsIndices[0],
|
|
&source[0],
|
|
source.size(),
|
|
(const float*)&mesh.vertex_positions[0],
|
|
mesh.vertex_positions.size(),
|
|
sizeof(XMFLOAT3),
|
|
target_index_count,
|
|
target_error
|
|
);
|
|
target_error *= 0.5f;
|
|
}
|
|
assert(result > 0);
|
|
physicsIndices.resize(result);
|
|
}
|
|
|
|
physicsIndices.shrink_to_fit();
|
|
|
|
// Remap physics indices to point to physics indices:
|
|
physicsToGraphicsVertexMapping.clear();
|
|
wi::unordered_map<uint32_t, size_t> physicsVertices;
|
|
for (size_t i = 0; i < physicsIndices.size(); ++i)
|
|
{
|
|
const uint32_t graphicsInd = physicsIndices[i];
|
|
if (physicsVertices.count(graphicsInd) == 0)
|
|
{
|
|
physicsVertices[graphicsInd] = physicsToGraphicsVertexMapping.size();
|
|
physicsToGraphicsVertexMapping.push_back(graphicsInd);
|
|
}
|
|
physicsIndices[i] = (uint32_t)physicsVertices[graphicsInd];
|
|
}
|
|
physicsToGraphicsVertexMapping.shrink_to_fit();
|
|
|
|
// BoneQueue is used for assigning the highest weighted fixed number of bones (soft body nodes) to a graphics vertex
|
|
static constexpr int influence = 8;
|
|
struct BoneQueue
|
|
{
|
|
struct Bone
|
|
{
|
|
uint32_t index = 0;
|
|
float weight = 0;
|
|
constexpr bool operator<(const Bone& other) const { return weight < other.weight; }
|
|
constexpr bool operator>(const Bone& other) const { return weight > other.weight; }
|
|
};
|
|
Bone bones[influence];
|
|
constexpr void add(uint32_t index, float weight)
|
|
{
|
|
int mini = 0;
|
|
for (int i = 1; i < arraysize(bones); ++i)
|
|
{
|
|
if (bones[i].weight < bones[mini].weight)
|
|
{
|
|
mini = i;
|
|
}
|
|
}
|
|
if (weight > bones[mini].weight)
|
|
{
|
|
bones[mini].weight = weight;
|
|
bones[mini].index = index;
|
|
}
|
|
}
|
|
void finalize()
|
|
{
|
|
std::sort(bones, bones + arraysize(bones), std::greater<Bone>());
|
|
// Note: normalization of bone weights will be done in MeshComponent::CreateRenderData()
|
|
}
|
|
constexpr XMUINT4 get_indices() const
|
|
{
|
|
return XMUINT4(
|
|
influence < 1 ? 0 : bones[0].index,
|
|
influence < 2 ? 0 : bones[1].index,
|
|
influence < 3 ? 0 : bones[2].index,
|
|
influence < 4 ? 0 : bones[3].index
|
|
);
|
|
}
|
|
constexpr XMUINT4 get_indices2() const
|
|
{
|
|
return XMUINT4(
|
|
influence < 5 ? 0 : bones[4].index,
|
|
influence < 6 ? 0 : bones[5].index,
|
|
influence < 7 ? 0 : bones[6].index,
|
|
influence < 8 ? 0 : bones[7].index
|
|
);
|
|
}
|
|
constexpr XMFLOAT4 get_weights() const
|
|
{
|
|
return XMFLOAT4(
|
|
influence < 1 ? 0 : bones[0].weight,
|
|
influence < 2 ? 0 : bones[1].weight,
|
|
influence < 3 ? 0 : bones[2].weight,
|
|
influence < 4 ? 0 : bones[3].weight
|
|
);
|
|
}
|
|
constexpr XMFLOAT4 get_weights2() const
|
|
{
|
|
return XMFLOAT4(
|
|
influence < 5 ? 0 : bones[4].weight,
|
|
influence < 6 ? 0 : bones[5].weight,
|
|
influence < 7 ? 0 : bones[6].weight,
|
|
influence < 8 ? 0 : bones[7].weight
|
|
);
|
|
}
|
|
};
|
|
|
|
// Create skinning bone vertex data:
|
|
mesh.vertex_boneindices.resize(mesh.vertex_positions.size());
|
|
mesh.vertex_boneweights.resize(mesh.vertex_positions.size());
|
|
if (influence > 4)
|
|
{
|
|
mesh.vertex_boneindices2.resize(mesh.vertex_positions.size());
|
|
mesh.vertex_boneweights2.resize(mesh.vertex_positions.size());
|
|
}
|
|
wi::jobsystem::context ctx;
|
|
wi::jobsystem::Dispatch(ctx, (uint32_t)mesh.vertex_positions.size(), 64, [&](wi::jobsystem::JobArgs args) {
|
|
const XMFLOAT3 position = mesh.vertex_positions[args.jobIndex];
|
|
|
|
BoneQueue bones;
|
|
for (size_t physicsInd = 0; physicsInd < physicsToGraphicsVertexMapping.size(); ++physicsInd)
|
|
{
|
|
const uint32_t graphicsInd = physicsToGraphicsVertexMapping[physicsInd];
|
|
const XMFLOAT3 position2 = mesh.vertex_positions[graphicsInd];
|
|
const float dist = wi::math::DistanceSquared(position, position2);
|
|
// Note: 0.01 correction is carefully tweaked so that cloth_test and sponza curtains look good
|
|
// (larger values blow up the curtains, lower values make the shading of the cloth look bad)
|
|
const float weight = 1.0f / (0.01f + dist);
|
|
bones.add((uint32_t)physicsInd, weight);
|
|
}
|
|
|
|
bones.finalize();
|
|
mesh.vertex_boneindices[args.jobIndex] = bones.get_indices();
|
|
mesh.vertex_boneweights[args.jobIndex] = bones.get_weights();
|
|
if (influence > 4)
|
|
{
|
|
mesh.vertex_boneindices2[args.jobIndex] = bones.get_indices2();
|
|
mesh.vertex_boneweights2[args.jobIndex] = bones.get_weights2();
|
|
}
|
|
});
|
|
wi::jobsystem::Wait(ctx);
|
|
mesh.CreateRenderData();
|
|
}
|
|
}
|
|
|
|
void CameraComponent::CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV)
|
|
{
|
|
zNearP = newNear;
|
|
zFarP = newFar;
|
|
width = newWidth;
|
|
height = newHeight;
|
|
fov = newFOV;
|
|
|
|
SetCustomProjectionEnabled(false);
|
|
|
|
UpdateCamera();
|
|
}
|
|
void CameraComponent::UpdateCamera()
|
|
{
|
|
if (!IsCustomProjectionEnabled())
|
|
{
|
|
XMStoreFloat4x4(&Projection, XMMatrixPerspectiveFovLH(fov, width / height, zFarP, zNearP)); // reverse zbuffer!
|
|
Projection.m[2][0] = jitter.x;
|
|
Projection.m[2][1] = jitter.y;
|
|
}
|
|
|
|
XMVECTOR _Eye = XMLoadFloat3(&Eye);
|
|
XMVECTOR _At = XMLoadFloat3(&At);
|
|
XMVECTOR _Up = XMLoadFloat3(&Up);
|
|
|
|
XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
|
|
XMStoreFloat4x4(&View, _V);
|
|
|
|
XMMATRIX _P = XMLoadFloat4x4(&Projection);
|
|
XMMATRIX _InvP = XMMatrixInverse(nullptr, _P);
|
|
XMStoreFloat4x4(&InvProjection, _InvP);
|
|
|
|
XMMATRIX _VP = XMMatrixMultiply(_V, _P);
|
|
XMStoreFloat4x4(&View, _V);
|
|
XMStoreFloat4x4(&VP, _VP);
|
|
XMMATRIX _InvV = XMMatrixInverse(nullptr, _V);
|
|
XMStoreFloat4x4(&InvView, _InvV);
|
|
XMStoreFloat3x3(&rotationMatrix, _InvV);
|
|
XMStoreFloat4x4(&InvVP, XMMatrixInverse(nullptr, _VP));
|
|
|
|
frustum.Create(_VP);
|
|
}
|
|
void CameraComponent::TransformCamera(const XMMATRIX& W)
|
|
{
|
|
XMVECTOR _Eye = XMVector3Transform(XMVectorSet(0, 0, 0, 1), W);
|
|
XMVECTOR _At = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 0, 1, 0), W));
|
|
XMVECTOR _Up = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W));
|
|
|
|
XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
|
|
XMStoreFloat4x4(&View, _V);
|
|
|
|
XMStoreFloat3x3(&rotationMatrix, XMMatrixInverse(nullptr, _V));
|
|
|
|
XMStoreFloat3(&Eye, _Eye);
|
|
XMStoreFloat3(&At, _At);
|
|
XMStoreFloat3(&Up, _Up);
|
|
}
|
|
void CameraComponent::Reflect(const XMFLOAT4& plane)
|
|
{
|
|
XMVECTOR _Eye = XMLoadFloat3(&Eye);
|
|
XMVECTOR _At = XMLoadFloat3(&At);
|
|
XMVECTOR _Up = XMLoadFloat3(&Up);
|
|
XMMATRIX _Ref = XMMatrixReflect(XMLoadFloat4(&plane));
|
|
|
|
clipPlaneOriginal = plane;
|
|
|
|
// reverse clipping if behind clip plane ("if underwater")
|
|
clipPlane = plane;
|
|
float d = XMVectorGetX(XMPlaneDotCoord(XMLoadFloat4(&clipPlane), _Eye));
|
|
if (d < 0)
|
|
{
|
|
clipPlane.x *= -1;
|
|
clipPlane.y *= -1;
|
|
clipPlane.z *= -1;
|
|
clipPlane.w *= -1;
|
|
}
|
|
|
|
_Eye = XMVector3Transform(_Eye, _Ref);
|
|
_At = XMVector3TransformNormal(_At, _Ref);
|
|
_Up = XMVector3TransformNormal(_Up, _Ref);
|
|
|
|
XMStoreFloat3(&Eye, _Eye);
|
|
XMStoreFloat3(&At, _At);
|
|
XMStoreFloat3(&Up, _Up);
|
|
|
|
UpdateCamera();
|
|
}
|
|
void CameraComponent::Lerp(const CameraComponent& a, const CameraComponent& b, float t)
|
|
{
|
|
SetDirty();
|
|
|
|
width = wi::math::Lerp(a.width, b.width, t);
|
|
height = wi::math::Lerp(a.height, b.height, t);
|
|
zNearP = wi::math::Lerp(a.zNearP, b.zNearP, t);
|
|
zFarP = wi::math::Lerp(a.zFarP, b.zFarP, t);
|
|
fov = wi::math::Lerp(a.fov, b.fov, t);
|
|
focal_length = wi::math::Lerp(a.focal_length, b.focal_length, t);
|
|
aperture_size = wi::math::Lerp(a.aperture_size, b.aperture_size, t);
|
|
aperture_shape = wi::math::Lerp(a.aperture_shape, b.aperture_shape, t);
|
|
}
|
|
|
|
void ScriptComponent::CreateFromFile(const std::string& filename)
|
|
{
|
|
this->filename = filename;
|
|
resource = wi::resourcemanager::Load(filename);
|
|
script.clear(); // will be created on first Update()
|
|
}
|
|
|
|
void SoundComponent::Play()
|
|
{
|
|
_flags |= PLAYING;
|
|
wi::audio::Play(&soundinstance);
|
|
}
|
|
void SoundComponent::Stop()
|
|
{
|
|
_flags &= ~PLAYING;
|
|
wi::audio::Stop(&soundinstance);
|
|
}
|
|
void SoundComponent::SetLooped(bool value)
|
|
{
|
|
soundinstance.SetLooped(value);
|
|
if (value)
|
|
{
|
|
_flags |= LOOPED;
|
|
wi::audio::CreateSoundInstance(&soundResource.GetSound(), &soundinstance);
|
|
}
|
|
else
|
|
{
|
|
_flags &= ~LOOPED;
|
|
wi::audio::ExitLoop(&soundinstance);
|
|
}
|
|
}
|
|
void SoundComponent::SetDisable3D(bool value)
|
|
{
|
|
if (value)
|
|
{
|
|
_flags |= DISABLE_3D;
|
|
}
|
|
else
|
|
{
|
|
_flags &= ~DISABLE_3D;
|
|
}
|
|
wi::audio::CreateSoundInstance(&soundResource.GetSound(), &soundinstance);
|
|
}
|
|
|
|
}
|