Merge pull request #1186 from SaschaWillems/slang_shaders

[WIP] Add Slang shaders
This commit is contained in:
Sascha Willems 2025-06-03 16:38:27 +02:00 committed by GitHub
commit 0951c1d362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
518 changed files with 12348 additions and 27 deletions

4
.gitignore vendored
View file

@ -240,4 +240,6 @@ android/.idea/**
libs/vulkan/*.so
.vscode/*
.vscode/*
__pycache__**

View file

@ -68,7 +68,7 @@ Once built, examples can be run from the bin directory. The list of available co
-vs, --vsync: Enable V-Sync
-f, --fullscreen: Start in fullscreen mode
-w, --width: Set window width
-s, --shaders: Select shader type to use (glsl or hlsl)
-s, --shaders: Select shader type to use (glsl, slang, hlsl)
-g, --gpu: Select GPU to run on
-gl, --listgpus: Display a list of available Vulkan devices
-b, --benchmark: Run example in benchmark mode
@ -83,7 +83,7 @@ Note that some examples require specific device features, and if you are on a mu
## Shaders
Vulkan consumes shaders in an intermediate representation called SPIR-V. This makes it possible to use different shader languages by compiling them to that bytecode format. The primary shader language used here is [GLSL](shaders/glsl) but most samples also come with [HLSL](shaders/hlsl) shader sources.
Vulkan consumes shaders in an intermediate representation called SPIR-V. This makes it possible to use different shader languages by compiling them to that bytecode format. The primary shader language used here is [GLSL](shaders/glsl), most samples also come with [slang](shaders/slang/) and [HLSL](shaders/hlsl) shader sources, making it easy to compare the differences between those shading languages.
## A note on synchronization

View file

@ -89,6 +89,15 @@ VkResult VulkanExampleBase::createInstance()
}
}
// Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions
if (shaderDir == "slang") {
if (apiVersion < VK_API_VERSION_1_1) {
apiVersion = VK_API_VERSION_1_1;
}
enabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
}
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = name.c_str();

View file

@ -391,8 +391,8 @@ public:
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, color)), // Location 2: Texture coordinates
// Per-Instance attributes
// These are fetched for each instance rendered
vks::initializers::vertexInputAttributeDescription(1, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)), // Location 4: Position
vks::initializers::vertexInputAttributeDescription(1, 5, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)), // Location 5: Scale
vks::initializers::vertexInputAttributeDescription(1, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)), // Location 4: Position
vks::initializers::vertexInputAttributeDescription(1, 4, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)), // Location 5: Scale
};
inputState.pVertexBindingDescriptions = bindingDescriptions.data();
inputState.pVertexAttributeDescriptions = attributeDescriptions.data();

View file

@ -82,6 +82,8 @@ public:
VkDebugReportCallbackEXT debugReportCallback{};
std::string shaderDir = "glsl";
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr)
{
// Create the buffer handle
@ -132,11 +134,19 @@ public:
vks::android::loadVulkanLibrary();
#endif
if (commandLineParser.isSet("shaders")) {
shaderDir = commandLineParser.getValueAsString("shaders", "glsl");
}
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Vulkan headless example";
appInfo.pEngineName = "VulkanExample";
appInfo.apiVersion = VK_API_VERSION_1_0;
// Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions
if (shaderDir == "slang") {
appInfo.apiVersion = VK_API_VERSION_1_1;
}
/*
Vulkan instance creation (without surface extensions)
@ -159,7 +169,7 @@ public:
bool layersAvailable = true;
for (auto layerName : validationLayers) {
bool layerAvailable = false;
for (auto instanceLayer : instanceLayers) {
for (auto& instanceLayer : instanceLayers) {
if (strcmp(instanceLayer.layerName, layerName) == 0) {
layerAvailable = true;
break;
@ -260,8 +270,15 @@ public:
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
std::vector<const char*> deviceExtensions = {};
// Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions
if (shaderDir == "slang") {
deviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
}
#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset)
// SRS - When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
// When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
uint32_t deviceExtCount = 0;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, nullptr);
if (deviceExtCount > 0)
@ -410,10 +427,6 @@ public:
VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(SpecializationData), &specializationData);
std::string shaderDir = "glsl";
if (commandLineParser.isSet("shaders")) {
shaderDir = commandLineParser.getValueAsString("shaders", "glsl");
}
const std::string shadersPath = getShaderBasePath() + shaderDir + "/computeheadless/";
VkPipelineShaderStageCreateInfo shaderStage = {};

View file

@ -97,6 +97,8 @@ public:
VkDebugReportCallbackEXT debugReportCallback{};
std::string shaderDir = "glsl";
uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
@ -163,11 +165,19 @@ public:
vks::android::loadVulkanLibrary();
#endif
if (commandLineParser.isSet("shaders")) {
shaderDir = commandLineParser.getValueAsString("shaders", "glsl");
}
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Vulkan headless example";
appInfo.pEngineName = "VulkanExample";
appInfo.apiVersion = VK_API_VERSION_1_0;
// Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions
if (shaderDir == "slang") {
appInfo.apiVersion = VK_API_VERSION_1_1;
}
/*
Vulkan instance creation (without surface extensions)
@ -190,7 +200,7 @@ public:
bool layersAvailable = true;
for (auto layerName : validationLayers) {
bool layerAvailable = false;
for (auto instanceLayer : instanceLayers) {
for (auto& instanceLayer : instanceLayers) {
if (strcmp(instanceLayer.layerName, layerName) == 0) {
layerAvailable = true;
break;
@ -290,8 +300,15 @@ public:
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
std::vector<const char*> deviceExtensions = {};
// Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions
if (shaderDir == "slang") {
deviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
}
#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset)
// SRS - When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
// When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
uint32_t deviceExtCount = 0;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, nullptr);
if (deviceExtCount > 0)
@ -643,7 +660,6 @@ public:
pipelineCreateInfo.pVertexInputState = &vertexInputState;
std::string shaderDir = "glsl";
if (commandLineParser.isSet("shaders")) {
shaderDir = commandLineParser.getValueAsString("shaders", "glsl");
}

View file

@ -1,10 +1,8 @@
/*
Vulkan Example - Cascaded shadow mapping for directional light sources
Copyright by Sascha Willems - www.saschawillems.de
Copyright (c) 2016-2025 by Sascha Willems - www.saschawillems.de
This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
/*
This example implements projective cascaded shadow mapping. This technique splits up the camera frustum into
multiple frustums with each getting its own full-res shadow map, implemented as a layered depth-only image.
The shader then selects the proper shadow map layer depending on what split of the frustum the depth value
@ -175,7 +173,7 @@ public:
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
// Floor
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
models.terrain.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
// Trees
@ -189,7 +187,7 @@ public:
for (auto& position : positions) {
pushConstBlock.position = glm::vec4(position, 0.0f);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
// This will also bind the texture images to set 1
models.tree.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
}
@ -413,7 +411,7 @@ public:
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debugShadowMap);
PushConstBlock pushConstBlock = {};
pushConstBlock.cascadeIndex = displayDepthMapCascadeIndex;
vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
}
@ -490,7 +488,7 @@ public:
// Shared pipeline layout (scene and depth map debug display)
{
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0);
std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage };
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
@ -500,7 +498,7 @@ public:
// Depth pass pipeline layout
{
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0);
std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage };
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
pipelineLayoutCreateInfo.pushConstantRangeCount = 1;

View file

@ -1,3 +1,9 @@
# Shaders
This folder contains the shaders used by the samples. Source files are available as GLSL and HLSL and also come with precompiled SPIR-V files that are consumed by the samples. To recompile shaders you can use the `compileshaders.py` scripts in the respective folders or any other means that can generate Vulkan SPIR-V from GLSL or HLSL. One such option is [this extension for Visual Studio](https://github.com/SaschaWillems/SPIRV-VSExtension).
This folder contains the shaders used by the samples. Source files are available in GLSL, HLSL and [slang](https://shader-slang.org/) and also come with precompiled SPIR-V files that are consumed by the samples. To recompile shaders you can use the `compileshaders.py` scripts in the respective folders or any other means that can generate Vulkan SPIR-V from GLSL, HLSL or slang. One such option is [this extension for Visual Studio](https://github.com/SaschaWillems/SPIRV-VSExtension).
Note that not all samples may come with all shading language variants. So some samples that have GLSL source files might not come with HLSL and/or slang source files.
A note for using **slang** shaders: These require a different SPIR-V environment than glsl/hlsl. When selecting slang shaders, the base requirement for all samples is raised to at least Vulkan 1.1 with the SPIRV 1.4 extension.
If you want to compile **slang** shaders to SPIR-V, please use the latest release from [here](https://github.com/shader-slang/slang/releases) to get the latest bug fixes and features required for some of the samples.

View file

@ -6,8 +6,8 @@ layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec3 inColor;
// Instanced attributes
layout (location = 4) in vec3 instancePos;
layout (location = 5) in float instanceScale;
layout (location = 3) in vec3 instancePos;
layout (location = 4) in float instanceScale;
layout (binding = 0) uniform UBO
{

View file

@ -6,8 +6,8 @@ struct VSInput
[[vk::location(1)]] float3 Normal : NORMAL0;
[[vk::location(2)]] float3 Color : COLOR0;
// Instanced attributes
[[vk::location(4)]] float3 instancePos : TEXCOORD0;
[[vk::location(5)]] float instanceScale : TEXCOORD1;
[[vk::location(3)]] float3 instancePos : TEXCOORD0;
[[vk::location(4)]] float instanceScale : TEXCOORD1;
};
struct UBO

86
shaders/slang/_rename.py Normal file
View file

@ -0,0 +1,86 @@
# Copyright (C) 2025 by Sascha Willems - www.saschawillems.de
# This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
from shutil import move
# To match required file names to fother shading languages that don't support multiple entry points, shader files may need to be renamed for some samples
def checkRenameFiles(samplename):
mappings = {}
match samplename:
case "displacement":
mappings = {
"displacement.vert.spv": "base.vert.spv",
"displacement.frag.spv": "base.frag.spv",
}
case "geometryshader":
mappings = {
"normaldebug.vert.spv": "base.vert.spv",
"normaldebug.frag.spv": "base.frag.spv",
}
case "graphicspipelinelibrary":
mappings = {
"uber.vert.spv": "shared.vert.spv",
}
case "raytracingbasic":
mappings = {
"raytracingbasic.rchit.spv": "closesthit.rchit.spv",
"raytracingbasic.rmiss.spv": "miss.rmiss.spv",
"raytracingbasic.rgen.spv": "raygen.rgen.spv",
}
case "raytracingcallable":
mappings = {
"raytracingcallable.rchit.spv": "closesthit.rchit.spv",
"raytracingcallable.rmiss.spv": "miss.rmiss.spv",
"raytracingcallable.rgen.spv": "raygen.rgen.spv",
}
case "raytracinggltf":
mappings = {
"raytracinggltf.rchit.spv": "closesthit.rchit.spv",
"raytracinggltf.rmiss.spv": "miss.rmiss.spv",
"raytracinggltf.rgen.spv": "raygen.rgen.spv",
"raytracinggltf.rahit.spv": "anyhit.rahit.spv",
}
case "raytracingpositionfetch":
mappings = {
"raytracingpositionfetch.rchit.spv": "closesthit.rchit.spv",
"raytracingpositionfetch.rmiss.spv": "miss.rmiss.spv",
"raytracingpositionfetch.rgen.spv": "raygen.rgen.spv",
}
case "raytracingreflections":
mappings = {
"raytracingreflections.rchit.spv": "closesthit.rchit.spv",
"raytracingreflections.rmiss.spv": "miss.rmiss.spv",
"raytracingreflections.rgen.spv": "raygen.rgen.spv",
}
case "raytracingsbtdata":
mappings = {
"raytracingsbtdata.rchit.spv": "closesthit.rchit.spv",
"raytracingsbtdata.rmiss.spv": "miss.rmiss.spv",
"raytracingsbtdata.rgen.spv": "raygen.rgen.spv",
}
case "raytracingshadows":
mappings = {
"raytracingshadows.rchit.spv": "closesthit.rchit.spv",
"raytracingshadows.rmiss.spv": "miss.rmiss.spv",
"raytracingshadows.rgen.spv": "raygen.rgen.spv",
}
case "raytracingtextures":
mappings = {
"raytracingtextures.rchit.spv": "closesthit.rchit.spv",
"raytracingtextures.rmiss.spv": "miss.rmiss.spv",
"raytracingtextures.rgen.spv": "raygen.rgen.spv",
"raytracingtextures.rahit.spv": "anyhit.rahit.spv",
}
case "raytracingintersection":
mappings = {
"raytracingintersection.rchit.spv": "closesthit.rchit.spv",
"raytracingintersection.rmiss.spv": "miss.rmiss.spv",
"raytracingintersection.rgen.spv": "raygen.rgen.spv",
"raytracingintersection.rint.spv": "intersection.rint.spv",
}
case "viewportarray":
mappings = {
"scene.geom.spv": "multiview.geom.spv",
}
for x, y in mappings.items():
move(samplename + "\\" + x, samplename + "\\" + y)

Binary file not shown.

View file

@ -0,0 +1,43 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
Sampler2D fontTexture;
struct VSInput
{
float2 Pos : POSITION0;
float2 UV;
float4 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
float4 Color;
};
struct PushConstants
{
float2 scale;
float2 translate;
};
[shader("vertex")]
VSOutput vertexMain(VSInput input, uniform PushConstants pushConstants)
{
VSOutput output;
output.Pos = float4(input.Pos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
output.UV = input.UV;
output.Color = input.Color;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return input.Color * fontTexture.Sample(input.UV);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,45 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float4 Pos;
float2 UV;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Color;
float2 UV;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
Sampler2D colorMapSampler;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.UV = input.UV;
output.Color = input.Color;
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return float4(input.Color, 1);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,63 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
struct UBO
{
float blurScale;
float blurStrength;
};
ConstantBuffer<UBO> ubo;
Sampler2D samplerColor;
[[SpecializationConstant]] const int blurdirection = 0;
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float weight[5];
weight[0] = 0.227027;
weight[1] = 0.1945946;
weight[2] = 0.1216216;
weight[3] = 0.054054;
weight[4] = 0.016216;
float2 textureSize;
samplerColor.GetDimensions(textureSize.x, textureSize.y);
float2 tex_offset = 1.0 / textureSize * ubo.blurScale; // gets size of single texel
float3 result = samplerColor.Sample(input.UV).rgb * weight[0]; // current fragment's contribution
for(int i = 1; i < 5; ++i)
{
if (blurdirection == 1)
{
// H
result += samplerColor.Sample(input.UV + float2(tex_offset.x * i, 0.0)).rgb * weight[i] * ubo.blurScale;
result += samplerColor.Sample(input.UV - float2(tex_offset.x * i, 0.0)).rgb * weight[i] * ubo.blurScale;
}
else
{
// V
result += samplerColor.Sample(input.UV + float2(0.0, tex_offset.y * i)).rgb * weight[i] * ubo.blurScale;
result += samplerColor.Sample(input.UV - float2(0.0, tex_offset.y * i)).rgb * weight[i] * ubo.blurScale;
}
}
return float4(result, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,70 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float4 Pos;
float2 UV;
float3 Color;
float3 Normal;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float2 UV;
float3 Color;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
Sampler2D colorMapSampler;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Normal = input.Normal;
output.Color = input.Color;
output.UV = input.UV;
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
float3 lightPos = float3(-5.0, -5.0, 0.0);
float4 pos = mul(ubo.view, mul(ubo.model, input.Pos));
output.Normal = mul((float4x3)mul(ubo.view, ubo.model), input.Normal).xyz;
output.LightVec = lightPos - pos.xyz;
output.ViewVec = -pos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 ambient = float3(0.0f, 0.0f, 0.0f);
// Adjust light calculations for glow color
if ((input.Color.r >= 0.9) || (input.Color.g >= 0.9) || (input.Color.b >= 0.9))
{
ambient = input.Color * 0.25;
}
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 diffuse = max(dot(N, L), 0.0) * input.Color;
float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.75f, 0.75f, 0.75f);
return float4(ambient + diffuse + specular, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,41 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
}
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 UVW;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
SamplerCube samplerCubeMap;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.UVW = input.Pos;
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return samplerCubeMap.Sample(input.UVW);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,54 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
Sampler2D samplerColorMap;
struct VSInput
{
float3 Pos;
float3 Normal;
float2 UV;
float3 Color;
};
struct MatrixReference {
float4x4 matrix;
};
struct PushConsts {
// Pointer to the buffer with the scene's MVP matrix
ConstBufferPointer<MatrixReference> sceneDataReference;
// Pointer to the buffer for the data for each model
ConstBufferPointer<MatrixReference> modelDataReference;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float2 UV;
};
[shader("vertex")]
VSOutput vertexMain(VSInput input, uniform PushConsts pushConstants)
{
MatrixReference sceneData = pushConstants.sceneDataReference.get();
MatrixReference modelData = pushConstants.modelDataReference.get();
VSOutput output;
output.Normal = input.Normal;
output.Color = input.Color;
output.UV = input.UV;
output.Pos = mul(sceneData.matrix, mul(modelData.matrix, float4(input.Pos.xyz, 1.0)));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return samplerColorMap.Sample(input.UV) * float4(input.Color, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,132 @@
# Copyright (C) 2025 by Sascha Willems - www.saschawillems.de
# This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
import argparse
import fileinput
import os
import subprocess
import sys
from _rename import *
parser = argparse.ArgumentParser(description='Compile all slang shaders')
parser.add_argument('--slangc', type=str, help='path to slangc executable')
parser.add_argument('--sample', type=str, help='can be used to compile shaders for a single sample only')
args = parser.parse_args()
def findCompiler():
def isExe(path):
return os.path.isfile(path) and os.access(path, os.X_OK)
if args.slangc != None and isExe(args.slangc):
return args.slangc
exe_name = "slangc"
if os.name == "nt":
exe_name += ".exe"
for exe_dir in os.environ["PATH"].split(os.pathsep):
full_path = os.path.join(exe_dir, exe_name)
if isExe(full_path):
return full_path
sys.exit("Could not find slangc executable on PATH, and was not specified with --slangc")
def getShaderStages(filename):
stages = []
with open(filename) as f:
filecontent = f.read()
if '[shader("vertex")]' in filecontent:
stages.append("vertex")
if '[shader("fragment")]' in filecontent:
stages.append("fragment")
if '[shader("raygeneration")]' in filecontent:
stages.append("raygeneration")
if '[shader("miss")]' in filecontent:
stages.append("miss")
if '[shader("closesthit")]' in filecontent:
stages.append("closesthit")
if '[shader("callable")]' in filecontent:
stages.append("callable")
if '[shader("intersection")]' in filecontent:
stages.append("intersection")
if '[shader("anyhit")]' in filecontent:
stages.append("anyhit")
if '[shader("compute")]' in filecontent:
stages.append("compute")
if '[shader("amplification")]' in filecontent:
stages.append("amplification")
if '[shader("mesh")]' in filecontent:
stages.append("mesh")
if '[shader("geometry")]' in filecontent:
stages.append("geometry")
if '[shader("hull")]' in filecontent:
stages.append("hull")
if '[shader("domain")]' in filecontent:
stages.append("domain")
f.close()
return stages
compiler_path = findCompiler()
print("Found slang compiler at %s", compiler_path)
compile_single_sample = ""
if args.sample != None:
compile_single_sample = args.sample
if (not os.path.isdir(compile_single_sample)):
print("ERROR: No directory found with name %s" % compile_single_sample)
exit(-1)
dir_path = os.path.dirname(os.path.realpath(__file__))
dir_path = dir_path.replace('\\', '/')
for root, dirs, files in os.walk(dir_path):
folder_name = os.path.basename(root)
if (compile_single_sample != "" and folder_name != compile_single_sample):
continue
for file in files:
if file.endswith(".slang"):
input_file = os.path.join(root, file)
# Slang can store multiple shader stages in a single file, we need to split into separate SPIR-V files for the sample framework
stages = getShaderStages(input_file)
print("Compiling %s" % input_file)
output_base_file_name = input_file
for stage in stages:
entry_point = stage + "Main"
output_ext = ""
match stage:
case "vertex":
output_ext = ".vert"
case "fragment":
output_ext = ".frag"
case "raygeneration":
output_ext = ".rgen"
case "miss":
output_ext = ".rmiss"
case "closesthit":
output_ext = ".rchit"
case "callable":
output_ext = ".rcall"
case "intersection":
output_ext = ".rint"
case "anyhit":
output_ext = ".rahit"
case "compute":
output_ext = ".comp"
case "mesh":
output_ext = ".mesh"
case "amplification":
output_ext = ".task"
case "geometry":
output_ext = ".geom"
case "hull":
output_ext = ".tesc"
case "domain":
output_ext = ".tese"
output_file = output_base_file_name + output_ext + ".spv"
output_file = output_file.replace(".slang", "")
print(output_file)
res = subprocess.call("%s %s -profile spirv_1_4 -matrix-layout-column-major -target spirv -o %s -entry %s -stage %s -warnings-disable 39001" % (compiler_path, input_file, output_file, entry_point, stage), shell=True)
if res != 0:
print("Error %s", res)
sys.exit(res)
checkRenameFiles(folder_name)

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,190 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float2 UV;
float3 Normal;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
float3 Normal;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 modelview;
float4 lightPos;
};
[[vk::binding(0,0)]] ConstantBuffer<UBO> ubo;
[[vk::binding(1,0)]] Sampler2D samplerColor;
struct Particle {
float4 pos;
float4 vel;
float4 uv;
float4 normal;
};
[[vk::binding(0,0)]] StructuredBuffer<Particle> particleIn;
[[vk::binding(1,0)]] RWStructuredBuffer<Particle> particleOut;
struct UBOCompute
{
float deltaT;
float particleMass;
float springStiffness;
float damping;
float restDistH;
float restDistV;
float restDistD;
float sphereRadius;
float4 spherePos;
float4 gravity;
int2 particleCount;
};
[[vk::binding(2, 0)]] ConstantBuffer<UBOCompute> params;
float3 springForce(float3 p0, float3 p1, float restDist)
{
float3 dist = p0 - p1;
return normalize(dist) * params.springStiffness * (length(dist) - restDist);
}
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.UV = input.UV;
output.Normal = input.Normal.xyz;
float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
output.Pos = mul(ubo.projection, eyePos);
float4 pos = float4(input.Pos, 1.0);
float3 lPos = ubo.lightPos.xyz;
output.LightVec = lPos - pos.xyz;
output.ViewVec = -pos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 color = samplerColor.Sample(input.UV).rgb;
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 diffuse = max(dot(N, L), 0.15) * float3(1, 1, 1);
float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.2, 0.2, 0.2);
return float4(diffuse * color.rgb + specular, 1.0);
}
[shader("compute")]
[numthreads(10, 10, 1)]
void computeMain(uint3 id: SV_DispatchThreadID, uniform uint calculateNormals)
{
uint index = id.y * params.particleCount.x + id.x;
if (index > params.particleCount.x * params.particleCount.y)
return;
// Initial force from gravity
float3 force = params.gravity.xyz * params.particleMass;
float3 pos = particleIn[index].pos.xyz;
float3 vel = particleIn[index].vel.xyz;
// Spring forces from neighboring particles
// left
if (id.x > 0) {
force += springForce(particleIn[index-1].pos.xyz, pos, params.restDistH);
}
// right
if (id.x < params.particleCount.x - 1) {
force += springForce(particleIn[index + 1].pos.xyz, pos, params.restDistH);
}
// upper
if (id.y < params.particleCount.y - 1) {
force += springForce(particleIn[index + params.particleCount.x].pos.xyz, pos, params.restDistV);
}
// lower
if (id.y > 0) {
force += springForce(particleIn[index - params.particleCount.x].pos.xyz, pos, params.restDistV);
}
// upper-left
if ((id.x > 0) && (id.y < params.particleCount.y - 1)) {
force += springForce(particleIn[index + params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
}
// lower-left
if ((id.x > 0) && (id.y > 0)) {
force += springForce(particleIn[index - params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
}
// upper-right
if ((id.x < params.particleCount.x - 1) && (id.y < params.particleCount.y - 1)) {
force += springForce(particleIn[index + params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
}
// lower-right
if ((id.x < params.particleCount.x - 1) && (id.y > 0)) {
force += springForce(particleIn[index - params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
}
force += (-params.damping * vel);
// Integrate
float3 f = force * (1.0 / params.particleMass);
particleOut[index].pos = float4(pos + vel * params.deltaT + 0.5 * f * params.deltaT * params.deltaT, 1.0);
particleOut[index].vel = float4(vel + f * params.deltaT, 0.0);
// Sphere collision
float3 sphereDist = particleOut[index].pos.xyz - params.spherePos.xyz;
if (length(sphereDist) < params.sphereRadius + 0.01) {
// If the particle is inside the sphere, push it to the outer radius
particleOut[index].pos.xyz = params.spherePos.xyz + normalize(sphereDist) * (params.sphereRadius + 0.01);
// Cancel out velocity
particleOut[index].vel = float4(0, 0, 0, 0);
}
// Normals
if (calculateNormals == 1) {
float3 normal = float3(0, 0, 0);
float3 a, b, c;
if (id.y > 0) {
if (id.x > 0) {
a = particleIn[index - 1].pos.xyz - pos;
b = particleIn[index - params.particleCount.x - 1].pos.xyz - pos;
c = particleIn[index - params.particleCount.x].pos.xyz - pos;
normal += cross(a,b) + cross(b,c);
}
if (id.x < params.particleCount.x - 1) {
a = particleIn[index - params.particleCount.x].pos.xyz - pos;
b = particleIn[index - params.particleCount.x + 1].pos.xyz - pos;
c = particleIn[index + 1].pos.xyz - pos;
normal += cross(a,b) + cross(b,c);
}
}
if (id.y < params.particleCount.y - 1) {
if (id.x > 0) {
a = particleIn[index + params.particleCount.x].pos.xyz - pos;
b = particleIn[index + params.particleCount.x - 1].pos.xyz - pos;
c = particleIn[index - 1].pos.xyz - pos;
normal += cross(a,b) + cross(b,c);
}
if (id.x < params.particleCount.x - 1) {
a = particleIn[index + 1].pos.xyz - pos;
b = particleIn[index + params.particleCount.x + 1].pos.xyz - pos;
c = particleIn[index + params.particleCount.x].pos.xyz - pos;
normal += cross(a,b) + cross(b,c);
}
}
particleOut[index].normal = float4(normalize(normal), 0.0f);
}
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,55 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float2 UV;
float3 Normal;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 modelview;
float4 lightPos;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
output.Pos = mul(ubo.projection, eyePos);
float4 pos = float4(input.Pos, 1.0);
float3 lPos = ubo.lightPos.xyz;
output.LightVec = lPos - pos.xyz;
output.ViewVec = -pos.xyz;
output.Normal = input.Normal;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 color = float3(0.5, 0.5, 0.5);
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 diffuse = max(dot(N, L), 0.15);
float3 specular = pow(max(dot(R, V), 0.0), 32.0);
return float4(diffuse * color.rgb + specular, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,115 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
#define MAX_LOD_LEVEL_COUNT 6
[[SpecializationConstant]] const int MAX_LOD_LEVEL = 5;
struct InstanceData
{
float3 pos;
float scale;
};
StructuredBuffer<InstanceData> instances;
// Same layout as VkDrawIndexedIndirectCommand
struct IndexedIndirectCommand
{
uint indexCount;
uint instanceCount;
uint firstIndex;
uint vertexOffset;
uint firstInstance;
};
RWStructuredBuffer<IndexedIndirectCommand> indirectDraws;
// Binding 2: Uniform block object with matrices
struct UBO
{
float4x4 projection;
float4x4 modelview;
float4 cameraPos;
float4 frustumPlanes[6];
};
ConstantBuffer<UBO> ubo;
// Binding 3: Indirect draw stats
struct UBOOut
{
uint drawCount;
uint lodCount[MAX_LOD_LEVEL_COUNT];
};
RWStructuredBuffer<UBOOut> uboOut;
// Binding 4: level-of-detail information
struct LOD
{
uint firstIndex;
uint indexCount;
float distance;
float _pad0;
};
StructuredBuffer<LOD> lods;
bool frustumCheck(float4 pos, float radius)
{
// Check sphere against frustum planes
for (int i = 0; i < 6; i++)
{
if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
{
return false;
}
}
return true;
}
[shader("compute")]
[numthreads(16, 1, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
uint idx = GlobalInvocationID.x;
uint temp;
// Clear stats on first invocation
if (idx == 0)
{
InterlockedExchange(uboOut[0].drawCount, 0, temp);
for (uint i = 0; i < MAX_LOD_LEVEL + 1; i++)
{
InterlockedExchange(uboOut[0].lodCount[i], 0, temp);
}
}
float4 pos = float4(instances[idx].pos.xyz, 1.0);
// Check if object is within current viewing frustum
if (frustumCheck(pos, 1.0))
{
indirectDraws[idx].instanceCount = 1;
// Increase number of indirect draw counts
InterlockedAdd(uboOut[0].drawCount, 1, temp);
// Select appropriate LOD level based on distance to camera
uint lodLevel = MAX_LOD_LEVEL;
for (uint i = 0; i < MAX_LOD_LEVEL; i++)
{
if (distance(instances[idx].pos.xyz, ubo.cameraPos.xyz) < lods[i].distance)
{
lodLevel = i;
break;
}
}
indirectDraws[idx].firstIndex = lods[lodLevel].firstIndex;
indirectDraws[idx].indexCount = lods[lodLevel].indexCount;
// Update stats
InterlockedAdd(uboOut[0].lodCount[lodLevel], 1, temp);
}
else
{
indirectDraws[idx].instanceCount = 0;
}
}

Binary file not shown.

View file

@ -0,0 +1,56 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float4 Pos : POSITION0;
float3 Normal;
float3 Color;
// Instanced attributes
float3 instancePos;
float instanceScale;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 modelview;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Color = input.Color;
output.Normal = input.Normal;
float4 pos = float4((input.Pos.xyz * input.instanceScale) + input.instancePos, 1.0);
output.Pos = mul(ubo.projection, mul(ubo.modelview, pos));
float4 wPos = mul(ubo.modelview, float4(pos.xyz, 1.0));
float4 lPos = float4(0.0, 10.0, 50.0, 1.0);
output.LightVec = lPos.xyz - pos.xyz;
output.ViewVec = -pos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 ambient = float3(0.25, 0.25, 0.25);
float3 diffuse = max(dot(N, L), 0.0).xxx;
return float4((ambient + diffuse) * input.Color, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,34 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
RWStructuredBuffer<uint> values;
[[SpecializationConstant]] const uint BUFFER_ELEMENTS = 32;
uint fibonacci(uint n) {
if(n <= 1){
return n;
}
uint curr = 1;
uint prev = 1;
for(uint i = 2; i < n; ++i) {
uint temp = curr;
curr += prev;
prev = temp;
}
return curr;
}
[numthreads(1, 1, 1)]
[shader("compute")]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
uint index = GlobalInvocationID.x;
if (index >= BUFFER_ELEMENTS)
return;
values[index] = fibonacci(values[index]);
}

Binary file not shown.

View file

@ -0,0 +1,56 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float4 Pos;
float4 Vel;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float PSize : SV_PointSize;
float GradientPos;
float2 CenterPos;
float PointSize;
};
Sampler2D samplerColorMap;
Sampler2D samplerGradientRamp;
struct UBO
{
float4x4 projection;
float4x4 modelview;
float2 screendim;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
const float spriteSize = 0.005 * input.Pos.w; // Point size influenced by mass (stored in input.Pos.w);
float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
float4 projectedCorner = mul(ubo.projection, float4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w));
output.PSize = output.PointSize = clamp(ubo.screendim.x * projectedCorner.x / projectedCorner.w, 1.0, 128.0);
output.Pos = mul(ubo.projection, eyePos);
output.CenterPos = ((output.Pos.xy / output.Pos.w) + 1.0) * 0.5 * ubo.screendim;
output.GradientPos = input.Vel.w;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 color = samplerGradientRamp.Sample(float2(input.GradientPos, 0.0)).rgb;
float2 PointCoord = (input.Pos.xy - input.CenterPos.xy) / input.PointSize + 0.5;
return float4(samplerColorMap.Sample(PointCoord).rgb * color, 1);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,77 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct Particle
{
float4 pos;
float4 vel;
};
// Binding 0 : Position storage buffer
RWStructuredBuffer<Particle> particles;
struct UBO
{
float deltaT;
int particleCount;
float gravity;
float power;
float soften;
};
ConstantBuffer<UBO> ubo;
#define MAX_SHARED_DATA_SIZE 1024
[[SpecializationConstant]] const int SHARED_DATA_SIZE = 512;
[[SpecializationConstant]] const float GRAVITY = 0.002;
[[SpecializationConstant]] const float POWER = 0.75;
[[SpecializationConstant]] const float SOFTEN = 0.0075;
// Share data between computer shader invocations to speed up caluclations
groupshared float4 sharedData[MAX_SHARED_DATA_SIZE];
[shader("compute")]
[numthreads(256, 1, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID, uint3 LocalInvocationID : SV_GroupThreadID)
{
// Current SSBO index
uint index = GlobalInvocationID.x;
if (index >= ubo.particleCount)
return;
float4 position = particles[index].pos;
float4 velocity = particles[index].vel;
float4 acceleration = float4(0, 0, 0, 0);
for (int i = 0; i < ubo.particleCount; i += SHARED_DATA_SIZE)
{
if (i + LocalInvocationID.x < ubo.particleCount)
{
sharedData[LocalInvocationID.x] = particles[i + LocalInvocationID.x].pos;
}
else
{
sharedData[LocalInvocationID.x] = float4(0, 0, 0, 0);
}
GroupMemoryBarrierWithGroupSync();
for (int j = 0; j < 256; j++)
{
float4 other = sharedData[j];
float3 len = other.xyz - position.xyz;
acceleration.xyz += ubo.gravity * len * other.w / pow(dot(len, len) + ubo.soften, ubo.power);
}
GroupMemoryBarrierWithGroupSync();
}
particles[index].vel.xyz += ubo.deltaT * acceleration.xyz;
// Gradient texture position
particles[index].vel.w += 0.1 * ubo.deltaT;
if (particles[index].vel.w > 1.0) {
particles[index].vel.w -= 1.0;
}
}

Binary file not shown.

View file

@ -0,0 +1,31 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct Particle
{
float4 pos;
float4 vel;
};
// Binding 0 : Position storage buffer
RWStructuredBuffer<Particle> particles;
struct UBO
{
float deltaT;
int particleCount;
};
ConstantBuffer<UBO> ubo;
[shader("compute")]
[numthreads(256, 1, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
int index = int(GlobalInvocationID.x);
float4 position = particles[index].pos;
float4 velocity = particles[index].vel;
position += ubo.deltaT * velocity;
particles[index].pos = position;
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,120 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
[[vk::binding(0, 0)]] Sampler2D samplerColorMap;
[[vk::binding(1, 0)]] Sampler2D samplerGradientRamp;
struct VSInput
{
float2 Pos : POSITION0;
float4 GradientPos : POSITION1;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float PointSize : SV_PointSize;
float4 Color : COLOR0;
float GradientPos : POSITION0;
};
struct Particle
{
float2 pos;
float2 vel;
float4 gradientPos;
};
// Binding 0 : Position storage buffer
[[vk::binding(0, 0)]] RWStructuredBuffer<Particle> particles;
struct UBO
{
float deltaT;
float destX;
float destY;
int particleCount;
};
[[vk::binding(1, 0)]] ConstantBuffer<UBO> ubo;
struct PushConsts
{
float2 screendim;
};
float2 attraction(float2 pos, float2 attractPos)
{
float2 delta = attractPos - pos;
const float damp = 0.5;
float dDampedDot = dot(delta, delta) + damp;
float invDist = 1.0f / sqrt(dDampedDot);
float invDistCubed = invDist*invDist*invDist;
return delta * invDistCubed * 0.0035;
}
float2 repulsion(float2 pos, float2 attractPos)
{
float2 delta = attractPos - pos;
float targetDistance = sqrt(dot(delta, delta));
return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
}
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.PointSize = 8.0;
output.Color = float4(0.035, 0.035, 0.035, 0.035);
output.GradientPos = input.GradientPos.x;
output.Pos = float4(input.Pos.xy, 1.0, 1.0);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input, float2 pointCoord: SV_PointCoord)
{
float3 color = samplerGradientRamp.Sample(float2(input.GradientPos, 0.0)).rgb;
return float4(samplerColorMap.Sample(pointCoord).rgb * color, 1.0);
}
[shader("compute")]
[numthreads(256, 1, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
// Current SSBO index
uint index = GlobalInvocationID.x;
// Don't try to write beyond particle count
if (index >= ubo.particleCount) {
return;
}
// Read position and velocity
float2 vVel = particles[index].vel.xy;
float2 vPos = particles[index].pos.xy;
float2 destPos = float2(ubo.destX, ubo.destY);
float2 delta = destPos - vPos;
float targetDistance = sqrt(dot(delta, delta));
vVel += repulsion(vPos, destPos.xy) * 0.05;
// Move by velocity
vPos += vVel * ubo.deltaT;
// collide with boundary
if ((vPos.x < -1.0) || (vPos.x > 1.0) || (vPos.y < -1.0) || (vPos.y > 1.0)) {
vVel = (-vVel * 0.1) + attraction(vPos, destPos) * 12;
} else {
particles[index].pos.xy = vPos;
}
// Write back
particles[index].vel.xy = vVel;
particles[index].gradientPos.x += 0.02 * ubo.deltaT;
if (particles[index].gradientPos.x > 1.0) {
particles[index].gradientPos.x -= 1.0;
}
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,258 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org)
#define EPSILON 0.0001
#define MAXLEN 1000.0
#define SHADOW 0.5
#define RAYBOUNCES 2
#define REFLECTIONS true
#define REFLECTIONSTRENGTH 0.4
#define REFLECTIONFALLOFF 0.5
#define SceneObjectTypeSphere 0
#define SceneObjectTypePlane 1
RWTexture2D<float4> resultImage;
struct Camera
{
float3 pos;
float3 lookat;
float fov;
};
struct UBO
{
float3 lightPos;
float aspectRatio;
float4 fogColor;
Camera camera;
float4x4 rotMat;
};
ConstantBuffer<UBO> ubo;
struct SceneObject
{
float4 objectProperties;
float3 diffuse;
float specular;
int id;
int objectType;
};
StructuredBuffer<SceneObject> sceneObjects;
void reflectRay(inout float3 rayD, in float3 mormal)
{
rayD = rayD + 2.0 * -dot(mormal, rayD) * mormal;
}
// Lighting =========================================================
float lightDiffuse(float3 normal, float3 lightDir)
{
return clamp(dot(normal, lightDir), 0.1, 1.0);
}
float lightSpecular(float3 normal, float3 lightDir, float specularFactor)
{
float3 viewVec = normalize(ubo.camera.pos);
float3 halfVec = normalize(lightDir + viewVec);
return pow(clamp(dot(normal, halfVec), 0.0, 1.0), specularFactor);
}
// Sphere ===========================================================
float sphereIntersect(in float3 rayO, in float3 rayD, in SceneObject sphere)
{
float3 oc = rayO - sphere.objectProperties.xyz;
float b = 2.0 * dot(oc, rayD);
float c = dot(oc, oc) - sphere.objectProperties.w * sphere.objectProperties.w;
float h = b*b - 4.0*c;
if (h < 0.0)
{
return -1.0;
}
float t = (-b - sqrt(h)) / 2.0;
return t;
}
float3 sphereNormal(in float3 pos, in SceneObject sphere)
{
return (pos - sphere.objectProperties.xyz) / sphere.objectProperties.w;
}
// Plane ===========================================================
float planeIntersect(float3 rayO, float3 rayD, SceneObject plane)
{
float d = dot(rayD, plane.objectProperties.xyz);
if (d == 0.0)
return 0.0;
float t = -(plane.objectProperties.w + dot(rayO, plane.objectProperties.xyz)) / d;
if (t < 0.0)
return 0.0;
return t;
}
int intersect(in float3 rayO, in float3 rayD, inout float resT)
{
int id = -1;
float t = MAXLEN;
uint sceneObjectsLength;
uint sceneObjectsStride;
sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
for (int i = 0; i < sceneObjectsLength; i++) {
// Sphere
if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
t = sphereIntersect(rayO, rayD, sceneObjects[i]);
}
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
t = planeIntersect(rayO, rayD, sceneObjects[i]);
}
if ((t > EPSILON) && (t < resT))
{
id = sceneObjects[i].id;
resT = t;
}
}
return id;
}
float calcShadow(in float3 rayO, in float3 rayD, in int objectId, inout float t)
{
uint sceneObjectsLength;
uint sceneObjectsStride;
sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
for (int i = 0; i < sceneObjectsLength; i++) {
if (sceneObjects[i].id == objectId)
continue;
float tLoc = MAXLEN;
// Sphere
if (sceneObjects[i].objectType == SceneObjectTypeSphere)
{
tLoc = sphereIntersect(rayO, rayD, sceneObjects[i]);
}
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane)
{
tLoc = planeIntersect(rayO, rayD, sceneObjects[i]);
}
if ((tLoc > EPSILON) && (tLoc < t))
{
t = tLoc;
return SHADOW;
}
}
return 1.0;
}
float3 fog(in float t, in float3 color)
{
return lerp(color, ubo.fogColor.rgb, clamp(sqrt(t*t)/20.0, 0.0, 1.0));
}
float3 renderScene(inout float3 rayO, inout float3 rayD, inout int id)
{
float3 color = float3(0, 0, 0);
float t = MAXLEN;
// Get intersected object ID
int objectID = intersect(rayO, rayD, t);
if (objectID == -1)
{
return color;
}
float3 pos = rayO + t * rayD;
float3 lightVec = normalize(ubo.lightPos - pos);
float3 normal;
uint sceneObjectsLength;
uint sceneObjectsStride;
sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride);
for (int i = 0; i < sceneObjectsLength; i++) {
if (objectID == sceneObjects[i].id)
{
// Sphere
if (sceneObjects[i].objectType == SceneObjectTypeSphere) {
normal = sphereNormal(pos, sceneObjects[i]);
}
// Plane
if (sceneObjects[i].objectType == SceneObjectTypePlane) {
normal = sceneObjects[i].objectProperties.xyz;
}
// Lighting
float diffuse = lightDiffuse(normal, lightVec);
float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular);
color = diffuse * sceneObjects[i].diffuse + specular;
}
}
if (id == -1)
return color;
id = objectID;
// Shadows
t = length(ubo.lightPos - pos);
color *= calcShadow(pos, lightVec, id, t);
// Fog
color = fog(t, color);
// Reflect ray for next render pass
reflectRay(rayD, normal);
rayO = pos;
return color;
}
[shader("compute")]
[numthreads(16, 16, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
int2 dim;
resultImage.GetDimensions(dim.x, dim.y);
float2 uv = float2(GlobalInvocationID.xy) / dim;
float3 rayO = ubo.camera.pos;
float3 rayD = normalize(float3((-1.0 + 2.0 * uv) * float2(ubo.aspectRatio, 1.0), -1.0));
// Basic color path
int id = 0;
float3 finalColor = renderScene(rayO, rayD, id);
// Reflection
if (REFLECTIONS)
{
float reflectionStrength = REFLECTIONSTRENGTH;
for (int i = 0; i < RAYBOUNCES; i++)
{
float3 reflectionColor = renderScene(rayO, rayD, id);
finalColor = (1.0 - reflectionStrength) * finalColor + reflectionStrength * lerp(reflectionColor, finalColor, 1.0 - reflectionStrength);
reflectionStrength *= REFLECTIONFALLOFF;
}
}
resultImage[int2(GlobalInvocationID.xy)] = float4(finalColor, 0.0);
}

Binary file not shown.

View file

@ -0,0 +1,28 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
Sampler2D samplerColor;
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f + -1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return samplerColor.Sample(float2(input.UV.x, 1.0 - input.UV.y));
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,34 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
import shared;
[shader("compute")]
[numthreads(16, 16, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
float imageData[9];
// Fetch neighbouring texels
int n = -1;
for (int i=-1; i<2; ++i)
{
for(int j=-1; j<2; ++j)
{
n++;
float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
imageData[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
}
}
float kernel[9];
kernel[0] = -1.0/8.0; kernel[1] = -1.0/8.0; kernel[2] = -1.0/8.0;
kernel[3] = -1.0/8.0; kernel[4] = 1.0; kernel[5] = -1.0/8.0;
kernel[6] = -1.0/8.0; kernel[7] = -1.0/8.0; kernel[8] = -1.0/8.0;
float4 res = float4(conv(kernel, imageData, 0.1, 0.0).xxx, 1.0);
resultImage[int2(GlobalInvocationID.xy)] = res;
}

Binary file not shown.

View file

@ -0,0 +1,34 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
import shared;
[shader("compute")]
[numthreads(16, 16, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
float imageData[9];
// Fetch neighbouring texels
int n = -1;
for (int i=-1; i<2; ++i)
{
for(int j=-1; j<2; ++j)
{
n++;
float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
imageData[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
}
}
float kernel[9];
kernel[0] = -1.0; kernel[1] = 0.0; kernel[2] = 0.0;
kernel[3] = 0.0; kernel[4] = -1.0; kernel[5] = 0.0;
kernel[6] = 0.0; kernel[7] = 0.0; kernel[8] = 2.0;
float4 res = float4(conv(kernel, imageData, 1.0, 0.50).xxx, 1.0);
resultImage[int2(GlobalInvocationID.xy)] = res;
}

View file

@ -0,0 +1,20 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
module shared;
public Texture2D inputImage;
public RWTexture2D<float4> resultImage;
public float conv(in float kernel[9], in float data[9], in float denom, in float offset)
{
float res = 0.0;
for (int i=0; i<9; ++i)
{
res += kernel[i] * data[i];
}
return saturate(res/denom + offset);
}

Binary file not shown.

View file

@ -0,0 +1,43 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
import shared;
[shader("compute")]
[numthreads(16, 16, 1)]
void computeMain(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
float r[9];
float g[9];
float b[9];
// Fetch neighbouring texels
int n = -1;
for (int i=-1; i<2; ++i)
{
for(int j=-1; j<2; ++j)
{
n++;
float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
r[n] = rgb.r;
g[n] = rgb.g;
b[n] = rgb.b;
}
}
float kernel[9];
kernel[0] = -1.0; kernel[1] = -1.0; kernel[2] = -1.0;
kernel[3] = -1.0; kernel[4] = 9.0; kernel[5] = -1.0;
kernel[6] = -1.0; kernel[7] = -1.0; kernel[8] = -1.0;
float4 res = float4(
conv(kernel, r, 1.0, 0.0),
conv(kernel, g, 1.0, 0.0),
conv(kernel, b, 1.0, 0.0),
1.0);
resultImage[int2(GlobalInvocationID.xy)] = res;
}

Binary file not shown.

View file

@ -0,0 +1,41 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float2 UV;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
struct UBO
{
float4x4 projection;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
Sampler2D samplerColor;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.UV = input.UV;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return samplerColor.Sample(input.UV);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,66 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float3 Normal;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
struct Node
{
float4x4 transform;
};
[[vk::binding(0,1)]] ConstantBuffer<Node> node;
[shader("vertex")]
VSOutput vertexMain(VSInput input, uniform float4 baseColorFactor)
{
VSOutput output;
output.Normal = input.Normal;
output.Color = baseColorFactor.rgb;
float4 pos = float4(input.Pos, 1.0);
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, mul(node.transform, pos))));
output.Normal = mul((float4x3)mul(ubo.view, mul(ubo.model, node.transform)), input.Normal).xyz;
float4 localpos = mul(ubo.view, mul(ubo.model, mul(node.transform, pos)));
float3 lightPos = float3(10.0f, -10.0f, 10.0f);
output.LightVec = lightPos.xyz - localpos.xyz;
output.ViewVec = -localpos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 ambient = float3(0.1, 0.1, 0.1);
float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,28 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
Sampler2D samplerColor;
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return samplerColor.Sample(input.UV);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,38 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Color;
};
struct UBO
{
float4x4 projection;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Color = input.Color;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos, 1.0)));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return float4(input.Color, 1);
}

Binary file not shown.

View file

@ -0,0 +1,11 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
[shader("fragment")]
float4 fragmentMain()
{
return float4(1.0, 1.0, 1.0, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,73 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float3 Normal;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 model;
float4 lightPos;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Normal = input.Normal;
output.Color = input.Color;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
// Output the vertex position using debug printf
printf("Position = %v4f", pos);
output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
float3 lPos = mul((float4x3)ubo.model, ubo.lightPos.xyz).xyz;
output.LightVec = lPos - pos.xyz;
output.ViewVec = -pos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
// Desaturate color
float3 color = float3(lerp(input.Color, dot(float3(0.2126, 0.7152, 0.0722), input.Color).xxx, 0.65));
// High ambient colors because mesh materials are pretty dark
float3 ambient = color * float3(1.0, 1.0, 1.0);
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 diffuse = max(dot(N, L), 0.0) * color;
float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
float intensity = dot(N, L);
float shade = 1.0;
shade = intensity < 0.5 ? 0.75 : shade;
shade = intensity < 0.35 ? 0.6 : shade;
shade = intensity < 0.25 ? 0.5 : shade;
shade = intensity < 0.1 ? 0.25 : shade;
return float4(input.Color * 3.0 * shade, 1);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,41 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float4 Pos;
float3 Normal;
float2 UV;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Color;
};
struct UBO
{
float4x4 projection;
float4x4 model;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Color = input.Color;
output.Pos = mul(ubo.projection, mul(ubo.model, input.Pos));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
return float4(input.Color, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,55 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
[[vk::binding(1, 0)]] Sampler2D samplerColor;
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * float2(2.0f, 2.0f) + float2(-1.0f, -1.0f), 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
// Single pass gauss blur
const float2 texOffset = float2(0.01, 0.01);
float2 tc0 = input.UV + float2(-texOffset.x, -texOffset.y);
float2 tc1 = input.UV + float2( 0.0, -texOffset.y);
float2 tc2 = input.UV + float2(+texOffset.x, -texOffset.y);
float2 tc3 = input.UV + float2(-texOffset.x, 0.0);
float2 tc4 = input.UV + float2( 0.0, 0.0);
float2 tc5 = input.UV + float2(+texOffset.x, 0.0);
float2 tc6 = input.UV + float2(-texOffset.x, +texOffset.y);
float2 tc7 = input.UV + float2( 0.0, +texOffset.y);
float2 tc8 = input.UV + float2(+texOffset.x, +texOffset.y);
float4 col0 = samplerColor.Sample(tc0);
float4 col1 = samplerColor.Sample(tc1);
float4 col2 = samplerColor.Sample(tc2);
float4 col3 = samplerColor.Sample(tc3);
float4 col4 = samplerColor.Sample(tc4);
float4 col5 = samplerColor.Sample(tc5);
float4 col6 = samplerColor.Sample(tc6);
float4 col7 = samplerColor.Sample(tc7);
float4 col8 = samplerColor.Sample(tc8);
float4 sum = (1.0 * col0 + 2.0 * col1 + 1.0 * col2 +
2.0 * col3 + 4.0 * col4 + 2.0 * col5 +
1.0 * col6 + 2.0 * col7 + 1.0 * col8) / 16.0;
return float4(sum.rgb, 1.0);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,71 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float3 Normal;
float2 UV;
float3 Color;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float2 UV;
float3 ViewVec;
float3 LightVec;
};
struct UBO
{
float4x4 projection;
float4x4 model;
float4 lightPos;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output;
output.Color = input.Color;
output.UV = input.UV;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
float3 lPos = mul((float4x3)ubo.model, ubo.lightPos.xyz).xyz;
output.LightVec = lPos - pos.xyz;
output.ViewVec = -pos.xyz;
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
// Desaturate color
float3 color = float3(lerp(input.Color, dot(float3(0.2126,0.7152,0.0722), input.Color).xxx, 0.65));
// High ambient colors because mesh materials are pretty dark
float3 ambient = color * float3(1.0, 1.0, 1.0);
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 diffuse = max(dot(N, L), 0.0) * color;
float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
float intensity = dot(N,L);
float shade = 1.0;
shade = intensity < 0.5 ? 0.75 : shade;
shade = intensity < 0.35 ? 0.6 : shade;
shade = intensity < 0.25 ? 0.5 : shade;
shade = intensity < 0.1 ? 0.25 : shade;
return float4(input.Color * 3.0 * shade, 1);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,110 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
[[vk::binding(1, 0)]] Sampler2D samplerposition;
[[vk::binding(2, 0)]] Sampler2D samplerNormal;
[[vk::binding(3, 0)]] Sampler2D samplerAlbedo;
struct Light {
float4 position;
float3 color;
float radius;
};
struct UBO
{
Light lights[6];
float4 viewPos;
int displayDebugTarget;
};
[[vk::binding(4, 0)]] ConstantBuffer<UBO> ubo;
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
// Get G-Buffer values
float3 fragPos = samplerposition.Sample(input.UV).rgb;
float3 normal = samplerNormal.Sample(input.UV).rgb;
float4 albedo = samplerAlbedo.Sample(input.UV);
float3 fragcolor;
// Debug display
if (ubo.displayDebugTarget > 0) {
switch (ubo.displayDebugTarget) {
case 1:
fragcolor.rgb = fragPos;
break;
case 2:
fragcolor.rgb = normal;
break;
case 3:
fragcolor.rgb = albedo.rgb;
break;
case 4:
fragcolor.rgb = albedo.aaa;
break;
}
return float4(fragcolor, 1.0);
}
#define lightCount 6
#define ambient 0.0
// Ambient part
fragcolor = albedo.rgb * ambient;
for(int i = 0; i < lightCount; ++i)
{
// Vector to light
float3 L = ubo.lights[i].position.xyz - fragPos;
// Distance from light to fragment position
float dist = length(L);
// Viewer to fragment
float3 V = ubo.viewPos.xyz - fragPos;
V = normalize(V);
//if(dist < ubo.lights[i].radius)
{
// Light to fragment
L = normalize(L);
// Attenuation
float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
// Diffuse part
float3 N = normalize(normal);
float NdotL = max(0.0, dot(N, L));
float3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
// Specular part
// Specular map values are stored in alpha of albedo mrt
float3 R = reflect(-L, N);
float NdotR = max(0.0, dot(R, V));
float3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 16.0) * atten;
fragcolor += diff + spec;
}
}
return float4(fragcolor, 1.0);
}

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more