diff --git a/shaders/slang/conditionalrender/model.slang b/shaders/slang/conditionalrender/model.slang new file mode 100644 index 00000000..fa4b8f9f --- /dev/null +++ b/shaders/slang/conditionalrender/model.slang @@ -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; + +struct Node +{ + float4x4 transform; +}; +[[vk::binding(0,1)]] ConstantBuffer 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); +} \ No newline at end of file diff --git a/shaders/slang/conservativeraster/fullscreen.slang b/shaders/slang/conservativeraster/fullscreen.slang new file mode 100644 index 00000000..d9705f60 --- /dev/null +++ b/shaders/slang/conservativeraster/fullscreen.slang @@ -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); +} \ No newline at end of file diff --git a/shaders/slang/conservativeraster/triangle.slang b/shaders/slang/conservativeraster/triangle.slang new file mode 100644 index 00000000..bc26b8f7 --- /dev/null +++ b/shaders/slang/conservativeraster/triangle.slang @@ -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; + +[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); +} \ No newline at end of file diff --git a/shaders/slang/conservativeraster/triangleoverlay.slang b/shaders/slang/conservativeraster/triangleoverlay.slang new file mode 100644 index 00000000..6915ce36 --- /dev/null +++ b/shaders/slang/conservativeraster/triangleoverlay.slang @@ -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); +} \ No newline at end of file diff --git a/shaders/slang/descriptorbuffer/cube.slang b/shaders/slang/descriptorbuffer/cube.slang new file mode 100644 index 00000000..7d6e3ad3 --- /dev/null +++ b/shaders/slang/descriptorbuffer/cube.slang @@ -0,0 +1,51 @@ +/* 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; +}; + +struct UBOCamera { + float4x4 projection; + float4x4 view; +}; +ConstantBuffer uboCamera; + +struct UBOModel { + float4x4 local; +}; +[[vk::binding(0, 1)]] ConstantBuffer uboModel; + +[[vk::binding(0, 2)]] Sampler2D samplerColorMap; + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.Normal = input.Normal; + output.Color = input.Color; + output.UV = input.UV; + output.Pos = mul(uboCamera.projection, mul(uboCamera.view, mul(uboModel.local, float4(input.Pos.xyz, 1.0)))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + return samplerColorMap.Sample(input.UV) * float4(input.Color, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbrbasic/pbr.slang b/shaders/slang/pbrbasic/pbr.slang new file mode 100644 index 00000000..242d0fc9 --- /dev/null +++ b/shaders/slang/pbrbasic/pbr.slang @@ -0,0 +1,138 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 WorldPos; + float3 Normal; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; + float4x4 view; + float3 camPos; +}; +ConstantBuffer ubo; + +struct UBOParams { + float4 lights[4]; +}; +ConstantBuffer uboParams; + +struct Material { + [[vk::offset(12)]] float roughness; + [[vk::offset(16)]] float metallic; + [[vk::offset(20)]] float r; + [[vk::offset(24)]] float g; + [[vk::offset(28)]] float b; +}; +[[vk::push_constant]] Material material; + +static const float PI = 3.14159265359; + +// Normal Distribution function -------------------------------------- +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +// Geometric Shadowing function -------------------------------------- +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +// Fresnel function ---------------------------------------------------- +float3 F_Schlick(float cosTheta, Material material) +{ + float3 F0 = lerp(float3(0.04, 0.04, 0.04), float3(material.r, material.g, material.b), material.metallic); // * material.specular + float3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); + return F; +} + +// Specular BRDF composition -------------------------------------------- + +float3 BRDF(float3 L, float3 V, float3 N, Material material) +{ + // Precalculate vectors and dot products + float3 H = normalize (V + L); + float dotNV = clamp(dot(N, V), 0.0, 1.0); + float dotNL = clamp(dot(N, L), 0.0, 1.0); + float dotLH = clamp(dot(L, H), 0.0, 1.0); + float dotNH = clamp(dot(N, H), 0.0, 1.0); + + // Light color fixed + float3 lightColor = float3(1.0, 1.0, 1.0); + + float3 color = float3(0.0, 0.0, 0.0); + + if (dotNL > 0.0) + { + float rroughness = max(0.05, material.roughness); + // D = Normal distribution (Distribution of the microfacets) + float D = D_GGX(dotNH, material.roughness); + // G = Geometric shadowing term (Microfacets shadowing) + float G = G_SchlicksmithGGX(dotNL, dotNV, rroughness); + // F = Fresnel factor (Reflectance depending on angle of incidence) + float3 F = F_Schlick(dotNV, material); + + float3 spec = D * F * G / (4.0 * dotNL * dotNV); + + color += spec * dotNL * lightColor; + } + + return color; +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input, uniform float3 objPos) +{ + VSOutput output; + float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz; + output.WorldPos = locPos + objPos; + output.Normal = mul((float3x3)ubo.model, input.Normal); + output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 N = normalize(input.Normal); + float3 V = normalize(ubo.camPos - input.WorldPos); + + // Specular contribution + float3 Lo = float3(0.0, 0.0, 0.0); + for (int i = 0; i < 4; i++) { + float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos); + Lo += BRDF(L, V, N, material); + }; + + // Combine with ambient + float3 color = float3(material.r, material.g, material.b) * 0.02; + color += Lo; + + // Gamma correct + color = pow(color, float3(0.4545, 0.4545, 0.4545)); + + return float4(color, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbribl/filtercube.slang b/shaders/slang/pbribl/filtercube.slang new file mode 100644 index 00000000..6c848459 --- /dev/null +++ b/shaders/slang/pbribl/filtercube.slang @@ -0,0 +1,25 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 UVW; +}; + +[shader("vertex")] +VSOutput vertexMain(VSInput input, uniform float4x4 mvp) +{ + VSOutput output; + output.UVW = input.Pos; + output.Pos = mul(mvp, float4(input.Pos.xyz, 1.0)); + return output; +} diff --git a/shaders/slang/pbribl/genbrdflut.slang b/shaders/slang/pbribl/genbrdflut.slang new file mode 100644 index 00000000..b40d7b16 --- /dev/null +++ b/shaders/slang/pbribl/genbrdflut.slang @@ -0,0 +1,108 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + + struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; +}; + +[[SpecializationConstant]] const uint NUM_SAMPLES = 1024u; + +#define PI 3.1415926536 + +// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(float2 co) +{ + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt= dot(co.xy ,float2(a,b)); + float sn= fmod(dt,3.14); + return frac(sin(sn) * c); +} + +float2 hammersley2d(uint i, uint N) +{ + // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + uint bits = (i << 16u) | (i >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return float2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0); + float3 tangentX = normalize(cross(up, normal)); + float3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Geometric Shadowing function +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float k = (roughness * roughness) / 2.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +float2 BRDF(float NoV, float roughness) +{ + // Normal always points along z-axis for the 2D lookup + const float3 N = float3(0.0, 0.0, 1.0); + float3 V = float3(sqrt(1.0 - NoV*NoV), 0.0, NoV); + + float2 LUT = float2(0.0, 0.0); + for(uint i = 0u; i < NUM_SAMPLES; i++) { + float2 Xi = hammersley2d(i, NUM_SAMPLES); + float3 H = importanceSample_GGX(Xi, roughness, N); + float3 L = 2.0 * dot(V, H) * H - V; + + float dotNL = max(dot(N, L), 0.0); + float dotNV = max(dot(N, V), 0.0); + float dotVH = max(dot(V, H), 0.0); + float dotNH = max(dot(H, N), 0.0); + + if (dotNL > 0.0) { + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + float G_Vis = (G * dotVH) / (dotNH * dotNV); + float Fc = pow(1.0 - dotVH, 5.0); + LUT += float2((1.0 - Fc) * G_Vis, Fc * G_Vis); + } + } + return LUT / float(NUM_SAMPLES); +} + +[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 float4(BRDF(input.UV.x, input.UV.y), 0.0, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbribl/irradiancecube.slang b/shaders/slang/pbribl/irradiancecube.slang new file mode 100644 index 00000000..31cc96ea --- /dev/null +++ b/shaders/slang/pbribl/irradiancecube.slang @@ -0,0 +1,39 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +SamplerCube samplerEnv; + +struct PushConsts { + [[vk::offset(64)]] float deltaPhi; + [[vk::offset(68)]] float deltaTheta; +}; +[[vk::push_constant]] PushConsts consts; + +#define PI 3.1415926535897932384626433832795 + +[shader("fragment")] +float4 fragmentMain(float3 inPos) +{ + float3 N = normalize(inPos.xyz); + float3 up = float3(0.0, 1.0, 0.0); + float3 right = normalize(cross(up, N)); + up = cross(N, right); + + const float TWO_PI = PI * 2.0; + const float HALF_PI = PI * 0.5; + + float3 color = float3(0.0, 0.0, 0.0); + uint sampleCount = 0u; + for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) { + for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) { + float3 tempVec = cos(phi) * right + sin(phi) * up; + float3 sampleVector = cos(theta) * N + sin(theta) * tempVec; + color += samplerEnv.Sample(sampleVector).rgb * cos(theta) * sin(theta); + sampleCount++; + } + } + return float4(PI * color / float(sampleCount), 1.0); +} diff --git a/shaders/slang/pbribl/pbribl.slang b/shaders/slang/pbribl/pbribl.slang new file mode 100644 index 00000000..6ced37ff --- /dev/null +++ b/shaders/slang/pbribl/pbribl.slang @@ -0,0 +1,193 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 WorldPos; + float3 Normal; + float2 UV; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; + float4x4 view; + float3 camPos; +}; +ConstantBuffer ubo; + +struct UBOParams { + float4 lights[4]; + float exposure; + float gamma; +}; +ConstantBuffer uboParams; + +SamplerCube samplerIrradiance; +Sampler2D samplerBRDFLUT; +SamplerCube prefilteredMapSampler; + +struct Material { + [[vk::offset(12)]] float roughness; + [[vk::offset(16)]] float metallic; + [[vk::offset(20)]] float specular; + [[vk::offset(24)]] float r; + [[vk::offset(28)]] float g; + [[vk::offset(32)]] float b; +}; +[[vk::push_constant]] Material material; + +#define PI 3.1415926535897932384626433832795 +#define ALBEDO float3(material.r, material.g, material.b) + +// From http://filmicgames.com/archives/75 +float3 Uncharted2Tonemap(float3 x) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F; +} + +// Normal Distribution function -------------------------------------- +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +// Geometric Shadowing function -------------------------------------- +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +// Fresnel function ---------------------------------------------------- +float3 F_Schlick(float cosTheta, float3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} +float3 F_SchlickR(float cosTheta, float3 F0, float roughness) +{ + return F0 + (max((1.0 - roughness).xxx, F0) - F0) * pow(1.0 - cosTheta, 5.0); +} + +float3 prefilteredReflection(float3 R, float roughness) +{ + const float MAX_REFLECTION_LOD = 9.0; // todo: param/const + float lod = roughness * MAX_REFLECTION_LOD; + float lodf = floor(lod); + float lodc = ceil(lod); + float3 a = prefilteredMapSampler.SampleLevel(R, lodf).rgb; + float3 b = prefilteredMapSampler.SampleLevel(R, lodc).rgb; + return lerp(a, b, lod - lodf); +} + +float3 specularContribution(float3 L, float3 V, float3 N, float3 F0, float metallic, float roughness) +{ + // Precalculate vectors and dot products + float3 H = normalize (V + L); + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotNV = clamp(dot(N, V), 0.0, 1.0); + float dotNL = clamp(dot(N, L), 0.0, 1.0); + + // Light color fixed + float3 lightColor = float3(1.0, 1.0, 1.0); + + float3 color = float3(0.0, 0.0, 0.0); + + if (dotNL > 0.0) { + // D = Normal distribution (Distribution of the microfacets) + float D = D_GGX(dotNH, roughness); + // G = Geometric shadowing term (Microfacets shadowing) + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + // F = Fresnel factor (Reflectance depending on angle of incidence) + float3 F = F_Schlick(dotNV, F0); + float3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001); + float3 kD = (float3(1.0, 1.0, 1.0) - F) * (1.0 - metallic); + color += (kD * ALBEDO / PI + spec) * dotNL; + } + + return color; +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input, uniform float3 objPos) +{ + VSOutput output; + float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz; + output.WorldPos = locPos + objPos; + output.Normal = mul((float3x3)ubo.model, input.Normal); + output.UV = input.UV; + output.UV.y = 1.0 - input.UV.y; + output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 N = normalize(input.Normal); + float3 V = normalize(ubo.camPos - input.WorldPos); + float3 R = reflect(-V, N); + + float metallic = material.metallic; + float roughness = material.roughness; + + float3 F0 = float3(0.04, 0.04, 0.04); + F0 = lerp(F0, ALBEDO, metallic); + + float3 Lo = float3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++) { + float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos); + Lo += specularContribution(L, V, N, F0, metallic, roughness); + } + + float2 brdf = samplerBRDFLUT.Sample(float2(max(dot(N, V), 0.0), roughness)).rg; + float3 reflection = prefilteredReflection(R, roughness).rgb; + float3 irradiance = samplerIrradiance.Sample(N).rgb; + + // Diffuse based on irradiance + float3 diffuse = irradiance * ALBEDO; + + float3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness); + + // Specular reflectance + float3 specular = reflection * (F * brdf.x + brdf.y); + + // Ambient part + float3 kD = 1.0 - F; + kD *= 1.0 - metallic; + float3 ambient = (kD * diffuse + specular); + + float3 color = ambient + Lo; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx)); + // Gamma correction + color = pow(color, (1.0f / uboParams.gamma).xxx); + + return float4(color, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbribl/prefilterenvmap.slang b/shaders/slang/pbribl/prefilterenvmap.slang new file mode 100644 index 00000000..d33cd37c --- /dev/null +++ b/shaders/slang/pbribl/prefilterenvmap.slang @@ -0,0 +1,109 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +SamplerCube samplerEnv; + +struct PushConsts { + [[vk::offset(64)]] float roughness; + [[vk::offset(68)]] uint numSamples; +}; +[[vk::push_constant]] PushConsts consts; + +#define PI 3.1415926536 + +// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(float2 co) +{ + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt= dot(co.xy ,float2(a,b)); + float sn= fmod(dt,3.14); + return frac(sin(sn) * c); +} + +float2 hammersley2d(uint i, uint N) +{ + // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + uint bits = (i << 16u) | (i >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return float2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0); + float3 tangentX = normalize(cross(up, normal)); + float3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Normal Distribution function +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +float3 prefilterEnvMap(float3 R, float roughness) +{ + float3 N = R; + float3 V = R; + float3 color = float3(0.0, 0.0, 0.0); + float totalWeight = 0.0; + int2 envMapDims; + samplerEnv.GetDimensions(envMapDims.x, envMapDims.y); + float envMapDim = float(envMapDims.x); + for(uint i = 0u; i < consts.numSamples; i++) { + float2 Xi = hammersley2d(i, consts.numSamples); + float3 H = importanceSample_GGX(Xi, roughness, N); + float3 L = 2.0 * dot(V, H) * H - V; + float dotNL = clamp(dot(N, L), 0.0, 1.0); + if(dotNL > 0.0) { + // Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/ + + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotVH = clamp(dot(V, H), 0.0, 1.0); + + // Probability Distribution Function + float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001; + // Slid angle of current smple + float omegaS = 1.0 / (float(consts.numSamples) * pdf); + // Solid angle of 1 pixel across all cube faces + float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim); + // Biased (+1.0) mip level for better result + float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f); + color += samplerEnv.SampleLevel(L, mipLevel).rgb * dotNL; + totalWeight += dotNL; + + } + } + return (color / totalWeight); +} + +[shader("fragment")] +float4 fragmentMain(float3 inPos) +{ + float3 N = normalize(inPos.xyz); + return float4(prefilterEnvMap(N, consts.roughness), 1.0); +} diff --git a/shaders/slang/pbribl/skybox.slang b/shaders/slang/pbribl/skybox.slang new file mode 100644 index 00000000..809fedab --- /dev/null +++ b/shaders/slang/pbribl/skybox.slang @@ -0,0 +1,70 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 UVW; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; +}; +ConstantBuffer ubo; + +struct UBOParams { + float4 lights[4]; + float exposure; + float gamma; +}; +ConstantBuffer uboParams; + +SamplerCube samplerEnv; + +// From http://filmicworlds.com/blog/filmic-tonemapping-operators/ +float3 Uncharted2Tonemap(float3 color) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + float W = 11.2; + return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.UVW = input.Pos; + output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 color = samplerEnv.Sample(input.UVW).rgb; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx)); + // Gamma correction + color = pow(color, (1.0f / uboParams.gamma).xxx); + + return float4(color, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbrtexture/filtercube.slang b/shaders/slang/pbrtexture/filtercube.slang new file mode 100644 index 00000000..6c848459 --- /dev/null +++ b/shaders/slang/pbrtexture/filtercube.slang @@ -0,0 +1,25 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 UVW; +}; + +[shader("vertex")] +VSOutput vertexMain(VSInput input, uniform float4x4 mvp) +{ + VSOutput output; + output.UVW = input.Pos; + output.Pos = mul(mvp, float4(input.Pos.xyz, 1.0)); + return output; +} diff --git a/shaders/slang/pbrtexture/genbrdflut.slang b/shaders/slang/pbrtexture/genbrdflut.slang new file mode 100644 index 00000000..b40d7b16 --- /dev/null +++ b/shaders/slang/pbrtexture/genbrdflut.slang @@ -0,0 +1,108 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + + struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; +}; + +[[SpecializationConstant]] const uint NUM_SAMPLES = 1024u; + +#define PI 3.1415926536 + +// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(float2 co) +{ + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt= dot(co.xy ,float2(a,b)); + float sn= fmod(dt,3.14); + return frac(sin(sn) * c); +} + +float2 hammersley2d(uint i, uint N) +{ + // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + uint bits = (i << 16u) | (i >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return float2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0); + float3 tangentX = normalize(cross(up, normal)); + float3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Geometric Shadowing function +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float k = (roughness * roughness) / 2.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +float2 BRDF(float NoV, float roughness) +{ + // Normal always points along z-axis for the 2D lookup + const float3 N = float3(0.0, 0.0, 1.0); + float3 V = float3(sqrt(1.0 - NoV*NoV), 0.0, NoV); + + float2 LUT = float2(0.0, 0.0); + for(uint i = 0u; i < NUM_SAMPLES; i++) { + float2 Xi = hammersley2d(i, NUM_SAMPLES); + float3 H = importanceSample_GGX(Xi, roughness, N); + float3 L = 2.0 * dot(V, H) * H - V; + + float dotNL = max(dot(N, L), 0.0); + float dotNV = max(dot(N, V), 0.0); + float dotVH = max(dot(V, H), 0.0); + float dotNH = max(dot(H, N), 0.0); + + if (dotNL > 0.0) { + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + float G_Vis = (G * dotVH) / (dotNH * dotNV); + float Fc = pow(1.0 - dotVH, 5.0); + LUT += float2((1.0 - Fc) * G_Vis, Fc * G_Vis); + } + } + return LUT / float(NUM_SAMPLES); +} + +[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 float4(BRDF(input.UV.x, input.UV.y), 0.0, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbrtexture/irradiancecube.slang b/shaders/slang/pbrtexture/irradiancecube.slang new file mode 100644 index 00000000..31cc96ea --- /dev/null +++ b/shaders/slang/pbrtexture/irradiancecube.slang @@ -0,0 +1,39 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +SamplerCube samplerEnv; + +struct PushConsts { + [[vk::offset(64)]] float deltaPhi; + [[vk::offset(68)]] float deltaTheta; +}; +[[vk::push_constant]] PushConsts consts; + +#define PI 3.1415926535897932384626433832795 + +[shader("fragment")] +float4 fragmentMain(float3 inPos) +{ + float3 N = normalize(inPos.xyz); + float3 up = float3(0.0, 1.0, 0.0); + float3 right = normalize(cross(up, N)); + up = cross(N, right); + + const float TWO_PI = PI * 2.0; + const float HALF_PI = PI * 0.5; + + float3 color = float3(0.0, 0.0, 0.0); + uint sampleCount = 0u; + for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) { + for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) { + float3 tempVec = cos(phi) * right + sin(phi) * up; + float3 sampleVector = cos(theta) * N + sin(theta) * tempVec; + color += samplerEnv.Sample(sampleVector).rgb * cos(theta) * sin(theta); + sampleCount++; + } + } + return float4(PI * color / float(sampleCount), 1.0); +} diff --git a/shaders/slang/pbrtexture/pbrtexture.slang b/shaders/slang/pbrtexture/pbrtexture.slang new file mode 100644 index 00000000..0d7bbef0 --- /dev/null +++ b/shaders/slang/pbrtexture/pbrtexture.slang @@ -0,0 +1,198 @@ +// Copyright 2020 Google LLC + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; + float4 Tangent; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 WorldPos; + float3 Normal; + float2 UV; + float3 Tangent; +}; + +struct UBO { + float4x4 projection; + float4x4 model; + float4x4 view; + float3 camPos; +}; +ConstantBuffer ubo; + +struct UBOParams { + float4 lights[4]; + float exposure; + float gamma; +}; +ConstantBuffer uboParams; + +SamplerCube samplerIrradiance; +Sampler2D samplerBRDFLUT; +SamplerCube prefilteredMapSampler; + +Sampler2D albedoMapSampler; +Sampler2D normalMapSampler; +Sampler2D aoMapSampler; +Sampler2D metallicMapSampler; +Sampler2D roughnessMapSampler; + +#define PI 3.1415926535897932384626433832795 +#define ALBEDO(uv) pow(albedoMapSampler.Sample(uv).rgb, float3(2.2, 2.2, 2.2)) + +// From http://filmicgames.com/archives/75 +float3 Uncharted2Tonemap(float3 x) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F; +} + +// Normal Distribution function -------------------------------------- +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +// Geometric Shadowing function -------------------------------------- +float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + float GL = dotNL / (dotNL * (1.0 - k) + k); + float GV = dotNV / (dotNV * (1.0 - k) + k); + return GL * GV; +} + +// Fresnel function ---------------------------------------------------- +float3 F_Schlick(float cosTheta, float3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} +float3 F_SchlickR(float cosTheta, float3 F0, float roughness) +{ + return F0 + (max((1.0 - roughness).xxx, F0) - F0) * pow(1.0 - cosTheta, 5.0); +} + +float3 prefilteredReflection(float3 R, float roughness) +{ + const float MAX_REFLECTION_LOD = 9.0; // todo: param/const + float lod = roughness * MAX_REFLECTION_LOD; + float lodf = floor(lod); + float lodc = ceil(lod); + float3 a = prefilteredMapSampler.SampleLevel(R, lodf).rgb; + float3 b = prefilteredMapSampler.SampleLevel(R, lodc).rgb; + return lerp(a, b, lod - lodf); +} + +float3 specularContribution(float2 inUV, float3 L, float3 V, float3 N, float3 F0, float metallic, float roughness) +{ + // Precalculate vectors and dot products + float3 H = normalize (V + L); + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotNV = clamp(dot(N, V), 0.0, 1.0); + float dotNL = clamp(dot(N, L), 0.0, 1.0); + + // Light color fixed + float3 lightColor = float3(1.0, 1.0, 1.0); + + float3 color = float3(0.0, 0.0, 0.0); + + if (dotNL > 0.0) { + // D = Normal distribution (Distribution of the microfacets) + float D = D_GGX(dotNH, roughness); + // G = Geometric shadowing term (Microfacets shadowing) + float G = G_SchlicksmithGGX(dotNL, dotNV, roughness); + // F = Fresnel factor (Reflectance depending on angle of incidence) + float3 F = F_Schlick(dotNV, F0); + float3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001); + float3 kD = (float3(1.0, 1.0, 1.0) - F) * (1.0 - metallic); + color += (kD * ALBEDO(inUV) / PI + spec) * dotNL; + } + + return color; +} + +float3 calculateNormal(VSOutput input) +{ + float3 tangentNormal = normalMapSampler.Sample(input.UV).xyz * 2.0 - 1.0; + + float3 N = normalize(input.Normal); + float3 T = normalize(input.Tangent); + float3 B = normalize(cross(N, T)); + float3x3 TBN = transpose(float3x3(T, B, N)); + + return normalize(mul(TBN, tangentNormal)); +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz; + output.WorldPos = locPos; + output.Normal = mul((float3x3)ubo.model, input.Normal); + output.Tangent = mul((float3x3)ubo.model, input.Tangent.xyz); + output.UV = input.UV; + output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 N = calculateNormal(input); + float3 V = normalize(ubo.camPos - input.WorldPos); + float3 R = reflect(-V, N); + + float metallic = metallicMapSampler.Sample(input.UV).r; + float roughness = roughnessMapSampler.Sample(input.UV).r; + + float3 F0 = float3(0.04, 0.04, 0.04); + F0 = lerp(F0, ALBEDO(input.UV), metallic); + + float3 Lo = float3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++) { + float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos); + Lo += specularContribution(input.UV, L, V, N, F0, metallic, roughness); + } + + float2 brdf = samplerBRDFLUT.Sample(float2(max(dot(N, V), 0.0), roughness)).rg; + float3 reflection = prefilteredReflection(R, roughness).rgb; + float3 irradiance = samplerIrradiance.Sample(N).rgb; + + // Diffuse based on irradiance + float3 diffuse = irradiance * ALBEDO(input.UV); + + float3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness); + + // Specular reflectance + float3 specular = reflection * (F * brdf.x + brdf.y); + + // Ambient part + float3 kD = 1.0 - F; + kD *= 1.0 - metallic; + float3 ambient = (kD * diffuse + specular) * aoMapSampler.Sample(input.UV).rrr; + + float3 color = ambient + Lo; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx)); + // Gamma correction + color = pow(color, (1.0f / uboParams.gamma).xxx); + + return float4(color, 1.0); +} \ No newline at end of file diff --git a/shaders/slang/pbrtexture/prefilterenvmap.slang b/shaders/slang/pbrtexture/prefilterenvmap.slang new file mode 100644 index 00000000..d33cd37c --- /dev/null +++ b/shaders/slang/pbrtexture/prefilterenvmap.slang @@ -0,0 +1,109 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +SamplerCube samplerEnv; + +struct PushConsts { + [[vk::offset(64)]] float roughness; + [[vk::offset(68)]] uint numSamples; +}; +[[vk::push_constant]] PushConsts consts; + +#define PI 3.1415926536 + +// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ +float random(float2 co) +{ + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt= dot(co.xy ,float2(a,b)); + float sn= fmod(dt,3.14); + return frac(sin(sn) * c); +} + +float2 hammersley2d(uint i, uint N) +{ + // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html + uint bits = (i << 16u) | (i >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + float rdi = float(bits) * 2.3283064365386963e-10; + return float2(float(i) /float(N), rdi); +} + +// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf +float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal) +{ + // Maps a 2D point to a hemisphere with spread based on roughness + float alpha = roughness * roughness; + float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // Tangent space + float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0); + float3 tangentX = normalize(cross(up, normal)); + float3 tangentY = normalize(cross(normal, tangentX)); + + // Convert to world Space + return normalize(tangentX * H.x + tangentY * H.y + normal * H.z); +} + +// Normal Distribution function +float D_GGX(float dotNH, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0; + return (alpha2)/(PI * denom*denom); +} + +float3 prefilterEnvMap(float3 R, float roughness) +{ + float3 N = R; + float3 V = R; + float3 color = float3(0.0, 0.0, 0.0); + float totalWeight = 0.0; + int2 envMapDims; + samplerEnv.GetDimensions(envMapDims.x, envMapDims.y); + float envMapDim = float(envMapDims.x); + for(uint i = 0u; i < consts.numSamples; i++) { + float2 Xi = hammersley2d(i, consts.numSamples); + float3 H = importanceSample_GGX(Xi, roughness, N); + float3 L = 2.0 * dot(V, H) * H - V; + float dotNL = clamp(dot(N, L), 0.0, 1.0); + if(dotNL > 0.0) { + // Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/ + + float dotNH = clamp(dot(N, H), 0.0, 1.0); + float dotVH = clamp(dot(V, H), 0.0, 1.0); + + // Probability Distribution Function + float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001; + // Slid angle of current smple + float omegaS = 1.0 / (float(consts.numSamples) * pdf); + // Solid angle of 1 pixel across all cube faces + float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim); + // Biased (+1.0) mip level for better result + float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f); + color += samplerEnv.SampleLevel(L, mipLevel).rgb * dotNL; + totalWeight += dotNL; + + } + } + return (color / totalWeight); +} + +[shader("fragment")] +float4 fragmentMain(float3 inPos) +{ + float3 N = normalize(inPos.xyz); + return float4(prefilterEnvMap(N, consts.roughness), 1.0); +} diff --git a/shaders/slang/pbrtexture/skybox.slang b/shaders/slang/pbrtexture/skybox.slang new file mode 100644 index 00000000..809fedab --- /dev/null +++ b/shaders/slang/pbrtexture/skybox.slang @@ -0,0 +1,70 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 UVW; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; +}; +ConstantBuffer ubo; + +struct UBOParams { + float4 lights[4]; + float exposure; + float gamma; +}; +ConstantBuffer uboParams; + +SamplerCube samplerEnv; + +// From http://filmicworlds.com/blog/filmic-tonemapping-operators/ +float3 Uncharted2Tonemap(float3 color) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + float W = 11.2; + return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.UVW = input.Pos; + output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0))); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 color = samplerEnv.Sample(input.UVW).rgb; + + // Tone mapping + color = Uncharted2Tonemap(color * uboParams.exposure); + color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx)); + // Gamma correction + color = pow(color, (1.0f / uboParams.gamma).xxx); + + return float4(color, 1.0); +} \ No newline at end of file