Compare commits
6 Commits
7bdf76c202
...
870b18b1fa
| Author | SHA1 | Date |
|---|---|---|
|
|
870b18b1fa | |
|
|
0d5af2b525 | |
|
|
b902d08ece | |
|
|
641ad3ea77 | |
|
|
bad0a850a1 | |
|
|
1747339072 |
41
README.md
41
README.md
|
|
@ -4,39 +4,50 @@ A Vulkan based renderer created with Vulkan 1.3 in C++.
|
||||||
|
|
||||||
## Features (Current and Planned)
|
## Features (Current and Planned)
|
||||||
|
|
||||||
- [ ] Forward Rendering
|
|
||||||
- [ ] glTF 2.0 Support
|
- [ ] glTF 2.0 Support
|
||||||
- [ ] Load Vertex Data
|
- [X] Load Vertex Data
|
||||||
- [ ] Load Material Data
|
- [X] Load Material Data
|
||||||
- [ ] Load Animation Data
|
- [ ] Load Animation Data
|
||||||
- [ ] Load Camera
|
- [ ] Load Camera
|
||||||
- [ ] Load Lights
|
- [ ] Load Lights
|
||||||
- [ ] Support Specular Materials
|
- [ ] Support Specular Materials
|
||||||
- [ ] Bindless Descriptors
|
- [X] Bindless Descriptors
|
||||||
- [ ] PBR
|
- [X] Simplified Descriptor Creation Pipeline
|
||||||
- [ ] IBL
|
- [ ] Debugging
|
||||||
|
- [ ] Tracy Integration
|
||||||
|
- [ ] Dear ImGui Integration
|
||||||
|
- [ ] Transparency
|
||||||
|
- [ ] Sorted
|
||||||
|
- [ ] Order Independent
|
||||||
- [ ] Shadows v1
|
- [ ] Shadows v1
|
||||||
- [ ] Omnidirectional Cubemap Shadows
|
- [ ] Omnidirectional Cubemap Shadows
|
||||||
- [ ] Spot Lights
|
- [ ] Spot Lights
|
||||||
- [ ] Directional Shadows
|
- [ ] Directional Shadows
|
||||||
- [ ] Cascaded Shadows
|
- [ ] Cascaded Shadows
|
||||||
- [ ] PCF
|
- [ ] PCF
|
||||||
- [ ] Simplified Descriptor Creation Pipeline
|
- [ ] Lighting / Shading Pipelines
|
||||||
- [ ] Deferred Rendering
|
- [ ] Blinn-Phong
|
||||||
|
- [ ] PBR
|
||||||
|
- [ ] IBL
|
||||||
|
- [ ] Rendering Techniques
|
||||||
|
- [ ] Forward Rendering
|
||||||
|
- [ ] Deferred Rendering
|
||||||
|
- [ ] Clustered-Forward Rendering
|
||||||
- [ ] Ambient Occlusion
|
- [ ] Ambient Occlusion
|
||||||
- [ ] SSAO
|
- [ ] SSAO
|
||||||
- [ ] HBAO
|
- [ ] HBAO
|
||||||
- [ ] VXAO/SDFAO
|
- [ ] VXAO
|
||||||
|
- [ ] SDFAO
|
||||||
- [ ] RTX AO
|
- [ ] RTX AO
|
||||||
- [ ] Reflection
|
- [ ] Reflection
|
||||||
- [ ] ScreenSpace Reflection (SSR)
|
- [ ] ScreenSpace Reflection (SSR)
|
||||||
- [ ] Cubemap/Probe Reflection
|
- [ ] Cubemap/Probe Reflection
|
||||||
- [ ] Forward+ Rendering
|
- [ ] Ray-Traced Reflection
|
||||||
- [ ] Global Illumination
|
- [ ] Global Illumination
|
||||||
- [ ] Precomputed Radiance Transfer
|
- [ ] Precomputed Radiance Transfer
|
||||||
- [ ] Voxel Cone Tracing
|
- [ ] Voxel Cone Tracing
|
||||||
- [ ] SDFGI
|
- [ ] SDFGI
|
||||||
- [ ] RTXGI
|
- [ ] RTXGI
|
||||||
- [ ] Shadows v2
|
- [ ] Shadows v2
|
||||||
- [ ] Omnidirectional Dual Paraboloid Shadows
|
- [ ] Omnidirectional Dual Paraboloid Shadows
|
||||||
- [ ] Perspective Shadow Mapping
|
- [ ] Perspective Shadow Mapping
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ add_library(util_helper STATIC
|
||||||
helpers.h
|
helpers.h
|
||||||
helpers.cpp
|
helpers.cpp
|
||||||
frame.cpp
|
frame.cpp
|
||||||
frame.h)
|
frame.h
|
||||||
|
gpu_resource_manager.cpp
|
||||||
|
gpu_resource_manager.h)
|
||||||
|
|
||||||
target_link_libraries(util_helper PRIVATE aster_core)
|
target_link_libraries(util_helper PRIVATE aster_core)
|
||||||
target_include_directories(util_helper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(util_helper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// =============================================
|
// =============================================
|
||||||
// Aster: pipeline_utils.h
|
// Aster: gpu_resource_manager.cpp
|
||||||
// Copyright (c) 2020-2024 Anish Bhobe
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
// =============================================
|
// =============================================
|
||||||
|
|
||||||
#include "render_resource_manager.h"
|
#include "gpu_resource_manager.h"
|
||||||
|
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
// =============================================
|
// =============================================
|
||||||
// Aster: pipeline_utils.h
|
// Aster: gpu_resource_manager.h
|
||||||
// Copyright (c) 2020-2024 Anish Bhobe
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
// =============================================
|
// =============================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
#include "buffer.h"
|
|
||||||
#include "image.h"
|
|
||||||
|
|
||||||
#include <EASTL/deque.h>
|
#include <EASTL/deque.h>
|
||||||
#include <EASTL/vector_map.h>
|
#include <EASTL/vector_map.h>
|
||||||
|
|
||||||
struct Device;
|
struct Device;
|
||||||
struct Texture;
|
struct Texture;
|
||||||
struct UniformStorageBuffer;
|
struct StorageBuffer;
|
||||||
|
|
||||||
struct GpuResourceHandle
|
struct GpuResourceHandle
|
||||||
{
|
{
|
||||||
|
|
@ -8,11 +8,13 @@ find_path(TINYGLTF_INCLUDE_DIRS "tiny_gltf.h")
|
||||||
add_executable(model_render model_render.cpp
|
add_executable(model_render model_render.cpp
|
||||||
pipeline_utils.cpp
|
pipeline_utils.cpp
|
||||||
pipeline_utils.h
|
pipeline_utils.h
|
||||||
render_resource_manager.cpp
|
|
||||||
render_resource_manager.h
|
|
||||||
model_loader.cpp
|
model_loader.cpp
|
||||||
model_loader.h
|
model_loader.h
|
||||||
)
|
light_manager.cpp
|
||||||
|
light_manager.h
|
||||||
|
nodes.cpp
|
||||||
|
nodes.h)
|
||||||
|
|
||||||
add_shader(model_render shader/model.vs.hlsl)
|
add_shader(model_render shader/model.vs.hlsl)
|
||||||
add_shader(model_render shader/model.ps.hlsl)
|
add_shader(model_render shader/model.ps.hlsl)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
// =============================================
|
||||||
|
// Aster: light_manager.cpp
|
||||||
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
#include "light_manager.h"
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "glm/ext/matrix_transform.hpp"
|
||||||
|
|
||||||
|
struct Light
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
vec3 um_Position;
|
||||||
|
vec3 um_Direction;
|
||||||
|
};
|
||||||
|
f32 m_Range; // < 0.0 for invalid
|
||||||
|
u32 m_Color_; // LSB is used for flags. (R G B Flags)
|
||||||
|
f32 m_Intensity;
|
||||||
|
|
||||||
|
constexpr static u32 MAX_GEN = 0x40;
|
||||||
|
constexpr static u32 GEN_MASK = MAX_GEN - 1;
|
||||||
|
|
||||||
|
constexpr static u32 TYPE_MASK = 0xC0;
|
||||||
|
constexpr static u32 TYPE_INVALID = 0x0;
|
||||||
|
constexpr static u32 TYPE_DIRECTIONAL = 1 << 6;
|
||||||
|
constexpr static u32 TYPE_POINT = 2 << 6;
|
||||||
|
constexpr static u32 TYPE_SPOT = 3 << 6; // Currently Unused
|
||||||
|
|
||||||
|
constexpr static u32 COLOR_MASK = ~(GEN_MASK | TYPE_MASK);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static Checks
|
||||||
|
|
||||||
|
// Ensure layouts are exact.
|
||||||
|
static_assert(offsetof(DirectionalLight, m_Direction) == offsetof(Light, um_Direction));
|
||||||
|
static_assert(offsetof(DirectionalLight, m_Color_) == offsetof(Light, m_Color_));
|
||||||
|
static_assert(offsetof(DirectionalLight, m_Intensity) == offsetof(Light, m_Intensity));
|
||||||
|
static_assert(sizeof(DirectionalLight) <= sizeof(Light));
|
||||||
|
|
||||||
|
// Ensure layouts are exact.
|
||||||
|
static_assert(offsetof(PointLight, m_Position) == offsetof(Light, um_Position));
|
||||||
|
static_assert(offsetof(PointLight, m_Range) == offsetof(Light, m_Range));
|
||||||
|
static_assert(offsetof(PointLight, m_Color_) == offsetof(Light, m_Color_));
|
||||||
|
static_assert(offsetof(PointLight, m_Intensity) == offsetof(Light, m_Intensity));
|
||||||
|
static_assert(sizeof(PointLight) <= sizeof(Light));
|
||||||
|
|
||||||
|
// Ensure bitmask are in the right place.
|
||||||
|
static_assert((Light::TYPE_MASK & Light::TYPE_INVALID) == Light::TYPE_INVALID);
|
||||||
|
static_assert((Light::TYPE_MASK & Light::TYPE_DIRECTIONAL) == Light::TYPE_DIRECTIONAL);
|
||||||
|
static_assert((Light::TYPE_MASK & Light::TYPE_POINT) == Light::TYPE_POINT);
|
||||||
|
static_assert((Light::TYPE_MASK & Light::TYPE_SPOT) == Light::TYPE_SPOT);
|
||||||
|
static_assert(Light::COLOR_MASK == 0xFFFFFF00);
|
||||||
|
|
||||||
|
inline u32
|
||||||
|
ToColor32(const vec4 &col)
|
||||||
|
{
|
||||||
|
const u32 r = Cast<u32>(eastl::min(col.r, 1.0f) * 255.99f);
|
||||||
|
const u32 g = Cast<u32>(eastl::min(col.g, 1.0f) * 255.99f);
|
||||||
|
const u32 b = Cast<u32>(eastl::min(col.b, 1.0f) * 255.99f);
|
||||||
|
const u32 a = Cast<u32>(eastl::min(col.a, 1.0f) * 255.99f);
|
||||||
|
|
||||||
|
return r << 24 | g << 16 | b << 8 | a;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32
|
||||||
|
ToColor32(const vec3 &col)
|
||||||
|
{
|
||||||
|
const u32 r = Cast<u32>(eastl::min(col.r, 1.0f) * 255.99f);
|
||||||
|
const u32 g = Cast<u32>(eastl::min(col.g, 1.0f) * 255.99f);
|
||||||
|
const u32 b = Cast<u32>(eastl::min(col.b, 1.0f) * 255.99f);
|
||||||
|
constexpr u32 a = 255;
|
||||||
|
|
||||||
|
return r << 24 | g << 16 | b << 8 | a;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightManager::LightManager(GpuResourceManager *resourceManager)
|
||||||
|
: m_ResourceManager{resourceManager}
|
||||||
|
, m_DirectionalLightCount{}
|
||||||
|
, m_PointLightCount{}
|
||||||
|
, m_MetaInfo{}
|
||||||
|
, m_GpuBufferCapacity_{0}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LightManager::~LightManager()
|
||||||
|
{
|
||||||
|
m_ResourceManager->Release(m_MetaInfo.m_LightBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightManager::LightManager(LightManager &&other) noexcept
|
||||||
|
: m_ResourceManager(other.m_ResourceManager)
|
||||||
|
, m_Lights(std::move(other.m_Lights))
|
||||||
|
, m_DirectionalLightCount(other.m_DirectionalLightCount)
|
||||||
|
, m_PointLightCount(other.m_PointLightCount)
|
||||||
|
, m_MetaInfo(other.m_MetaInfo)
|
||||||
|
, m_GpuBufferCapacity_(other.m_GpuBufferCapacity_)
|
||||||
|
{
|
||||||
|
other.m_MetaInfo.m_LightBuffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
LightManager &
|
||||||
|
LightManager::operator=(LightManager &&other) noexcept
|
||||||
|
{
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
m_ResourceManager = other.m_ResourceManager;
|
||||||
|
m_Lights = std::move(other.m_Lights);
|
||||||
|
m_DirectionalLightCount = other.m_DirectionalLightCount;
|
||||||
|
m_PointLightCount = other.m_PointLightCount;
|
||||||
|
m_MetaInfo = other.m_MetaInfo;
|
||||||
|
other.m_MetaInfo.m_LightBuffer = {};
|
||||||
|
m_GpuBufferCapacity_ = other.m_GpuBufferCapacity_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightHandle
|
||||||
|
LightManager::AddDirectional(const vec3 &direction, const vec3 &color)
|
||||||
|
{
|
||||||
|
const vec3 normDirection = normalize(direction);
|
||||||
|
if (m_DirectionalLightCount < m_MetaInfo.m_DirectionalLightMaxCount)
|
||||||
|
{
|
||||||
|
u16 index = 0;
|
||||||
|
for (auto &light : m_Lights)
|
||||||
|
{
|
||||||
|
if (light.m_Range < 0)
|
||||||
|
{
|
||||||
|
const u8 gen = light.m_Color_ & Light::GEN_MASK;
|
||||||
|
|
||||||
|
light.m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen;
|
||||||
|
light.m_Range = 1.0f;
|
||||||
|
light.um_Direction = normDirection;
|
||||||
|
|
||||||
|
m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT;
|
||||||
|
|
||||||
|
return {Light::TYPE_DIRECTIONAL, gen, index};
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
assert(index < m_MetaInfo.m_DirectionalLightMaxCount); // Gap not found. But must exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case we will end up intersecting, we move point lights away. (2 at a time)
|
||||||
|
if (m_DirectionalLightCount == m_MetaInfo.m_DirectionalLightMaxCount &&
|
||||||
|
m_MetaInfo.m_DirectionalLightMaxCount == m_MetaInfo.m_PointLightOffset)
|
||||||
|
{
|
||||||
|
const u16 oldPointLightOffset = m_MetaInfo.m_PointLightOffset;
|
||||||
|
const u32 pointLightMaxCount = m_MetaInfo.m_PointLightMaxCount;
|
||||||
|
// Might cause a capacity increase, but I want to use that for my gpu buffer resize.
|
||||||
|
m_Lights.push_back();
|
||||||
|
m_Lights.push_back();
|
||||||
|
|
||||||
|
if (m_MetaInfo.m_PointLightMaxCount > 0) // Edge Case: nullptr at size 0
|
||||||
|
{
|
||||||
|
Light *oldPointStart = m_Lights.data() + oldPointLightOffset;
|
||||||
|
Light *newPointStart = oldPointStart + 2;
|
||||||
|
|
||||||
|
static_assert(std::is_trivially_copyable_v<Light>);
|
||||||
|
memcpy(newPointStart, oldPointStart, pointLightMaxCount * sizeof *newPointStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_MetaInfo.m_PointLightOffset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u8 gen = 0; // New light
|
||||||
|
m_Lights[m_DirectionalLightCount].m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen;
|
||||||
|
m_Lights[m_DirectionalLightCount].m_Range = 1.0f;
|
||||||
|
m_Lights[m_DirectionalLightCount].um_Direction = normDirection;
|
||||||
|
const u16 index = m_DirectionalLightCount;
|
||||||
|
|
||||||
|
++m_DirectionalLightCount;
|
||||||
|
++m_MetaInfo.m_DirectionalLightMaxCount;
|
||||||
|
|
||||||
|
return {Light::TYPE_DIRECTIONAL, gen, index};
|
||||||
|
}
|
||||||
|
|
||||||
|
LightHandle
|
||||||
|
LightManager::AddPoint(const vec3 & position, const vec3 &color, const f32 radius)
|
||||||
|
{
|
||||||
|
assert(m_PointLightCount <= m_MetaInfo.m_PointLightMaxCount);
|
||||||
|
assert(radius >= 0.0f);
|
||||||
|
if (m_PointLightCount < m_MetaInfo.m_PointLightMaxCount)
|
||||||
|
{
|
||||||
|
Light *light = m_Lights.data() + m_MetaInfo.m_PointLightOffset;
|
||||||
|
for (u32 index = 0; index < m_MetaInfo.m_PointLightMaxCount; ++index)
|
||||||
|
{
|
||||||
|
if (light->m_Range < 0)
|
||||||
|
{
|
||||||
|
const u8 gen = light->m_Color_ & Light::GEN_MASK;
|
||||||
|
|
||||||
|
light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen;
|
||||||
|
light->m_Range = radius;
|
||||||
|
light->um_Position = position;
|
||||||
|
|
||||||
|
m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT;
|
||||||
|
|
||||||
|
return {Light::TYPE_POINT, gen, Cast<u16>(index)};
|
||||||
|
}
|
||||||
|
++light;
|
||||||
|
}
|
||||||
|
assert(false); // gap must exists.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Lights.push_back();
|
||||||
|
const u16 index = m_PointLightCount;
|
||||||
|
|
||||||
|
Light *light = &m_Lights[index + m_MetaInfo.m_PointLightOffset];
|
||||||
|
constexpr u8 gen = 0; // New light
|
||||||
|
|
||||||
|
light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen;
|
||||||
|
light->m_Range = radius;
|
||||||
|
light->um_Position = position;
|
||||||
|
|
||||||
|
++m_PointLightCount;
|
||||||
|
++m_MetaInfo.m_PointLightMaxCount;
|
||||||
|
|
||||||
|
m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT;
|
||||||
|
|
||||||
|
return {Light::TYPE_POINT, gen, index};
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LightManager::Update()
|
||||||
|
{
|
||||||
|
const u16 requiredBufferCapacity = eastl::min(Cast<u16>(m_Lights.capacity()), MAX_LIGHTS);
|
||||||
|
if ((m_GpuBufferCapacity_ & CAPACITY_MASK) < requiredBufferCapacity)
|
||||||
|
{
|
||||||
|
StorageBuffer newBuffer;
|
||||||
|
newBuffer.Init(m_ResourceManager->m_Device, requiredBufferCapacity * sizeof m_Lights[0], true, "Light Buffer");
|
||||||
|
m_GpuBufferCapacity_ = requiredBufferCapacity | UPDATE_REQUIRED_BIT;
|
||||||
|
|
||||||
|
m_ResourceManager->Release(m_MetaInfo.m_LightBuffer);
|
||||||
|
m_MetaInfo.m_LightBuffer = m_ResourceManager->Commit(&newBuffer);
|
||||||
|
}
|
||||||
|
if (m_GpuBufferCapacity_ & UPDATE_REQUIRED_BIT)
|
||||||
|
{
|
||||||
|
m_ResourceManager->Write(m_MetaInfo.m_LightBuffer, 0, m_Lights.size() * sizeof m_Lights[0], m_Lights.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LightManager::RemoveLight(const LightHandle handle)
|
||||||
|
{
|
||||||
|
const u8 handleGen = handle.m_Generation;
|
||||||
|
|
||||||
|
|
||||||
|
if (handle.m_Type == Light::TYPE_DIRECTIONAL)
|
||||||
|
{
|
||||||
|
Light *lightSlot = &m_Lights[handle.m_Index];
|
||||||
|
const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK;
|
||||||
|
if (slotGen > handleGen)
|
||||||
|
{
|
||||||
|
WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightSlot->m_Range = -1.0f;
|
||||||
|
lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN;
|
||||||
|
--m_DirectionalLightCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle.m_Type == Light::TYPE_POINT)
|
||||||
|
{
|
||||||
|
Light *lightSlot = &m_Lights[handle.m_Index + m_MetaInfo.m_PointLightOffset];
|
||||||
|
const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK;
|
||||||
|
if (slotGen > handleGen)
|
||||||
|
{
|
||||||
|
WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightSlot->m_Range = -1.0f;
|
||||||
|
lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN;
|
||||||
|
--m_PointLightCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// =============================================
|
||||||
|
// Aster: light_manager.h
|
||||||
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "global.h"
|
||||||
|
|
||||||
|
// TODO: Separate files so you only import handles.
|
||||||
|
#include "gpu_resource_manager.h"
|
||||||
|
|
||||||
|
struct DirectionalLight
|
||||||
|
{
|
||||||
|
vec3 m_Direction;
|
||||||
|
u32 m_UnusedPadding0_;
|
||||||
|
u32 m_Color_; // LSB is used for flags. (R G B Flags)
|
||||||
|
f32 m_Intensity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PointLight
|
||||||
|
{
|
||||||
|
vec3 m_Position;
|
||||||
|
f32 m_Range;
|
||||||
|
u32 m_Color_; // LSB is used for flags. (R G B Flags)
|
||||||
|
f32 m_Intensity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LightHandle
|
||||||
|
{
|
||||||
|
u8 m_Type;
|
||||||
|
u8 m_Generation;
|
||||||
|
u16 m_Index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Light;
|
||||||
|
|
||||||
|
struct LightManager
|
||||||
|
{
|
||||||
|
constexpr static u16 MAX_LIGHTS = MaxValue<u16>;
|
||||||
|
struct LightMetaInfo
|
||||||
|
{
|
||||||
|
// The number of directional lights is relatively low (1 - 2) and will almost never change in a scene.
|
||||||
|
// We can use that with Offset = 0, and point light at further offsets.
|
||||||
|
// This way we don't need to move point lights often.
|
||||||
|
|
||||||
|
BufferHandle m_LightBuffer; // 04 04
|
||||||
|
u16 m_PointLightMaxCount; // 02 06
|
||||||
|
u16 m_PointLightOffset; // 02 08
|
||||||
|
u16 m_DirectionalLightMaxCount; // 02 10
|
||||||
|
u16 m_UnusedPadding0 = 0; // 02 12
|
||||||
|
};
|
||||||
|
|
||||||
|
GpuResourceManager *m_ResourceManager;
|
||||||
|
eastl::vector<Light> m_Lights;
|
||||||
|
|
||||||
|
// We don't need a Directional Light free list. We will just brute force iterate.
|
||||||
|
u16 m_DirectionalLightCount;
|
||||||
|
// TODO: A point light free list. We will brute force until we have a lot (100+) of point lights.
|
||||||
|
u16 m_PointLightCount;
|
||||||
|
|
||||||
|
LightMetaInfo m_MetaInfo;
|
||||||
|
// Using lower bit for flags. Use CAPACITY_MASK for value.
|
||||||
|
u16 m_GpuBufferCapacity_;
|
||||||
|
|
||||||
|
// Using lower bit. Capacity can be directly a multiple of 2
|
||||||
|
// Thus, range is up to MaxValue<u16>
|
||||||
|
constexpr static u16 UPDATE_REQUIRED_BIT = 1;
|
||||||
|
constexpr static u16 CAPACITY_MASK = ~(UPDATE_REQUIRED_BIT);
|
||||||
|
|
||||||
|
LightHandle AddDirectional(const vec3 &direction, const vec3 &color);
|
||||||
|
LightHandle AddPoint(const vec3& position, const vec3 &color, f32 radius);
|
||||||
|
void Update();
|
||||||
|
void RemoveLight(LightHandle handle);
|
||||||
|
|
||||||
|
explicit LightManager(GpuResourceManager *resourceManager);
|
||||||
|
~LightManager();
|
||||||
|
|
||||||
|
LightManager(LightManager &&other) noexcept;
|
||||||
|
LightManager &operator=(LightManager &&other) noexcept;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(LightManager);
|
||||||
|
};
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "image.h"
|
#include "image.h"
|
||||||
#include "render_resource_manager.h"
|
#include "gpu_resource_manager.h"
|
||||||
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
#include <EASTL/array.h>
|
#include <EASTL/array.h>
|
||||||
|
|
@ -349,10 +349,9 @@ ModelLoader::LoadModel(cstr path, cstr name, bool batched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
#pragma region Vertex Data
|
||||||
vertexData.resize(vertexPositions.size());
|
vertexData.resize(vertexPositions.size());
|
||||||
|
|
||||||
#pragma region Normal
|
|
||||||
// Normal Coords
|
// Normal Coords
|
||||||
if (prim.attributes.contains(ANormal))
|
if (prim.attributes.contains(ANormal))
|
||||||
{
|
{
|
||||||
|
|
@ -395,8 +394,7 @@ ModelLoader::LoadModel(cstr path, cstr name, bool batched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
|
||||||
#pragma region UV0
|
|
||||||
// UV0
|
// UV0
|
||||||
if (prim.attributes.contains(ATexCoord0))
|
if (prim.attributes.contains(ATexCoord0))
|
||||||
{
|
{
|
||||||
|
|
@ -421,8 +419,7 @@ ModelLoader::LoadModel(cstr path, cstr name, bool batched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
|
||||||
#pragma region Color
|
|
||||||
if (prim.attributes.contains(AColor0))
|
if (prim.attributes.contains(AColor0))
|
||||||
{
|
{
|
||||||
tinygltf::Accessor *colorAccessor = &model.accessors[prim.attributes[AColor0]];
|
tinygltf::Accessor *colorAccessor = &model.accessors[prim.attributes[AColor0]];
|
||||||
|
|
@ -673,115 +670,6 @@ Model::operator=(Model &&other) noexcept
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32
|
|
||||||
Nodes::Add(const mat4 &transform, const i32 parent)
|
|
||||||
{
|
|
||||||
m_Dirty = true;
|
|
||||||
const u32 index = Count();
|
|
||||||
m_Transforms.push_back(transform);
|
|
||||||
m_GlobalTransforms.push_back(transform);
|
|
||||||
const u32 parentVal = (parent < 0 ? ROOT_BIT : parent & PARENT_MASK) | DIRTY_BIT;
|
|
||||||
m_Parents_.push_back(parentVal);
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mat4 &
|
|
||||||
Nodes::Get(const u32 index) const
|
|
||||||
{
|
|
||||||
return m_Transforms[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Nodes::Set(const u32 index, const mat4 &transform)
|
|
||||||
{
|
|
||||||
m_Dirty = true;
|
|
||||||
m_Transforms[index] = transform;
|
|
||||||
m_Parents_[index] |= DIRTY_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mat4 &
|
|
||||||
Nodes::operator[](const u32 index) const
|
|
||||||
{
|
|
||||||
return m_Transforms[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
mat4 &
|
|
||||||
Nodes::operator[](const u32 index)
|
|
||||||
{
|
|
||||||
m_Dirty = true;
|
|
||||||
m_Parents_[index] |= DIRTY_BIT;
|
|
||||||
return m_Transforms[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
u32
|
|
||||||
Nodes::Count() const
|
|
||||||
{
|
|
||||||
return Cast<u32>(m_Transforms.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
usize
|
|
||||||
Nodes::GetGlobalTransformByteSize() const
|
|
||||||
{
|
|
||||||
return m_GlobalTransforms.size() * sizeof m_GlobalTransforms[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const mat4 *
|
|
||||||
Nodes::GetGlobalTransformPtr() const
|
|
||||||
{
|
|
||||||
return m_GlobalTransforms.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Nodes::Update()
|
|
||||||
{
|
|
||||||
if (!m_Dirty)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto transformIter = m_Transforms.begin();
|
|
||||||
auto globalTransformIter = m_GlobalTransforms.begin();
|
|
||||||
auto parentIter = m_Parents_.begin();
|
|
||||||
const auto parentEnd = m_Parents_.end();
|
|
||||||
|
|
||||||
while (parentIter != parentEnd)
|
|
||||||
{
|
|
||||||
const bool isRoot = *parentIter & ROOT_BIT;
|
|
||||||
const bool isDirty = *parentIter & DIRTY_BIT;
|
|
||||||
|
|
||||||
if (isRoot)
|
|
||||||
{
|
|
||||||
if (isDirty)
|
|
||||||
{
|
|
||||||
// Copy-update if the root is dirty.
|
|
||||||
*globalTransformIter = *transformIter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const u32 parentIdx = *parentIter & PARENT_MASK;
|
|
||||||
const bool isParentDirty = m_Parents_[parentIdx] & DIRTY_BIT;
|
|
||||||
if (isDirty || isParentDirty)
|
|
||||||
{
|
|
||||||
// Update w.r.t parent if either local or parent transforms updated.
|
|
||||||
*globalTransformIter = m_GlobalTransforms[parentIdx] * *transformIter;
|
|
||||||
m_Parents_[parentIdx] |= DIRTY_BIT; // Set dirty to propagate the update.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++parentIter;
|
|
||||||
++globalTransformIter;
|
|
||||||
++transformIter;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u32 &parentValue : m_Parents_)
|
|
||||||
{
|
|
||||||
parentValue &= ~DIRTY_BIT; // Unset dirty.
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Dirty = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mat4 &
|
const mat4 &
|
||||||
Model::GetModelTransform() const
|
Model::GetModelTransform() const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
#include "render_resource_manager.h"
|
#include "gpu_resource_manager.h"
|
||||||
|
#include "nodes.h"
|
||||||
|
|
||||||
#include <tiny_gltf.h>
|
#include <tiny_gltf.h>
|
||||||
|
|
||||||
|
|
@ -27,32 +28,6 @@ struct MeshPrimitive
|
||||||
i32 m_TransformIdx;
|
i32 m_TransformIdx;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Nodes
|
|
||||||
{
|
|
||||||
eastl::vector<mat4> m_Transforms;
|
|
||||||
eastl::vector<mat4> m_GlobalTransforms;
|
|
||||||
/// Parents are also used for bookkeeping
|
|
||||||
eastl::vector<u32> m_Parents_;
|
|
||||||
bool m_Dirty = true;
|
|
||||||
|
|
||||||
constexpr static u32 ROOT_BIT = 1u << 31;
|
|
||||||
constexpr static u32 DIRTY_BIT = 1u << 30;
|
|
||||||
constexpr static u32 PARENT_MASK = ~(ROOT_BIT | DIRTY_BIT);
|
|
||||||
|
|
||||||
u32 Add(const mat4 &transform, const i32 parent = -1);
|
|
||||||
[[nodiscard]] const mat4 &Get(const u32 index) const;
|
|
||||||
void Set(const u32 index, const mat4 &transform);
|
|
||||||
[[nodiscard]] u32 Count() const;
|
|
||||||
|
|
||||||
[[nodiscard]] const mat4 &operator[](const u32 index) const;
|
|
||||||
[[nodiscard]] mat4 &operator[](const u32 index);
|
|
||||||
|
|
||||||
[[nodiscard]] usize GetGlobalTransformByteSize() const;
|
|
||||||
[[nodiscard]] const mat4 *GetGlobalTransformPtr() const;
|
|
||||||
|
|
||||||
bool Update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Material
|
struct Material
|
||||||
{
|
{
|
||||||
vec4 m_AlbedoFactor; // 16 16
|
vec4 m_AlbedoFactor; // 16 16
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@
|
||||||
|
|
||||||
#include "frame.h"
|
#include "frame.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
#include "light_manager.h"
|
||||||
|
|
||||||
#include "model_loader.h"
|
#include "model_loader.h"
|
||||||
#include "pipeline_utils.h"
|
#include "pipeline_utils.h"
|
||||||
#include "render_resource_manager.h"
|
#include "gpu_resource_manager.h"
|
||||||
|
|
||||||
#include <EASTL/array.h>
|
#include <EASTL/array.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
|
|
@ -27,27 +28,13 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
constexpr u32 MAX_FRAMES_IN_FLIGHT = 3;
|
constexpr u32 MAX_FRAMES_IN_FLIGHT = 3;
|
||||||
constexpr auto MODEL_FILE = "model/OrientationTest.glb";
|
constexpr auto MODEL_FILE = "model/DamagedHelmet.glb";
|
||||||
|
|
||||||
struct ImageFile
|
|
||||||
{
|
|
||||||
u8 *m_Data = nullptr;
|
|
||||||
u32 m_Width = 0;
|
|
||||||
u32 m_Height = 0;
|
|
||||||
u32 m_NumChannels = 0;
|
|
||||||
bool m_Constant = false;
|
|
||||||
|
|
||||||
bool Load(cstr fileName);
|
|
||||||
bool Load(vec4 color);
|
|
||||||
[[nodiscard]] usize GetSize() const;
|
|
||||||
~ImageFile();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Camera
|
struct Camera
|
||||||
{
|
{
|
||||||
mat4 m_Model;
|
|
||||||
mat4 m_View;
|
mat4 m_View;
|
||||||
mat4 m_Perspective;
|
mat4 m_Perspective;
|
||||||
|
vec4 m_Position;
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
@ -88,18 +75,25 @@ main(int, char **)
|
||||||
GpuResourceManager resourceManager = {&device, 1000};
|
GpuResourceManager resourceManager = {&device, 1000};
|
||||||
|
|
||||||
ModelLoader modelLoader = {&resourceManager, commandQueue, queueAllocation.m_Family, queueAllocation.m_Family};
|
ModelLoader modelLoader = {&resourceManager, commandQueue, queueAllocation.m_Family, queueAllocation.m_Family};
|
||||||
|
LightManager lightManager = LightManager{&resourceManager};
|
||||||
|
|
||||||
auto model = modelLoader.LoadModel(MODEL_FILE);
|
Model model = modelLoader.LoadModel(MODEL_FILE);
|
||||||
|
|
||||||
Pipeline pipeline = CreatePipeline(&device, &swapchain, &resourceManager);
|
Pipeline pipeline = CreatePipeline(&device, &swapchain, &resourceManager);
|
||||||
|
|
||||||
Camera camera = {
|
Camera camera = {
|
||||||
.m_Model = {1.0f},
|
.m_View = glm::lookAt(vec3(0.0f, 2.0f, 2.0f), vec3(0.0f), vec3(0.0f, 1.0f, 0.0f)),
|
||||||
.m_View = glm::lookAt(vec3(0.0f, 12.0f, 12.0f), vec3(0.0f), vec3(0.0f, 1.0f, 0.0f)),
|
|
||||||
.m_Perspective = glm::perspective(
|
.m_Perspective = glm::perspective(
|
||||||
70_deg, Cast<f32>(swapchain.m_Extent.width) / Cast<f32>(swapchain.m_Extent.height), 0.1f, 100.0f),
|
70_deg, Cast<f32>(swapchain.m_Extent.width) / Cast<f32>(swapchain.m_Extent.height), 0.1f, 100.0f),
|
||||||
|
.m_Position = vec4{0.0f, 2.0f, 2.0f, 1.0f},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lightManager.AddDirectional(vec3(0.0f, -1.0f, 0.0f), {0.0f, 1.0f, 0.0f});
|
||||||
|
lightManager.AddPoint(vec3{2.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 15.0f);
|
||||||
|
lightManager.AddPoint(vec3{-2.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, 15.0f);
|
||||||
|
|
||||||
|
lightManager.Update();
|
||||||
|
|
||||||
vk::DescriptorPool descriptorPool;
|
vk::DescriptorPool descriptorPool;
|
||||||
vk::DescriptorSet descriptorSet;
|
vk::DescriptorSet descriptorSet;
|
||||||
|
|
||||||
|
|
@ -280,16 +274,23 @@ main(int, char **)
|
||||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 1, 1, &descriptorSet, 0, nullptr);
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 1, 1, &descriptorSet, 0, nullptr);
|
||||||
|
|
||||||
cmd.bindIndexBuffer(model.m_IndexBuffer.m_Buffer, 0, vk::IndexType::eUint32);
|
cmd.bindIndexBuffer(model.m_IndexBuffer.m_Buffer, 0, vk::IndexType::eUint32);
|
||||||
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, 0, sizeof model.m_Handles,
|
|
||||||
|
u32 pcbOffset = 0;
|
||||||
|
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof model.m_Handles,
|
||||||
&model.m_Handles);
|
&model.m_Handles);
|
||||||
|
pcbOffset += sizeof model.m_Handles;
|
||||||
|
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof lightManager.m_MetaInfo,
|
||||||
|
&lightManager.m_MetaInfo);
|
||||||
|
pcbOffset += sizeof lightManager.m_MetaInfo;
|
||||||
|
|
||||||
for (auto &prim : model.m_MeshPrimitives)
|
for (auto &prim : model.m_MeshPrimitives)
|
||||||
{
|
{
|
||||||
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, sizeof model.m_Handles,
|
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset,
|
||||||
sizeof prim.m_MaterialIdx, &prim.m_MaterialIdx);
|
sizeof prim.m_MaterialIdx, &prim.m_MaterialIdx);
|
||||||
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll,
|
pcbOffset += sizeof prim.m_MaterialIdx;
|
||||||
sizeof model.m_Handles + sizeof prim.m_MaterialIdx,
|
cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset,
|
||||||
sizeof prim.m_TransformIdx, &prim.m_TransformIdx);
|
sizeof prim.m_TransformIdx, &prim.m_TransformIdx);
|
||||||
|
pcbOffset += sizeof prim.m_TransformIdx;
|
||||||
cmd.drawIndexed(prim.m_IndexCount, 1, prim.m_FirstIndex, Cast<i32>(prim.m_VertexOffset), 0);
|
cmd.drawIndexed(prim.m_IndexCount, 1, prim.m_FirstIndex, Cast<i32>(prim.m_VertexOffset), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,65 +327,3 @@ main(int, char **)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
ImageFile::Load(vec4 color)
|
|
||||||
{
|
|
||||||
constexpr usize size = 512llu * 512llu * 4llu;
|
|
||||||
u8 *pData = new u8[size];
|
|
||||||
|
|
||||||
const vec4 color255 = 255.999f * color;
|
|
||||||
const glm::vec<4, u8> color8 = color255;
|
|
||||||
|
|
||||||
for (usize i = 0; i < size; i += 4)
|
|
||||||
{
|
|
||||||
memcpy(pData + i, &color8, sizeof color8);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Data = pData;
|
|
||||||
m_Constant = true;
|
|
||||||
m_Height = 512;
|
|
||||||
m_Width = 512;
|
|
||||||
m_NumChannels = 4;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
ImageFile::Load(cstr fileName)
|
|
||||||
{
|
|
||||||
int width, height, nrChannels;
|
|
||||||
m_Data = stbi_load(fileName, &width, &height, &nrChannels, 4);
|
|
||||||
ERROR_IF(!m_Data, "Could not load {}", fileName);
|
|
||||||
|
|
||||||
if (!m_Data)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Width = width;
|
|
||||||
m_Height = height;
|
|
||||||
m_NumChannels = 4;
|
|
||||||
m_Constant = false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize
|
|
||||||
ImageFile::GetSize() const
|
|
||||||
{
|
|
||||||
return Cast<usize>(m_Width) * m_Height * m_NumChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageFile::~ImageFile()
|
|
||||||
{
|
|
||||||
if (m_Constant)
|
|
||||||
{
|
|
||||||
delete[] Cast<u8 *>(m_Data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stbi_image_free(m_Data);
|
|
||||||
}
|
|
||||||
m_Data = nullptr;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// =============================================
|
||||||
|
// Aster: nodes.cpp
|
||||||
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
#include "nodes.h"
|
||||||
|
|
||||||
|
u32
|
||||||
|
Nodes::Add(const mat4 &transform, const i32 parent)
|
||||||
|
{
|
||||||
|
m_Dirty = true;
|
||||||
|
const u32 index = Count();
|
||||||
|
m_Transforms.push_back(transform);
|
||||||
|
m_GlobalTransforms.emplace_back(transform);
|
||||||
|
const u32 parentVal = (parent < 0 ? ROOT_BIT : parent & PARENT_MASK) | DIRTY_BIT;
|
||||||
|
m_Parents_.push_back(parentVal);
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mat4 &
|
||||||
|
Nodes::Get(const u32 index) const
|
||||||
|
{
|
||||||
|
return m_Transforms[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Nodes::Set(const u32 index, const mat4 &transform)
|
||||||
|
{
|
||||||
|
m_Dirty = true;
|
||||||
|
m_Transforms[index] = transform;
|
||||||
|
m_Parents_[index] |= DIRTY_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mat4 &
|
||||||
|
Nodes::operator[](const u32 index) const
|
||||||
|
{
|
||||||
|
return m_Transforms[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 &
|
||||||
|
Nodes::operator[](const u32 index)
|
||||||
|
{
|
||||||
|
m_Dirty = true;
|
||||||
|
m_Parents_[index] |= DIRTY_BIT;
|
||||||
|
return m_Transforms[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
u32
|
||||||
|
Nodes::Count() const
|
||||||
|
{
|
||||||
|
return Cast<u32>(m_Transforms.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
usize
|
||||||
|
Nodes::GetGlobalTransformByteSize() const
|
||||||
|
{
|
||||||
|
return m_GlobalTransforms.size() * sizeof m_GlobalTransforms[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Nodes::Transform *
|
||||||
|
Nodes::GetGlobalTransformPtr() const
|
||||||
|
{
|
||||||
|
return m_GlobalTransforms.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Nodes::Update()
|
||||||
|
{
|
||||||
|
if (!m_Dirty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto transformIter = m_Transforms.begin();
|
||||||
|
auto globalTransformIter = m_GlobalTransforms.begin();
|
||||||
|
auto parentIter = m_Parents_.begin();
|
||||||
|
const auto parentEnd = m_Parents_.end();
|
||||||
|
|
||||||
|
while (parentIter != parentEnd)
|
||||||
|
{
|
||||||
|
const bool isRoot = *parentIter & ROOT_BIT;
|
||||||
|
const bool isDirty = *parentIter & DIRTY_BIT;
|
||||||
|
|
||||||
|
if (isRoot)
|
||||||
|
{
|
||||||
|
if (isDirty)
|
||||||
|
{
|
||||||
|
// Copy-update if the root is dirty.
|
||||||
|
*globalTransformIter = *transformIter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const u32 parentIdx = *parentIter & PARENT_MASK;
|
||||||
|
const bool isParentDirty = m_Parents_[parentIdx] & DIRTY_BIT;
|
||||||
|
if (isDirty || isParentDirty)
|
||||||
|
{
|
||||||
|
// Update w.r.t parent if either local or parent transforms updated.
|
||||||
|
*globalTransformIter = m_GlobalTransforms[parentIdx].m_GlobalTransforms * *transformIter;
|
||||||
|
m_Parents_[parentIdx] |= DIRTY_BIT; // Set dirty to propagate the update.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++parentIter;
|
||||||
|
++globalTransformIter;
|
||||||
|
++transformIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 &parentValue : m_Parents_)
|
||||||
|
{
|
||||||
|
parentValue &= ~DIRTY_BIT; // Unset dirty.
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Dirty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// =============================================
|
||||||
|
// Aster: nodes.h
|
||||||
|
// Copyright (c) 2020-2024 Anish Bhobe
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "global.h"
|
||||||
|
|
||||||
|
#include "EASTL/vector.h"
|
||||||
|
|
||||||
|
struct Nodes
|
||||||
|
{
|
||||||
|
struct Transform
|
||||||
|
{
|
||||||
|
mat4 m_GlobalTransforms;
|
||||||
|
mat4 m_NormalTransforms;
|
||||||
|
|
||||||
|
explicit Transform(const mat4 &transform)
|
||||||
|
: m_GlobalTransforms(transform)
|
||||||
|
, m_NormalTransforms(transpose(inverse(mat3{transform})))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform &
|
||||||
|
operator=(const mat4 &transform)
|
||||||
|
{
|
||||||
|
m_GlobalTransforms = transform;
|
||||||
|
m_NormalTransforms = transpose(inverse(mat3{transform}));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eastl::vector<mat4> m_Transforms;
|
||||||
|
eastl::vector<Transform> m_GlobalTransforms;
|
||||||
|
/// Parents are also used for bookkeeping
|
||||||
|
eastl::vector<u32> m_Parents_;
|
||||||
|
bool m_Dirty = true;
|
||||||
|
|
||||||
|
constexpr static u32 ROOT_BIT = 1u << 31;
|
||||||
|
constexpr static u32 DIRTY_BIT = 1u << 30;
|
||||||
|
constexpr static u32 PARENT_MASK = ~(ROOT_BIT | DIRTY_BIT);
|
||||||
|
|
||||||
|
u32 Add(const mat4 &transform, const i32 parent = -1);
|
||||||
|
[[nodiscard]] const mat4 &Get(const u32 index) const;
|
||||||
|
void Set(const u32 index, const mat4 &transform);
|
||||||
|
[[nodiscard]] u32 Count() const;
|
||||||
|
|
||||||
|
[[nodiscard]] const mat4 &operator[](const u32 index) const;
|
||||||
|
[[nodiscard]] mat4 &operator[](const u32 index);
|
||||||
|
|
||||||
|
[[nodiscard]] usize GetGlobalTransformByteSize() const;
|
||||||
|
[[nodiscard]] const Transform *GetGlobalTransformPtr() const;
|
||||||
|
|
||||||
|
bool Update();
|
||||||
|
};
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "render_resource_manager.h"
|
#include "gpu_resource_manager.h"
|
||||||
#include "swapchain.h"
|
#include "swapchain.h"
|
||||||
|
|
||||||
#include <EASTL/array.h>
|
#include <EASTL/array.h>
|
||||||
|
|
@ -58,7 +58,7 @@ CreatePipeline(const Device *device, const Swapchain *swapchain, const GpuResour
|
||||||
vk::PushConstantRange pushConstantRange = {
|
vk::PushConstantRange pushConstantRange = {
|
||||||
.stageFlags = vk::ShaderStageFlagBits::eAll,
|
.stageFlags = vk::ShaderStageFlagBits::eAll,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
.size = 24,
|
.size = 36,
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
|
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,75 @@
|
||||||
|
|
||||||
typedef float4 PositionData;
|
|
||||||
typedef float2 UVData;
|
|
||||||
typedef float4 NormalData;
|
|
||||||
typedef float4 ColorData;
|
|
||||||
|
|
||||||
struct VertexData
|
struct VertexData
|
||||||
{
|
{
|
||||||
float4 m_Normal;
|
float4 Normal;
|
||||||
float2 m_TexCoord0;
|
float2 TexCoord0;
|
||||||
float2 m_TexCoord1;
|
float2 TexCoord1;
|
||||||
float4 m_Color0;
|
float4 Color0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransformData
|
struct TransformData
|
||||||
{
|
{
|
||||||
float4x4 transform;
|
float4x4 Transform;
|
||||||
|
float4x4 NormalTransform;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MaterialData
|
struct MaterialData
|
||||||
{
|
{
|
||||||
float m_AlbedoFactor[4];
|
float AlbedoFactor[4];
|
||||||
float m_EmissionFactor[3];
|
float EmissionFactor[3];
|
||||||
float m_MetalFactor;
|
float MetalFactor;
|
||||||
float m_RoughFactor;
|
float RoughFactor;
|
||||||
uint m_AlbedoTex;
|
uint AlbedoTex;
|
||||||
uint m_NormalTex;
|
uint NormalTex;
|
||||||
uint m_MetalRoughTex;
|
uint MetalRoughTex;
|
||||||
uint m_OcclusionTex;
|
uint OcclusionTex;
|
||||||
uint m_EmissionTex;
|
uint EmissionTex;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Block
|
struct Block
|
||||||
{
|
{
|
||||||
uint vertexBufferHandle;
|
uint VertexBufferHandle;
|
||||||
uint vertexDataHandle;
|
uint VertexDataHandle;
|
||||||
uint materialBufferHandle;
|
uint MaterialBufferHandle;
|
||||||
uint nodeBufferHandle;
|
uint NodeBufferHandle;
|
||||||
int m_MaterialIdx;
|
uint LightHandle;
|
||||||
uint m_NodeIdx;
|
uint PointLightIndexer;
|
||||||
|
uint DirectionLightIndexer;
|
||||||
|
int MaterialIdx;
|
||||||
|
uint NodeIdx;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Camera
|
struct Light
|
||||||
{
|
{
|
||||||
float4x4 model;
|
float Position[3];
|
||||||
float4x4 view;
|
float Range;
|
||||||
float4x4 proj;
|
uint Color;
|
||||||
|
float Intensity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CameraData
|
||||||
|
{
|
||||||
|
float4x4 View;
|
||||||
|
float4x4 Projection;
|
||||||
|
float4 Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Little Endian storage. First short is least significant.
|
||||||
|
#define IndexerCount(Indexer) (Indexer & 0xFFFF)
|
||||||
|
#define IndexerOffset(Indexer) ((Indexer & 0xFFFF0000) >> 16);
|
||||||
|
|
||||||
#define INVALID_HANDLE 0xFFFFFFFF
|
#define INVALID_HANDLE 0xFFFFFFFF
|
||||||
|
|
||||||
[[vk::binding(0, 0)]] StructuredBuffer<PositionData> vertexBuffer[];
|
[[vk::binding(0, 0)]] StructuredBuffer<float4> VertexBuffer[];
|
||||||
[[vk::binding(0, 0)]] StructuredBuffer<VertexData> vertexDataBuffer[];
|
[[vk::binding(0, 0)]] StructuredBuffer<VertexData> VertexDataBuffer[];
|
||||||
[[vk::binding(0, 0)]] StructuredBuffer<MaterialData> materialsBuffer[];
|
[[vk::binding(0, 0)]] StructuredBuffer<MaterialData> MaterialsBuffer[];
|
||||||
[[vk::binding(0, 0)]] StructuredBuffer<TransformData> nodeBuffer[];
|
[[vk::binding(0, 0)]] StructuredBuffer<TransformData> NodeBuffer[];
|
||||||
|
[[vk::binding(0, 0)]] StructuredBuffer<Light> LightBuffer[];
|
||||||
|
|
||||||
[[vk::binding(1, 0)]] Texture2D<float4> textures[];
|
[[vk::binding(1, 0)]] Texture2D<float4> Textures[];
|
||||||
[[vk::binding(1, 0)]] SamplerState immutableSamplers[];
|
[[vk::binding(1, 0)]] SamplerState ImmutableSamplers[];
|
||||||
|
|
||||||
[[vk::binding(0, 1)]] ConstantBuffer<Camera> camera;
|
[[vk::binding(0, 1)]] ConstantBuffer<CameraData> Camera;
|
||||||
|
|
||||||
[[vk::push_constant]]
|
[[vk::push_constant]]
|
||||||
Block pcb;
|
Block PushConstant;
|
||||||
|
|
@ -2,37 +2,99 @@
|
||||||
|
|
||||||
struct FS_Input
|
struct FS_Input
|
||||||
{
|
{
|
||||||
float4 inPosition : POSITION;
|
float4 InPosition : POSITION;
|
||||||
float4 inColor : COLOR0;
|
float4 InNormal : NORMAL;
|
||||||
float2 inUV : TEXCOORD0;
|
float4 InColor : COLOR0;
|
||||||
|
float2 InUV0 : TEXCOORD0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FS_Output
|
struct FS_Output
|
||||||
{
|
{
|
||||||
float4 outColor : SV_Target0;
|
float4 ColorTarget : SV_Target0;
|
||||||
};
|
};
|
||||||
|
|
||||||
float4 ArrayToVector(float arr[4])
|
float4 GetAlbedo(int MaterialIdx, float2 UV)
|
||||||
{
|
{
|
||||||
return float4(arr[0], arr[1], arr[2], arr[3]);
|
uint albedoTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].AlbedoTex;
|
||||||
}
|
|
||||||
|
|
||||||
float4 GetAlbedo(uint materialBufferId, int materialId, float2 uv)
|
|
||||||
{
|
|
||||||
uint albedoTexId = materialsBuffer[materialBufferId][materialId].m_AlbedoTex;
|
|
||||||
if (albedoTexId == INVALID_HANDLE)
|
if (albedoTexId == INVALID_HANDLE)
|
||||||
{
|
{
|
||||||
return ArrayToVector(materialsBuffer[materialBufferId][materialId].m_AlbedoFactor);
|
return (float4) MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].AlbedoFactor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return textures[albedoTexId].Sample(immutableSamplers[albedoTexId], uv);
|
return Textures[albedoTexId].Sample(ImmutableSamplers[albedoTexId], UV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FS_Output main(FS_Input stage_input)
|
float3 GetDirectionalLightInfluence(float3 Normal)
|
||||||
{
|
{
|
||||||
FS_Output output;
|
if (PushConstant.LightHandle == INVALID_HANDLE)
|
||||||
output.outColor = pcb.m_MaterialIdx < 0 ? stage_input.inColor : GetAlbedo(pcb.materialBufferHandle, pcb.m_MaterialIdx, stage_input.inUV);
|
return float3(0.0f, 0.0f, 0.0f);
|
||||||
return output;
|
|
||||||
|
uint Count = IndexerCount(PushConstant.DirectionLightIndexer);
|
||||||
|
|
||||||
|
float3 Contrib = 0.0f;
|
||||||
|
for (uint i = 0; i < Count; ++i)
|
||||||
|
{
|
||||||
|
Light Light = LightBuffer[PushConstant.LightHandle][i];
|
||||||
|
float3 LightDir = - (float3) Light.Position; // Position is actually direction for directionalLight; LightDir is Direction towards the light (-direction)
|
||||||
|
float DiffuseFactor = max(dot(Normal, LightDir), 0.0f);
|
||||||
|
|
||||||
|
float R = (Light.Color & 0xFF000000) >> 24;
|
||||||
|
float G = (Light.Color & 0x00FF0000) >> 16;
|
||||||
|
float B = (Light.Color & 0x0000FF00) >> 8;
|
||||||
|
|
||||||
|
float3 Color = float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255
|
||||||
|
|
||||||
|
float3 Diffuse = DiffuseFactor * Color;
|
||||||
|
|
||||||
|
Contrib += (Light.Range < 0 ? float3(0.0f, 0.0f, 0.0f) : Diffuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Contrib;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 GetPointLightInfluence(float3 Position, float3 Normal)
|
||||||
|
{
|
||||||
|
if (PushConstant.LightHandle == INVALID_HANDLE)
|
||||||
|
return float3(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
uint Offset = IndexerOffset(PushConstant.PointLightIndexer);
|
||||||
|
uint Count = IndexerCount(PushConstant.PointLightIndexer);
|
||||||
|
|
||||||
|
float3 Contrib = 0.0f;
|
||||||
|
for (uint i = 0; i < Count; ++i)
|
||||||
|
{
|
||||||
|
Light Light = LightBuffer[PushConstant.LightHandle][i + Offset];
|
||||||
|
float3 LightDir = normalize(((float3)Light.Position) - Position);
|
||||||
|
float DiffuseFactor = max(dot(Normal, LightDir), 0.0f);
|
||||||
|
|
||||||
|
float R = (Light.Color & 0xFF000000) >> 24;
|
||||||
|
float G = (Light.Color & 0x00FF0000) >> 16;
|
||||||
|
float B = (Light.Color & 0x0000FF00) >> 8;
|
||||||
|
|
||||||
|
float3 Color = float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255
|
||||||
|
|
||||||
|
float3 Diffuse = DiffuseFactor * Color;
|
||||||
|
|
||||||
|
Contrib += (Light.Range < 0 ? float3(0.0f, 0.0f, 0.0f) : Diffuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Contrib;
|
||||||
|
}
|
||||||
|
|
||||||
|
FS_Output main(FS_Input StageInput)
|
||||||
|
{
|
||||||
|
float3 Ambient = float3(0.02f, 0.02f, 0.02f);
|
||||||
|
|
||||||
|
float3 Normal = normalize(StageInput.InNormal.xyz);
|
||||||
|
float3 Position = StageInput.InPosition.xyz;
|
||||||
|
|
||||||
|
float4 ObjColor = PushConstant.MaterialIdx < 0 ? StageInput.InColor : GetAlbedo(PushConstant.MaterialIdx, StageInput.InUV0);
|
||||||
|
|
||||||
|
float3 Diffuse = GetDirectionalLightInfluence(Normal) + GetPointLightInfluence(Position, Normal);
|
||||||
|
|
||||||
|
FS_Output Output;
|
||||||
|
Output.ColorTarget = float4(ObjColor.rgb * (Diffuse + Ambient), ObjColor.a);
|
||||||
|
return Output;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,46 +2,66 @@
|
||||||
|
|
||||||
struct VS_Input
|
struct VS_Input
|
||||||
{
|
{
|
||||||
uint vertexIndex : SV_VertexID;
|
uint VertexIndex : SV_VertexID;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VS_Output
|
struct VS_Output
|
||||||
{
|
{
|
||||||
float4 outPosition : POSITION;
|
float4 WorldPosition : POSITION;
|
||||||
float4 outColor : COLOR0;
|
float4 WorldNormal : NORMAL;
|
||||||
float2 outUV : TEXCOORD0;
|
float4 VertexColor : COLOR0;
|
||||||
float4 position : SV_Position;
|
float2 UV0 : TEXCOORD0;
|
||||||
|
float4 VertexPosition : SV_Position;
|
||||||
};
|
};
|
||||||
|
|
||||||
float2 GetUV(uint bufferId, uint vertexIdx)
|
float2 GetUV(uint VertexIdx)
|
||||||
{
|
{
|
||||||
return (bufferId == INVALID_HANDLE) ? 0.0f.xx : vertexDataBuffer[bufferId][vertexIdx].m_TexCoord0.xy;
|
uint BufferId = PushConstant.VertexDataHandle;
|
||||||
|
return VertexDataBuffer[BufferId][VertexIdx].TexCoord0.xy;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 GetPosition(uint bufferId, uint vertexIdx)
|
float4 GetPosition(uint VertexIdx)
|
||||||
{
|
{
|
||||||
return float4(vertexBuffer[bufferId][vertexIdx].xyz, 1.0f);
|
uint BufferId = PushConstant.VertexBufferHandle;
|
||||||
|
return float4(VertexBuffer[BufferId][VertexIdx].xyz, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 GetColor(uint bufferId, uint vertexIdx)
|
float4 GetNormal(uint VertexIdx)
|
||||||
{
|
{
|
||||||
return (bufferId == INVALID_HANDLE) ? float4(1.0f, 0.0f, 1.0f, 1.0f) : vertexDataBuffer[bufferId][vertexIdx].m_Color0;
|
uint BufferId = PushConstant.VertexDataHandle;
|
||||||
|
return VertexDataBuffer[BufferId][VertexIdx].Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4x4 GetModel(uint bufferId, uint index)
|
float4 GetColor(uint VertexIdx)
|
||||||
{
|
{
|
||||||
return nodeBuffer[bufferId][index].transform;
|
uint BufferId = PushConstant.VertexDataHandle;
|
||||||
|
return VertexDataBuffer[BufferId][VertexIdx].Color0;
|
||||||
}
|
}
|
||||||
|
|
||||||
VS_Output main(VS_Input stage_input)
|
float4x4 GetNodeTransform(uint NodeIndex)
|
||||||
{
|
{
|
||||||
VS_Output output;
|
uint BufferId = PushConstant.NodeBufferHandle;
|
||||||
output.outPosition = GetPosition(pcb.vertexBufferHandle, stage_input.vertexIndex);
|
return NodeBuffer[BufferId][NodeIndex].Transform;
|
||||||
output.outUV = GetUV(pcb.vertexDataHandle, stage_input.vertexIndex);
|
}
|
||||||
output.outColor = GetColor(pcb.vertexDataHandle, stage_input.vertexIndex);
|
|
||||||
|
float4x4 GetNormalTransform(uint NodeIndex)
|
||||||
|
{
|
||||||
|
uint BufferId = PushConstant.NodeBufferHandle;
|
||||||
|
return NodeBuffer[BufferId][NodeIndex].NormalTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
VS_Output main(VS_Input StageInput)
|
||||||
|
{
|
||||||
|
VS_Output Output;
|
||||||
|
|
||||||
float4 globalPosition = mul(camera.model, mul(GetModel(pcb.nodeBufferHandle, pcb.m_NodeIdx), GetPosition(pcb.vertexBufferHandle, stage_input.vertexIndex)));
|
float4 GlobalPosition = mul(GetNodeTransform(PushConstant.NodeIdx), GetPosition(StageInput.VertexIndex));
|
||||||
float4 clipSpace = mul(camera.view, globalPosition);
|
float4 ClipSpace = mul(Camera.View, GlobalPosition);
|
||||||
output.position = mul(camera.proj, clipSpace);
|
|
||||||
return output;
|
Output.VertexPosition = mul(Camera.Projection, ClipSpace);
|
||||||
|
Output.WorldPosition = GlobalPosition;
|
||||||
|
Output.UV0 = GetUV(StageInput.VertexIndex);
|
||||||
|
Output.VertexColor = GetColor(StageInput.VertexIndex);
|
||||||
|
|
||||||
|
Output.WorldNormal = mul(GetNormalTransform(PushConstant.NodeIdx), GetNormal(StageInput.VertexIndex));
|
||||||
|
return Output;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue