diff --git a/shaders/slang/terraintessellation/skysphere.slang b/shaders/slang/terraintessellation/skysphere.slang new file mode 100644 index 00000000..1d11ad44 --- /dev/null +++ b/shaders/slang/terraintessellation/skysphere.slang @@ -0,0 +1,42 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV; +}; + +struct UBO +{ + float4x4 mvp; +}; +ConstantBuffer ubo; + +Sampler2D samplerColorMap; + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.Pos = mul(ubo.mvp, float4(input.Pos, 1.0)); + output.UV = input.UV; + return output; +} + + +[shader("fragment")] +float4 fragmentMain(VSOutput input) +{ + return samplerColorMap.Sample(input.UV); +} diff --git a/shaders/slang/terraintessellation/terrain.slang b/shaders/slang/terraintessellation/terrain.slang new file mode 100644 index 00000000..ad9d475e --- /dev/null +++ b/shaders/slang/terraintessellation/terrain.slang @@ -0,0 +1,252 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct DSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; + float3 ViewVec; + float3 LightVec; + float3 EyePos; + float3 WorldPos; +}; + +struct UBO +{ + float4x4 projection; + float4x4 modelview; + float4 lightPos; + float4 frustumPlanes[6]; + float displacementFactor; + float tessellationFactor; + float2 viewportDim; + float tessellatedEdgeSize; +}; +ConstantBuffer ubo; + +Sampler2D samplerHeight; +Sampler2DArray samplerLayers; + +struct HSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal : NORMAL0; + float2 UV : TEXCOORD0; +}; + +struct ConstantsHSOutput +{ + float TessLevelOuter[4] : SV_TessFactor; + float TessLevelInner[2] : SV_InsideTessFactor; +}; + +// Calculate the tessellation factor based on screen space +// dimensions of the edge +float screenSpaceTessFactor(float4 p0, float4 p1) +{ + // Calculate edge mid point + float4 midPoint = 0.5 * (p0 + p1); + // Sphere radius as distance between the control points + float radius = distance(p0, p1) / 2.0; + + // View space + float4 v0 = mul(ubo.modelview, midPoint); + + // Project into clip space + float4 clip0 = mul(ubo.projection, (v0 - float4(radius, float3(0.0, 0.0, 0.0)))); + float4 clip1 = mul(ubo.projection, (v0 + float4(radius, float3(0.0, 0.0, 0.0)))); + + // Get normalized device coordinates + clip0 /= clip0.w; + clip1 /= clip1.w; + + // Convert to viewport coordinates + clip0.xy *= ubo.viewportDim; + clip1.xy *= ubo.viewportDim; + + // Return the tessellation factor based on the screen size + // given by the distance of the two edge control points in screen space + // and a reference (min.) tessellation size for the edge set by the application + return clamp(distance(clip0, clip1) / ubo.tessellatedEdgeSize * ubo.tessellationFactor, 1.0, 64.0); +} + +// Checks the current's patch visibility against the frustum using a sphere check +// Sphere radius is given by the patch size +bool frustumCheck(float4 Pos, float2 inUV) +{ + // Fixed radius (increase if patch size is increased in example) + const float radius = 8.0f; + float4 pos = Pos; + pos.y -= samplerHeight.SampleLevel(inUV, 0.0).r * ubo.displacementFactor; + + // 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; +} + +ConstantsHSOutput ConstantsHS(InputPatch patch) +{ + ConstantsHSOutput output; + + if (!frustumCheck(patch[0].Pos, patch[0].UV)) + { + output.TessLevelInner[0] = 0.0; + output.TessLevelInner[1] = 0.0; + output.TessLevelOuter[0] = 0.0; + output.TessLevelOuter[1] = 0.0; + output.TessLevelOuter[2] = 0.0; + output.TessLevelOuter[3] = 0.0; + } + else + { + if (ubo.tessellationFactor > 0.0) + { + output.TessLevelOuter[0] = screenSpaceTessFactor(patch[3].Pos, patch[0].Pos); + output.TessLevelOuter[1] = screenSpaceTessFactor(patch[0].Pos, patch[1].Pos); + output.TessLevelOuter[2] = screenSpaceTessFactor(patch[1].Pos, patch[2].Pos); + output.TessLevelOuter[3] = screenSpaceTessFactor(patch[2].Pos, patch[3].Pos); + output.TessLevelInner[0] = lerp(output.TessLevelOuter[0], output.TessLevelOuter[3], 0.5); + output.TessLevelInner[1] = lerp(output.TessLevelOuter[2], output.TessLevelOuter[1], 0.5); + } + else + { + // Tessellation factor can be set to zero by example + // to demonstrate a simple passthrough + output.TessLevelInner[0] = 1.0; + output.TessLevelInner[1] = 1.0; + output.TessLevelOuter[0] = 1.0; + output.TessLevelOuter[1] = 1.0; + output.TessLevelOuter[2] = 1.0; + output.TessLevelOuter[3] = 1.0; + } + } + + return output; +} + +float3 sampleTerrainLayer(float2 inUV) +{ + // Define some layer ranges for sampling depending on terrain height + float2 layers[6]; + layers[0] = float2(-10.0, 10.0); + layers[1] = float2(5.0, 45.0); + layers[2] = float2(45.0, 80.0); + layers[3] = float2(75.0, 100.0); + layers[4] = float2(95.0, 140.0); + layers[5] = float2(140.0, 190.0); + + float3 color = float3(0.0, 0.0, 0.0); + + // Get height from displacement map + float height = samplerHeight.SampleLevel(inUV, 0.0).r * 255.0; + + for (int i = 0; i < 6; i++) + { + float range = layers[i].y - layers[i].x; + float weight = (range - abs(height - layers[i].y)) / range; + weight = max(0.0, weight); + color += weight * samplerLayers.Sample(float3(inUV * 16.0, i)).rgb; + } + + return color; +} + +float fog(float density, float4 FragCoord) +{ + const float LOG2 = -1.442695; + float dist = FragCoord.z / FragCoord.w * 0.1; + float d = density * dist; + return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0); +} + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.Pos = float4(input.Pos.xyz, 1.0); + output.UV = input.UV; + output.Normal = input.Normal; + return output; +} + +[shader("hull")] +[domain("quad")] +[partitioning("integer")] +[outputtopology("triangle_cw")] +[outputcontrolpoints(4)] +[patchconstantfunc("ConstantsHS")] +[maxtessfactor(20.0f)] +HSOutput hullMain(InputPatch patch, uint InvocationID: SV_OutputControlPointID) +{ + HSOutput output; + output.Pos = patch[InvocationID].Pos; + output.Normal = patch[InvocationID].Normal; + output.UV = patch[InvocationID].UV; + return output; +} + +[shader("domain")] +[domain("quad")] +DSOutput domainMain(ConstantsHSOutput input, float2 TessCoord: SV_DomainLocation, const OutputPatch patch) +{ + // Interpolate UV coordinates + DSOutput output; + float2 uv1 = lerp(patch[0].UV, patch[1].UV, TessCoord.x); + float2 uv2 = lerp(patch[3].UV, patch[2].UV, TessCoord.x); + output.UV = lerp(uv1, uv2, TessCoord.y); + + float3 n1 = lerp(patch[0].Normal, patch[1].Normal, TessCoord.x); + float3 n2 = lerp(patch[3].Normal, patch[2].Normal, TessCoord.x); + output.Normal = lerp(n1, n2, TessCoord.y); + + // Interpolate positions + float4 pos1 = lerp(patch[0].Pos, patch[1].Pos, TessCoord.x); + float4 pos2 = lerp(patch[3].Pos, patch[2].Pos, TessCoord.x); + float4 pos = lerp(pos1, pos2, TessCoord.y); + // Displace + pos.y -= samplerHeight.SampleLevel(output.UV, 0.0).r * ubo.displacementFactor; + // Perspective projection + output.Pos = mul(ubo.projection, mul(ubo.modelview, pos)); + + // Calculate vectors for lighting based on tessellated position + output.ViewVec = -pos.xyz; + output.LightVec = normalize(ubo.lightPos.xyz + output.ViewVec); + output.WorldPos = pos.xyz; + output.EyePos = mul(ubo.modelview, pos).xyz; + return output; +} + +[shader("fragment")] +float4 fragmentMain(DSOutput input) +{ + float3 N = normalize(input.Normal); + float3 L = normalize(input.LightVec); + float3 ambient = float3(0.5, 0.5, 0.5); + float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0); + float4 color = float4((ambient + diffuse) * sampleTerrainLayer(input.UV), 1.0); + const float4 fogColor = float4(0.47, 0.5, 0.67, 0.0); + return lerp(color, fogColor, fog(0.25, input.Pos)); +} diff --git a/shaders/slang/tessellation/base.slang b/shaders/slang/tessellation/base.slang new file mode 100644 index 00000000..e671b8df --- /dev/null +++ b/shaders/slang/tessellation/base.slang @@ -0,0 +1,46 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSInput +{ + float3 Pos; + float3 Normal; + float2 UV; +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct DSOutput +{ + float3 Normal; + float2 UV; +}; + +[[vk::binding(0, 1)]] Sampler2D samplerColorMap; + +[shader("vertex")] +VSOutput vertexMain(VSInput input) +{ + VSOutput output; + output.Pos = float4(input.Pos.xyz, 1.0); + output.Normal = input.Normal; + output.UV = input.UV; + return output; +} + +[shader("fragment")] +float4 fragmentMain(DSOutput input) +{ + float3 N = normalize(input.Normal); + float3 L = normalize(float3(0.0, -4.0, 4.0)); + float4 color = samplerColorMap.Sample(input.UV); + return float4(clamp(max(dot(N,L), 0.0), 0.2, 1.0) * color.rgb * 1.5, 1); +} diff --git a/shaders/slang/tessellation/passthrough.slang b/shaders/slang/tessellation/passthrough.slang new file mode 100644 index 00000000..b25454e7 --- /dev/null +++ b/shaders/slang/tessellation/passthrough.slang @@ -0,0 +1,83 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct HSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct DSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct ConstantsHSOutput +{ + float TessLevelOuter[3] : SV_TessFactor; + float TessLevelInner[2] : SV_InsideTessFactor; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; + float tessAlpha; + float tessLevel; +}; +ConstantBuffer ubo; + +ConstantsHSOutput ConstantsHS(InputPatch patch) +{ + ConstantsHSOutput output; + output.TessLevelInner[0] = 1; + output.TessLevelInner[1] = 1; + output.TessLevelOuter[0] = 1; + output.TessLevelOuter[1] = 1; + output.TessLevelOuter[2] = 1; + return output; +} + +[shader("hull")] +[domain("tri")] +[partitioning("integer")] +[outputtopology("triangle_cw")] +[outputcontrolpoints(3)] +[patchconstantfunc("ConstantsHS")] +[maxtessfactor(20.0f)] +HSOutput hullMain(InputPatch patch, uint InvocationID : SV_OutputControlPointID) +{ + HSOutput output; + output.Pos = patch[InvocationID].Pos; + output.Normal = patch[InvocationID].Normal; + output.UV = patch[InvocationID].UV; + return output; +} + +[shader("domain")] +[domain("tri")] +DSOutput domainMain(ConstantsHSOutput input, float3 TessCoord: SV_DomainLocation, const OutputPatch patch) +{ + DSOutput output = (DSOutput)0; + output.Pos = (TessCoord.x * patch[0].Pos) + + (TessCoord.y * patch[1].Pos) + + (TessCoord.z * patch[2].Pos); + output.Pos = mul(ubo.projection, mul(ubo.model, output.Pos)); + + output.Normal = TessCoord.x * patch[0].Normal + TessCoord.y * patch[1].Normal + TessCoord.z * patch[2].Normal; + output.UV = TessCoord.x * patch[0].UV + TessCoord.y * patch[1].UV + TessCoord.z * patch[2].UV; + return output; +} \ No newline at end of file diff --git a/shaders/slang/tessellation/pntriangles.slang b/shaders/slang/tessellation/pntriangles.slang new file mode 100644 index 00000000..f1761ff7 --- /dev/null +++ b/shaders/slang/tessellation/pntriangles.slang @@ -0,0 +1,216 @@ +/* Copyright (c) 2025, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct HSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; + float pnPatch[10]; +}; + +struct DSOutput +{ + float4 Pos : SV_POSITION; + float3 Normal; + float2 UV; +}; + +struct ConstantsHSOutput +{ + float TessLevelOuter[3] : SV_TessFactor; + float TessLevelInner[2] : SV_InsideTessFactor; +}; + +struct UBO +{ + float4x4 projection; + float4x4 model; + float tessAlpha; + float tessLevel; +}; +ConstantBuffer ubo; + +#define uvw TessCoord + +struct PnPatch +{ + float b210; + float b120; + float b021; + float b012; + float b102; + float b201; + float b111; + float n110; + float n011; + float n101; +}; + +PnPatch GetPnPatch(float pnPatch[10]) +{ + PnPatch output; + output.b210 = pnPatch[0]; + output.b120 = pnPatch[1]; + output.b021 = pnPatch[2]; + output.b012 = pnPatch[3]; + output.b102 = pnPatch[4]; + output.b201 = pnPatch[5]; + output.b111 = pnPatch[6]; + output.n110 = pnPatch[7]; + output.n011 = pnPatch[8]; + output.n101 = pnPatch[9]; + return output; +} + +void SetPnPatch(out float output[10], PnPatch patch) +{ + output[0] = patch.b210; + output[1] = patch.b120; + output[2] = patch.b021; + output[3] = patch.b012; + output[4] = patch.b102; + output[5] = patch.b201; + output[6] = patch.b111; + output[7] = patch.n110; + output[8] = patch.n011; + output[9] = patch.n101; +} + +float wij(float4 iPos, float3 iNormal, float4 jPos) +{ + return dot(jPos.xyz - iPos.xyz, iNormal); +} + +float vij(float4 iPos, float3 iNormal, float4 jPos, float3 jNormal) +{ + float3 Pj_minus_Pi = jPos.xyz - iPos.xyz; + float3 Ni_plus_Nj = iNormal + jNormal; + return 2.0 * dot(Pj_minus_Pi, Ni_plus_Nj) / dot(Pj_minus_Pi, Pj_minus_Pi); +} + +ConstantsHSOutput ConstantsHS(InputPatch patch) +{ + ConstantsHSOutput output; + output.TessLevelOuter[0] = ubo.tessLevel; + output.TessLevelOuter[1] = ubo.tessLevel; + output.TessLevelOuter[2] = ubo.tessLevel; + output.TessLevelInner[0] = ubo.tessLevel; + output.TessLevelInner[1] = ubo.tessLevel; + return output; +} + +[shader("domain")] +[domain("tri")] +DSOutput domainMain(ConstantsHSOutput input, float3 TessCoord: SV_DomainLocation, const OutputPatch patch) +{ + PnPatch pnPatch[3]; + pnPatch[0] = GetPnPatch(patch[0].pnPatch); + pnPatch[1] = GetPnPatch(patch[1].pnPatch); + pnPatch[2] = GetPnPatch(patch[2].pnPatch); + + DSOutput output = (DSOutput)0; + float3 uvwSquared = uvw * uvw; + float3 uvwCubed = uvwSquared * uvw; + + // extract control points + float3 b210 = float3(pnPatch[0].b210, pnPatch[1].b210, pnPatch[2].b210); + float3 b120 = float3(pnPatch[0].b120, pnPatch[1].b120, pnPatch[2].b120); + float3 b021 = float3(pnPatch[0].b021, pnPatch[1].b021, pnPatch[2].b021); + float3 b012 = float3(pnPatch[0].b012, pnPatch[1].b012, pnPatch[2].b012); + float3 b102 = float3(pnPatch[0].b102, pnPatch[1].b102, pnPatch[2].b102); + float3 b201 = float3(pnPatch[0].b201, pnPatch[1].b201, pnPatch[2].b201); + float3 b111 = float3(pnPatch[0].b111, pnPatch[1].b111, pnPatch[2].b111); + + // extract control normals + float3 n110 = normalize(float3(pnPatch[0].n110, pnPatch[1].n110, pnPatch[2].n110)); + float3 n011 = normalize(float3(pnPatch[0].n011, pnPatch[1].n011, pnPatch[2].n011)); + float3 n101 = normalize(float3(pnPatch[0].n101, pnPatch[1].n101, pnPatch[2].n101)); + + // compute texcoords + output.UV = TessCoord[2] * patch[0].UV + TessCoord[0] * patch[1].UV + TessCoord[1] * patch[2].UV; + + // normal + // Barycentric normal + float3 barNormal = TessCoord[2] * patch[0].Normal + TessCoord[0] * patch[1].Normal + TessCoord[1] * patch[2].Normal; + float3 pnNormal = patch[0].Normal * uvwSquared[2] + patch[1].Normal * uvwSquared[0] + patch[2].Normal * uvwSquared[1] + + n110 * uvw[2] * uvw[0] + n011 * uvw[0] * uvw[1] + n101 * uvw[2] * uvw[1]; + output.Normal = ubo.tessAlpha * pnNormal + (1.0 - ubo.tessAlpha) * barNormal; + + // compute interpolated pos + float3 barPos = TessCoord[2] * patch[0].Pos.xyz + + TessCoord[0] * patch[1].Pos.xyz + + TessCoord[1] * patch[2].Pos.xyz; + + // save some computations + uvwSquared *= 3.0; + + // compute PN position + float3 pnPos = patch[0].Pos.xyz * uvwCubed[2] + + patch[1].Pos.xyz * uvwCubed[0] + + patch[2].Pos.xyz * uvwCubed[1] + + b210 * uvwSquared[2] * uvw[0] + + b120 * uvwSquared[0] * uvw[2] + + b201 * uvwSquared[2] * uvw[1] + + b021 * uvwSquared[0] * uvw[1] + + b102 * uvwSquared[1] * uvw[2] + + b012 * uvwSquared[1] * uvw[0] + + b111 * 6.0 * uvw[0] * uvw[1] * uvw[2]; + + // final position and normal + float3 finalPos = (1.0 - ubo.tessAlpha) * barPos + ubo.tessAlpha * pnPos; + output.Pos = mul(ubo.projection, mul(ubo.model, float4(finalPos, 1.0))); + return output; +} + +[shader("hull")] +[domain("tri")] +[partitioning("fractional_odd")] +[outputtopology("triangle_cw")] +[outputcontrolpoints(3)] +[patchconstantfunc("ConstantsHS")] +[maxtessfactor(20.0f)] +HSOutput hullMain(InputPatch patch, uint InvocationID : SV_OutputControlPointID) +{ + HSOutput output; + // get data + output.Pos = patch[InvocationID].Pos; + output.Normal = patch[InvocationID].Normal; + output.UV = patch[InvocationID].UV; + + // set base + float P0 = patch[0].Pos[InvocationID]; + float P1 = patch[1].Pos[InvocationID]; + float P2 = patch[2].Pos[InvocationID]; + float N0 = patch[0].Normal[InvocationID]; + float N1 = patch[1].Normal[InvocationID]; + float N2 = patch[2].Normal[InvocationID]; + + // compute control points + PnPatch pnPatch; + pnPatch.b210 = (2.0*P0 + P1 - wij(patch[0].Pos, patch[0].Normal, patch[1].Pos)*N0)/3.0; + pnPatch.b120 = (2.0*P1 + P0 - wij(patch[1].Pos, patch[1].Normal, patch[0].Pos)*N1)/3.0; + pnPatch.b021 = (2.0*P1 + P2 - wij(patch[1].Pos, patch[1].Normal, patch[2].Pos)*N1)/3.0; + pnPatch.b012 = (2.0*P2 + P1 - wij(patch[2].Pos, patch[2].Normal, patch[1].Pos)*N2)/3.0; + pnPatch.b102 = (2.0*P2 + P0 - wij(patch[2].Pos, patch[2].Normal, patch[0].Pos)*N2)/3.0; + pnPatch.b201 = (2.0*P0 + P2 - wij(patch[0].Pos, patch[0].Normal, patch[2].Pos)*N0)/3.0; + float E = ( pnPatch.b210 + pnPatch.b120 + pnPatch.b021 + pnPatch.b012 + pnPatch.b102 + pnPatch.b201 ) / 6.0; + float V = (P0 + P1 + P2)/3.0; + pnPatch.b111 = E + (E - V)*0.5; + pnPatch.n110 = N0+N1-vij(patch[0].Pos, patch[0].Normal, patch[1].Pos, patch[1].Normal)*(P1-P0); + pnPatch.n011 = N1+N2-vij(patch[1].Pos, patch[1].Normal, patch[2].Pos, patch[2].Normal)*(P2-P1); + pnPatch.n101 = N2+N0-vij(patch[2].Pos, patch[2].Normal, patch[0].Pos, patch[0].Normal)*(P0-P2); + SetPnPatch(output.pnPatch, pnPatch); + + return output; +} \ No newline at end of file