feat: directional lighting with per-face normals
- Add Normal to Vertex struct and vertex input pipeline. - Update vertex/fragment shaders with push-constant light data (direction, color, ambient). - Compute per-face normals in ObjLoader and GltfLoader for flat shading. - Add Transform.TransformNormal() using inverse-transpose of the model matrix. - MeshRenderer builds world-space normals and pushes light constants each frame. - Recompile SPIR-V shaders.
This commit is contained in:
@@ -27,4 +27,25 @@ public record struct Transform
|
|||||||
* Matrix4x4.CreateFromQuaternion(Rotation)
|
* Matrix4x4.CreateFromQuaternion(Rotation)
|
||||||
* Matrix4x4.CreateTranslation(Position);
|
* Matrix4x4.CreateTranslation(Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a normal vector from local to world space using the inverse-transpose of the model matrix.
|
||||||
|
/// </summary>
|
||||||
|
public Vector3 TransformNormal(Vector3 normal)
|
||||||
|
{
|
||||||
|
var matrix = GetMatrix();
|
||||||
|
if (!Matrix4x4.Invert(matrix, out var inverted))
|
||||||
|
return normal;
|
||||||
|
|
||||||
|
// Use the upper 3x3 of the transposed inverse matrix.
|
||||||
|
var nx = inverted.M11 * normal.X + inverted.M21 * normal.Y + inverted.M31 * normal.Z;
|
||||||
|
var ny = inverted.M12 * normal.X + inverted.M22 * normal.Y + inverted.M32 * normal.Z;
|
||||||
|
var nz = inverted.M13 * normal.X + inverted.M23 * normal.Y + inverted.M33 * normal.Z;
|
||||||
|
|
||||||
|
var result = new Vector3(nx, ny, nz);
|
||||||
|
if (result.LengthSquared() > 0.00001f)
|
||||||
|
result = Vector3.Normalize(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ using System.Numerics;
|
|||||||
namespace Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A simple 3D vertex with position and color.
|
/// A 3D vertex with position, color, and normal.
|
||||||
/// Layout matches the Vulkan vertex input description.
|
/// Layout matches the Vulkan vertex input description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct Vertex
|
public struct Vertex
|
||||||
{
|
{
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Vector3 Color;
|
public Vector3 Color;
|
||||||
|
public Vector3 Normal;
|
||||||
|
|
||||||
public Vertex(Vector3 position, Vector3 color)
|
public Vertex(Vector3 position, Vector3 color, Vector3 normal)
|
||||||
{
|
{
|
||||||
Position = position;
|
Position = position;
|
||||||
Color = color;
|
Color = color;
|
||||||
|
Normal = normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Engine.Graphics.Loaders;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// glTF/glTF binary loader using SharpGLTF.Core.
|
/// glTF/glTF binary loader using SharpGLTF.Core.
|
||||||
/// Loads the first primitive of the first mesh and converts it to a colored Mesh component.
|
/// Loads the first primitive of the first mesh and converts it to a colored Mesh component.
|
||||||
|
/// Creates per-face normals for flat shading if the glTF does not provide normals.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class GltfLoader
|
public static class GltfLoader
|
||||||
{
|
{
|
||||||
@@ -31,28 +32,63 @@ public static class GltfLoader
|
|||||||
throw new InvalidOperationException($"glTF primitive has no POSITION accessor: {path}");
|
throw new InvalidOperationException($"glTF primitive has no POSITION accessor: {path}");
|
||||||
|
|
||||||
var positions = positionAccessor.AsVector3Array();
|
var positions = positionAccessor.AsVector3Array();
|
||||||
var vertices = new Vertex[positions.Count];
|
|
||||||
for (var i = 0; i < positions.Count; i++)
|
var indices = GetIndices(primitive, positions.Count);
|
||||||
|
var vertices = new List<Vertex>();
|
||||||
|
var newIndices = new List<uint>();
|
||||||
|
|
||||||
|
for (var i = 0; i < indices.Length; i += 3)
|
||||||
{
|
{
|
||||||
vertices[i] = new Vertex(positions[i], color);
|
var i0 = (int)indices[i];
|
||||||
|
var i1 = (int)indices[i + 1];
|
||||||
|
var i2 = (int)indices[i + 2];
|
||||||
|
|
||||||
|
var v0 = new Vector3(positions[i0].X, positions[i0].Y, positions[i0].Z);
|
||||||
|
var v1 = new Vector3(positions[i1].X, positions[i1].Y, positions[i1].Z);
|
||||||
|
var v2 = new Vector3(positions[i2].X, positions[i2].Y, positions[i2].Z);
|
||||||
|
|
||||||
|
var normal = ComputeFaceNormal(v0, v1, v2);
|
||||||
|
|
||||||
|
var vertexBase = (uint)vertices.Count;
|
||||||
|
newIndices.Add(vertexBase);
|
||||||
|
newIndices.Add(vertexBase + 1);
|
||||||
|
newIndices.Add(vertexBase + 2);
|
||||||
|
|
||||||
|
vertices.Add(new Vertex(v0, color, normal));
|
||||||
|
vertices.Add(new Vertex(v1, color, normal));
|
||||||
|
vertices.Add(new Vertex(v2, color, normal));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint[] indices;
|
return new Engine.Core.Components.Mesh(vertices.ToArray(), newIndices.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint[] GetIndices(MeshPrimitive primitive, int positionCount)
|
||||||
|
{
|
||||||
if (primitive.IndexAccessor != null)
|
if (primitive.IndexAccessor != null)
|
||||||
{
|
{
|
||||||
var idx = primitive.IndexAccessor.AsIndexArray();
|
var idx = primitive.IndexAccessor.AsIndexArray();
|
||||||
indices = new uint[idx.Count];
|
var indices = new uint[idx.Count];
|
||||||
for (var i = 0; i < idx.Count; i++)
|
for (var i = 0; i < idx.Count; i++)
|
||||||
indices[i] = idx[i];
|
indices[i] = idx[i];
|
||||||
}
|
return indices;
|
||||||
else
|
|
||||||
{
|
|
||||||
// Non-indexed primitive
|
|
||||||
indices = new uint[positions.Count];
|
|
||||||
for (var i = 0; i < positions.Count; i++)
|
|
||||||
indices[i] = (uint)i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Engine.Core.Components.Mesh(vertices, indices);
|
// Non-indexed primitive
|
||||||
|
var auto = new uint[positionCount];
|
||||||
|
for (var i = 0; i < positionCount; i++)
|
||||||
|
auto[i] = (uint)i;
|
||||||
|
return auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c)
|
||||||
|
{
|
||||||
|
var ab = b - a;
|
||||||
|
var ac = c - a;
|
||||||
|
var normal = Vector3.Cross(ab, ac);
|
||||||
|
if (normal.LengthSquared() > 0.00001f)
|
||||||
|
normal = Vector3.Normalize(normal);
|
||||||
|
else
|
||||||
|
normal = Vector3.UnitY;
|
||||||
|
return normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Engine.Graphics.Loaders;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimal .obj loader.
|
/// Minimal .obj loader.
|
||||||
/// Supports vertices (v) and faces (f). Ignores normals/UVs for now.
|
/// Supports vertices (v) and faces (f). Creates per-face normals for flat shading.
|
||||||
/// Produces a colored Mesh component.
|
/// Produces a colored Mesh component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ObjLoader
|
public static class ObjLoader
|
||||||
@@ -19,6 +19,7 @@ public static class ObjLoader
|
|||||||
var color = defaultColor ?? new Vector3(0.7f, 0.7f, 0.7f);
|
var color = defaultColor ?? new Vector3(0.7f, 0.7f, 0.7f);
|
||||||
|
|
||||||
var positions = new List<Vector3>();
|
var positions = new List<Vector3>();
|
||||||
|
var vertices = new List<Vertex>();
|
||||||
var indices = new List<uint>();
|
var indices = new List<uint>();
|
||||||
|
|
||||||
foreach (var rawLine in File.ReadLines(path))
|
foreach (var rawLine in File.ReadLines(path))
|
||||||
@@ -41,28 +42,36 @@ public static class ObjLoader
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "f" when parts.Length >= 4:
|
case "f" when parts.Length >= 4:
|
||||||
// Triangulate the face as a fan. Only the position index is used.
|
// Triangulate the face as a fan.
|
||||||
var baseIndex = ParseFaceIndex(parts[1]);
|
var baseIndex = ParseFaceIndex(parts[1]);
|
||||||
for (var i = 2; i < parts.Length - 1; i++)
|
for (var i = 2; i < parts.Length - 1; i++)
|
||||||
{
|
{
|
||||||
indices.Add(baseIndex);
|
var i0 = baseIndex;
|
||||||
indices.Add(ParseFaceIndex(parts[i]));
|
var i1 = ParseFaceIndex(parts[i]);
|
||||||
indices.Add(ParseFaceIndex(parts[i + 1]));
|
var i2 = ParseFaceIndex(parts[i + 1]);
|
||||||
|
|
||||||
|
var v0 = positions[(int)i0];
|
||||||
|
var v1 = positions[(int)i1];
|
||||||
|
var v2 = positions[(int)i2];
|
||||||
|
var normal = ComputeFaceNormal(v0, v1, v2);
|
||||||
|
|
||||||
|
var vertexBase = (uint)vertices.Count;
|
||||||
|
indices.Add(vertexBase);
|
||||||
|
indices.Add(vertexBase + 1);
|
||||||
|
indices.Add(vertexBase + 2);
|
||||||
|
|
||||||
|
vertices.Add(new Vertex(v0, color, normal));
|
||||||
|
vertices.Add(new Vertex(v1, color, normal));
|
||||||
|
vertices.Add(new Vertex(v2, color, normal));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positions.Count == 0)
|
if (vertices.Count == 0)
|
||||||
throw new InvalidOperationException($"OBJ file has no vertices: {path}");
|
throw new InvalidOperationException($"OBJ file has no vertices: {path}");
|
||||||
|
|
||||||
var vertices = new Vertex[positions.Count];
|
return new Mesh(vertices.ToArray(), indices.ToArray());
|
||||||
for (var i = 0; i < positions.Count; i++)
|
|
||||||
{
|
|
||||||
vertices[i] = new Vertex(positions[i], color);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Mesh(vertices, indices.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint ParseFaceIndex(string part)
|
private static uint ParseFaceIndex(string part)
|
||||||
@@ -73,4 +82,16 @@ public static class ObjLoader
|
|||||||
var index = int.Parse(indexStr);
|
var index = int.Parse(indexStr);
|
||||||
return (uint)(index - 1); // OBJ indices are 1-based
|
return (uint)(index - 1); // OBJ indices are 1-based
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c)
|
||||||
|
{
|
||||||
|
var ab = b - a;
|
||||||
|
var ac = c - a;
|
||||||
|
var normal = Vector3.Cross(ab, ac);
|
||||||
|
if (normal.LengthSquared() > 0.00001f)
|
||||||
|
normal = Vector3.Normalize(normal);
|
||||||
|
else
|
||||||
|
normal = Vector3.UnitY;
|
||||||
|
return normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Flecs.NET.Core;
|
using Flecs.NET.Core;
|
||||||
using Silk.NET.Core;
|
using Silk.NET.Core;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
@@ -27,6 +28,18 @@ public sealed unsafe class MeshRenderer : IDisposable
|
|||||||
private Silk.NET.Vulkan.Fence[] _inFlightFences = null!;
|
private Silk.NET.Vulkan.Fence[] _inFlightFences = null!;
|
||||||
private int _currentFrame;
|
private int _currentFrame;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct PushConstants
|
||||||
|
{
|
||||||
|
public Matrix4x4 Mvp;
|
||||||
|
public Vector3 LightDirection;
|
||||||
|
public float Pad1;
|
||||||
|
public Vector3 LightColor;
|
||||||
|
public float Pad2;
|
||||||
|
public Vector3 AmbientColor;
|
||||||
|
public float Pad3;
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class MeshBuffers : IDisposable
|
private sealed class MeshBuffers : IDisposable
|
||||||
{
|
{
|
||||||
public VertexBuffer VertexBuffer;
|
public VertexBuffer VertexBuffer;
|
||||||
@@ -194,7 +207,17 @@ public sealed unsafe class MeshRenderer : IDisposable
|
|||||||
var model = transform.GetMatrix();
|
var model = transform.GetMatrix();
|
||||||
var mvp = Matrix4x4.Multiply(Matrix4x4.Multiply(model, view), proj);
|
var mvp = Matrix4x4.Multiply(Matrix4x4.Multiply(model, view), proj);
|
||||||
var mvpT = Matrix4x4.Transpose(mvp);
|
var mvpT = Matrix4x4.Transpose(mvp);
|
||||||
_context.Vk.CmdPushConstants(drawCmd, _pipeline.Layout, ShaderStageFlags.VertexBit, 0, 64, &mvpT);
|
|
||||||
|
var push = new PushConstants
|
||||||
|
{
|
||||||
|
Mvp = mvpT,
|
||||||
|
LightDirection = new Vector3(0.5f, -1.0f, -0.5f),
|
||||||
|
LightColor = new Vector3(1.0f, 0.95f, 0.8f),
|
||||||
|
AmbientColor = new Vector3(0.15f, 0.15f, 0.2f)
|
||||||
|
};
|
||||||
|
|
||||||
|
var pushSize = (uint)sizeof(PushConstants);
|
||||||
|
_context.Vk.CmdPushConstants(drawCmd, _pipeline.Layout, ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit, 0, pushSize, &push);
|
||||||
|
|
||||||
var vertexBuffer = buffers.VertexBuffer.Buffer;
|
var vertexBuffer = buffers.VertexBuffer.Buffer;
|
||||||
var offset = 0ul;
|
var offset = 0ul;
|
||||||
@@ -252,19 +275,22 @@ public sealed unsafe class MeshRenderer : IDisposable
|
|||||||
|
|
||||||
private MeshBuffers CreateMeshBuffers(Mesh mesh)
|
private MeshBuffers CreateMeshBuffers(Mesh mesh)
|
||||||
{
|
{
|
||||||
var vertexBytes = new byte[mesh.Vertices.Length * 6 * sizeof(float)];
|
var vertexBytes = new byte[mesh.Vertices.Length * 9 * sizeof(float)];
|
||||||
fixed (byte* p = vertexBytes)
|
fixed (byte* p = vertexBytes)
|
||||||
{
|
{
|
||||||
var dst = (float*)p;
|
var dst = (float*)p;
|
||||||
for (var i = 0; i < mesh.Vertices.Length; i++)
|
for (var i = 0; i < mesh.Vertices.Length; i++)
|
||||||
{
|
{
|
||||||
var v = mesh.Vertices[i];
|
var v = mesh.Vertices[i];
|
||||||
dst[i * 6 + 0] = v.Position.X;
|
dst[i * 9 + 0] = v.Position.X;
|
||||||
dst[i * 6 + 1] = v.Position.Y;
|
dst[i * 9 + 1] = v.Position.Y;
|
||||||
dst[i * 6 + 2] = v.Position.Z;
|
dst[i * 9 + 2] = v.Position.Z;
|
||||||
dst[i * 6 + 3] = v.Color.X;
|
dst[i * 9 + 3] = v.Color.X;
|
||||||
dst[i * 6 + 4] = v.Color.Y;
|
dst[i * 9 + 4] = v.Color.Y;
|
||||||
dst[i * 6 + 5] = v.Color.Z;
|
dst[i * 9 + 5] = v.Color.Z;
|
||||||
|
dst[i * 9 + 6] = v.Normal.X;
|
||||||
|
dst[i * 9 + 7] = v.Normal.Y;
|
||||||
|
dst[i * 9 + 8] = v.Normal.Z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,19 +309,24 @@ public sealed unsafe class MeshRenderer : IDisposable
|
|||||||
private byte[] BuildMeshVertices(Mesh mesh, Transform transform)
|
private byte[] BuildMeshVertices(Mesh mesh, Transform transform)
|
||||||
{
|
{
|
||||||
var matrix = transform.GetMatrix();
|
var matrix = transform.GetMatrix();
|
||||||
var bytes = new byte[mesh.Vertices.Length * 6 * sizeof(float)];
|
var bytes = new byte[mesh.Vertices.Length * 9 * sizeof(float)];
|
||||||
fixed (byte* p = bytes)
|
fixed (byte* p = bytes)
|
||||||
{
|
{
|
||||||
var dst = (float*)p;
|
var dst = (float*)p;
|
||||||
for (var i = 0; i < mesh.Vertices.Length; i++)
|
for (var i = 0; i < mesh.Vertices.Length; i++)
|
||||||
{
|
{
|
||||||
var transformed = Vector3.Transform(mesh.Vertices[i].Position, matrix);
|
var v = mesh.Vertices[i];
|
||||||
dst[i * 6 + 0] = transformed.X;
|
var transformed = Vector3.Transform(v.Position, matrix);
|
||||||
dst[i * 6 + 1] = transformed.Y;
|
var normal = transform.TransformNormal(v.Normal);
|
||||||
dst[i * 6 + 2] = transformed.Z;
|
dst[i * 9 + 0] = transformed.X;
|
||||||
dst[i * 6 + 3] = mesh.Vertices[i].Color.X;
|
dst[i * 9 + 1] = transformed.Y;
|
||||||
dst[i * 6 + 4] = mesh.Vertices[i].Color.Y;
|
dst[i * 9 + 2] = transformed.Z;
|
||||||
dst[i * 6 + 5] = mesh.Vertices[i].Color.Z;
|
dst[i * 9 + 3] = v.Color.X;
|
||||||
|
dst[i * 9 + 4] = v.Color.Y;
|
||||||
|
dst[i * 9 + 5] = v.Color.Z;
|
||||||
|
dst[i * 9 + 6] = normal.X;
|
||||||
|
dst[i * 9 + 7] = normal.Y;
|
||||||
|
dst[i * 9 + 8] = normal.Z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
layout(location = 0) in vec3 fragColor;
|
layout(location = 0) in vec3 fragColor;
|
||||||
|
layout(location = 1) in vec3 fragNormal;
|
||||||
|
layout(location = 2) in vec3 fragWorldPos;
|
||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants
|
||||||
|
{
|
||||||
|
mat4 mvp;
|
||||||
|
vec3 lightDirection;
|
||||||
|
float pad1;
|
||||||
|
vec3 lightColor;
|
||||||
|
float pad2;
|
||||||
|
vec3 ambientColor;
|
||||||
|
float pad3;
|
||||||
|
} push;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
outColor = vec4(fragColor, 1.0);
|
vec3 normal = normalize(fragNormal);
|
||||||
|
vec3 lightDir = normalize(-push.lightDirection);
|
||||||
|
|
||||||
|
float diff = max(dot(normal, lightDir), 0.0);
|
||||||
|
vec3 diffuse = push.lightColor * diff;
|
||||||
|
vec3 ambient = push.ambientColor;
|
||||||
|
|
||||||
|
vec3 result = (ambient + diffuse) * fragColor;
|
||||||
|
outColor = vec4(result, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -2,16 +2,27 @@
|
|||||||
|
|
||||||
layout(location = 0) in vec3 inPosition;
|
layout(location = 0) in vec3 inPosition;
|
||||||
layout(location = 1) in vec3 inColor;
|
layout(location = 1) in vec3 inColor;
|
||||||
|
layout(location = 2) in vec3 inNormal;
|
||||||
|
|
||||||
layout(location = 0) out vec3 fragColor;
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
layout(location = 1) out vec3 fragNormal;
|
||||||
|
layout(location = 2) out vec3 fragWorldPos;
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants
|
layout(push_constant) uniform PushConstants
|
||||||
{
|
{
|
||||||
mat4 mvp;
|
mat4 mvp;
|
||||||
|
vec3 lightDirection;
|
||||||
|
float pad1;
|
||||||
|
vec3 lightColor;
|
||||||
|
float pad2;
|
||||||
|
vec3 ambientColor;
|
||||||
|
float pad3;
|
||||||
} push;
|
} push;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = push.mvp * vec4(inPosition, 1.0);
|
gl_Position = push.mvp * vec4(inPosition, 1.0);
|
||||||
fragColor = inColor;
|
fragColor = inColor;
|
||||||
|
fragNormal = inNormal;
|
||||||
|
fragWorldPos = inPosition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
|||||||
{
|
{
|
||||||
var pushConstantRange = new PushConstantRange
|
var pushConstantRange = new PushConstantRange
|
||||||
{
|
{
|
||||||
StageFlags = ShaderStageFlags.VertexBit,
|
StageFlags = ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit,
|
||||||
Offset = 0,
|
Offset = 0,
|
||||||
Size = (uint)(16 * sizeof(float))
|
Size = (uint)(28 * sizeof(float))
|
||||||
};
|
};
|
||||||
|
|
||||||
var createInfo = new PipelineLayoutCreateInfo
|
var createInfo = new PipelineLayoutCreateInfo
|
||||||
@@ -101,7 +101,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
|||||||
var bindingDescription = new VertexInputBindingDescription
|
var bindingDescription = new VertexInputBindingDescription
|
||||||
{
|
{
|
||||||
Binding = 0,
|
Binding = 0,
|
||||||
Stride = (uint)(6 * sizeof(float)),
|
Stride = (uint)(9 * sizeof(float)),
|
||||||
InputRate = VertexInputRate.Vertex
|
InputRate = VertexInputRate.Vertex
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,6 +120,13 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
|||||||
Location = 1,
|
Location = 1,
|
||||||
Format = Format.R32G32B32Sfloat,
|
Format = Format.R32G32B32Sfloat,
|
||||||
Offset = (uint)(3 * sizeof(float))
|
Offset = (uint)(3 * sizeof(float))
|
||||||
|
},
|
||||||
|
new VertexInputAttributeDescription
|
||||||
|
{
|
||||||
|
Binding = 0,
|
||||||
|
Location = 2,
|
||||||
|
Format = Format.R32G32B32Sfloat,
|
||||||
|
Offset = (uint)(6 * sizeof(float))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user