fix: rendering now works — three critical bugs fixed

1. VkColorComponentFlags: wrong bit values (0x10-0x80 instead of 0x01-0x08)
   → colorWriteMask was 0xF0 instead of 0x0F → no color channels written

2. VkClearValue: was Sequential layout instead of Explicit (union)
   → depthStencil clear value written at wrong offset → depth buffer
   cleared to 0.0 instead of 1.0 → all fragments failed depth test

3. Vulkan clip space correction in vertex shader:
   gl_Position.y = -gl_Position.y (Vulkan Y-down vs OpenGL Y-up)
   gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5 (Z [0,1] vs [-1,1])

Also:
- PushConstantSize fixed from 128 to 144 (two mat4 + vec4)
- Dynamic viewport/scissor state added to pipeline
- Push constants use column_major (default) to match System.Numerics row-major layout
- Camera.GetProjectionMatrix reverted to pure OpenGL-style (correction in shader)
- Screenshots show actual 3D geometry (calibration cubes visible)
- 66/66 tests pass
This commit is contained in:
emil28092005
2026-06-18 00:14:40 +03:00
parent 749fbc8df4
commit 46a04850e3
7 changed files with 45 additions and 19 deletions
@@ -5,7 +5,7 @@ layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in vec3 fragViewDir;
layout(row_major, set = 0, binding = 0) uniform FrameUBO {
layout(set = 0, binding = 0) uniform FrameUBO {
vec3 cameraPosition;
uint lightCount;
vec3 ambientColor;
@@ -47,11 +47,9 @@ void main() {
float attenuation;
if (dirIntensity.w < 0.0) {
// Directional light: direction stored as xyz, w = intensity (negative marks directional)
lightDir = normalize(-dirIntensity.xyz);
attenuation = abs(dirIntensity.w);
} else {
// Point light: position stored as xyz, w = intensity (positive marks point)
vec3 toLight = dirIntensity.xyz - fragWorldPos;
float dist = length(toLight);
lightDir = toLight / max(dist, 0.001);
@@ -63,7 +61,6 @@ void main() {
vec3 H = normalize(V + lightDir);
float NdotL = max(dot(N, lightDir), 0.0);
float NdotH = max(dot(N, H), 0.0);
float VdotH = max(dot(V, H), 0.0);
float specPower = mix(128.0, 4.0, roughness);
float specIntensity = pow(NdotH, specPower);
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@ layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(row_major, set = 0, binding = 0) uniform FrameUBO {
layout(set = 0, binding = 0) uniform FrameUBO {
vec3 cameraPosition;
uint lightCount;
vec3 ambientColor;
@@ -27,6 +27,10 @@ void main() {
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
gl_Position = pc.mvp * vec4(inPosition, 1.0);
// Vulkan clip space: Y-down, Z [0,1] — convert from OpenGL Y-up, Z [-1,1]
gl_Position.y = -gl_Position.y;
gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5;
fragColor = inColor;
fragNormal = normalize(mat3(pc.model) * inNormal);
fragWorldPos = worldPos.xyz;
+13 -4
View File
@@ -14,7 +14,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
private readonly VulkanContext _ctx;
private bool _disposed;
public const int PushConstantSize = 128;
public const int PushConstantSize = 144;
public const int FrameUboSize = 16 + 16 + 16 * 4 * 8;
public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass)
@@ -84,8 +84,8 @@ public sealed unsafe class VulkanPipeline : IDisposable
primitiveRestartEnable = 0
};
var viewport = new VkViewport { x = 0, y = 0, width = 0, height = 0, minDepth = 0, maxDepth = 1 };
var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 0, height = 0 } };
var viewport = new VkViewport { x = 0, y = 0, width = 1280, height = 720, minDepth = 0, maxDepth = 1 };
var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 1280, height = 720 } };
VkPipelineViewportStateCreateInfo viewportState;
viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo;
@@ -163,6 +163,15 @@ public sealed unsafe class VulkanPipeline : IDisposable
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = &blendAttachment;
var dynamicStates = stackalloc VkDynamicState[2];
dynamicStates[0] = VkDynamicState.Viewport;
dynamicStates[1] = VkDynamicState.Scissor;
VkPipelineDynamicStateCreateInfo dynamicState = default;
dynamicState.sType = VkStructureType.PipelineDynamicStateCreateInfo;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
var uboBinding = new VkDescriptorSetLayoutBinding
{
binding = 0,
@@ -226,7 +235,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
pipelineInfo.pMultisampleState = &multisampleState;
pipelineInfo.pDepthStencilState = &depthStencilState;
pipelineInfo.pColorBlendState = &colorBlendState;
pipelineInfo.pDynamicState = null;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipeLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
+2 -2
View File
@@ -475,7 +475,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider
};
Vk.vkCmdSetScissor(cmd, 0, 1, &scissor);
VkDescriptorSet ds = _descriptorSets[_currentFrame];
var ds = _descriptorSets[_currentFrame];
Vk.vkCmdBindDescriptorSets(cmd, 0, _pipeline.PipelineLayout, 0, 1, &ds, 0, null);
world.Each((Entity e, ref Transform transform, ref Mesh mesh, ref Material material) =>
@@ -502,7 +502,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider
proj = cam.GetProjectionMatrix();
});
var mvp = model * view * proj;
var mvp = proj * view * model;
var pushData = new byte[VulkanPipeline.PushConstantSize];
fixed (byte* pPush = pushData)
+24 -8
View File
@@ -212,6 +212,12 @@ public enum VkCullModeFlags : uint
FrontAndBack = 0x00000003,
}
public enum VkDynamicState : int
{
Viewport = 0,
Scissor = 1,
}
public enum VkFrontFace : int
{
CounterClockwise = 0,
@@ -241,10 +247,10 @@ public enum VkBlendOp : int
public enum VkColorComponentFlags : uint
{
R = 0x00000010,
G = 0x00000020,
B = 0x00000040,
A = 0x00000080,
R = 0x00000001,
G = 0x00000002,
B = 0x00000004,
A = 0x00000008,
}
public enum VkCompareOp : int
@@ -1169,6 +1175,16 @@ unsafe public struct VkPipelineColorBlendStateCreateInfo
public fixed float blendConstants[4];
}
[StructLayout(LayoutKind.Sequential)]
unsafe public struct VkPipelineDynamicStateCreateInfo
{
public VkStructureType sType;
public void* pNext;
public uint flags;
public uint dynamicStateCount;
public VkDynamicState* pDynamicStates;
}
[StructLayout(LayoutKind.Sequential)]
unsafe public struct VkGraphicsPipelineCreateInfo
{
@@ -1310,11 +1326,11 @@ unsafe public struct VkRenderPassBeginInfo
public VkClearValue* pClearValues;
}
[StructLayout(LayoutKind.Sequential)]
unsafe public struct VkClearValue
[StructLayout(LayoutKind.Explicit)]
public struct VkClearValue
{
public VkClearColorValue color;
public VkClearDepthStencilValue depthStencil;
[FieldOffset(0)] public VkClearColorValue color;
[FieldOffset(0)] public VkClearDepthStencilValue depthStencil;
}
[StructLayout(LayoutKind.Sequential)]