-
Notifications
You must be signed in to change notification settings - Fork 0
/
VolumetricPrimitives.hlsli
275 lines (241 loc) · 9.77 KB
/
VolumetricPrimitives.hlsli
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
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
//**********************************************************************************************
//
// VolumetricPrimitives.hlsli
//
// Ray marching of Metaballs (aka "Blobs").
// More info here: https://www.scratchapixel.com/lessons/advanced-rendering/rendering-distance-fields/blobbies
//
//**********************************************************************************************
#ifndef VOLUMETRICPRIMITIVESLIBRARY_H
#define VOLUMETRICPRIMITIVESLIBRARY_H
#include "RaytracingShaderHelper.hlsli"
struct Metaball
{
float3 center;
float radius;
};
// Calculate a magnitude of an influence from a Metaball charge.
// Return metaball potential range: <0,1>
// mbRadius - largest possible area of metaball contribution - AKA its bounding sphere.
float CalculateMetaballPotential(in float3 position, in Metaball blob, out float distance)
{
distance = length(position - blob.center);
if (distance <= blob.radius)
{
float d = distance;
// Quintic polynomial field function.
// The advantage of this polynomial is having smooth second derivative. Not having a smooth
// second derivative may result in a sharp and visually unpleasant normal vector jump.
// The field function should return 1 at distance 0 from a center, and 1 at radius distance,
// but this one gives f(0) = 0, f(radius) = 1, so we use the distance to radius instead.
d = blob.radius - d;
float r = d / blob.radius;
return r * r * r * (r * (r * 6 - 15) + 10);
}
return 0;
}
// Calculate field potential from all active metaballs.
float CalculateMetaballsPotential(in float3 position, in Metaball blobs[N_METABALLS], in UINT nActiveMetaballs)
{
float sumFieldPotential = 0;
#if USE_DYNAMIC_LOOPS
for (UINT j = 0; j < nActiveMetaballs; j++)
#else
for (UINT j = 0; j < N_METABALLS; j++)
#endif
{
float dummy;
sumFieldPotential += CalculateMetaballPotential(position, blobs[j], dummy);
}
return sumFieldPotential;
}
// Calculate a normal via central differences.
float3 CalculateMetaballsNormal(in float3 position, in Metaball blobs[N_METABALLS], in UINT nActiveMetaballs)
{
float e = 0.5773 * 0.00001;
return normalize(float3(
CalculateMetaballsPotential(position + float3(-e, 0, 0), blobs, nActiveMetaballs) -
CalculateMetaballsPotential(position + float3(e, 0, 0), blobs, nActiveMetaballs),
CalculateMetaballsPotential(position + float3(0, -e, 0), blobs, nActiveMetaballs) -
CalculateMetaballsPotential(position + float3(0, e, 0), blobs, nActiveMetaballs),
CalculateMetaballsPotential(position + float3(0, 0, -e), blobs, nActiveMetaballs) -
CalculateMetaballsPotential(position + float3(0, 0, e), blobs, nActiveMetaballs)));
}
void InitializeAnimatedMetaballs(out Metaball blobs[N_METABALLS], in float elapsedTime, in float cycleDuration)
{
// Metaball centers at t0 and t1 key frames.
#if N_METABALLS == 10
float3 keyFrameCenters[N_METABALLS][2] =
{
{ float3(-0.7, 0, 0),float3(0.7,0, 0) },
{ float3(0.7 , 0, 0), float3(-0.7, 0, 0) },
{ float3(0, -0.7, 0),float3(0, 0.7, 0) },
{ float3(0, 0.7, 0), float3(0, -0.7, 0) },
{ float3(0, 0, 0), float3(0, 0, 0) },
{ float3(-0.7, -0.7, 0),float3(0.7,0.7, 0) },
{ float3(0.7 , 0.7, 0), float3(-0.7, -0.7, 0) },
{ float3(0.7, -0.7, 0),float3(-0.7, 0.7, 0) },
{ float3(-0.7, 0.7, 0), float3(0.7, -0.7, 0) },
};
// Metaball field radii of max influence
float radii[N_METABALLS] = { 0.35, 0.35, 0.35, 0.35, 0.25, 0.35, 0.35, 0.35, 0.35};
#elif N_METABALLS == 27
// layers means the distance of the inner ball to the surface
const int layers = 1;
float3 keyFrameCenters[N_METABALLS][2];
// Metaball field radii of max influence
float radii[N_METABALLS];
uint index = 0;
float offset = 0.7;
// [-layers, layers]
[unroll] for (int x = -layers; x <= layers; x++) {
[unroll] for (int y = -layers; y <= layers; y++) {
[unroll] for (int z= -layers; z <= layers; z++) {
keyFrameCenters[index][0] = float3(x * offset, y * offset, z * offset);
keyFrameCenters[index][1] = -keyFrameCenters[index][0];
radii[index] = 0.25;
index += 1;
}
}
}
#elif N_METABALLS == 125
// layers means the distance of the inner ball to the surface
const int layers = 2;
float3 keyFrameCenters[N_METABALLS][2];
// Metaball field radii of max influence
float radii[N_METABALLS];
uint index = 0;
float offset = 0.3;
// [-layers, layers]
[unroll]for (int x = -layers; x <= layers; x++) {
[unroll]for (int y = -layers; y <= layers; y++) {
[unroll]for (int z= -layers; z <= layers; z++) {
keyFrameCenters[index][0] = float3(x * offset, y * offset, z * offset);
keyFrameCenters[index][1] = -keyFrameCenters[index][0];
radii[index] = 0.15;
index += 1;
}
}
}
#else //N_METABALLS == 3
float3 keyFrameCenters[N_METABALLS][2] =
{
{ float3(-0.7, 0, 0),float3(0.7,0, 0) },
{ float3(0.7 , 0, 0), float3(-0.7, 0, 0) },
{ float3(0, 0, 0), float3(0, 0, 0) }
};
// Metaball field radii of max influence
float radii[N_METABALLS] = { 0.35, 0.35, 0.25};
#endif
// Calculate animated metaball center positions.
float tAnimate = CalculateAnimationInterpolant(elapsedTime, cycleDuration);
for (UINT j = 0; j < N_METABALLS; j++)
{
blobs[j].center = lerp(keyFrameCenters[j][0], keyFrameCenters[j][1], tAnimate);
blobs[j].radius = radii[j];
}
}
// Find all metaballs that ray intersects.
// The passed in array is sorted to the first nActiveMetaballs.
void FindIntersectingMetaballs(in Ray ray, out float tmin, out float tmax, inout Metaball blobs[N_METABALLS], out UINT nActiveMetaballs)
{
// Find the entry and exit points for all metaball bounding spheres combined.
tmin = INFINITY;
tmax = -INFINITY;
nActiveMetaballs = 0;
for (UINT i = 0; i < N_METABALLS; i++)
{
float _thit, _tmax;
if (RaySolidSphereIntersectionTest(ray, _thit, _tmax, blobs[i].center, blobs[i].radius))
{
tmin = min(_thit, tmin);
tmax = max(_tmax, tmax);
#if LIMIT_TO_ACTIVE_METABALLS
blobs[nActiveMetaballs++] = blobs[i];
#else
nActiveMetaballs = N_METABALLS;
#endif
}
}
tmin = max(tmin, RayTMin());
tmax = min(tmax, RayTCurrent());
}
// Test if a ray with RayFlags and segment <RayTMin(), RayTCurrent()> intersects metaball field.
// The test sphere traces through the metaball field until it hits a threshold isosurface.
bool RayMetaballsIntersectionTest(in Ray ray, out float thit, out ProceduralPrimitiveAttributes attr, in float elapsedTime)
{
Metaball blobs[N_METABALLS];
InitializeAnimatedMetaballs(blobs, elapsedTime, 12.0f);
float tmin, tmax; // Ray extents to first and last metaball intersections.
UINT nActiveMetaballs = 0; // Number of metaballs's that the ray intersects.
FindIntersectingMetaballs(ray, tmin, tmax, blobs, nActiveMetaballs);
UINT MAX_STEPS = 128;
float t = tmin;
float minTStep = (tmax - tmin) / (MAX_STEPS / 1);
UINT iStep = 0;
const float Threshold = 0.25f;
float3 position = ray.origin + t * ray.direction;
float fieldPotentials[N_METABALLS]; // Field potentials for each metaball.
float sumFieldPotential = 0;
#if USE_DYNAMIC_LOOPS
for (UINT j = 0; j < nActiveMetaballs; j++)
#else
for (UINT j = 0; j < N_METABALLS; j++)
#endif
{
float distance;
fieldPotentials[j] = CalculateMetaballPotential(position, blobs[j], distance);
sumFieldPotential += fieldPotentials[j];
}
bool inside = false;
while (iStep++ < MAX_STEPS)
{
float3 position = ray.origin + t * ray.direction;
float fieldPotentials[N_METABALLS]; // Field potentials for each metaball.
float sumFieldPotential = 0; // Sum of all metaball field potentials.
// Calculate field potentials from all metaballs.
#if USE_DYNAMIC_LOOPS
for (UINT j = 0; j < nActiveMetaballs; j++)
#else
for (UINT j = 0; j < N_METABALLS; j++)
#endif
{
float distance;
fieldPotentials[j] = CalculateMetaballPotential(position, blobs[j], distance);
sumFieldPotential += fieldPotentials[j];
}
// Field potential threshold defining the isosurface.
// Threshold - valid range is (0, 1>, the larger the threshold the smaller the blob.
// Have we crossed the isosurface?
if (!inside && sumFieldPotential >= Threshold)
{
float3 normal = CalculateMetaballsNormal(position, blobs, nActiveMetaballs);
if (IsAValidHit(ray, t, normal))
{
thit = t;
attr.normal = normal;
return true;
}
}
if (inside && sumFieldPotential <= Threshold)
{
float3 normal = CalculateMetaballsNormal(position, blobs, nActiveMetaballs);
thit = t;
attr.normal = normal;
return true;
}
t += minTStep;
}
return false;
}
#endif // VOLUMETRICPRIMITIVESLIBRARY_H