Skip to content

Commit 701bee0

Browse files
alex-soklevAlexander Soklev
andauthored
Fix rtx viewshed rendering blank image (#711)
* Fix for wrong camera ray generation in viewshed. Apply correct terrain scaling for triangulation * Added a second blank line before _generate_primary_rays_kernel method to satisfy linter * Added a second blank line before _triangulate_terrain_kernel method to satisfy linter * More linter complaints. This time about comments Co-authored-by: Alexander Soklev <alexander.soklev@software-supreme.com>
1 parent 84a66b6 commit 701bee0

File tree

2 files changed

+32
-19
lines changed

2 files changed

+32
-19
lines changed

xrspatial/gpu_rtx/mesh_utils.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,23 @@
66
def create_triangulation(raster, optix):
77
datahash = np.uint64(hash(str(raster.data.get())))
88
optixhash = np.uint64(optix.getHash())
9+
10+
# Calculate a scale factor for the height that maintains the ratio
11+
# width/height
12+
x_coords = raster.indexes.get('x').values
13+
x_range = x_coords.max() - x_coords.min()
14+
H, W = raster.shape
15+
# Get the scale factor of the terrain height vs terrain size
16+
scaleFactor = x_range / raster.res[1]
17+
scale = scaleFactor * W / raster.res[1]
18+
919
if optixhash != datahash:
10-
H, W = raster.shape
1120
num_tris = (H - 1) * (W - 1) * 2
1221
verts = cupy.empty(H * W * 3, np.float32)
1322
triangles = cupy.empty(num_tris * 3, np.int32)
14-
1523
# Generate a mesh from the terrain (buffers are on the GPU, so
1624
# generation happens also on GPU)
17-
res = _triangulate_terrain(verts, triangles, raster)
25+
res = _triangulate_terrain(verts, triangles, raster, scale)
1826
if res:
1927
raise RuntimeError(
2028
f"Failed to generate mesh from terrain, error code: {res}")
@@ -24,10 +32,14 @@ def create_triangulation(raster, optix):
2432
raise RuntimeError(
2533
f"OptiX failed to build GAS, error code: {res}")
2634

35+
# Enable for debug purposes
36+
if False:
37+
write("mesh.stl", verts, triangles)
2738
# Clear some GPU memory that we no longer need
2839
verts = None
2940
triangles = None
3041
cupy.get_default_memory_pool().free_all_blocks()
42+
return scale
3143

3244

3345
@nb.cuda.jit

xrspatial/gpu_rtx/viewshed.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,40 @@
1717
# If a cell is invisible, its value is set to -1
1818
INVISIBLE = -1
1919

20+
CAMERA_HEIGHT = 10000
21+
2022

2123
@nb.cuda.jit
22-
def _generate_primary_rays_kernel(data, x_coords, y_coords, H, W):
24+
def _generate_primary_rays_kernel(data, H, W):
2325
"""
2426
A GPU kernel that given a set of x and y discrete coordinates on a raster
2527
terrain generates in @data a list of parallel rays that represent camera
2628
rays generated from an orthographic camera that is looking straight down
27-
at the surface from an origin height 10000.
29+
at the surface from an origin height CAMERA_HEIGHT.
2830
"""
2931
i, j = nb.cuda.grid(2)
3032
if i >= 0 and i < H and j >= 0 and j < W:
3133
if (j == W-1):
32-
data[i, j, 0] = x_coords[j] - 1e-3
34+
data[i, j, 0] = j - 1e-3
3335
else:
34-
data[i, j, 0] = x_coords[j] + 1e-3
36+
data[i, j, 0] = j + 1e-3
3537

3638
if (i == H-1):
37-
data[i, j, 1] = y_coords[i] - 1e-3
39+
data[i, j, 1] = i - 1e-3
3840
else:
39-
data[i, j, 1] = y_coords[i] + 1e-3
41+
data[i, j, 1] = i + 1e-3
4042

41-
data[i, j, 2] = 10000 # Location of the camera (height)
43+
data[i, j, 2] = CAMERA_HEIGHT # Location of the camera (height)
4244
data[i, j, 3] = 1e-3
4345
data[i, j, 4] = 0
4446
data[i, j, 5] = 0
4547
data[i, j, 6] = -1
4648
data[i, j, 7] = np.inf
4749

4850

49-
def _generate_primary_rays(rays, x_coords, y_coords, H, W):
51+
def _generate_primary_rays(rays, H, W):
5052
griddim, blockdim = calc_cuda_dims((H, W))
51-
d_y_coords = cupy.array(y_coords)
52-
d_x_coords = cupy.array(x_coords)
53-
_generate_primary_rays_kernel[griddim, blockdim](
54-
rays, d_x_coords, d_y_coords, H, W)
53+
_generate_primary_rays_kernel[griddim, blockdim](rays, H, W)
5554
return 0
5655

5756

@@ -94,6 +93,8 @@ def _generate_viewshed_rays_kernel(
9493
ray_dir = make_float3(camera_ray, 4)
9594
# calculate intersection point
9695
hit_p = add(ray_origin, mul(ray_dir, dist))
96+
97+
surfaceHit = hit_p
9798
# generate new ray origin, and a little offset to avoid
9899
# self-intersection
99100
new_origin = add(hit_p, mul(norm, 1e-3))
@@ -117,7 +118,7 @@ def _generate_viewshed_rays_kernel(
117118
viewshed_point = add(hit_p, float3(0, 0, observer_elev))
118119

119120
# calculate vector from SurfaceHit to VP
120-
new_dir = diff(viewshed_point, new_origin)
121+
new_dir = diff(viewshed_point, surfaceHit)
121122
# calculate distance from surface to VP
122123
length = math.sqrt(dot(new_dir, new_dir))
123124
# normalize the direction (vector v)
@@ -188,7 +189,7 @@ def _viewshed_rt(
188189
d_visgrid = cupy.empty((H, W), np.float32)
189190
d_vsrays = cupy.empty((H, W, 8), np.float32)
190191

191-
_generate_primary_rays(d_rays, x_coords, y_coords, H, W)
192+
_generate_primary_rays(d_rays, H, W)
192193
device = cupy.cuda.Device(0)
193194
device.synchronize()
194195
res = optix.trace(d_rays, d_hits, W*H)
@@ -230,6 +231,6 @@ def viewshed_gpu(
230231
raise TypeError("raster.data must be a cupy array")
231232

232233
optix = RTX()
233-
create_triangulation(raster, optix)
234+
scale = create_triangulation(raster, optix)
234235

235-
return _viewshed_rt(raster, optix, x, y, observer_elev, target_elev)
236+
return _viewshed_rt(raster, optix, x, y, observer_elev*scale, target_elev*scale)

0 commit comments

Comments
 (0)