-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spherical volume rendering #64
base: main
Are you sure you want to change the base?
Changes from 1 commit
f4e9c3b
ab78cab
af7663d
ba3ed48
ca6dbfb
d840d3f
83d793f
4cd83b3
726aa82
3cf8186
c9a5bff
065eb6d
ddb69fd
0501c6f
2cb14a8
ad96eb3
fb80cea
65a6abf
f43d577
d9a9771
1ffffb5
0474454
0031ee8
4e4fdec
dbd7240
afcef53
4f73cdd
ac9cd00
3cef5b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,39 @@ def add_data(self, field, no_ghost=False): | |
# Every time we change our data source, we wipe all existing ones. | ||
# We now set up our vertices into our current data source. | ||
vert, dx, le, re = [], [], [], [] | ||
|
||
# spherical-only: SHOULD ONLY DO THIS IF DATASET IS SPHERICAL | ||
# normal vectors and plane constants for min/max phi face | ||
phi_plane_le = [] | ||
phi_plane_re = [] | ||
|
||
axis_id = self.data_source.ds.coordinates.axis_id | ||
|
||
def calculate_phi_normal_plane(le_or_re): | ||
# calculates the parameters for a plane parallel to the z-axis and | ||
# passing through the x-y values defined by the polar angle phi | ||
# the result plane defines the boundary on +/-phi faces. | ||
# | ||
# eq of plane: | ||
# X dot N = d where N is a normal vector, d is a constant, | ||
# X is a position vector | ||
# this function returns N and d. | ||
phi = le_or_re[axis_id["phi"]] | ||
theta = le_or_re[axis_id["theta"]] | ||
r = le_or_re[axis_id["r"]] | ||
|
||
# prob can/should use a yt func here to ensure consistency.... | ||
z = r * np.cos(theta) | ||
r_xy = r * np.sin(theta) | ||
x = r_xy * np.cos(phi) | ||
y = r_xy * np.sin(phi) | ||
pt = np.array([x, y, z]) | ||
|
||
z_hat = np.array([0, 0, 1]) | ||
normal_vec = np.cross(pt, z_hat) | ||
d = np.dot(normal_vec, pt) | ||
return normal_vec, d | ||
|
||
self.min_val = +np.inf | ||
self.max_val = -np.inf | ||
if self.scale: | ||
|
@@ -65,6 +98,12 @@ def add_data(self, field, no_ghost=False): | |
le.append(block.LeftEdge.tolist()) | ||
re.append(block.RightEdge.tolist()) | ||
|
||
normal, const = calculate_phi_normal_plane(le[-1]) | ||
phi_plane_le.append(np.array([*normal, const])) | ||
|
||
normal, const = calculate_phi_normal_plane(re[-1]) | ||
phi_plane_re.append(np.array([*normal, const])) | ||
|
||
for (g, node, (sl, _dims, _gi)) in self.data_source.tiles.slice_traverse(): | ||
block = node.data | ||
self.blocks_by_grid[g.id - g._id_offset].append((id(block), i)) | ||
|
@@ -95,6 +134,15 @@ def add_data(self, field, no_ghost=False): | |
VertexAttribute(name="in_right_edge", data=re) | ||
) | ||
|
||
phi_plane_re = np.array(phi_plane_re, dtype="f4") | ||
phi_plane_le = np.array(phi_plane_le, dtype="f4") | ||
self.vertex_array.attributes.append( | ||
VertexAttribute(name="phi_plane_le", data=phi_plane_le) | ||
) | ||
self.vertex_array.attributes.append( | ||
VertexAttribute(name="phi_plane_re", data=phi_plane_re) | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So it occurs to me that we should have the cartesian volume rendering as the basic option, and we can/should have spherical as a subclass. Let's not try cramming it all in; @neutrinoceros 's work on having things be visible in index-space coordinates is encouraging me to think of it in both ways. That's not really specific here though, except that what we may want to do is to have this be a supplemental function that gets called if the component accessing the data is viewing it in spherical coordinates. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ya! I agree totally! I just jammed it in here to get things in initially working. Unjamming it and subclassing it relates to your vectorization question I think. If I vectorize that function, it'll be easier to have a sublcass that takes the output from the standard cartesian version to calculate the new spherical-only attributes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. latest push no longer breaks things for cartesian coords (it's now vectorized and only runs if spherical). I still want to keep thinking on how to better capture the different geometries though. I initially thought it may make sense to have a class SphericalBlockCollection(BlockCollection):
def add_data(self, field, no_ghost=False):
super().add_data(field, no_ghost=no_ghost)
<< --- calculate phi-normal planes from left, right edges --- >> would require us also saving the full |
||
# Now we set up our textures | ||
self._load_textures() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,11 @@ flat in mat4 inverse_pmvm; // always cartesian | |
flat in ivec3 texture_offset; | ||
out vec4 output_color; | ||
|
||
flat in vec4 phi_plane_le; | ||
flat in vec4 phi_plane_re; | ||
|
||
const float INFINITY = 1. / 0.; | ||
const float PI = 3.1415926535897932384626433832795; | ||
chrishavlin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bool within_bb(vec3 pos) | ||
{ | ||
bvec3 left = greaterThanEqual(pos, left_edge); | ||
|
@@ -45,6 +50,106 @@ vec4 cleanup_phase(in vec4 curr_color, in vec3 dir, in float t0, in float t1); | |
// void (vec3 tex_curr_pos, inout vec4 curr_color, float tdelta, float t, | ||
// vec3 direction); | ||
|
||
float get_ray_plane_intersection(vec3 p_normal, float p_constant, vec3 ray_origin, vec3 ray_dir) | ||
{ | ||
float n_dot_u = dot(p_normal, ray_dir); | ||
float n_dot_ro = dot(p_normal, ray_origin); | ||
// add check for n_dot_u == 0 (ray is parallel to plane) | ||
if (n_dot_u == float(0.0)) | ||
{ | ||
// the ray is parallel to the plane. there are either no intersections | ||
// or infinite intersections. In the second case, we are guaranteed | ||
// to intersect one of the other faces, so we can drop this plane. | ||
return INFINITY; | ||
} | ||
|
||
return (p_constant - n_dot_ro) / n_dot_u; | ||
} | ||
|
||
vec2 get_ray_sphere_intersection(float r, vec3 ray_origin, vec3 ray_dir) | ||
{ | ||
float dir_dot_dir = dot(ray_dir, ray_dir); | ||
float ro_dot_ro = dot(ray_origin, ray_origin); | ||
float dir_dot_ro = dot(ray_dir, ray_origin); | ||
float rsq = r * r; // could be calculated in vertex shader | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which r is this? could we actually calculate it in the vertex shader? eventually this would need to be once for every cell edge, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that would be the r from |
||
|
||
float a_2 = 2.0 * dir_dot_dir; | ||
float b = 2.0 * dir_dot_ro; | ||
float c = ro_dot_ro - rsq; | ||
float determinate = b*b - 2.0 * a_2 * c; | ||
float cutoff_val = 0.0; | ||
if (determinate < cutoff_val) | ||
{ | ||
return vec2(INFINITY, INFINITY); | ||
} | ||
else if (determinate == cutoff_val) | ||
{ | ||
return vec2(-b / a_2, INFINITY); | ||
} | ||
else | ||
{ | ||
return vec2((-b - sqrt(determinate))/ a_2, (-b + sqrt(determinate))/ a_2); | ||
} | ||
|
||
} | ||
|
||
vec2 get_ray_cone_intersection(float theta, vec3 ray_origin, vec3 ray_dir) | ||
{ | ||
// note: cos(theta) and vhat could be calculated in vertex shader | ||
float costheta; | ||
vec3 vhat; | ||
if (theta > PI/2.0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's entirely possible that this is what the compiler would do anyway, but I have found it to be simpler to write this as the sum of two different values multiplied by booleans. So you'd compute both and then add them. For instance, something like this, but with the casting done correctly:
something like that, but I'm forgetting the right casting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did see that suggestion on some glsl forums, in some cases it can be more efficient than the full if statements? I think it would simplify the code here, will make that change. |
||
{ | ||
// if theta is past PI/2, the cone will point in negative z and the | ||
// half angle should be measured from the -z axis, not +z. | ||
vhat = vec3(0.0, 0.0, -1.0); | ||
costheta = cos(PI - theta); | ||
} | ||
else | ||
{ | ||
vhat = vec3(0.0, 0.0, 1.0); | ||
costheta = cos(theta); | ||
} | ||
float cos2t = pow(costheta, 2); | ||
// note: theta = PI/2.0 is well defined. determinate = 0 in that case and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it may also be worth exploring using slight offsets instead of accounting for singularities; i.e., if |
||
// the cone becomes a plane in x-y. | ||
|
||
float dir_dot_vhat = dot(ray_dir, vhat); | ||
float dir_dot_dir = dot(ray_dir, ray_dir); | ||
float ro_dot_vhat = dot(ray_origin, vhat); | ||
float ro_dot_dir = dot(ray_origin, ray_dir); | ||
float ro_dot_ro = dot(ray_origin, ray_dir); | ||
|
||
float a_2 = 2.0*(pow(dir_dot_vhat, 2) - dir_dot_dir * cos2t); | ||
float b = 2.0 * (ro_dot_vhat * dir_dot_vhat - ro_dot_dir*cos2t); | ||
float c = pow(ro_dot_vhat, 2) - ro_dot_ro*cos2t; | ||
float determinate = b*b - 2.0 * a_2 * c; | ||
if (determinate < 0.0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This set of nested conditionals probably does need refactoring. I think single-level nested conditionals might be manageable by the compiler but we should avoid multi-level nesting. |
||
{ | ||
return vec2(INFINITY, INFINITY); | ||
} | ||
else if (determinate == 0.0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we do not want multiple return points There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There can be 2 real intersections of the cone though? In most cases, only one of the intersections would also be on the face of the spherical voxel. But in some cases there could be 2. |
||
{ | ||
return vec2(-b / a_2, INFINITY); | ||
} | ||
else | ||
{ | ||
vec2 t = vec2((-b - sqrt(determinate))/ a_2, (-b + sqrt(determinate))/ a_2); | ||
|
||
// check that t values are not for the shadow cone | ||
float cutoff_t = (-1.0 * ro_dot_vhat) / dir_dot_vhat; | ||
if (t[0] < cutoff_t) | ||
{ | ||
t[0] = INFINITY; | ||
} | ||
else if (t[1] < cutoff_t) | ||
{ | ||
t[1] = INFINITY; | ||
} | ||
return t; | ||
} | ||
} | ||
|
||
void spherical_coord_shortcircuit() | ||
{ | ||
vec3 ray_position = v_model.xyz; // now spherical | ||
|
@@ -53,6 +158,22 @@ void spherical_coord_shortcircuit() | |
vec3 dir = -normalize(camera_pos.xyz - ray_position_xyz); | ||
vec4 curr_color = vec4(0.0); | ||
|
||
// intersections | ||
vec2 t_sphere_outer = get_ray_sphere_intersection(right_edge[id_r], ray_position_xyz, dir); | ||
if (t_sphere_outer[0] == INFINITY && t_sphere_outer[1] == INFINITY) | ||
chrishavlin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// if there are no intersections with the outer sphere, then there | ||
// will be no intersections with the remaining faces and we can stop | ||
// looking. | ||
discard; | ||
} | ||
|
||
vec2 t_sphere_inner = get_ray_sphere_intersection(left_edge[id_r], ray_position_xyz, dir); | ||
float t_p_1 = get_ray_plane_intersection(vec3(phi_plane_le), phi_plane_le[3], ray_position_xyz, dir); | ||
float t_p_2 = get_ray_plane_intersection(vec3(phi_plane_re), phi_plane_re[3], ray_position_xyz, dir); | ||
vec2 t_cone_outer = get_ray_cone_intersection(right_edge[id_theta], ray_position_xyz, dir); | ||
vec2 t_cone_inner= get_ray_cone_intersection(left_edge[id_theta], ray_position_xyz, dir); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so once we have these, what do we do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// do the texture evaluation in the native coordinate space | ||
vec3 range = (right_edge + dx/2.0) - (left_edge - dx/2.0); | ||
vec3 nzones = range / dx; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we spend enough time in this function that it makes sense to vectorize it? i.e., call if on a list of coordinates and then return an array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vectorizing this is a good idea! and I think will simplify un-tangling the spherical-only part of the calculation from the base cartesian version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is vectorized now! see https://github.com/yt-project/yt_idv/pull/64/files#diff-7d5c480ce667d255f45fb76e3578809a9840831967712edc900858d049cc5517