diff --git a/shaders/slang/parallaxmapping/parallax.slang b/shaders/slang/parallaxmapping/parallax.slang new file mode 100644 index 00000000..aa0c1048 --- /dev/null +++ b/shaders/slang/parallaxmapping/parallax.slang @@ -0,0 +1,150 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct UBOScene +{ + float4x4 projection; + float4x4 view; + float4x4 model; + float4 lightPos; + float4 cameraPos; +}; +ConstantBuffer uboScene; + +Sampler2D samplerColorMap; +Sampler2D samplerNormalHeightMap; + +struct UBOParams +{ + float heightScale; + float parallaxBias; + float numLayers; + int mappingMode; +}; +ConstantBuffer uboParams; + +struct VSInput +{ + float3 Pos; + float2 UV; + float3 Normal; + float4 Tangent; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; + float3 TangentLightPos; + float3 TangentViewPos; + float3 TangentFragPos; +}; + +float2 parallaxMapping(float2 uv, float3 viewDir) +{ + float height = 1.0 - samplerNormalHeightMap.SampleLevel(uv, 0.0).a; + float2 p = viewDir.xy * (height * (uboParams.heightScale * 0.5) + uboParams.parallaxBias) / viewDir.z; + return uv - p; +} + +float2 steepParallaxMapping(float2 uv, float3 viewDir) +{ + float layerDepth = 1.0 / uboParams.numLayers; + float currLayerDepth = 0.0; + float2 deltaUV = viewDir.xy * uboParams.heightScale / (viewDir.z * uboParams.numLayers); + float2 currUV = uv; + float height = 1.0 - samplerNormalHeightMap.SampleLevel(currUV, 0.0).a; + for (int i = 0; i < uboParams.numLayers; i++) { + currLayerDepth += layerDepth; + currUV -= deltaUV; + height = 1.0 - samplerNormalHeightMap.SampleLevel(currUV, 0.0).a; + if (height < currLayerDepth) { + break; + } + } + return currUV; +} + +float2 parallaxOcclusionMapping(float2 uv, float3 viewDir) +{ + float layerDepth = 1.0 / uboParams.numLayers; + float currLayerDepth = 0.0; + float2 deltaUV = viewDir.xy * uboParams.heightScale / (viewDir.z * uboParams.numLayers); + float2 currUV = uv; + float height = 1.0 - samplerNormalHeightMap.SampleLevel(currUV, 0.0).a; + for (int i = 0; i < uboParams.numLayers; i++) { + currLayerDepth += layerDepth; + currUV -= deltaUV; + height = 1.0 - samplerNormalHeightMap.SampleLevel(currUV, 0.0).a; + if (height < currLayerDepth) { + break; + } + } + float2 prevUV = currUV + deltaUV; + float nextDepth = height - currLayerDepth; + float prevDepth = 1.0 - samplerNormalHeightMap.SampleLevel(prevUV, 0.0).a - currLayerDepth + layerDepth; + return lerp(currUV, prevUV, nextDepth / (nextDepth - prevDepth)); +} + +[shader("vertex")] + +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.Pos = mul(uboScene.projection, mul(uboScene.view, mul(uboScene.model, float4(input.Pos, 1.0f)))); + output.UV = input.UV; + + float3 N = normalize(input.Normal); + float3 T = normalize(input.Tangent.xyz); + float3 B = normalize(cross(N, T)); + float3x3 TBN = float3x3(T, B, N); + + output.TangentLightPos = mul(TBN, uboScene.lightPos.xyz); + output.TangentViewPos = mul(TBN, uboScene.cameraPos.xyz); + output.TangentFragPos = mul(TBN, mul(uboScene.model, float4(input.Pos, 1.0)).xyz); + return output; +} + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + float3 V = normalize(input.TangentViewPos - input.TangentFragPos); + float2 uv = input.UV; + + if (uboParams.mappingMode == 0) { + // Color only + return samplerColorMap.Sample(input.UV); + } else { + switch (uboParams.mappingMode) { + case 2: + uv = parallaxMapping(input.UV, V); + break; + case 3: + uv = steepParallaxMapping(input.UV, V); + break; + case 4: + uv = parallaxOcclusionMapping(input.UV, V); + break; + } + + // Discard fragments at texture border + if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { + clip(-1); + } + + float3 N = normalize(samplerNormalHeightMap.SampleLevel(uv, 0.0).rgb * 2.0 - 1.0); + float3 L = normalize(input.TangentLightPos - input.TangentFragPos); + float3 R = reflect(-L, N); + float3 H = normalize(L + V); + + float3 color = samplerColorMap.Sample(uv).rgb; + float3 ambient = 0.2 * color; + float3 diffuse = max(dot(L, N), 0.0) * color; + float3 specular = float3(0.15, 0.15, 0.15) * pow(max(dot(N, H), 0.0), 32.0); + + return float4(ambient + diffuse + specular, 1.0f); + } +}