mirror of
https://github.com/antopilo/Nuake.git
synced 2026-09-09 20:08:57 +03:00
Push previous work
This commit is contained in:
@@ -52,8 +52,8 @@ VkPipeline PipelineBuilder::BuildPipeline(VkDevice device)
|
||||
|
||||
if (ColorBlendAttachment.size() == 0)
|
||||
{
|
||||
Logger::Log("Forgot blend attachment!", "vulkan", CRITICAL);
|
||||
assert(false && "Error");
|
||||
Logger::Log("Forgot blend attachment?", "vulkan", CRITICAL);
|
||||
//assert(false && "Error");
|
||||
}
|
||||
|
||||
// completely clear VertexInputStateCreateInfo, as we have no need for it
|
||||
|
||||
@@ -18,6 +18,18 @@
|
||||
|
||||
namespace Nuake
|
||||
{
|
||||
struct CameraView
|
||||
{
|
||||
Matrix4 View;
|
||||
Matrix4 Projection;
|
||||
Matrix4 ViewProjection;
|
||||
Matrix4 InverseView;
|
||||
Matrix4 InverseProjection;
|
||||
Vector3 Position;
|
||||
float Near;
|
||||
float Far;
|
||||
};
|
||||
|
||||
struct LightResource
|
||||
{
|
||||
|
||||
@@ -31,6 +43,7 @@ namespace Nuake
|
||||
std::map<UUID, Ref<VkMesh>> Meshes;
|
||||
std::map<UUID, Ref<VulkanImage>> Images;
|
||||
std::map<UUID, Ref<VulkanImage>> Light;
|
||||
std::vector<CameraView> Cameras;
|
||||
|
||||
// Bindless buffer layouts
|
||||
VkDescriptorSetLayout CameraDescriptorLayout;
|
||||
@@ -41,14 +54,17 @@ namespace Nuake
|
||||
VkDescriptorSetLayout MaterialDescriptorLayout;
|
||||
VkDescriptorSetLayout TexturesDescriptorLayout;
|
||||
VkDescriptorSetLayout LightsDescriptorLayout;
|
||||
VkDescriptorSetLayout CamerasDescriptorLayout;
|
||||
|
||||
VkDescriptorSet CameraDescriptor;
|
||||
VkDescriptorSet ModelDescriptor;
|
||||
VkDescriptorSet SamplerDescriptor;
|
||||
VkDescriptorSet MaterialDescriptor;
|
||||
VkDescriptorSet LightsDescriptor;
|
||||
VkDescriptorSet CamerasDescriptor;
|
||||
|
||||
std::map<UUID, uint32_t> BindlessTextureMapping;
|
||||
std::map<UUID, uint32_t> CameraMapping;
|
||||
|
||||
public:
|
||||
VkDescriptorSet TextureDescriptor;
|
||||
@@ -77,11 +93,16 @@ namespace Nuake
|
||||
Ref<VulkanImage> GetTexture(const UUID& id);
|
||||
std::vector<Ref<VulkanImage>> GetAllTextures();
|
||||
|
||||
void AddCamera(const UUID& id, const CameraView& camera);
|
||||
CameraView GetCamera(const UUID& id);
|
||||
std::vector<CameraView> GetAllCameras();
|
||||
void ClearCameras();
|
||||
|
||||
std::vector<VkDescriptorSetLayout> GetBindlessLayout();
|
||||
|
||||
uint32_t GetBindlessTextureID(const UUID& id);
|
||||
void RecreateBindlessTextures();
|
||||
|
||||
void RecreateBindlessCameras();
|
||||
private:
|
||||
void CreateBindlessLayout();
|
||||
};
|
||||
|
||||
@@ -130,6 +130,7 @@ namespace Nuake
|
||||
constexpr uint32_t MAX_MODEL_MATRIX = 3000;
|
||||
constexpr uint32_t MAX_MATERIAL = 1000;
|
||||
constexpr uint32_t MAX_TEXTURES = 500;
|
||||
constexpr uint32_t MAX_CAMERAS = 100;
|
||||
constexpr uint32_t MAX_LIGHTS = 100;
|
||||
class VkRenderer
|
||||
{
|
||||
|
||||
@@ -119,6 +119,34 @@ std::vector<Ref<VulkanImage>> GPUResources::GetAllTextures()
|
||||
return allImages;
|
||||
}
|
||||
|
||||
void GPUResources::AddCamera(const UUID& id, const CameraView& camera)
|
||||
{
|
||||
Cameras.push_back(camera);
|
||||
CameraMapping[id] = Cameras.size() - 1;
|
||||
}
|
||||
|
||||
CameraView GPUResources::GetCamera(const UUID& id)
|
||||
{
|
||||
if (CameraMapping.find(id) != CameraMapping.end())
|
||||
{
|
||||
return Cameras[CameraMapping[id]];
|
||||
}
|
||||
|
||||
Logger::Log("Camera with ID does not exist", "vulkan", CRITICAL);
|
||||
return Cameras[0];
|
||||
}
|
||||
|
||||
std::vector<CameraView> GPUResources::GetAllCameras()
|
||||
{
|
||||
return Cameras;
|
||||
}
|
||||
|
||||
void GPUResources::ClearCameras()
|
||||
{
|
||||
Cameras.clear();
|
||||
CameraMapping.clear();
|
||||
}
|
||||
|
||||
void GPUResources::CreateBindlessLayout()
|
||||
{
|
||||
auto& vk = VkRenderer::Get();
|
||||
@@ -179,7 +207,18 @@ void GPUResources::CreateBindlessLayout()
|
||||
LightsDescriptorLayout = builder.Build(device, VK_SHADER_STAGE_ALL_GRAPHICS);
|
||||
}
|
||||
|
||||
TextureDescriptor = VkRenderer::Get().GetDescriptorAllocator().Allocate(VkRenderer::Get().GetDevice(), TexturesDescriptorLayout);
|
||||
// bindless cameras
|
||||
{
|
||||
DescriptorLayoutBuilder builder;
|
||||
builder.AddBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, MAX_CAMERAS);
|
||||
CamerasDescriptorLayout = builder.Build(device, VK_SHADER_STAGE_ALL_GRAPHICS);
|
||||
}
|
||||
|
||||
auto& vk = VkRenderer::Get();
|
||||
auto device = vk.GetDevice();
|
||||
auto allocator = vk.GetDescriptorAllocator();
|
||||
TextureDescriptor = allocator.Allocate(device, TexturesDescriptorLayout);
|
||||
CamerasDescriptor = allocator.Allocate(device, CamerasDescriptorLayout);
|
||||
}
|
||||
|
||||
void GPUResources::RecreateBindlessTextures()
|
||||
@@ -210,6 +249,15 @@ void GPUResources::RecreateBindlessTextures()
|
||||
vkUpdateDescriptorSets(VkRenderer::Get().GetDevice(), 1, &write, 0, nullptr);
|
||||
}
|
||||
|
||||
void GPUResources::RecreateBindlessCameras()
|
||||
{
|
||||
if (!CamerasDescriptor)
|
||||
{
|
||||
CreateBindlessLayout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayout> GPUResources::GetBindlessLayout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayout> layouts = {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "Pipeline/RenderPipeline.h"
|
||||
|
||||
#include "src/Rendering/Vulkan/DescriptorLayoutBuilder.h"
|
||||
#include <src\Scene\Components\CameraComponent.h>
|
||||
|
||||
using namespace Nuake;
|
||||
|
||||
@@ -82,10 +83,29 @@ void VkSceneRenderer::BeginScene(RenderContext inContext)
|
||||
throw std::runtime_error("Draw image is not initialized");
|
||||
}
|
||||
|
||||
// Build camera view list
|
||||
auto view = scene->m_Registry.view<TransformComponent, CameraComponent>();
|
||||
for (auto e : view)
|
||||
{
|
||||
auto [transform, camera] = view.get<TransformComponent, CameraComponent>(e);
|
||||
CameraView camData{};
|
||||
camData.View = camera.CameraInstance->GetTransform();
|
||||
camData.Projection = camera.CameraInstance->GetPerspective();
|
||||
camData.InverseView = glm::inverse(camData.Projection);
|
||||
camData.InverseProjection = glm::inverse(camData.Projection);
|
||||
camData.Position = transform.GetGlobalTransform()[3];
|
||||
camData.Near = camera.CameraInstance->Near;
|
||||
camData.Far = camera.CameraInstance->Far;
|
||||
GPUResources::Get().AddCamera(camera.ID, camData);
|
||||
}
|
||||
|
||||
// Execute light
|
||||
PassRenderContext passCtx = { };
|
||||
passCtx.scene = inContext.CurrentScene;
|
||||
passCtx.commandBuffer = inContext.CommandBuffer;
|
||||
passCtx.resolution = Context.Size;
|
||||
|
||||
ShadowPipeline.Execute(passCtx);
|
||||
GBufferPipeline.Execute(passCtx);
|
||||
}
|
||||
ModelPushConstant modelPushConstant{};
|
||||
@@ -262,61 +282,118 @@ void VkSceneRenderer::CreateDescriptors()
|
||||
|
||||
void VkSceneRenderer::CreatePipelines()
|
||||
{
|
||||
//ShadowPipeline = RenderPipeline();
|
||||
//auto& shadowPass = ShadowPipeline.AddPass("Shadow");
|
||||
//shadowPass.AddAttachment("Depth", ImageFormat::D32F, ImageUsage::Depth);
|
||||
//shadowPass.SetShaders(Shaders["shading_vert"], Shaders["shading_frag"]);
|
||||
//shadowPass.SetPreRender([&](PassRenderContext& ctx) {
|
||||
// std::vector<VkDescriptorSet> descriptors2 = { CameraBufferDescriptors, ModelBufferDescriptor };
|
||||
// vkCmdBindDescriptorSets(
|
||||
// ctx.commandBuffer,
|
||||
// VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
// ctx.renderPass->PipelineLayout,
|
||||
// 0, // firstSet
|
||||
// 2, // descriptorSetCount
|
||||
// descriptors2.data(), // pointer to the descriptor set(s)
|
||||
// 0, // dynamicOffsetCount
|
||||
// nullptr // dynamicOffsets
|
||||
// );
|
||||
//
|
||||
// // Bind material
|
||||
// vkCmdBindDescriptorSets(
|
||||
// ctx.commandBuffer,
|
||||
// VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
// ctx.renderPass->PipelineLayout,
|
||||
// 4, // firstSet
|
||||
// 1, // descriptorSetCount
|
||||
// &MaterialBufferDescriptor, // pointer to the descriptor set(s)
|
||||
// 0, // dynamicOffsetCount
|
||||
// nullptr // dynamicOffsets
|
||||
// );
|
||||
//
|
||||
// vkCmdBindDescriptorSets(
|
||||
// ctx.commandBuffer,
|
||||
// VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
// ctx.renderPass->PipelineLayout,
|
||||
// 5, // firstSet
|
||||
// 1, // descriptorSetCount
|
||||
// &GPUResources::Get().TextureDescriptor, // pointer to the descriptor set(s)
|
||||
// 0, // dynamicOffsetCount
|
||||
// nullptr // dynamicOffsets
|
||||
// );
|
||||
//
|
||||
// vkCmdBindDescriptorSets(
|
||||
// ctx.commandBuffer,
|
||||
// VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
// ctx.renderPass->PipelineLayout,
|
||||
// 6, // firstSet
|
||||
// 1, // descriptorSetCount
|
||||
// &LightBufferDescriptor, // pointer to the descriptor set(s)
|
||||
// 0, // dynamicOffsetCount
|
||||
// nullptr // dynamicOffsets
|
||||
// );
|
||||
//
|
||||
// vkCmdBindDescriptorSets(ctx.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx.renderPass->PipelineLayout, 3, 1, //&SamplerDescriptor, 0, nullptr);
|
||||
//});
|
||||
//shadowPass.SetRender([&](PassRenderContext& ctx) {});
|
||||
ShadowPipeline = RenderPipeline();
|
||||
auto& shadowPass = ShadowPipeline.AddPass("Shadow");
|
||||
shadowPass.AddAttachment("Depth", ImageFormat::D32F, ImageUsage::Depth);
|
||||
shadowPass.SetShaders(Shaders["shadow_vert"], Shaders["shadow_frag"]);
|
||||
shadowPass.SetPushConstant<ModelPushConstant>(modelPushConstant);
|
||||
shadowPass.SetPreRender([&](PassRenderContext& ctx) {
|
||||
std::vector<VkDescriptorSet> descriptors2 = { CameraBufferDescriptors, ModelBufferDescriptor };
|
||||
vkCmdBindDescriptorSets(
|
||||
ctx.commandBuffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
0, // firstSet
|
||||
2, // descriptorSetCount
|
||||
descriptors2.data(), // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
// Bind material
|
||||
vkCmdBindDescriptorSets(
|
||||
ctx.commandBuffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
4, // firstSet
|
||||
1, // descriptorSetCount
|
||||
&MaterialBufferDescriptor, // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
vkCmdBindDescriptorSets(
|
||||
ctx.commandBuffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
5, // firstSet
|
||||
1, // descriptorSetCount
|
||||
&GPUResources::Get().TextureDescriptor, // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
vkCmdBindDescriptorSets(
|
||||
ctx.commandBuffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
6, // firstSet
|
||||
1, // descriptorSetCount
|
||||
&LightBufferDescriptor, // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
vkCmdBindDescriptorSets(ctx.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx.renderPass->PipelineLayout, 3, 1, &SamplerDescriptor, 0, nullptr);
|
||||
});
|
||||
shadowPass.SetRender([&](PassRenderContext& ctx) {
|
||||
auto& cmd = ctx.commandBuffer;
|
||||
auto& scene = ctx.scene;
|
||||
auto& vk = VkRenderer::Get();
|
||||
|
||||
// Draw the scene
|
||||
{
|
||||
ZoneScopedN("Render Models");
|
||||
auto view = scene->m_Registry.view<TransformComponent, ModelComponent, VisibilityComponent>();
|
||||
for (auto e : view)
|
||||
{
|
||||
auto [transform, mesh, visibility] = view.get<TransformComponent, ModelComponent, VisibilityComponent>(e);
|
||||
if (!mesh.ModelResource || !visibility.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Entity entity = Entity((entt::entity)e, scene.get());
|
||||
for (auto& m : mesh.ModelResource->GetMeshes())
|
||||
{
|
||||
Ref<VkMesh> vkMesh = m->GetVkMesh();
|
||||
Matrix4 globalTransform = transform.GetGlobalTransform();
|
||||
|
||||
auto descSet = vkMesh->GetDescriptorSet();
|
||||
vkCmdBindDescriptorSets(
|
||||
cmd,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
2, // firstSet
|
||||
1, // descriptorSetCount
|
||||
&descSet, // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
// Bind texture descriptor set
|
||||
Ref<Material> material = m->GetMaterial();
|
||||
Ref<VulkanImage> albedo = GPUResources::Get().GetTexture(material->AlbedoImage);
|
||||
|
||||
modelPushConstant.Index = ModelMatrixMapping[entity.GetID()];
|
||||
modelPushConstant.MaterialIndex = MeshMaterialMapping[vkMesh->GetID()];
|
||||
|
||||
vkCmdPushConstants(
|
||||
cmd,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
VK_SHADER_STAGE_ALL_GRAPHICS, // Stage matching the pipeline layout
|
||||
0, // Offset
|
||||
sizeof(ModelPushConstant), // Size of the push constant
|
||||
&modelPushConstant // Pointer to the value
|
||||
);
|
||||
|
||||
vkCmdBindIndexBuffer(cmd, vkMesh->GetIndexBuffer()->GetBuffer(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdDrawIndexed(cmd, vkMesh->GetIndexBuffer()->GetSize() / sizeof(uint32_t), 1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ShadowPipeline.Build();
|
||||
|
||||
GBufferPipeline = RenderPipeline();
|
||||
auto& gBufferPass = GBufferPipeline.AddPass("GBuffer");
|
||||
@@ -332,11 +409,11 @@ void VkSceneRenderer::CreatePipelines()
|
||||
ctx.commandBuffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
ctx.renderPass->PipelineLayout,
|
||||
0, // firstSet
|
||||
2, // descriptorSetCount
|
||||
descriptors2.data(), // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
0, // firstSet
|
||||
2, // descriptorSetCount
|
||||
descriptors2.data(), // pointer to the descriptor set(s)
|
||||
0, // dynamicOffsetCount
|
||||
nullptr // dynamicOffsets
|
||||
);
|
||||
|
||||
// Bind material
|
||||
@@ -433,7 +510,6 @@ void VkSceneRenderer::CreatePipelines()
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
auto& shadingPass = GBufferPipeline.AddPass("Shading");
|
||||
shadingPass.SetShaders(Shaders["shading_vert"], Shaders["shading_frag"]);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "src/Core/Core.h"
|
||||
#include "src/Resource/Serializable.h"
|
||||
#include "src/Rendering/Camera.h"
|
||||
#include "src/Resource/UUID.h"
|
||||
|
||||
namespace Nuake
|
||||
{
|
||||
@@ -13,8 +14,8 @@ namespace Nuake
|
||||
class CameraComponent : public Component
|
||||
{
|
||||
NUAKECOMPONENT(CameraComponent, "Camera")
|
||||
|
||||
public:
|
||||
UUID ID;
|
||||
Ref<Camera> CameraInstance;
|
||||
TransformComponent* transformComponent;
|
||||
|
||||
|
||||
@@ -54,16 +54,10 @@ StructuredBuffer<Light> lights;
|
||||
|
||||
struct PSInput {
|
||||
float4 Position : SV_Position;
|
||||
float3 Color : TEXCOORD0;
|
||||
float2 UV : TEXCOORD1;
|
||||
float3 Normal : TEXCOORD2;
|
||||
float3x3 TBN : TEXCOORD3;
|
||||
};
|
||||
|
||||
struct PSOutput {
|
||||
float4 oColor0 : SV_TARGET;
|
||||
float4 oNormal : SV_TARGET1;
|
||||
float4 oMaterial : SV_TARGET2;
|
||||
};
|
||||
|
||||
struct ModelPushConstant
|
||||
@@ -78,59 +72,5 @@ ModelPushConstant pushConstants;
|
||||
PSOutput main(PSInput input)
|
||||
{
|
||||
PSOutput output;
|
||||
|
||||
Material inMaterial = material[pushConstants.materialIndex];
|
||||
// NORMAL
|
||||
// TODO use TBN matrix
|
||||
float3 normal = float3(0.5, 0.5, 1.0);
|
||||
if(inMaterial.hasNormal == 1)
|
||||
{
|
||||
// Sample from texture.
|
||||
}
|
||||
|
||||
normal = mul(input.TBN, normal);
|
||||
normal = input.Normal / 2.0f + 0.5f;
|
||||
output.oNormal = float4(normal, 1.0f);
|
||||
|
||||
// MATERIAL
|
||||
|
||||
// ALBEDO COLOR
|
||||
float4 albedoColor = float4(inMaterial.albedo.xyz, 1.0f);
|
||||
if(inMaterial.hasAlbedo == 1)
|
||||
{
|
||||
float4 albedoSample = textures[inMaterial.albedoTextureId].Sample(mySampler, input.UV);
|
||||
|
||||
// Alpha cutout?
|
||||
if(albedoSample.a < 0.001f)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
albedoColor.xyz = albedoSample.xyz;
|
||||
}
|
||||
output.oColor0 = albedoColor;
|
||||
|
||||
// MATERIAL PROPERTIES
|
||||
float metalnessValue = inMaterial.metalnessValue;
|
||||
if(inMaterial.hasMetalness == 1)
|
||||
{
|
||||
// TODO: Sample from metal texture
|
||||
}
|
||||
|
||||
float aoValue = inMaterial.aoValue;
|
||||
if(inMaterial.hasAO == 1)
|
||||
{
|
||||
// TODO: Sample from AO texture
|
||||
}
|
||||
|
||||
float roughnessValue = inMaterial.roughnessValue;
|
||||
if(inMaterial.hasRoughness == 1)
|
||||
{
|
||||
// TODO: Sample from roughness texture
|
||||
}
|
||||
|
||||
float3 materialOuput = float3(inMaterial.metalnessValue, inMaterial.aoValue, inMaterial.roughnessValue);
|
||||
|
||||
output.oMaterial = float4(materialOuput, 1.0f);
|
||||
return output;
|
||||
}
|
||||
@@ -57,10 +57,6 @@ ModelPushConstant pushConstants;
|
||||
// Outputs
|
||||
struct VSOutput {
|
||||
float4 Position : SV_Position;
|
||||
float3 Color : TEXCOORD0;
|
||||
float2 UV : TEXCOORD1;
|
||||
float3 Normal : TEXCOORD2;
|
||||
float3x3 TBN : TEXCOORD3;
|
||||
};
|
||||
|
||||
// Main vertex shader
|
||||
@@ -69,7 +65,6 @@ VSOutput main(uint vertexIndex : SV_VertexID)
|
||||
VSOutput output;
|
||||
|
||||
Camera camData = camera[0];
|
||||
|
||||
ModelData modelData = model[pushConstants.modelIndex];
|
||||
|
||||
// Load vertex data from the buffer
|
||||
@@ -77,13 +72,6 @@ VSOutput main(uint vertexIndex : SV_VertexID)
|
||||
|
||||
// Output the position of each vertex
|
||||
output.Position = mul(camData.proj, mul(camData.view, mul(modelData.model, float4(v.position, 1.0f))));
|
||||
output.Color = normalize(float3(v.position.xyz));
|
||||
output.UV = float2(v.uv_x, v.uv_y);
|
||||
output.Normal = normalize(v.normal);
|
||||
|
||||
float3 T = normalize(mul((float3x3)modelData.model, normalize(v.tangent.xyz)));
|
||||
float3 B = normalize(mul((float3x3)modelData.model, normalize(v.bitangent.xyz)));
|
||||
float3 N = normalize(mul((float3x3)modelData.model, normalize(v.normal)).xyz);
|
||||
output.TBN = transpose(float3x3(T, B, N));
|
||||
return output;
|
||||
}
|
||||
Reference in New Issue
Block a user