Skip to content

Commit

Permalink
Merge pull request nglviewer#887 from fredludlow/fix-cylinder-impostor
Browse files Browse the repository at this point in the history
Fix for cylinder impostors in orthographic code
  • Loading branch information
giagitom authored Oct 29, 2021
2 parents 6d79aa9 + f65c797 commit 172cfb8
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 76 deletions.
11 changes: 11 additions & 0 deletions src/buffer/mappedalignedbox-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
import { BufferParameters, BufferData } from './buffer'
import MappedBuffer from './mapped-buffer'

// +Y /
// 0**********2
// * | / **
// * |/ * *
// -----------3---- +X
// * /| * *
// * / | * *
// 1/**|******4
// / | * *
// / | **
// +Z | 5
const mapping = new Float32Array([
-1.0, 1.0, -1.0,
-1.0, -1.0, -1.0,
Expand Down
165 changes: 104 additions & 61 deletions src/shader/CylinderImpostor.frag
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,33 @@ float calcClip( vec3 cameraPos ){

void main(){

vec3 point = w.xyz / w.w;

// unpacking
vec3 base = base_radius.xyz;
float vRadius = base_radius.w;
vec3 end = end_b.xyz;
float b = end_b.w;

vec3 end_cyl = end;
vec3 surface_point = point;

vec3 ray_target = surface_point;
vec3 ray_origin = vec3(0.0);
vec3 ray_direction = mix(normalize(ray_origin - ray_target), vec3(0.0, 0.0, 1.0), ortho);
// The coordinates of the fragment, somewhere on the aligned mapped box
vec3 ray_target = w.xyz / w.w;

// unpack variables
vec3 base = base_radius.xyz; // center of the base (far end), in modelView space
float vRadius = base_radius.w; // radius in model view space
vec3 end = end_b.xyz; // center of the end (near end) in modelView
float b = end_b.w; // b is flag to decide if we're flipping this cylinder (see vertex shader)

vec3 ray_origin = vec3(0.0); // Camera position for perspective mode
vec3 ortho_ray_direction = vec3(0.0, 0.0, 1.0); // Ray is cylinder -> camera
vec3 persp_ray_direction = normalize(ray_origin - ray_target); // Ditto

vec3 ray_direction = mix(persp_ray_direction, ortho_ray_direction, ortho);

// basis is the rotation matrix for cylinder-aligned coords -> modelView
// (or post-multiply to reverse, see below)
mat3 basis = mat3( U, V, axis );

vec3 diff = ray_target - 0.5 * (base + end_cyl);
// diff is vector from center of cylinder to target
vec3 diff = ray_target - 0.5 * (base + end);

// P is point transformed back to cylinder-aligned (post-multiplied)
vec3 P = diff * basis;

// angle (cos) between cylinder cylinder_axis and ray direction
// axis looks towards camera (see vertex shader)
float dz = dot( axis, ray_direction );

float radius2 = vRadius*vRadius;
Expand All @@ -116,58 +123,71 @@ void main(){

// calculate a dicriminant of the above quadratic equation
float d = a1*a1 - a0*a2;
if (d < 0.0)
// outside of the cylinder
if (d < 0.0) {
// Point outside of the cylinder, becomes significant in perspective mode when camera is close
// to the cylinder
discard;

}
float dist = (-a1 + sqrt(d)) / a2;

// point of intersection on cylinder surface
vec3 new_point = ray_target + dist * ray_direction;

vec3 tmp_point = new_point - base;
vec3 _normal = normalize( tmp_point - axis * dot(tmp_point, axis) );
// point of intersection on cylinder surface (how far 'behind' the box surface the curved section of the cylinder would be)
vec3 surface_point = ray_target + dist * ray_direction;

ray_origin = mix( ray_origin, surface_point, ortho );
vec3 base_to_surface = surface_point - base;
// Calculates surface normal (of cylinder side) by finding point along cylinder axis in line with tmp_point
vec3 _normal = normalize( base_to_surface - axis * dot(base_to_surface, axis) );

// test caps
float front_cap_test = dot( tmp_point, axis );
float end_cap_test = dot((new_point - end_cyl), axis);
float base_cap_test = dot( base_to_surface, axis );
float end_cap_test = dot((surface_point - end), axis);

// to calculate caps, simply check the angle between
// the point of intersection - cylinder end vector
// and a cap plane normal (which is the cylinder cylinder_axis)
// if the angle < 0, the point is outside of cylinder
// test front cap
// test base cap

#ifndef CAP
vec3 new_point2 = ray_target + ( (-a1 - sqrt(d)) / a2 ) * ray_direction;
vec3 tmp_point2 = new_point2 - base;
#endif

// flat
if (front_cap_test < 0.0)
if (base_cap_test < 0.0) // The (extended) surface point falls outside the cylinder - beyond the base (away from camera)
{
// ray-plane intersection
float dNV = dot(-axis, ray_direction);
if (dNV < 0.0)
discard;
float near = dot(-axis, (base)) / dNV;
vec3 front_point = ray_direction * near + ray_origin;
// Ortho mode - surface point is ray_target
float dNV;
float near;
vec3 front_point;
if ( ortho == 1.0 ) {
front_point = ray_target;
} else {
dNV = dot(-axis, ray_direction);
// @fredludlow: Explicit discard is not required here?
// if (dNV < 0.0) {
// discard;
// }
near = dot(-axis, (base)) / dNV;
front_point = ray_direction * near + ray_origin;
}
// within the cap radius?
if (dot(front_point - base, front_point-base) > radius2)
if (dot(front_point - base, front_point-base) > radius2) {
discard;
}

#ifdef CAP
new_point = front_point;
surface_point = front_point;
_normal = axis;
#else
new_point = ray_target + ( (-a1 - sqrt(d)) / a2 ) * ray_direction;
// Calculate interior point
surface_point = ray_target + ( (-a1 - sqrt(d)) / a2 ) * ray_direction;
dNV = dot(-axis, ray_direction);
near = dot(axis, end_cyl) / dNV;
near = dot(axis, end) / dNV;
new_point2 = ray_direction * near + ray_origin;
if (dot(new_point2 - end_cyl, new_point2-base) < radius2)
if (dot(new_point2 - end, new_point2-base) < radius2) {
discard;
}
interior = true;
#endif
}
Expand All @@ -178,58 +198,79 @@ void main(){
// flat
if( end_cap_test > 0.0 )
{
// ray-plane intersection
float dNV = dot(axis, ray_direction);
if (dNV < 0.0)
discard;
float near = dot(axis, end_cyl) / dNV;
vec3 end_point = ray_direction * near + ray_origin;
// @fredludlow: NOTE: Perspective and ortho behaviour is quite different here. In perspective mode
// it is possible to see the inside face of the mapped aligned box and these points should be
// discarded. This occcurs when the camera is focused on one end of the cylinder and the cylinder
// is not quite in line with the camera (In orthographic mode this view is not possible).
// It is also possible to see the back face of the near (end) cap when looking nearly side-on.
float dNV;
float near;
vec3 end_point;
if ( ortho == 1.0 ) {
end_point = ray_target;
} else {
dNV = dot(axis, ray_direction);
if (dNV < 0.0) {
// Viewing inside/back face of end-cap
discard;
}
near = dot(axis, end) / dNV;
end_point = ray_direction * near + ray_origin;
}

// within the cap radius?
if( dot(end_point - end_cyl, end_point-base) > radius2 )
if( dot(end_point - end, end_point-base) > radius2 ) {
discard;

}
#ifdef CAP
new_point = end_point;
surface_point = end_point;
_normal = axis;
#else
new_point = ray_target + ( (-a1 - sqrt(d)) / a2 ) * ray_direction;
// Looking down the tube at an interior point, but check to see if interior point is
// within range:
surface_point = ray_target + ( (-a1 - sqrt(d)) / a2 ) * ray_direction;
dNV = dot(-axis, ray_direction);
near = dot(-axis, (base)) / dNV;
new_point2 = ray_direction * near + ray_origin;
if (dot(new_point2 - base, new_point2-base) < radius2)
if (dot(new_point2 - base, new_point2-base) < radius2) {
// Looking down the tube, which should be open-ended
discard;
}
interior = true;
#endif
}

gl_FragDepthEXT = calcDepth( new_point );
gl_FragDepthEXT = calcDepth( surface_point );


#ifdef NEAR_CLIP
if( calcClip( new_point ) > 0.0 ){
if( calcClip( surface_point ) > 0.0 ){
dist = (-a1 - sqrt(d)) / a2;
new_point = ray_target + dist * ray_direction;
if( calcClip( new_point ) > 0.0 )
surface_point = ray_target + dist * ray_direction;
if( calcClip( surface_point ) > 0.0 ) {
discard;
}
interior = true;
gl_FragDepthEXT = calcDepth( new_point );
gl_FragDepthEXT = calcDepth( surface_point );
if( gl_FragDepthEXT >= 0.0 ){
gl_FragDepthEXT = max( 0.0, calcDepth( vec3( - ( clipNear - 0.5 ) ) ) + ( 0.0000001 / vRadius ) );
}
}else if( gl_FragDepthEXT <= 0.0 ){
dist = (-a1 - sqrt(d)) / a2;
new_point = ray_target + dist * ray_direction;
surface_point = ray_target + dist * ray_direction;
interior = true;
gl_FragDepthEXT = calcDepth( new_point );
gl_FragDepthEXT = calcDepth( surface_point );
if( gl_FragDepthEXT >= 0.0 ){
gl_FragDepthEXT = 0.0 + ( 0.0000001 / vRadius );
}
}
#else
if( gl_FragDepthEXT <= 0.0 ){
dist = (-a1 - sqrt(d)) / a2;
new_point = ray_target + dist * ray_direction;
surface_point = ray_target + dist * ray_direction;
interior = true;
gl_FragDepthEXT = calcDepth( new_point );
gl_FragDepthEXT = calcDepth( surface_point );
if( gl_FragDepthEXT >= 0.0 ){
gl_FragDepthEXT = 0.0 + ( 0.0000001 / vRadius );
}
Expand All @@ -238,10 +279,12 @@ void main(){

// this is a workaround necessary for Mac
// otherwise the modified fragment won't clip properly
if (gl_FragDepthEXT < 0.0)
if (gl_FragDepthEXT < 0.0) {
discard;
if (gl_FragDepthEXT > 1.0)
}
if (gl_FragDepthEXT > 1.0) {
discard;
}

#ifdef PICKING

Expand All @@ -251,11 +294,11 @@ void main(){

#else

vec3 vViewPosition = -new_point;
vec3 vViewPosition = -surface_point;
vec3 vNormal = _normal;
vec3 vColor;

if( distSq3( new_point, end_cyl ) < distSq3( new_point, base ) ){
if( distSq3( surface_point, end ) < distSq3( surface_point, base ) ){
if( b < 0.0 ){
vColor = vColor1;
}else{
Expand Down
44 changes: 29 additions & 15 deletions src/shader/CylinderImpostor.vert
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ attribute vec3 position1;
attribute vec3 position2;
attribute float radius;

varying vec3 axis;
varying vec4 base_radius;
varying vec4 end_b;
varying vec3 U;
varying vec3 V;
varying vec4 w;
varying vec3 axis; // Cylinder axis
varying vec4 base_radius; // base position and cylinder radius packed into a vec4
varying vec4 end_b; // End position and "b" flag which indicates whether pos1/2 is flipped
varying vec3 U; // axis, U, V form orthogonal basis aligned to the cylinder
varying vec3 V;
varying vec4 w; // The position of the vertex after applying the mapping

#ifdef PICKING
#include unpack_color
Expand All @@ -61,24 +61,32 @@ void main(){
vColor2 = color2;
#endif

// vRadius = radius;
// Pack the radius
base_radius.w = radius * matrixScale( modelViewMatrix );

vec3 center = position;
// position is supplied by mapped-buffer.ts as midpoint of position1 and 2
vec3 center = position;

vec3 dir = normalize( position2 - position1 );
float ext = length( position2 - position1 ) / 2.0;
float ext = length( position2 - position1 ) / 2.0; // Half-length of cylinder

// Determine which direction the camera is in (in molecule coords)
// using cameraPosition fails on some machines, not sure why
// vec3 cam_dir = normalize( cameraPosition - mix( center, vec3( 0.0 ), ortho ) );
vec3 cam_dir;
if( ortho == 0.0 ){
cam_dir = ( modelViewMatrixInverse * vec4( 0, 0, 0, 1 ) ).xyz - center;
// Equivalent to, but see note above
// cam_dir = normalize( cameraPosition - center );
}else{
// Orthographic camera looks along -Z
cam_dir = ( modelViewMatrixInverse * vec4( 0, 0, 1, 0 ) ).xyz;
}
cam_dir = normalize( cam_dir );

vec3 ldir;
// ldir is the cylinder's direction (center->end) in model coords
// It will always point towards the camera
vec3 ldir;

float b = dot( cam_dir, dir );
end_b.w = b;
Expand All @@ -89,29 +97,35 @@ void main(){
else
ldir = ext * dir;

vec3 left = normalize( cross( cam_dir, ldir ) );
left = radius * left;
// left, up and ldir are orthogonal coordinates aligned with cylinder (ldir)
// scaled to the length and radius of the box
vec3 left = radius * normalize( cross( cam_dir, ldir ) );
vec3 up = radius * normalize( cross( left, ldir ) );

// transform to modelview coordinates

// Normalized versions of ldir, up and left, these can be used to convert
// from modelView <-> cylinder-aligned
axis = normalize( normalMatrix * ldir );
U = normalize( normalMatrix * up );
V = normalize( normalMatrix * left );

// Transform the base (the distant cap) and pack its coordinate
vec4 base4 = modelViewMatrix * vec4( center - ldir, 1.0 );
base_radius.xyz = base4.xyz / base4.w;

vec4 top_position = modelViewMatrix * vec4( center + ldir, 1.0 );
vec4 end4 = top_position;
// Similarly with the end (the near cap)
vec4 end4 = modelViewMatrix * vec4( center + ldir, 1.0 );
end_b.xyz = end4.xyz / end4.w;

// w is effective coordinate (apply the mapping)
w = modelViewMatrix * vec4(
center + mapping.x*ldir + mapping.y*left + mapping.z*up, 1.0
);

gl_Position = projectionMatrix * w;

// avoid clipping (1.0 seems to induce flickering with some drivers)
// Is this required?
gl_Position.z = 0.99;

}

0 comments on commit 172cfb8

Please sign in to comment.