Commits

Cliff Biffle  committed 60c7318

Changed how perspective is computed. My original formula was wrong. I was merely trying to run the perspective projection formula backwards. This gave a decent approximation at middle distances, but showed warping at extremes. Today I realized I had over-simplified the problem.

The new version (most of which lives in KVAbstractRenderer) is based around raycasting, given the lens's known FOV and aspect ratio. It's memoized a little too aggressively -- I should be able to square-root the footprint later, since raycasting is separable across X and Y in this case.

As a side effect, the vector computations implicitly scale the scene, so I was able to remove some GL scaling code.

  • Participants
  • Parent commits 9476999

Comments (0)

Files changed (7)

File KVAbstractRenderer.h

 // Abstract base class for KVRenderers.  Factors out a wee
 // bit of boilerplate code.
 @interface KVAbstractRenderer : NSObject <KVRenderer> {
+  vec3f_t rayField[240][320];
 }
 
 // These properties are synthesized for the benefit of
 
 - (vec3f_t) colorAsRGBVector;
 
+- (void) projectInto: (vec3f_t *) coord x: (int) x y: (int) y z: (float) z;
+
 @end
+
+typedef vec3f_t (*KVAbstractRendererProjector)(id, SEL, vec3f_t *, int, int, float);

File KVAbstractRenderer.m

  */
 
 #import "KVAbstractRenderer.h"
+#import "utility.h"
+
+@interface KVAbstractRenderer ()
+- (void) buildRayField;
+@end
 
 @implementation KVAbstractRenderer
 #pragma mark --- NSObject Overrides
 - init {
   if ((self = [super init])) {
     self.color = [NSColor colorWithDeviceRed: 1.F green: 1.F blue: 1.F alpha: 1.F];
+    [self buildRayField];
   }
   return self;
 }
   };
 }
 
+- (void) projectInto: (vec3f_t *)coord x: (int) x y: (int) y z: (float) z {
+  int cx = x - 320, cy = y - 240;
+  int xRay, yRay;
+  float xSign = 1, ySign = 1;
+  if (cx < 0) {
+    xRay = -cx - 1;
+  } else {
+    xRay = cx;
+    xSign = -1.F;
+  }
+  if (cy < 0) {
+    yRay = -cy - 1;
+  } else {
+    yRay = cy;
+    ySign = -1.F;
+  }
+  vec3f_t ray = rayField[yRay][xRay];
+  vec3f_t coords = vector_scale(ray, z);
+  coords.x *= xSign;
+  coords.y *= ySign;
+
+  *coord = coords;
+}
+
+// Pre-computes the projection of each camera X/Y coordinate into OpenGL's
+// X/Y space.  Essentially, this is undoing the perspective effect of the
+// camera's wide-angle lens, by projecting rays.
+// It's interesting that the Kinect's depth samples are *not* the length of
+// the ray, which is what I naïvely expected, but rather distance from the
+// XY plane containing the camera.  The depth can thus be natively used as
+// OpenGL's Z coordinate, and the Z of every projected ray will be 1.
+// We store it anyway because it simplifies rendering later on.
+- (void) buildRayField {
+  // We assume square pixels, and derive the Y FOV from the X.
+  static const float fovXDegrees = 57.F;  // Pretty close to actual?
+  
+  static const float fovX = fovXDegrees / 180.F * (float) M_PI;
+  for (int y = 0; y < 240; y++) {
+    float theta = (y / 320.F) * fovX/2.F;
+    for (int x = 0; x < 320; x++) {
+      float psi = (x / 320.F) * fovX/2.F;
+      rayField[y][x] = (vec3f_t) {
+        .x = sinf(psi),
+        .y = sinf(theta),
+        .z = 1.F,
+      };
+    }
+  }
+}
+
 @end

File KVDepthView.m

   glRotatef(spinY, 0, 1, 0);
   glRotatef(spinX, 1, 0, 0);
   
-  // Determined empirically.
-  GLfloat xyAdj = 0.8F;
-  
-  glScalef(-1.F/640.F * xyAdj, -1.F/640.F * xyAdj, 1);
   GLfloat lightPos[4] = { 0, 0, -1, 1 };
   glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
 }

File KVPointCloudRenderer.m

   // Avoid the property message send for a significant perf win.
   // (Sending a message doubles our render time.)
   BOOL lights = self.useLighting;
