Skip to content

Commit c58fd18

Browse files
committed
Add slang shaders for tessellation samples
1 parent c2e3b49 commit c58fd18

5 files changed

Lines changed: 639 additions & 0 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* Copyright (c) 2025, Sascha Willems
2+
*
3+
* SPDX-License-Identifier: MIT
4+
*
5+
*/
6+
7+
struct VSInput
8+
{
9+
float3 Pos;
10+
float3 Normal;
11+
float2 UV;
12+
};
13+
14+
struct VSOutput
15+
{
16+
float4 Pos : SV_POSITION;
17+
float2 UV;
18+
};
19+
20+
struct UBO
21+
{
22+
float4x4 mvp;
23+
};
24+
ConstantBuffer<UBO> ubo;
25+
26+
Sampler2D samplerColorMap;
27+
28+
[shader("vertex")]
29+
VSOutput vertexMain(VSInput input)
30+
{
31+
VSOutput output;
32+
output.Pos = mul(ubo.mvp, float4(input.Pos, 1.0));
33+
output.UV = input.UV;
34+
return output;
35+
}
36+
37+
38+
[shader("fragment")]
39+
float4 fragmentMain(VSOutput input)
40+
{
41+
return samplerColorMap.Sample(input.UV);
42+
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/* Copyright (c) 2025, Sascha Willems
2+
*
3+
* SPDX-License-Identifier: MIT
4+
*
5+
*/
6+
7+
struct VSInput
8+
{
9+
float3 Pos;
10+
float3 Normal;
11+
float2 UV;
12+
};
13+
14+
struct VSOutput
15+
{
16+
float4 Pos : SV_POSITION;
17+
float3 Normal;
18+
float2 UV;
19+
};
20+
21+
struct DSOutput
22+
{
23+
float4 Pos : SV_POSITION;
24+
float3 Normal;
25+
float2 UV;
26+
float3 ViewVec;
27+
float3 LightVec;
28+
float3 EyePos;
29+
float3 WorldPos;
30+
};
31+
32+
struct UBO
33+
{
34+
float4x4 projection;
35+
float4x4 modelview;
36+
float4 lightPos;
37+
float4 frustumPlanes[6];
38+
float displacementFactor;
39+
float tessellationFactor;
40+
float2 viewportDim;
41+
float tessellatedEdgeSize;
42+
};
43+
ConstantBuffer<UBO> ubo;
44+
45+
Sampler2D samplerHeight;
46+
Sampler2DArray samplerLayers;
47+
48+
struct HSOutput
49+
{
50+
float4 Pos : SV_POSITION;
51+
float3 Normal : NORMAL0;
52+
float2 UV : TEXCOORD0;
53+
};
54+
55+
struct ConstantsHSOutput
56+
{
57+
float TessLevelOuter[4] : SV_TessFactor;
58+
float TessLevelInner[2] : SV_InsideTessFactor;
59+
};
60+
61+
// Calculate the tessellation factor based on screen space
62+
// dimensions of the edge
63+
float screenSpaceTessFactor(float4 p0, float4 p1)
64+
{
65+
// Calculate edge mid point
66+
float4 midPoint = 0.5 * (p0 + p1);
67+
// Sphere radius as distance between the control points
68+
float radius = distance(p0, p1) / 2.0;
69+
70+
// View space
71+
float4 v0 = mul(ubo.modelview, midPoint);
72+
73+
// Project into clip space
74+
float4 clip0 = mul(ubo.projection, (v0 - float4(radius, float3(0.0, 0.0, 0.0))));
75+
float4 clip1 = mul(ubo.projection, (v0 + float4(radius, float3(0.0, 0.0, 0.0))));
76+
77+
// Get normalized device coordinates
78+
clip0 /= clip0.w;
79+
clip1 /= clip1.w;
80+
81+
// Convert to viewport coordinates
82+
clip0.xy *= ubo.viewportDim;
83+
clip1.xy *= ubo.viewportDim;
84+
85+
// Return the tessellation factor based on the screen size
86+
// given by the distance of the two edge control points in screen space
87+
// and a reference (min.) tessellation size for the edge set by the application
88+
return clamp(distance(clip0, clip1) / ubo.tessellatedEdgeSize * ubo.tessellationFactor, 1.0, 64.0);
89+
}
90+
91+
// Checks the current's patch visibility against the frustum using a sphere check
92+
// Sphere radius is given by the patch size
93+
bool frustumCheck(float4 Pos, float2 inUV)
94+
{
95+
// Fixed radius (increase if patch size is increased in example)
96+
const float radius = 8.0f;
97+
float4 pos = Pos;
98+
pos.y -= samplerHeight.SampleLevel(inUV, 0.0).r * ubo.displacementFactor;
99+
100+
// Check sphere against frustum planes
101+
for (int i = 0; i < 6; i++) {
102+
if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
103+
{
104+
return false;
105+
}
106+
}
107+
return true;
108+
}
109+
110+
ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 4> patch)
111+
{
112+
ConstantsHSOutput output;
113+
114+
if (!frustumCheck(patch[0].Pos, patch[0].UV))
115+
{
116+
output.TessLevelInner[0] = 0.0;
117+
output.TessLevelInner[1] = 0.0;
118+
output.TessLevelOuter[0] = 0.0;
119+
output.TessLevelOuter[1] = 0.0;
120+
output.TessLevelOuter[2] = 0.0;
121+
output.TessLevelOuter[3] = 0.0;
122+
}
123+
else
124+
{
125+
if (ubo.tessellationFactor > 0.0)
126+
{
127+
output.TessLevelOuter[0] = screenSpaceTessFactor(patch[3].Pos, patch[0].Pos);
128+
output.TessLevelOuter[1] = screenSpaceTessFactor(patch[0].Pos, patch[1].Pos);
129+
output.TessLevelOuter[2] = screenSpaceTessFactor(patch[1].Pos, patch[2].Pos);
130+
output.TessLevelOuter[3] = screenSpaceTessFactor(patch[2].Pos, patch[3].Pos);
131+
output.TessLevelInner[0] = lerp(output.TessLevelOuter[0], output.TessLevelOuter[3], 0.5);
132+
output.TessLevelInner[1] = lerp(output.TessLevelOuter[2], output.TessLevelOuter[1], 0.5);
133+
}
134+
else
135+
{
136+
// Tessellation factor can be set to zero by example
137+
// to demonstrate a simple passthrough
138+
output.TessLevelInner[0] = 1.0;
139+
output.TessLevelInner[1] = 1.0;
140+
output.TessLevelOuter[0] = 1.0;
141+
output.TessLevelOuter[1] = 1.0;
142+
output.TessLevelOuter[2] = 1.0;
143+
output.TessLevelOuter[3] = 1.0;
144+
}
145+
}
146+
147+
return output;
148+
}
149+
150+
float3 sampleTerrainLayer(float2 inUV)
151+
{
152+
// Define some layer ranges for sampling depending on terrain height
153+
float2 layers[6];
154+
layers[0] = float2(-10.0, 10.0);
155+
layers[1] = float2(5.0, 45.0);
156+
layers[2] = float2(45.0, 80.0);
157+
layers[3] = float2(75.0, 100.0);
158+
layers[4] = float2(95.0, 140.0);
159+
layers[5] = float2(140.0, 190.0);
160+
161+
float3 color = float3(0.0, 0.0, 0.0);
162+
163+
// Get height from displacement map
164+
float height = samplerHeight.SampleLevel(inUV, 0.0).r * 255.0;
165+
166+
for (int i = 0; i < 6; i++)
167+
{
168+
float range = layers[i].y - layers[i].x;
169+
float weight = (range - abs(height - layers[i].y)) / range;
170+
weight = max(0.0, weight);
171+
color += weight * samplerLayers.Sample(float3(inUV * 16.0, i)).rgb;
172+
}
173+
174+
return color;
175+
}
176+
177+
float fog(float density, float4 FragCoord)
178+
{
179+
const float LOG2 = -1.442695;
180+
float dist = FragCoord.z / FragCoord.w * 0.1;
181+
float d = density * dist;
182+
return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0);
183+
}
184+
185+
[shader("vertex")]
186+
VSOutput vertexMain(VSInput input)
187+
{
188+
VSOutput output;
189+
output.Pos = float4(input.Pos.xyz, 1.0);
190+
output.UV = input.UV;
191+
output.Normal = input.Normal;
192+
return output;
193+
}
194+
195+
[shader("hull")]
196+
[domain("quad")]
197+
[partitioning("integer")]
198+
[outputtopology("triangle_cw")]
199+
[outputcontrolpoints(4)]
200+
[patchconstantfunc("ConstantsHS")]
201+
[maxtessfactor(20.0f)]
202+
HSOutput hullMain(InputPatch<VSOutput, 4> patch, uint InvocationID: SV_OutputControlPointID)
203+
{
204+
HSOutput output;
205+
output.Pos = patch[InvocationID].Pos;
206+
output.Normal = patch[InvocationID].Normal;
207+
output.UV = patch[InvocationID].UV;
208+
return output;
209+
}
210+
211+
[shader("domain")]
212+
[domain("quad")]
213+
DSOutput domainMain(ConstantsHSOutput input, float2 TessCoord: SV_DomainLocation, const OutputPatch<HSOutput, 4> patch)
214+
{
215+
// Interpolate UV coordinates
216+
DSOutput output;
217+
float2 uv1 = lerp(patch[0].UV, patch[1].UV, TessCoord.x);
218+
float2 uv2 = lerp(patch[3].UV, patch[2].UV, TessCoord.x);
219+
output.UV = lerp(uv1, uv2, TessCoord.y);
220+
221+
float3 n1 = lerp(patch[0].Normal, patch[1].Normal, TessCoord.x);
222+
float3 n2 = lerp(patch[3].Normal, patch[2].Normal, TessCoord.x);
223+
output.Normal = lerp(n1, n2, TessCoord.y);
224+
225+
// Interpolate positions
226+
float4 pos1 = lerp(patch[0].Pos, patch[1].Pos, TessCoord.x);
227+
float4 pos2 = lerp(patch[3].Pos, patch[2].Pos, TessCoord.x);
228+
float4 pos = lerp(pos1, pos2, TessCoord.y);
229+
// Displace
230+
pos.y -= samplerHeight.SampleLevel(output.UV, 0.0).r * ubo.displacementFactor;
231+
// Perspective projection
232+
output.Pos = mul(ubo.projection, mul(ubo.modelview, pos));
233+
234+
// Calculate vectors for lighting based on tessellated position
235+
output.ViewVec = -pos.xyz;
236+
output.LightVec = normalize(ubo.lightPos.xyz + output.ViewVec);
237+
output.WorldPos = pos.xyz;
238+
output.EyePos = mul(ubo.modelview, pos).xyz;
239+
return output;
240+
}
241+
242+
[shader("fragment")]
243+
float4 fragmentMain(DSOutput input)
244+
{
245+
float3 N = normalize(input.Normal);
246+
float3 L = normalize(input.LightVec);
247+
float3 ambient = float3(0.5, 0.5, 0.5);
248+
float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
249+
float4 color = float4((ambient + diffuse) * sampleTerrainLayer(input.UV), 1.0);
250+
const float4 fogColor = float4(0.47, 0.5, 0.67, 0.0);
251+
return lerp(color, fogColor, fog(0.25, input.Pos));
252+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Copyright (c) 2025, Sascha Willems
2+
*
3+
* SPDX-License-Identifier: MIT
4+
*
5+
*/
6+
7+
struct VSInput
8+
{
9+
float3 Pos;
10+
float3 Normal;
11+
float2 UV;
12+
};
13+
14+
struct VSOutput
15+
{
16+
float4 Pos : SV_POSITION;
17+
float3 Normal;
18+
float2 UV;
19+
};
20+
21+
struct DSOutput
22+
{
23+
float3 Normal;
24+
float2 UV;
25+
};
26+
27+
[[vk::binding(0, 1)]] Sampler2D samplerColorMap;
28+
29+
[shader("vertex")]
30+
VSOutput vertexMain(VSInput input)
31+
{
32+
VSOutput output;
33+
output.Pos = float4(input.Pos.xyz, 1.0);
34+
output.Normal = input.Normal;
35+
output.UV = input.UV;
36+
return output;
37+
}
38+
39+
[shader("fragment")]
40+
float4 fragmentMain(DSOutput input)
41+
{
42+
float3 N = normalize(input.Normal);
43+
float3 L = normalize(float3(0.0, -4.0, 4.0));
44+
float4 color = samplerColorMap.Sample(input.UV);
45+
return float4(clamp(max(dot(N,L), 0.0), 0.2, 1.0) * color.rgb * 1.5, 1);
46+
}

0 commit comments

Comments
 (0)