Skip to content

Commit 3a1e0d2

Browse files
committed
Cross hairs on render view (#24)
1 parent bc3d303 commit 3a1e0d2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+997
-818
lines changed

DepthPicking/README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
## About
3+
4+
It is often useful to convert the location of a mouse click to object coordinates (e.g. to select the object). We also want objects that are closer to the camera to occlude more distant objects. OpenGL and other accelerated graphics libraries use the `depth buffer` to hide occluded objects and aid [mouse picking](https://www.cs.cornell.edu/courses/cs4620/2017sp/cs4621/lecture08/exhibit03.html). Unfortunately, the default depth buffer does not work for volume ray casting. Here we describe a simple solution.
5+
6+
This description assumes a knowledge of volume ray casting. Good introductions are provided by [Philip Rideout](https://prideout.net/blog/old/blog/index.html@p=64.html) and [Will Usher](https://www.willusher.io/webgl/2019/01/13/volume-rendering-with-webgl). As they note, we have the vertex shader draw a simple cube, with a ray launched for each pixel from the front of the cube to the back. Therefore, the depth recorded by the vertex shader refers to the front of the cube, not the rendered surface. Consider the image below: the left panel shows the front-face of the cube, whereas the right panel shows the rendered object. Note that some rays travel completely through the air, never striking the object, while other rays strike the brain's surface close to the cube's front face. Therefore, we tune our shaders to explicitly set the depth buffer, setting [gl_FragDepth](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/gl_FragDepth.xhtml) for each voxel.
7+
8+
![volume rendering and cube defining ray starts](cube_render.png)
9+
10+
## Writing the depth buffer
11+
12+
The code below extends [Will Usher's](https://www.willusher.io/webgl/2019/01/13/volume-rendering-with-webgl) description of a ray casting volume renderer. Please see his page for a full description. Our goal here is to simply extend his code to record the depth of the surface in the OpenGL depth buffer `gl_FragDepth`. The ray traverses the volume from front to back, absorbing color from voxels. Here we use `isHit` to detect the first non-transparent voxel.
13+
14+
```
15+
int isHit = 0; //used to detect first non-transparent voxel
16+
for (float t = t_hit.x; t < t_hit.y; t += dt) {
17+
float val = texture(volume, p).r;
18+
vec4 val_color = vec4(texture(transfer_fcn, vec2(val, 0.5)).rgb, val);
19+
p += ray_dir * dt;
20+
if (val_color.a < 0.01) continue; //skip transparent samples
21+
if (!isHit) { //our first non-transparent voxel
22+
isHit = 1;
23+
gl_FragDepth = ((ModelViewProjectionMatrix * vec4(pos, 1.0)).z + 1.0) * 0.5;
24+
}
25+
color.rgb += (1.0 - color.a) * val_color.a * val_color.rgb;
26+
color.a += (1.0 - color.a) * val_color.a;
27+
if (color.a >= 0.95) {
28+
break;
29+
}
30+
}
31+
```
32+
33+
The effect is shown in the image below. Our shader sets both the fragment color (upper left) and fragment depth. Once we have set the depth buffer, we can use it to occlude other objects. In the lower panel we subsequently draw three orthogonal lines to show the crosshair. In the image we actually draw these lines twice. First, we draw opaque lines where we enable the [GL_DEPTH_TEST](https://learnopengl.com/Advanced-OpenGL/Depth-testing) with GL_LEQUAL. These lines are occluded when they are behind the rendering surface. Next we draw translucent lines where we disable the depth test, creating faint lines at locations where the line is beneath the rendered surface.
34+
35+
![color and depth buffers](cross_hair.png)
36+
37+
## Reading the depth buffer
38+
39+
Writing to the depth buffer is sufficient to draw occluded items. However, we may also want to determine where the user clicked. For example, we might want the crosshairs to shift move to the location on the volumes surface where the user clicked.
40+
41+
You can read the depth buffer at canvas pixel X,Y with the following command (make sure to set your glDrawBuffer and glReadBuffer correctly):
42+
```
43+
glReadPixels( X, Y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, @depth);
44+
```
45+
46+
Historically, users had access to the [gluUnProject](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluUnProject.xml) function. The [NeHe tutorial](http://nehe.gamedev.net/article/using_gluunproject/16013/) describes the usage nicely. This allows us to take the combination of the mouse position and depth buffer and compute the selected location in the object's space. Unfortunately, this function is not available in most modern graphics libraries, but it only takes a few lines of code to reconstruct. The code below provides the transform for [GLSL](https://community.khronos.org/t/converting-gl-fragcoord-to-model-space/57397), though you can find solutions for other tools like [WebGL](https://github.com/bringhurst/webgl-unproject) and [Python](http://pyopengl.sourceforge.net/documentation/manual-3.0/gluUnProject.html),
47+
48+
The input is the mouse horizontal and vertical position (gl_FragCoord.xy) and the depth buffer value at that location (gl_FragCoord.z). It also gets receives the viewport width (view.x), canvas height (view.y) left corner (view.z) and bottom corner (view.w). Note that the variable `gl_ModelViewProjectionMatrixInverse` does not exist in modern OpenGL or webGL, but this is simply the inverse of the matrix used by the vertex shader to warp the vertices to screen space.
49+
50+
```
51+
vec4 v = vec4(2.0*(gl_FragCoord.x-view.x)/view.z-1.0,
52+
2.0*(gl_FragCoord.y-view.y)/view.w-1.0,
53+
2.0*texture2DRect(DepthTex,gl_FragCoord.xy).z-1.0,
54+
1.0 );
55+
v = gl_ModelViewProjectionMatrixInverse * v;
56+
v /= v.w;
57+
```
58+
59+
This formula reverses the work of a vertex shader. The vertex shader warps a location from model space to our 2D screen space. Here we warp the screen space to the model's space. This allows us to determine where the user clicked in our volume and update the coordinates accordingly.

DepthPicking/cross_hair.png

146 KB
Loading

DepthPicking/cube_render.png

139 KB
Loading

MRIcroGL.app/Contents/Resources/shader/Default.glsl

+9-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void main() {
2929
vec4 samplePos = vec4(start.xyz, 0.0);
3030
vec4 clipPos = applyClip(dir, samplePos, len);
3131
float opacityCorrection = stepSize/sliceSize;
32+
gl_FragDepth = 1.0;
3233
//fast pass - optional
3334
fastPass (len, dir, intensityVol, samplePos);
3435
#if ( __VERSION__ > 300 )
@@ -59,12 +60,17 @@ void main() {
5960
//end fastpass - optional
6061
float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453);
6162
samplePos += deltaDir * ran;
63+
int nHit = 0;
6264
vec3 defaultDiffuse = vec3(0.5, 0.5, 0.5);
6365
while (samplePos.a <= len) {
6466
colorSample = texture3Df(intensityVol,samplePos.xyz);
6567
if (colorSample.a > 0.0) {
6668
colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
67-
bgNearest = min(samplePos.a,bgNearest);
69+
if (nHit < 1) {
70+
nHit ++;
71+
bgNearest = samplePos.a;
72+
setDepthBuffer(samplePos.xyz);
73+
}
6874
gradSample= texture3Df(gradientVol,samplePos.xyz);
6975
gradSample.rgb = normalize(gradSample.rgb*2.0 - 1.0);
7076
//reusing Normals http://www.marcusbannerman.co.uk/articles/VolumeRendering.html
@@ -82,6 +88,7 @@ void main() {
8288
samplePos += deltaDir;
8389
} //while samplePos.a < len
8490
colAcc.a = colAcc.a/0.95;
91+
8592
colAcc.a *= backAlpha;
8693
if ( overlays< 1 ) {
8794
#if ( __VERSION__ > 300 )
@@ -147,6 +154,7 @@ void main() {
147154
}
148155
colAcc.rgb = mix(colAcc.rgb, overAcc.rgb, overMix);
149156
colAcc.a = max(colAcc.a, overAcc.a);
157+
//if (nHit < 1 ) discard;
150158
#if ( __VERSION__ > 300 )
151159
FragColor = colAcc;
152160
#else

MRIcroGL.app/Contents/Resources/shader/Edges.glsl

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void main() {
3838
vec4 samplePos = vec4(start.xyz, 0.0);
3939
vec4 clipPos = applyClip(dir, samplePos, len);
4040
float opacityCorrection = stepSize/sliceSize;
41+
gl_FragDepth = 1.0;
4142
//fast pass - optional
4243
fastPass (len, dir, intensityVol, samplePos);
4344
#if ( __VERSION__ > 300 )
@@ -71,11 +72,16 @@ void main() {
7172
float boundAcc = 0.0;
7273
vec3 defaultDiffuse = vec3(0.5, 0.5, 0.5);
7374
const float kEarlyTermination = 0.95;
75+
int nHit = 0;
7476
while (samplePos.a <= len) {
7577
colorSample = texture3Df(intensityVol,samplePos.xyz);
7678
samplePos += deltaDir;
7779
if (colorSample.a < 0.001) continue;
78-
bgNearest = min(samplePos.a,bgNearest);
80+
if (nHit < 1) {
81+
nHit ++;
82+
bgNearest = samplePos.a;
83+
setDepthBuffer(samplePos.xyz);
84+
}
7985
colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
8086
gradSample = texture3Df(gradientVol,samplePos.xyz);
8187
gradSample.rgb = normalize(gradSample.rgb*2.0 - 1.0);

MRIcroGL.app/Contents/Resources/shader/FX.glsl

-156
This file was deleted.

MRIcroGL.app/Contents/Resources/shader/Glass.glsl

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ void main() {
2828
vec3 dir = backPosition - start;
2929
float len = length(dir);
3030
dir = normalize(dir);
31+
gl_FragDepth = 1.0;
32+
int nHit = 0;
3133
vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize);
3234
vec4 gradSample, colorSample;
3335
float bgNearest = len; //assume no hit
@@ -73,7 +75,11 @@ void main() {
7375
while (samplePos.a <= len) {
7476
gradSample= texture3Df(gradientVol,samplePos.xyz);
7577
if (gradSample.a > 0.0) {
76-
bgNearest = min(samplePos.a, bgNearest);
78+
if (nHit < 1) {
79+
nHit ++;
80+
bgNearest = samplePos.a;
81+
setDepthBuffer(samplePos.xyz);
82+
}
7783
gradSample.rgb = normalize(gradSample.rgb*2.0 - 1.0);
7884
colorSample = gradSample;
7985
if (gradSample.a > boundThresh) {

MRIcroGL.app/Contents/Resources/shader/MIP.glsl

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//pref
1+
\\\//pref
22
overAlpha|float|0.0|0.8|2.0|Ability to see overlay images added to background
33
//frag
44
uniform float overAlpha = 0.8;
@@ -14,6 +14,7 @@ void main() {
1414
vec3 dir = backPosition - start;
1515
float len = length(dir);
1616
dir = normalize(dir);
17+
gl_FragDepth = 1.0;
1718
vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize);
1819
vec4 gradSample, colorSample;
1920
float bgNearest = len; //assume no hit
@@ -44,10 +45,10 @@ void main() {
4445
if ( overlays < 1 ) { //pass without overlays
4546
while (samplePos.a <= len) {
4647
colorSample = texture3Df(intensityVol,samplePos.xyz);
47-
//if (colorSample.a > colAcc.a) //ties generate errors for TT_desai_dd_mpm
48-
// colAcc = colorSample;
49-
if (colorSample.a > colAcc.a) //ties generate errors for TT_desai_dd_mpm
48+
if (colorSample.a > colAcc.a) {//ties generate errors for TT_desai_dd_mpm
5049
colAcc = colorSample+0.00001;
50+
setDepthBuffer(samplePos.xyz);
51+
}
5152
samplePos += deltaDir;
5253
} //while samplePos.a < len
5354
//colAcc.a = step(0.001, colAcc.a); //good for templates...

0 commit comments

Comments
 (0)