+  KVAbstractRendererProjector projector =
+      (KVAbstractRendererProjector) [self methodForSelector: @selector(projectInto:x:y:z:)];
   
   vec3f_t baseColor = [self colorAsRGBVector];
   glColor3fv(&baseColor.x);
 
   glBegin(GL_POINTS);
   
+  vec3f_t coords;
   for (int y = 0; y < 480; y++) {
     for (int x = 0; x < 640; x++) {
-      GLfloat d = zs->z[y][x];
-      if (d == 0.F) continue;
+      GLfloat depth = zs->z[y][x];
+      if (depth == 0.F) continue;
       
-      vec3f_t coords = correctPerspective(x, y, d);
+      projector(self, @selector(projectInto:x:y:z:), &coords, x, y, depth);
       
       if (!lights) {
-        GLfloat c = 1.F - (d / 9.F);
-        vec3f_t falseColor = vector_scale(&baseColor, c);
+        GLfloat c = 1.F - (coords.z / 9.F);
+        vec3f_t falseColor = vector_scale(baseColor, c);
         glColor3fv(&falseColor.x);
       }
       

File KVQuadRenderer.m

                              colors: (const colors_t *)colors_unused {
   // Avoid message send for large performance win.
   BOOL lightsOn = self.useLighting;
-  
+  KVAbstractRendererProjector projector =
+      (KVAbstractRendererProjector) [self methodForSelector: @selector(projectInto:x:y:z:)];
+
   vec3f_t normal;
   
   glBegin(GL_QUADS);
         minDepth = fminf(minDepth, d);
         maxDepth = fmaxf(maxDepth, d);
         
-        coords[i] = correctPerspective(xs[i], ys[i], d);
+        projector(self, @selector(projectInto:x:y:z:), &coords[i], xs[i], ys[i], d);
         colors[i] = 1.F - (d / 9.F);
       }
       
       
       for (int i = 0; i < 4; i++) {
         if (!lightsOn) {
-          vec3f_t falseColor = vector_scale(&baseColor, colors[i]);
+          vec3f_t falseColor = vector_scale(baseColor, colors[i]);
           glColor3fv(&falseColor.x);
         }
         glVertex3fv(&coords[i].x);

File KVTriStripRenderer.m

   
   // Avoid message send for a large performance win.
   BOOL lightsOn = self.useLighting;
-  
+  KVAbstractRendererProjector projector =
+      (KVAbstractRendererProjector) [self methodForSelector: @selector(projectInto:x:y:z:)];
+
   vec3f_t normal;
   // Last two points rendered, for normal calculation.
   vec3f_t prev[2];
+  vec3f_t coord;
 
   vec3f_t baseColor = [self colorAsRGBVector];
   glColor3fv(&baseColor.x);
           }
         }
         
-        vec3f_t coord = correctPerspective(x, y + step, d);
+        projector(self, @selector(projectInto:x:y:z:), &coord, x, y + step, d);
         
         compute_normal(&prev[0], &prev[1], &coord, &normal);
         glNormal3fv(&normal.x);
 
         if (!lightsOn) {
           GLfloat color = 1.F - (d / 9.F);
-          vec3f_t falseColor = vector_scale(&baseColor, color);
+          vec3f_t falseColor = vector_scale(baseColor, color);
           glColor3fv(&falseColor.x);
         }
         glVertex3fv(&coord.x);
 
 #include "types.h"
 
-static inline vec3f_t correctPerspective(int x, int y, float z) {
-  float dr = 1.F + z;
-  return (vec3f_t) {
-    .x = (x - 320) * dr,
-    .y = (y - 240) * dr,
-    .z = z
-  };
-}
-
 static inline void vector_subtract(const vec3f_t *a, const vec3f_t *b, vec3f_t *out) {
 	*out = (vec3f_t) {
 		a->x - b->x,
 	};
 }
 
-static inline vec3f_t vector_scale(const vec3f_t *v, float scale) {
+static inline vec3f_t vector_scale(vec3f_t v, float scale) {
   return (vec3f_t) {
-    v->x * scale,
-    v->y * scale,
-    v->z * scale,
+    v.x * scale,
+    v.y * scale,
+    v.z * scale,
   };
 }