add support for skeletal animation

This commit is contained in:
2025-11-28 00:01:42 +08:00
parent 56d01b9c34
commit 018a4cb2c6
32 changed files with 2300 additions and 287 deletions

View File

@ -7,21 +7,100 @@
#include "Prism/Renderer/Renderer.h"
#include "Prism/Renderer/Shader.h"
static void ImGuiShowHelpMarker(const char* desc)
{
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
namespace {
enum class PropertyFlag
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
None = 0, ColorProperty = 1
};
void Property(const std::string& name, bool& value)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
ImGui::Checkbox(id.c_str(), &value);
ImGui::PopItemWidth();
ImGui::NextColumn();
}
void Property(const std::string& name, float& value, float min = -1.0f, float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
ImGui::SliderFloat(id.c_str(), &value, min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
}
void Property(const std::string& name, glm::vec3& value, float min = -1.0f, float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
if ((int)flags & (int)PropertyFlag::ColorProperty)
ImGui::ColorEdit3(id.c_str(), glm::value_ptr(value), ImGuiColorEditFlags_NoInputs);
else
ImGui::SliderFloat3(id.c_str(), glm::value_ptr(value), min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
}
void Property(const std::string& name, glm::vec3& value, PropertyFlag flags)
{
Property(name, value, -1.0f, 1.0f, flags);
}
void Property(const std::string& name, glm::vec4& value, float min = -1.0f, float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
if ((int)flags & (int)PropertyFlag::ColorProperty)
ImGui::ColorEdit4(id.c_str(), glm::value_ptr(value), ImGuiColorEditFlags_NoInputs);
else
ImGui::SliderFloat4(id.c_str(), glm::value_ptr(value), min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
}
void Property(const std::string& name, glm::vec4& value, PropertyFlag flags)
{
Property(name, value, -1.0f, 1.0f, flags);
}
static void ImGuiShowHelpMarker(const char* desc)
{
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
}
DemoLayer::DemoLayer()
: m_ClearColor{ 0.1f, 0.1f, 0.1f, 1.0f }, m_Scene(Scene::Spheres),
: m_ClearColor{ 0.1f, 0.1f, 0.1f, 1.0f }, m_Scene(Scene::Model),
m_Camera(glm::perspectiveFov(glm::radians(45.0f), 1280.0f, 720.0f, 0.1f, 10000.0f))
{
}
@ -35,8 +114,10 @@ void DemoLayer::OnAttach()
m_SimplePBRShader.reset(Prism::Shader::Create("assets/shaders/simplepbr.glsl"));
m_QuadShader.reset(Prism::Shader::Create("assets/shaders/quad.glsl"));
m_HDRShader.reset(Prism::Shader::Create("assets/shaders/hdr.glsl"));
m_Mesh.reset(new Prism::Mesh("assets/meshes/cerberus.fbx"));
m_SphereMesh.reset(new Prism::Mesh("assets/models/Sphere.fbx"));
m_GridShader.reset(Prism::Shader::Create("assets/shaders/Grid.glsl"));
m_Mesh.reset(new Prism::Mesh("assets/models/m1911/m1911.fbx"));
m_SphereMesh.reset(new Prism::Mesh("assets/models/Sphere1m.fbx"));
m_PlaneMesh.reset(new Prism::Mesh("assets/models/Plane1m.fbx"));
// Editor
m_CheckerboardTex.reset(Prism::Texture2D::Create("assets/editor/Checkerboard.tga"));
@ -49,8 +130,36 @@ void DemoLayer::OnAttach()
m_Framebuffer.reset(Prism::FrameBuffer::Create(1280, 720, Prism::FramebufferFormat::RGBA16F));
m_FinalPresentBuffer.reset(Prism::FrameBuffer::Create(1280, 720, Prism::FramebufferFormat::RGBA8));
m_PBRMaterial.reset(new Prism::Material(m_SimplePBRShader));
float x = -4.0f;
float roughness = 0.0f;
for (int i = 0; i < 8; i++)
{
Prism::Ref<Prism::MaterialInstance> mi(new Prism::MaterialInstance(m_PBRMaterial));
mi->Set("u_Metalness", 1.0f);
mi->Set("u_Roughness", roughness);
mi->Set("u_ModelMatrix", glm::translate(glm::mat4(1.0f), glm::vec3(x, 0.0f, 0.0f)));
x += 1.1f;
roughness += 0.15f;
m_MetalSphereMaterialInstances.push_back(mi);
}
x = -4.0f;
roughness = 0.0f;
for (int i = 0; i < 8; i++)
{
Prism::Ref<Prism::MaterialInstance> mi(new Prism::MaterialInstance(m_PBRMaterial));
mi->Set("u_Metalness", 0.0f);
mi->Set("u_Roughness", roughness);
mi->Set("u_ModelMatrix", translate(glm::mat4(1.0f), glm::vec3(x, 1.2f, 0.0f)));
x += 1.1f;
roughness += 0.15f;
m_DielectricSphereMaterialInstances.push_back(mi);
}
// Create Quad
float x = -1, y = -1;
x = -1;
float y = -1;
float width = 2, height = 2;
struct QuadVertex
{
@ -77,7 +186,7 @@ void DemoLayer::OnAttach()
uint32_t* indices = new uint32_t[6] { 0, 1, 2, 2, 3, 0, };
m_IndexBuffer.reset(Prism::IndexBuffer::Create());
m_IndexBuffer->SetData(indices, 6 * sizeof(unsigned int));
m_IndexBuffer->SetData(indices, 6 * sizeof(uint32_t));
m_Light.Direction = { -0.5f, -0.5f, 1.0f };
m_Light.Radiance = { 1.0f, 1.0f, 1.0f };
@ -87,7 +196,7 @@ void DemoLayer::OnDetach()
{
}
void DemoLayer::OnUpdate(Prism::TimeStep deltaTime)
void DemoLayer::OnUpdate(const Prism::TimeStep deltaTime)
{
{
// THINGS TO LOOK AT:
@ -108,12 +217,31 @@ void DemoLayer::OnUpdate(Prism::TimeStep deltaTime)
m_QuadShader->UploadUniformBuffer(quadShaderUB);
m_QuadShader->Bind();
// m_EnvironmentIrradiance->Bind(0);
m_QuadShader->SetMat4("u_InverseVP", inverse(viewProjection));
m_EnvironmentCubeMap->Bind(0);
m_VertexBuffer->Bind();
m_IndexBuffer->Bind();
Renderer::DrawIndexed(m_IndexBuffer->GetCount(), false);
m_PBRMaterial->Set("u_AlbedoColor", m_AlbedoInput.Color);
m_PBRMaterial->Set("u_Metalness", m_MetalnessInput.Value);
m_PBRMaterial->Set("u_Roughness", m_RoughnessInput.Value);
m_PBRMaterial->Set("u_ViewProjectionMatrix", viewProjection);
m_PBRMaterial->Set("u_ModelMatrix", scale(mat4(1.0f), vec3(m_MeshScale)));
m_PBRMaterial->Set("lights", m_Light);
m_PBRMaterial->Set("u_CameraPosition", m_Camera.GetPosition());
m_PBRMaterial->Set("u_RadiancePrefilter", m_RadiancePrefilter ? 1.0f : 0.0f);
m_PBRMaterial->Set("u_AlbedoTexToggle", m_AlbedoInput.UseTexture ? 1.0f : 0.0f);
m_PBRMaterial->Set("u_NormalTexToggle", m_NormalInput.UseTexture ? 1.0f : 0.0f);
m_PBRMaterial->Set("u_MetalnessTexToggle", m_MetalnessInput.UseTexture ? 1.0f : 0.0f);
m_PBRMaterial->Set("u_RoughnessTexToggle", m_RoughnessInput.UseTexture ? 1.0f : 0.0f);
m_PBRMaterial->Set("u_EnvMapRotation", m_EnvMapRotation);
m_PBRMaterial->Set("u_EnvRadianceTex", m_EnvironmentCubeMap);
m_PBRMaterial->Set("u_EnvIrradianceTex", m_EnvironmentIrradiance);
m_PBRMaterial->Set("u_BRDFLUTTexture", m_BRDFLUT);
/*
Prism::UniformBufferDeclaration<sizeof(mat4) * 2 + sizeof(vec3) * 4 + sizeof(float) * 8, 14> simplePbrShaderUB;
simplePbrShaderUB.Push("u_ViewProjectionMatrix", viewProjection);
simplePbrShaderUB.Push("u_ModelMatrix", mat4(1.0f));
@ -134,53 +262,52 @@ void DemoLayer::OnUpdate(Prism::TimeStep deltaTime)
m_EnvironmentCubeMap->Bind(10);
m_EnvironmentIrradiance->Bind(11);
m_BRDFLUT->Bind(15);
m_SimplePBRShader->Bind();
*/
if (m_AlbedoInput.TextureMap)
m_AlbedoInput.TextureMap->Bind(1);
m_PBRMaterial->Set("u_AlbedoTexture", m_AlbedoInput.TextureMap);
if (m_NormalInput.TextureMap)
m_NormalInput.TextureMap->Bind(2);
m_PBRMaterial->Set("u_NormalTexture", m_NormalInput.TextureMap);
if (m_MetalnessInput.TextureMap)
m_MetalnessInput.TextureMap->Bind(3);
m_PBRMaterial->Set("u_MetalnessTexture", m_MetalnessInput.TextureMap);
if (m_RoughnessInput.TextureMap)
m_RoughnessInput.TextureMap->Bind(4);
m_PBRMaterial->Set("u_RoughnessTexture", m_RoughnessInput.TextureMap);
if (m_Scene == Scene::Spheres)
{
// Metals
float roughness = 0.0f;
float x = -88.0f;
for (int i = 0; i < 8; i++)
{
m_SimplePBRShader->SetMat4("u_ModelMatrix", translate(mat4(1.0f), vec3(x, 0.0f, 0.0f)));
m_SimplePBRShader->SetFloat("u_Roughness", roughness);
m_SimplePBRShader->SetFloat("u_Metalness", 1.0f);
m_SphereMesh->Render();
roughness += 0.15f;
x += 22.0f;
m_MetalSphereMaterialInstances[i]->Bind();
m_SphereMesh->Render(deltaTime, m_SimplePBRShader.get());
}
// Dielectrics
roughness = 0.0f;
x = -88.0f;
for (int i = 0; i < 8; i++)
{
m_SimplePBRShader->SetMat4("u_ModelMatrix", translate(mat4(1.0f), vec3(x, 22.0f, 0.0f)));
m_SimplePBRShader->SetFloat("u_Roughness", roughness);
m_SimplePBRShader->SetFloat("u_Metalness", 0.0f);
m_SphereMesh->Render();
roughness += 0.15f;
x += 22.0f;
m_DielectricSphereMaterialInstances[i]->Bind();
m_SphereMesh->Render(deltaTime, m_SimplePBRShader.get());
}
}
else if (m_Scene == Scene::Model)
{
m_Mesh->Render();
if (m_Mesh)
{
m_PBRMaterial->Bind();
m_Mesh->Render(deltaTime, m_SimplePBRShader.get());
}
}
m_GridShader->Bind();
m_GridShader->SetMat4("u_MVP", viewProjection * glm::scale(glm::mat4(1.0f), glm::vec3(16.0f)));
m_GridShader->SetFloat("u_Scale", m_GridScale);
m_GridShader->SetFloat("u_Res", m_GridSize);
m_PlaneMesh->Render(deltaTime, m_GridShader.get());
m_Framebuffer->Unbind();
m_FinalPresentBuffer->Bind();
@ -311,35 +438,27 @@ void DemoLayer::OnImGuiRender()
#endif
// Editor Panel ------------------------------------------------------------------------------
ImGui::Begin("Settings");
if (ImGui::TreeNode("Shaders"))
{
auto& shaders = Prism::Shader::s_AllShaders;
for (auto& shader : shaders)
{
if (ImGui::TreeNode(shader->GetName().c_str()))
{
std::string buttonName = "Reload##" + shader->GetName();
if (ImGui::Button(buttonName.c_str()))
shader->Reload();
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::Begin("Model");
ImGui::RadioButton("Spheres", (int*)&m_Scene, (int)Scene::Spheres);
ImGui::SameLine();
ImGui::RadioButton("Model", (int*)&m_Scene, (int)Scene::Model);
ImGui::ColorEdit4("Clear Color", m_ClearColor);
ImGui::Begin("Environment");
ImGui::SliderFloat3("Light Dir", glm::value_ptr(m_Light.Direction), -1, 1);
ImGui::ColorEdit3("Light Radiance", glm::value_ptr(m_Light.Radiance));
ImGui::SliderFloat("Light Multiplier", &m_LightMultiplier, 0.0f, 5.0f);
ImGui::SliderFloat("Exposure", &m_Exposure, 0.0f, 10.0f);
auto cameraForward = m_Camera.GetForwardDirection();
ImGui::Text("Camera Forward: %.2f, %.2f, %.2f", cameraForward.x, cameraForward.y, cameraForward.z);
ImGui::Columns(2);
ImGui::AlignTextToFramePadding();
Property("Light Direction", m_Light.Direction);
Property("Light Radiance", m_Light.Radiance, PropertyFlag::ColorProperty);
Property("Light Multiplier", m_LightMultiplier, 0.0f, 5.0f);
Property("Exposure", m_Exposure, 0.0f, 5.0f);
Property("Radiance Prefiltering", m_RadiancePrefilter);
Property("Env Map Rotation", m_EnvMapRotation, -360.0f, 360.0f);
ImGui::Columns(1);
ImGui::End();
ImGui::Separator();
{
@ -357,12 +476,6 @@ void DemoLayer::OnImGuiRender()
}
ImGui::Separator();
ImGui::Text("Shader Parameters");
ImGui::Checkbox("Radiance Prefiltering", &m_RadiancePrefilter);
ImGui::SliderFloat("Env Map Rotation", &m_EnvMapRotation, -360.0f, 360.0f);
ImGui::Separator();
// Textures ------------------------------------------------------------------------------
{
// Albedo
@ -494,6 +607,22 @@ void DemoLayer::OnImGuiRender()
}
}
if (ImGui::TreeNode("Shaders"))
{
auto& shaders = Prism::Shader::GetAllShaders();
for (auto& shader : shaders)
{
if (ImGui::TreeNode(shader->GetName().c_str()))
{
std::string buttonName = "Reload##" + shader->GetName();
if (ImGui::Button(buttonName.c_str()))
shader->Reload();
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::Separator();
ImGui::End();
@ -509,7 +638,8 @@ void DemoLayer::OnImGuiRender()
ImGui::End();
ImGui::PopStyleVar();
if (m_Mesh)
m_Mesh->OnImGuiRender();
}

View File

@ -8,6 +8,7 @@
#include "Prism.h"
#include "Prism/Renderer/Camera.h"
#include "Prism/Renderer/FrameBuffer.h"
#include "Prism/Renderer/Material.h"
#include "Prism/Renderer/Mesh.h"
class DemoLayer : public Prism::Layer
@ -25,19 +26,28 @@ public:
private:
float m_ClearColor[4];
std::unique_ptr<Prism::Shader> m_Shader;
std::unique_ptr<Prism::Shader> m_PBRShader;
std::unique_ptr<Prism::Shader> m_SimplePBRShader;
std::unique_ptr<Prism::Shader> m_QuadShader;
std::unique_ptr<Prism::Shader> m_HDRShader;
std::unique_ptr<Prism::Mesh> m_Mesh;
std::unique_ptr<Prism::Mesh> m_SphereMesh;
std::unique_ptr<Prism::Texture2D> m_BRDFLUT;
Prism::Ref<Prism::Shader> m_SimplePBRShader;
Prism::Scope<Prism::Shader> m_QuadShader;
Prism::Scope<Prism::Shader> m_HDRShader;
Prism::Scope<Prism::Shader> m_GridShader;
Prism::Scope<Prism::Mesh> m_Mesh;
Prism::Scope<Prism::Mesh> m_SphereMesh, m_PlaneMesh;
Prism::Ref<Prism::Texture2D> m_BRDFLUT;
Prism::Ref<Prism::Material> m_PBRMaterial;
std::vector<Prism::Ref<Prism::MaterialInstance>> m_MetalSphereMaterialInstances;
std::vector<Prism::Ref<Prism::MaterialInstance>> m_DielectricSphereMaterialInstances;
float m_GridScale = 16.025f, m_GridSize = 0.025f;
float m_MeshScale = 1.0f;
Prism::Ref<Prism::Shader> m_Shader;
Prism::Ref<Prism::Shader> m_PBRShader;
struct AlbedoInput
{
glm::vec3 Color = { 0.972f, 0.96f, 0.915f }; // Silver, from https://docs.unrealengine.com/en-us/Engine/Rendering/Materials/PhysicallyBased
std::unique_ptr<Prism::Texture2D> TextureMap;
Prism::Ref<Prism::Texture2D> TextureMap;
bool SRGB = true;
bool UseTexture = false;
};
@ -45,7 +55,7 @@ private:
struct NormalInput
{
std::unique_ptr<Prism::Texture2D> TextureMap;
Prism::Ref<Prism::Texture2D> TextureMap;
bool UseTexture = false;
};
NormalInput m_NormalInput;
@ -53,7 +63,7 @@ private:
struct MetalnessInput
{
float Value = 1.0f;
std::unique_ptr<Prism::Texture2D> TextureMap;
Prism::Ref<Prism::Texture2D> TextureMap;
bool UseTexture = false;
};
MetalnessInput m_MetalnessInput;
@ -61,16 +71,16 @@ private:
struct RoughnessInput
{
float Value = 0.5f;
std::unique_ptr<Prism::Texture2D> TextureMap;
Prism::Ref<Prism::Texture2D> TextureMap;
bool UseTexture = false;
};
RoughnessInput m_RoughnessInput;
std::unique_ptr<Prism::FrameBuffer> m_Framebuffer, m_FinalPresentBuffer;
Prism::Ref<Prism::FrameBuffer> m_Framebuffer, m_FinalPresentBuffer;
std::unique_ptr<Prism::VertexBuffer> m_VertexBuffer;
std::unique_ptr<Prism::IndexBuffer> m_IndexBuffer;
std::unique_ptr<Prism::TextureCube> m_EnvironmentCubeMap, m_EnvironmentIrradiance;
Prism::Ref<Prism::VertexBuffer> m_VertexBuffer;
Prism::Ref<Prism::IndexBuffer> m_IndexBuffer;
Prism::Ref<Prism::TextureCube> m_EnvironmentCubeMap, m_EnvironmentIrradiance;
Prism::Camera m_Camera;
@ -96,7 +106,7 @@ private:
Scene m_Scene;
// Editor resources
std::unique_ptr<Prism::Texture2D> m_CheckerboardTex;
Prism::Ref<Prism::Texture2D> m_CheckerboardTex;
};

View File

@ -208,7 +208,7 @@ void TestLayer::OnUpdate(Prism::TimeStep deltaTime)
m_Shader->Bind();
m_Mesh->Render();
m_Mesh->Render(deltaTime, m_Shader.get());
// m_VertexBuffer->Bind();
// m_IndexBuffer->Bind();

Binary file not shown.

View File

@ -0,0 +1,17 @@
# Blender v2.80 (sub 75) OBJ File: 'Plane1m.blend'
# www.blender.org
mtllib Plane1m.mtl
o Plane
v -0.500000 0.000000 0.500000
v 0.500000 0.000000 0.500000
v -0.500000 0.000000 -0.500000
v 0.500000 0.000000 -0.500000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 1.000000
vn 0.0000 1.0000 0.0000
usemtl None
s off
f 2/1/1 3/2/1 1/3/1
f 2/1/1 4/4/1 3/2/1

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@ -0,0 +1,50 @@
// Simple Texture Shader
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 4) in vec2 a_TexCoord;
uniform mat4 u_MVP;
out vec2 v_TexCoord;
void main()
{
vec4 position = u_MVP * vec4(a_Position, 1.0);
gl_Position = position;
v_TexCoord = a_TexCoord;
}
#type fragment
#version 430
layout(location = 0) out vec4 color;
uniform sampler2D u_Texture;
uniform float u_Scale;
uniform float u_Res;
in vec2 v_TexCoord;
/*void main()
{
color = texture(u_Texture, v_TexCoord * 8.0);
}*/
float grid(vec2 st, float res)
{
vec2 grid = fract(st);
return step(res, grid.x) * step(res, grid.y);
}
void main()
{
float scale = u_Scale;
float resolution = u_Res;
float x = grid(v_TexCoord * scale, resolution);
color = vec4(vec3(0.2), 0.5) * (1.0 - x);
}

View File

@ -18,25 +18,41 @@ layout(location = 2) in vec3 a_Tangent;
layout(location = 3) in vec3 a_Binormal;
layout(location = 4) in vec2 a_TexCoord;
layout(location = 5) in ivec4 a_BoneIndices;
layout(location = 6) in vec4 a_BoneWeights;
uniform mat4 u_ViewProjectionMatrix;
uniform mat4 u_ModelMatrix;
const int MAX_BONES = 100;
uniform mat4 u_BoneTransforms[100];
out VertexOutput
{
vec3 WorldPosition;
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
vec3 Binormal;
} vs_Output;
void main()
{
vs_Output.WorldPosition = vec3(mat4(u_ModelMatrix) * vec4(a_Position, 1.0));
vs_Output.Normal = a_Normal;
mat4 boneTransform = u_BoneTransforms[a_BoneIndices[0]] * a_BoneWeights[0];
boneTransform += u_BoneTransforms[a_BoneIndices[1]] * a_BoneWeights[1];
boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2];
boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3];
vec4 localPosition = boneTransform * vec4(a_Position, 1.0);
vs_Output.WorldPosition = vec3(u_ModelMatrix * boneTransform * vec4(a_Position, 1.0));
vs_Output.Normal = mat3(boneTransform) * a_Normal;
vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y);
vs_Output.WorldNormals = mat3(u_ModelMatrix) * mat3(a_Tangent, a_Binormal, a_Normal);
vs_Output.Binormal = mat3(boneTransform) * a_Binormal;
gl_Position = u_ViewProjectionMatrix * u_ModelMatrix * vec4(a_Position, 1.0);
//gl_Position = u_ViewProjectionMatrix * u_ModelMatrix * vec4(a_Position, 1.0);
gl_Position = u_ViewProjectionMatrix * u_ModelMatrix * localPosition;
}
#type fragment
@ -62,6 +78,7 @@ in VertexOutput
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
vec3 Binormal;
} vs_Input;
layout(location=0) out vec4 color;