forked from tachimarten/TachiTess
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTachiTess.cginc
306 lines (264 loc) · 12.9 KB
/
TachiTess.cginc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// A surface tessellation shader using the Walton & Meek technique [1].
//
// Copyright (c) 2023 Tachi. Licensed under either the Apache 2.0 license or MIT license, at your
// option.
//
// [1]: D.J. Walton and D.S. Meek. "A triangular G1 patch from boundary curves." *Computer-Aided
// Design* 28, no. 2 (1996): 113-123.
#ifndef TACHITESS_CGINC
#define TACHITESS_CGINC
#include "Tessellation.cginc"
float _TessFactor;
float _MaxTessLevel;
// Walton & Meek tessellation
// Interpolates 3-vectors with the given weights.
float3 interpolateFloat3(float3 a, float3 b, float3 c, float3 lambda) {
return a * lambda.x + b * lambda.y + c * lambda.z;
}
// Normalizes a 3-vector, with reasonable behavior with lengths close to zero.
float3 safeNormalize(float3 v) {
float scale = min(abs(v.x), min(abs(v.y), abs(v.z)));
if (scale != 0.0)
v /= scale;
float len = length(v);
if (len != 0.0)
v /= len;
return v;
}
// Projects vector a onto b, with reasonable behavior around lengths close to zero.
float3 safeProject(float3 a, float3 b) {
float3 c = dot(a, b);
float sqLen = dot(b, b);
return sqLen > 0.00001 ? c / sqLen : c;
}
// Swaps two 3-vectors.
void swap3(inout float3 inoutA, inout float3 inoutB) {
float3 tmp = inoutA;
inoutA = inoutB;
inoutB = tmp;
}
// Returns true if v0 is lexicographically less than v3.
bool edgeWinding(float3 v0, float3 v3) {
return (v0.x < v3.x || (v0.x == v3.x && v0.y < v3.y) ||
(v0.x == v3.x && v0.y == v3.y && v0.z < v3.z));
}
// Computes the cubic control points for the boundary curves.
void computeEdgeControlPoints(float3 v0, float3 v3, float3 n0, float3 n3,
out float3 outV1, out float3 outV2) {
bool winding = edgeWinding(v0, v3);
if (!winding) {
swap3(v0, v3);
swap3(n0, n3);
}
float d = length(v3 - v0);
float3 gamma = safeNormalize(v3 - v0);
float a = dot(n0, n3);
float2 a01 = float2(dot(n0, gamma), dot(n3, gamma));
float2 rhoSigma = 6.0 * (2.0 * a01.xy + a * a01.yx) / (4.0 - a * a);
float3 v1 = v0 + d * (6.0 * gamma - 2.0 * rhoSigma.x * n0 + rhoSigma.y * n3) / 18.0;
float3 v2 = v3 - d * (6.0 * gamma + rhoSigma.x * n0 - 2.0 * rhoSigma.y * n3) / 18.0;
outV1 = winding ? v1 : v2;
outV2 = winding ? v2 : v1;
}
// Evaluates a cubic Bézier curve.
float3 evalEdgePoint(float3 v0, float3 v1, float3 v2, float3 v3, float t) {
if (!edgeWinding(v0, v3)) {
swap3(v0, v3);
swap3(v1, v2);
t = 1.0 - t;
}
float3 u1 = lerp(v1, v2, t);
return lerp(lerp(lerp(v0, v1, t), u1, t), lerp(u1, lerp(v2, v3, t), t), t);
}
// Degree elevates a cubic Bézier curve to a quartic one.
void degreeElevate(float3 p0, float3 p1, float3 p2, float3 p3,
out float3 outQP1, out float3 outQP2, out float3 outQP3) {
outQP1 = lerp(p0, p1, 0.75);
outQP2 = lerp(p1, p2, 0.50);
outQP3 = lerp(p2, p3, 0.25);
}
// Computes the face control points for one edge of a Gregory patch.
void computeFaceControlPoints(float3 v0, float3 v1, float3 v2, float3 v3,
float3 l1, float3 l2, float3 l3, float3 lPrev,
float3 lNext, float3 n0, float3 n1,
out float3 outG1, out float3 outG2) {
float3 w0 = v1 - v0, w1 = v2 - v1, w2 = v3 - v2;
float3 d0 = lerp(lPrev - v0, lPrev - l1, 0.5); // in the plane of n0
float3 d3 = lerp(lNext - v3, lNext - l3, 0.5); // in the plane of n1
// All of these are normalized:
float3 a0 = cross(n0, safeNormalize(w0)); // bitangent at v0
float3 a2 = cross(n1, safeNormalize(w2)); // bitangent at v3
float3 a1 = safeNormalize(a0 + a2); // average bitangent
float lambda0 = safeProject(d0, w0);
float lambda1 = safeProject(d3, w2);
float mu0 = dot(d0, a0);
float mu1 = dot(d3, a2);
outG1 = lerp(l1, l2, 0.5) + lerp(lambda0 * w1, lambda1 * w0, 1.0 / 3.0) +
lerp(mu0 * a1, mu1 * a0, 1.0 / 3.0);
outG2 = lerp(l2, l3, 0.5) + lerp(lambda0 * w2, lambda1 * w1, 2.0 / 3.0) +
lerp(mu0 * a2, mu1 * a1, 2.0 / 3.0);
}
// Generalized de Casteljau subdivision.
float3 evalBezierTriangle4(float3 p400, float3 p310, float3 p301, float3 p220,
float3 p211, float3 p202, float3 p130, float3 p121,
float3 p112, float3 p103, float3 p040, float3 p031,
float3 p022, float3 p013, float3 p004,
float3 lambda) {
float3 q030 = interpolateFloat3(p130, p040, p031, lambda);
float3 q021 = interpolateFloat3(p121, p031, p022, lambda);
float3 q120 = interpolateFloat3(p220, p130, p121, lambda);
float3 q012 = interpolateFloat3(p112, p022, p013, lambda);
float3 q111 = interpolateFloat3(p211, p121, p112, lambda);
float3 q210 = interpolateFloat3(p310, p220, p211, lambda);
float3 q003 = interpolateFloat3(p103, p013, p004, lambda);
float3 q102 = interpolateFloat3(p202, p112, p103, lambda);
float3 q201 = interpolateFloat3(p301, p211, p202, lambda);
float3 q300 = interpolateFloat3(p400, p310, p301, lambda);
float3 r020 = interpolateFloat3(q120, q030, q021, lambda);
float3 r011 = interpolateFloat3(q111, q021, q012, lambda);
float3 r110 = interpolateFloat3(q210, q120, q111, lambda);
float3 r002 = interpolateFloat3(q102, q012, q003, lambda);
float3 r101 = interpolateFloat3(q201, q111, q102, lambda);
float3 r200 = interpolateFloat3(q300, q210, q201, lambda);
float3 s010 = interpolateFloat3(r110, r020, r011, lambda);
float3 s001 = interpolateFloat3(r101, r011, r002, lambda);
float3 s100 = interpolateFloat3(r200, r110, r101, lambda);
return interpolateFloat3(s100, s010, s001, lambda);
}
// Evaluates a quartic Gregory patch.
float3 evalGregoryPatch(float3 p004, float3 p013, float3 p022, float3 p031,
float3 p040, float3 p130, float3 p220, float3 p310,
float3 p400, float3 p301, float3 p202, float3 p103,
float3 g01, float3 g02, float3 g11, float3 g12,
float3 g21, float3 g22, float3 tessCoord) {
float u = tessCoord.x, v = tessCoord.y, w = tessCoord.z;
float3 p112 = w > 0.99 ? g22 : (u * g22 + v * g01) / (u + v);
float3 p121 = v > 0.99 ? g02 : (w * g02 + u * g11) / (w + u);
float3 p211 = u > 0.99 ? g12 : (v * g12 + w * g21) / (v + w);
return evalBezierTriangle4(p400, p310, p301, p220, p211, p202, p130, p121,
p112, p103, p040, p031, p022, p013, p004,
tessCoord);
}
// Evaluates tessellation using the Walton & Meek approach.
void mainTessEval(inout appdata_full inoutVertex,
float3 p400,
float3 p040,
float3 p004,
float3 n400,
float3 n040,
float3 n004,
float3 tessCoord) {
// Switch to world space.
// FIXME: I'm not sure if this is necessary, but let's be paranoid for now.
p400 = mul(unity_ObjectToWorld, p400);
p040 = mul(unity_ObjectToWorld, p040);
p004 = mul(unity_ObjectToWorld, p004);
n400 = safeNormalize(mul(unity_ObjectToWorld, n400));
n040 = safeNormalize(mul(unity_ObjectToWorld, n040));
n004 = safeNormalize(mul(unity_ObjectToWorld, n004));
// Compute boundary curves.
float3 p012_3, p021_3, p120_3, p210_3, p201_3, p102_3;
computeEdgeControlPoints(p004, p040, n004, n040, p012_3, p021_3);
computeEdgeControlPoints(p040, p400, n040, n400, p120_3, p210_3);
computeEdgeControlPoints(p400, p004, n400, n004, p201_3, p102_3);
// Degree elevate cubic to quartic.
float3 p013, p022, p031, p130, p220, p310, p301, p202, p103;
degreeElevate(p004, p012_3, p021_3, p040, p013, p022, p031);
degreeElevate(p040, p120_3, p210_3, p400, p130, p220, p310);
degreeElevate(p400, p201_3, p102_3, p004, p301, p202, p103);
// Compute face points.
float3 g01, g02, g11, g12, g21, g22;
computeFaceControlPoints(p004, p012_3, p021_3, p040, p013, p022, p031,
p103, p130, n004, n040, g01, g02);
computeFaceControlPoints(p040, p120_3, p210_3, p400, p130, p220, p310,
p031, p301, n040, n400, g11, g12);
computeFaceControlPoints(p400, p201_3, p102_3, p004, p301, p202, p103,
p310, p013, n400, n004, g21, g22);
if (tessCoord.x == 0.0 || tessCoord.y == 0.0 || tessCoord.z == 0.0) {
float3 v0, v1, v2, v3;
float t;
if (tessCoord.x == 0.0) {
v0 = p004;
v1 = p012_3;
v2 = p021_3;
v3 = p040;
t = tessCoord.y;
} else if (tessCoord.y == 0.0) {
v0 = p400;
v1 = p201_3;
v2 = p102_3;
v3 = p004;
t = tessCoord.z;
} else {
v0 = p040;
v1 = p120_3;
v2 = p210_3;
v3 = p400;
t = tessCoord.x;
}
inoutVertex.vertex.xyz = mul(unity_WorldToObject, evalEdgePoint(v0, v1, v2, v3, t));
return;
}
// Evaluate Gregory patch.
inoutVertex.vertex.xyz = mul(unity_WorldToObject, evalGregoryPatch(p004,
p013,
p022,
p031,
p040,
p130,
p220,
p310,
p400,
p301,
p202,
p103,
g01,
g02,
g11,
g12,
g21,
g22,
tessCoord));
}
float tessEdge(float3 p0, float3 p1, float3 n0, float3 n1, float tessFactor,
float maxTessLevel) {
// Be careful of cracks here!
float edgeLengthTessLevel =
length(p1 - p0) / lerp(-p1.z, -p0.z, 0.5) * tessFactor;
float normalTessLevel = 1.0 - max(0.0, min(dot(n0, float3(0.0, 0.0, 1.0)),
dot(n1, float3(0.0, 0.0, 1.0))));
return min(maxTessLevel, edgeLengthTessLevel * normalTessLevel);
}
// Entry point for computation of tessellation factors.
float4 mainTessControl(appdata_full v0, appdata_full v1, appdata_full v2) {
float3 p0 = mul(UNITY_MATRIX_MV, float4(v0.vertex)).xyz;
float3 p1 = mul(UNITY_MATRIX_MV, float4(v1.vertex)).xyz;
float3 p2 = mul(UNITY_MATRIX_MV, float4(v2.vertex)).xyz;
// FIXME: should be inverse transpose!
float3 origin = mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0)).xyz;
float3 n0 =
safeNormalize(mul(UNITY_MATRIX_MV, float4(v0.normal, 1.0)).xyz - origin);
float3 n1 =
safeNormalize(mul(UNITY_MATRIX_MV, float4(v1.normal, 1.0)).xyz - origin);
float3 n2 =
safeNormalize(mul(UNITY_MATRIX_MV, float4(v2.normal, 1.0)).xyz - origin);
float4 tessLevel;
float tessFactor = _TessFactor;
tessLevel.x = tessEdge(p2, p1, n2, n1, _TessFactor, _MaxTessLevel);
tessLevel.y = tessEdge(p0, p2, n0, n2, _TessFactor, _MaxTessLevel);
tessLevel.z = tessEdge(p1, p0, n1, n0, _TessFactor, _MaxTessLevel);
tessLevel.w = (tessLevel.x + tessLevel.y + tessLevel.z) / 3.0;
return tessLevel;
}
// A preprocessor hack that allows us to capture individual untessellated vertices in our
// domain shader, which Unity surface shaders don't ordinarily allow us to do.
#define fakeVert(v) \
mainTessEval(v, \
vi[0].vertex.xyz, vi[1].vertex.xyz, vi[2].vertex.xyz, \
vi[0].normal, vi[1].normal, vi[2].normal, \
bary)
// Fractional odd partitioning looks bad, and surface shaders unfortunately hammer that mode
// in. Use a preprocessor hack to switch it to integer mode.
#undef UNITY_partitioning
#define UNITY_partitioning(x) partitioning("integer")
#endif