Ejemplo n.º 1
0
  def blinn_phong_shading_per_light(self,viewer_direction,light,isect):
      '''
      Compute the color of the pixel for the given light source, viewer direction
      and intersection point.
      viewer_direction: a numpy array of size 3 containing the direction of the viewer
      light: is of type Light which should be used for shading
      isect: is of type IntersectionResult that contains the intersection point,
             normal and material.
      '''
      
      # Compute the color only for this instance. The caller is responsible for
      # mixing colors for different lights.
      color = np.array([0., 0., 0.])
      #TODO ====== BEGIN SOLUTION =====

      # some important bits
      l = GT.normalize(light.pointFrom - isect.p)
      v = viewer_direction
      H =  GT.normalize(l + v)

      # calculate the diffuse component
      I_diffuse = np.multiply(light.color*light.power, isect.material.diffuse) * max(np.dot(isect.n, l), 0)

      # and get the specular component
      I_specular = np.multiply(light.color*light.power, isect.material.specular) * np.dot(H, isect.n)**isect.material.hardness

      color = I_diffuse + I_specular

      # ===== END SOLUTION HERE =====
      return color
Ejemplo n.º 2
0
  def intersect(self, ray):
    '''
    Implement intersection between the ray and the current object and
    return IntersectionResult variable (isect) which will store the
    intersection point, the normal at the intersection and material of the
    object at the intersection point. The variable isect should contain the
    nearest intersection point and all its properties.
    '''
    isect = IntersectionResult()

    global EPS_DISTANCE # use this for testing if a variable is close to 0
    # ===== BEGIN SOLUTION HERE =====
    tEye = np.dot(np.append(ray.eyePoint, [1]), self.Minv)
    tDir = np.dot(np.append(ray.viewDirection, [0]), self.Minv)

    e = [tEye[0], tEye[1], tEye[2]]
    d = [tDir[0], tDir[1], tDir[2]]

    s = np.linalg.norm(GT.normalize(d) / np.linalg.norm(d))

    newRay = Ray(e, GT.normalize(d))

    for c in self.children:
        i = c.intersect(newRay)
        if i.t < isect.t:
            isect = i

    rp = np.dot(np.append(isect.p, [0]), self.M)
    isect.p = [rp[0], rp[1], rp[2]]
    isect.t = isect.t * s
    # ===== END SOLUTION HERE =====
    return isect
Ejemplo n.º 3
0
Archivo: Scene.py Proyecto: danhp/socs
  def blinn_phong_shading_per_light(self,viewer_direction,light,isect):
      '''
      Compute the color of the pixel for the given light source, viewer direction
      and intersection point.
      viewer_direction: a numpy array of size 3 containing the direction of the viewer
      light: is of type Light which should be used for shading
      isect: is of type IntersectionResult that contains the intersection point,
             normal and material.
      '''

      # Compute the color only for this instance. The caller is responsible for
      # mixing colors for different lights.
      color = np.array([0., 0., 0.])
      # ====== BEGIN SOLUTION =====
      l = GT.normalize(light.pointFrom - viewer_direction)
      r = -GT.normalize(l - 2 * np.dot(l, isect.n) * isect.n)
      lv = l + viewer_direction
      h = lv / np.linalg.norm(lv)

      Idiff = light.power * isect.material.diffuse * max(np.dot(l, isect.n), 0.0)
      Idiff = np.clip(Idiff, 0, 1)

      Ispec = light.power * isect.material.specular * math.pow(max(np.dot(h, isect.n), 0.0), isect.material.hardness)
      Ispec = np.clip(Ispec, 0, 1)

      color = color + Idiff + Ispec
      # ===== END SOLUTION HERE =====
      return color
Ejemplo n.º 4
0
    def intersect(self, ray):
        '''
    Implement intersection between the ray and the current object and
    return IntersectionResult variable (isect) which will store the
    intersection point, the normal at the intersection and material of the
    object at the intersection point. The variable isect should contain the
    nearest intersection point and all its properties.
    '''
        isect = IntersectionResult()
        transformedRay = Ray()
        # Eyepoint transformed
        transformedRay.eyePoint = np.dot(self.Minv,
                                         np.append(ray.eyePoint, [1]))
        transformedRay.eyePoint = transformedRay.eyePoint[:
                                                          3] / transformedRay.eyePoint[
                                                              3]
        # Ray transformed, since it's a vector not from origin need to do this calc
        ray_tip = ray.eyePoint + ray.viewDirection
        trans_ray_tip = np.dot(self.Minv, np.append(ray_tip, [1]))
        trans_ray_tip = trans_ray_tip[:3] / trans_ray_tip[3]
        transformedRay.viewDirection = GT.normalize(trans_ray_tip -
                                                    transformedRay.eyePoint)

        global EPS_DISTANCE  # use this for testing if a variable is close to 0
        # TODO ===== BEGIN SOLUTION HERE =====
        intersections = []

        # Get all the intersections
        for child in self.children:
            intersection = child.intersect(transformedRay)
            if intersection.t == np.inf:
                continue

            # Point transformed
            intersection.p = np.dot(self.M, np.append(intersection.p, [1]))
            intersection.p = intersection.p[:3] / intersection.p[3]

            # Distance in world coords
            intersection.t = np.linalg.norm(intersection.p - ray.eyePoint)

            # Normal, inverse transpose because it's a normal to remove scale issues
            intersection.n = GT.normalize(
                np.dot(np.transpose(self.Minv), np.append(intersection.n,
                                                          [0]))[:3])

            intersections.append(intersection)

        min_isect = isect

        # Get the closest intersection
        for iss in intersections:
            if (iss.t < min_isect.t):
                min_isect = iss

        isect = min_isect
        # ===== END SOLUTION HERE =====
        return isect
Ejemplo n.º 5
0
  def __init__(self, M = np.eye(4), params = None):
    self.children = []
    self.M = M
    if params is not None:
        rot_angles = np.array(params.get('rotation', [0., 0., 0.]))
        translate_amount = np.array(params.get('translation', [0., 0., 0.]))
        scale_amount = np.array(params.get('scale', [1., 1., 1.]))
        # compute the transformation matrix that gets applied to all children of this node
        Tform = GT.translate(translate_amount) *  GT.rotateX(rot_angles[0]) * \
            GT.rotateY(rot_angles[1]) * GT.rotateZ(rot_angles[2]) * \
            GT.scale(scale_amount)
        self.M = Tform.getA()

    self.Minv = np.linalg.inv(self.M)
Ejemplo n.º 6
0
  def get_visible_lights(self, isect):
      '''
      isect is variable of type IntersectionResult. isect.p contains the
      intersection point. This function should return a python list containing
      all the lights that are "visible" from isect.p position. A light source
      is visible if there are no objects between the point and the light source.
      All light sources are of type Light (Light class is defined in HelperClasses.py).
      The light sources in the scene is stored in the variable Scene.lights
      (accessed using self.lights). Your returned list should be a subset
      of self.lights
      '''

      # you need to loop over the lights and return those that are visible from the position in result
      visibleLights = []

      # TODO ====== BEGIN SOLUTION =====
      for l in self.lights:
        ray = Ray()
        ray.eyePoint = isect.p
        ray.viewDirection = GT.normalize(l.pointFrom - isect.p)
        nearest_isect = self.get_nearest_object_intersection(ray)

        # PLEASE NOTE: while i used to have
        # np.fabs(nearest_isect.t -  np.linalg.norm(l.pointFrom - isect.p)) <= EPS_DISTANCE
        # This gave more different results than the solution images, as such
        # I kept this which gave a closer version to what scene4 gave
        # As was said in the forums, this shouldn't matter for grading as it only gives
        # slightly different shading results
        if nearest_isect.t == np.inf or nearest_isect.t >= np.linalg.norm(l.pointFrom - isect.p):
          # no further intersection than to the given point
          visibleLights.append(l)

      # ===== END SOLUTION HERE =====
      return visibleLights
Ejemplo n.º 7
0
  def create_ray(self, row, col):
      '''
      Create ray (i.e. origin and direction) from a pixel specified by (row, col).
      The ray originates at the camera's eye and goes through the point in space
      that corresponds to the image pixel coordinate (row, col). Take a look at
      the Camera class to see the variables that can be used here.
      '''
      ray = Ray()  # construct an empty ray
      '''
      The ray origin is set in this starter code. You need to compute and set
      the ray.viewDirection variable inside the solution block below.
      Note: GeomTransform.py implements a function called normalize for
      normalizing vectors (with appropriate check for division by zero).
      For a vector v, you can get the normalized vector as:
      normalized_v = GT.normalize(v)
      '''
      cam = self.render.camera  # use a local variable to save some typing
      ray.eyePoint = cam.pointFrom  # origin of the ray

      # TODO ====== BEGIN SOLUTION ======
      # Get pixel center positions
      normalized_x = (2.0*float(col))/(cam.imageWidth) - 1.0
      normalized_y = 1.0 - (2.0*float(row))/(cam.imageHeight)

      # Get other factors
      ch = cam.top / cam.near
      cw = ch * cam.aspect
      cx = cam.cameraXAxis * cw
      cy = cam.cameraYAxis * ch

      ray.viewDirection = GT.normalize((cam.lookat + normalized_x*cx + normalized_y*cy))

      # ===== END SOLUTION =====
      return ray
Ejemplo n.º 8
0
    def __init__(self, M=np.eye(4), params=None):
        self.children = []
        self.M = M
        if params is not None:
            rot_angles = np.array(params.get('rotation', [0., 0., 0.]))
            translate_amount = np.array(params.get('translation',
                                                   [0., 0., 0.]))
            scale_amount = np.array(params.get('scale', [1., 1., 1.]))
            # compute the transformation matrix that
            # gets applied to all children of this node
            Tform = GT.translate(translate_amount) * GT.rotateX(rot_angles[0]) * \
                GT.rotateY(rot_angles[1]) * GT.rotateZ(rot_angles[2]) * \
                GT.scale(scale_amount)
            self.M = Tform.getA()

        self.Minv = np.linalg.inv(self.M)
Ejemplo n.º 9
0
Archivo: Scene.py Proyecto: danhp/socs
  def create_ray(self,row,col):
      '''
      Create ray (i.e. origin and direction) from a pixel specified by (row, col).
      The ray originates at the camera's eye and goes through the point in space
      that corresponds to the image pixel coordinate (row, col). Take a look at
      the Camera class to see the variables that can be used here.
      '''
      ray = Ray() # construct an empty ray
      '''
      The ray origin is set in this starter code. You need to compute and set
      the ray.viewDirection variable inside the solution block below.
      Note: GeomTransform.py implements a function called normalize for
      normalizing vectors (with appropriate check for division by zero).
      For a vector v, you can get the normalized vector as:
      normalized_v = GT.normalize(v)
      '''
      cam = self.render.camera # use a local variable to save some typing
      ray.eyePoint = cam.pointFrom # origin of the ray

      # ====== BEGIN SOLUTION ======
      dx = col / cam.imageWidth
      dy = row / cam.imageHeight

      stepx = dx * (cam.right - cam.left)
      stepy = dy * (cam.top - cam.bottom)

      ray.viewDirection = GT.normalize(cam.lookat + [cam.left + stepx, cam.top - stepy, 0])
      # ====== END SOLUTION =======
      return ray
Ejemplo n.º 10
0
Archivo: Scene.py Proyecto: danhp/socs
  def get_visible_lights(self, isect):
      '''
      isect is variable of type IntersectionResult. isect.p contains the
      intersection point. This function should return a python list containing
      all the lights that are "visible" from isect.p position. A light source
      is visible if there are no objects between the point and the light source.
      All light sources are of type Light (Light class is defined in HelperClasses.py).
      The light sources in the scene is stored in the variable Scene.lights
      (accessed using self.lights). Your returned list should be a subset
      of self.lights
      '''

      #you need to loop over the lights and return those that are visible from the position in result
      visibleLights = []
      #====== BEGIN SOLUTION =====
      for l in self.lights:
          ray = Ray()
          ray.eyePoint = isect.p
          ray.viewDirection = GT.normalize(l.pointFrom - ray.eyePoint)

          d = np.linalg.norm(l.pointFrom - ray.eyePoint)
          i = self.get_nearest_object_intersection(ray)

          if i.material is None or i.t >= d - 1e-9:
              visibleLights.append(l)
      # ===== END SOLUTION HERE =====
      return visibleLights
Ejemplo n.º 11
0
 def test_basic_ray_intersection_parallel_to_zaxis(self):
     ''' shoot a ray from a point on the z-axis towards the center '''
     expected_pt = [0.0, 2.0, 0.0]
     expected_normal = GT.normalize(expected_pt)
     test_intersection_with_result(self.scene_node, origin=[0, 2, 10], 
                                   direction=[0, 0, -1],
                                   isect_pt = expected_pt, 
                                   isect_normal= expected_normal,
                                   isect_dist = 10 - expected_pt[2])                                      
Ejemplo n.º 12
0
 def __init__(self, params = {}):
     #normal=[0.0,1.0,0.0], material = Material(), material2 = 0 ):
     self.normal = GT.normalize(np.array(params.get('normal', [0.0,1.0,0.0])))
     material_list = params.get('material', [Material(), None])
     if type(material_list) is not list:
         self.material = material_list
         self.material2 = None
     else:
         self.material = material_list[0]
         self.material2 = material_list[1]
Ejemplo n.º 13
0
 def __init__(self, params={}):
     # normal=[0.0,1.0,0.0], material = Material(), material2 = 0 ):
     self.normal = GT.normalize(
         np.array(params.get('normal', [0.0, 1.0, 0.0])))
     material_list = params.get('material', [Material(), None])
     if type(material_list) is not list:
         self.material = material_list
         self.material2 = None
     else:
         self.material = material_list[0]
         self.material2 = material_list[1]
Ejemplo n.º 14
0
 def test_angle_between_left_right_extreme_rays(self):
     '''
     Test if the angle between left-most and right-most rays are consistent 
     with the fov
     '''
     camera = self.scene.render.camera
     # find the middle row
     halfH = int((camera.imageHeight - 1) / 2.)
     
     # compute the ray going through the left edge of the image
     rayLeft = self.scene.create_ray(halfH, 0)
     
     # compute the ray going through the right edge of the image
     rayRight = self.scene.create_ray(halfH, camera.imageWidth)
     
     cos_theta = np.dot(GT.normalize(rayLeft.viewDirection), GT.normalize(rayRight.viewDirection))
     est_angle_deg = np.rad2deg(np.arccos(cos_theta))
     #print(est_angle_deg)
     # check fov
     nptest.assert_approx_equal(est_angle_deg, camera.aspect * camera.fov)
Ejemplo n.º 15
0
 def test_angle_between_top_bottom_extreme_rays(self):
     '''
     Test if the angle between top-most and bottom-most rays are consistent 
     with the fov
     '''
     camera = self.scene.render.camera
             
     # find the middle col
     halfW = int((camera.imageWidth - 1) / 2.)
     
     # compute the ray going through the top edge of the image
     rayTop = self.scene.create_ray(0, halfW)
     
     # compute the ray going through the bottom edge of the image
     rayBottom = self.scene.create_ray(camera.imageHeight, halfW)
     
     cos_theta = np.dot(GT.normalize(rayTop.viewDirection), GT.normalize(rayBottom.viewDirection))
     
     # check fov
     nptest.assert_approx_equal(cos_theta, np.cos(np.deg2rad(camera.fov)))
Ejemplo n.º 16
0
    def test_angle_between_top_bottom_extreme_rays(self):
        '''
        Test if the angle between top-most and bottom-most rays are consistent
        with the fov
        '''
        camera = self.scene.render.camera

        # find the middle col
        halfW = int((camera.imageWidth - 1) / 2.)

        # compute the ray going through the top edge of the image
        rayTop = self.scene.create_ray(0, halfW)

        # compute the ray going through the bottom edge of the image
        rayBottom = self.scene.create_ray(camera.imageHeight, halfW)

        cos_theta = np.dot(GT.normalize(rayTop.viewDirection),
                           GT.normalize(rayBottom.viewDirection))
        est_angle_deg = np.rad2deg(np.arccos(cos_theta))
        # check fov
        nptest.assert_approx_equal(est_angle_deg, camera.fov, significant=5)
Ejemplo n.º 17
0
  def blinn_phong_shading_per_light(self, viewer_direction, light, isect):
      '''
      Compute the color of the pixel for the given light source, viewer direction
      and intersection point.
      viewer_direction: a numpy array of size 3 containing the direction of the viewer
      light: is of type Light which should be used for shading
      isect: is of type IntersectionResult that contains the intersection point,
             normal and material.
      '''
      # Compute the color only for this instance. The caller is responsible for
      # mixing colors for different lights.
      color = np.array([0., 0., 0.])

      # TODO ====== BEGIN SOLUTION =====
      lightDir = GT.normalize(light.pointFrom - isect.p)
      halfway = GT.normalize(lightDir + viewer_direction)
      specular = (self.saturate(np.dot(isect.n, halfway))**isect.material.hardness) * isect.material.specular
      diffuse = self.saturate(np.dot(isect.n, lightDir)) * isect.material.diffuse
      color = (diffuse + specular) * light.power * light.color
      # ===== END SOLUTION HERE =====
      return color
Ejemplo n.º 18
0
 def __compute_projection_view_parameters__(self):
     '''
     This function is only called from the Camera class for computing
     the relevant projection-view parameters. The function should only get called
     by the Camera.__init__  method. The variables set here can be used for
     ray construction.
     '''
     self.lookat = GT.normalize(self.pointTo - self.pointFrom)
     self.aspect = float(self.imageWidth)/self.imageHeight
     
     # compute frustum 
     self.top = self.near * math.tan(0.5*math.radians(self.fov))
     self.bottom = -self.top
     self.right = self.top*self.aspect
     self.left = -self.right
     
     # Compute the camera coordinate system
     self.cameraZAxis = self.lookat
     self.cameraXAxis = np.cross(self.cameraZAxis, self.up)
     assert(np.linalg.norm(self.cameraXAxis) > 0.) # we don't want the camera coordinate system to collapse
     self.cameraXAxis = GT.normalize(self.cameraXAxis)
     self.cameraYAxis = GT.normalize(np.cross(self.cameraXAxis,self.cameraZAxis))
Ejemplo n.º 19
0
 def test_intersection_pos_diag_ray_consistency(self):
     eye = [10, 0, 10]
     viewDir = [-1, 0, -1]
     
     ray = Ray(eye, GT.normalize(viewDir))
     
     
     result0 = self.sphere.intersect(ray)
     #print(result0)
     
     eye = [5, 0, 5]
     viewDir = [-1, 0, -1]
     
     ray = Ray(eye, GT.normalize(viewDir))
     
     result1 = self.sphere.intersect(ray)
     #print(result1)        
     
     nptest.assert_array_almost_equal(result0.p, result1.p)
     nptest.assert_array_almost_equal(result0.n, result1.p)
     nptest.assert_almost_equal(result0.t, 10 * np.sqrt(2.) - 1)    
     nptest.assert_almost_equal(result1.t, 5 * np.sqrt(2.) - 1)    
Ejemplo n.º 20
0
    def test_intersection_pos_diag_ray_consistency(self):
        eye = [10, 0, 10]
        viewDir = [-1, 0, -1]

        ray = Ray(eye, GT.normalize(viewDir))


        result0 = self.sphere.intersect(ray)
        #print(result0)

        eye = [5, 0, 5]
        viewDir = [-1, 0, -1]

        ray = Ray(eye, GT.normalize(viewDir))

        result1 = self.sphere.intersect(ray)
        #print(result1)

        nptest.assert_array_almost_equal(result0.p, result1.p)
        nptest.assert_array_almost_equal(result0.n, result1.p)
        nptest.assert_almost_equal(result0.t, 10 * np.sqrt(2.) - 1)
        nptest.assert_almost_equal(result1.t, 5 * np.sqrt(2.) - 1)
Ejemplo n.º 21
0
def test_intersection_with_result(obj, origin, direction, isect_pt, isect_normal, isect_dist):
    '''test intersection for a given object and  '''
    ray = Ray(origin, GT.normalize(direction))
    #result = IntersectionResult()
    
    result = obj.intersect(ray)
    #print "p",result.p, isect_pt
    nptest.assert_array_almost_equal(result.p, isect_pt)
    if isect_normal is not None:
        # isect_normal might be set to None when the normal is undefined
        #print "n",result.n, isect_normal
        nptest.assert_array_almost_equal(result.n, isect_normal)
    nptest.assert_almost_equal(result.t, isect_dist)
Ejemplo n.º 22
0
def test_intersection_with_result(obj, origin, direction, isect_pt,
                                  isect_normal, isect_dist):
    '''test intersection for a given object and  '''
    ray = Ray(origin, GT.normalize(direction))
    # result = IntersectionResult()

    result = obj.intersect(ray)
    # print "p",result.p, isect_pt
    nptest.assert_array_almost_equal(result.p, isect_pt)
    if isect_normal is not None:
        # isect_normal might be set to None when the normal is undefined
        # print "n",result.n, isect_normal
        nptest.assert_array_almost_equal(result.n, isect_normal)
    nptest.assert_almost_equal(result.t, isect_dist)
Ejemplo n.º 23
0
    def test_angle_between_left_right_extreme_rays(self):
        '''
        Test if the angle between left-most and right-most rays are consistent
        with the fov
        '''
        camera = self.scene.render.camera
        # find the middle row
        halfH = int((camera.imageHeight - 1) / 2.)

        # compute the ray going through the left edge of the image
        rayLeft = self.scene.create_ray(halfH, 0)

        # compute the ray going through the right edge of the image
        rayRight = self.scene.create_ray(halfH, camera.imageWidth)

        cos_theta = np.dot(GT.normalize(rayLeft.viewDirection),
                           GT.normalize(rayRight.viewDirection))
        est_angle_deg = np.rad2deg(np.arccos(cos_theta))
        #print(est_angle_deg)
        # check fov
        nptest.assert_approx_equal(est_angle_deg,
                                   camera.aspect * camera.fov,
                                   significant=5)
Ejemplo n.º 24
0
    def __compute_projection_view_parameters__(self):
        '''
      This function is only called from the Camera class for computing
      the relevant projection-view parameters. The function should only get called
      by the Camera.__init__  method. The variables set here can be used for
      ray construction.
      '''
        self.lookat = GT.normalize(self.pointTo - self.pointFrom)
        self.aspect = float(self.imageWidth) / self.imageHeight

        # compute frustum
        self.top = self.near * math.tan(0.5 * math.radians(self.fov))
        self.bottom = -self.top
        self.right = self.top * self.aspect
        self.left = -self.right

        # Compute the camera coordinate system
        self.cameraZAxis = self.lookat
        self.cameraXAxis = np.cross(self.cameraZAxis, self.up)
        assert (np.linalg.norm(self.cameraXAxis) > 0.
                )  # we don't want the camera coordinate system to collapse
        self.cameraXAxis = GT.normalize(self.cameraXAxis)
        self.cameraYAxis = GT.normalize(
            np.cross(self.cameraXAxis, self.cameraZAxis))
Ejemplo n.º 25
0
def get_angle_axis_matrix(u, v, default_rot_axis):
    '''
    returns a rotation matrix that will align u with v. If the angle of rotation is 0 then it
    returns an identity matrix, if angle is 180 then it rotates about the default_rot_axis
    otherwise this function returns a matrix corresponding to angle-axis rotation that is
    implemented in GeomTransform.rotate
    '''
    u = GT.normalize(u)
    v = GT.normalize(v)
    axis_of_rotation = GT.normalize(np.cross(u, v))
    cos_theta = np.dot(u, v)
    # due to floating point error abs(u.v) can be > 1, in which case arccos will be nan
    if -1. <= cos_theta <= 1.:
        angle_deg = np.rad2deg(np.arccos(cos_theta))
    else: # here it's either -1-eps or 1 + eps
        angle_deg = 0. if cos_theta > 0. else 180.
    M = np.eye(4)
    if abs(angle_deg - 180.) < 1e-6:
        M = GT.rotate(angle_deg, default_rot_axis)
        axis_of_rotation = default_rot_axis
    elif abs(angle_deg) > 1e-6 and abs(angle_deg - 180.) > 1e-6:
        M = GT.rotate(angle_deg, axis_of_rotation)
        
    return M, angle_deg, axis_of_rotation 
Ejemplo n.º 26
0
  def renderScene(self):
    """
    
    The method renderScene is called once to draw all the pixels of the scene.  For each
    pixel, a ray is cast from the eye position through the location of the pixel on the
    near plane of the frustrum. The closest intersection is then computed by intersecting
    the ray with all the objects of the scene.  From that intersection point, rays are
    casted towards all the lights of the scene.  Based on if the point is exposed to the
    light, the shadow and the diffuse and specular lighting contributions can be computed
    and summed up for all lights.
    
    """
    
    # Initialize the renderer.
    self.render.init(self.render.camera.imageWidth, self.render.camera.imageHeight)
    
    for pixel in self.render.getPixel():
        '''
        pixel is a list containing the image coordinate of a pixel i.e. 
        pixel = [col, row] 
        '''
       
        # create a ray from the eye position and goes through the pixel
        ray = self.create_ray(pixel[1], pixel[0])
        
        # set the default color to the background color
        color = self.render.bgcolor
        
        nearest_isect = self.get_nearest_object_intersection(ray)

        if nearest_isect.is_valid_intersection(): # valid intersection
            color = self.ambient[:3] * nearest_isect.material.ambient[:3] # ambient color is used when the point is in shadow
            # get a list of light sources that are visible from the nearest intersection point
            visible_lights = self.get_visible_lights(nearest_isect)
            nearest_isect.n = GT.normalize(nearest_isect.n) # ensure that the returned normals are normalized
            if len(visible_lights) > 0: # light-shadow
                '''
                Compute the color based on the material found in nearest_isect.material
                and the light sources visible from nearest_isect.p position.
                '''
                for light in visible_lights:
                    color += self.blinn_phong_shading_per_light(-ray.viewDirection, light, nearest_isect)
        
        #At this point color should be a floating-point numpy array of 3 elements
        #and is the final color of the pixel.
        self.render.setPixel(pixel, color)
        
    self.render.save()  
Ejemplo n.º 27
0
   def setUp(self):
       '''
       Create a scene with a camera placed at [0, 0, 4] and looking at the
       world origin. 
       '''
       scene = Scene()
       self.scene = scene
 
       #Camera
       camera = Camera({'from':np.array([1.,4.,-2.]), 
                        'to':np.array([1.,-2.,4]), 
                        'up':GT.normalize(np.array([-1.,1.,0.])), 
                        'fov':45, 
                        'width':10001, 'height':10001}) # choose very high dimension to avoid rounding error
       render = Render({'camera':camera})
       scene.render = render
Ejemplo n.º 28
0
  def renderScene(self):
    """

    The method renderScene is called once to draw all the pixels of the scene.  For each
    pixel, a ray is cast from the eye position through the location of the pixel on the
    near plane of the frustrum. The closest intersection is then computed by intersecting
    the ray with all the objects of the scene.  From that intersection point, rays are
    casted towards all the lights of the scene.  Based on if the point is exposed to the
    light, the shadow and the diffuse and specular lighting contributions can be computed
    and summed up for all lights.

    """

    # Initialize the renderer.
    self.render.init(self.render.camera.imageWidth, self.render.camera.imageHeight)

    for pixel in self.render.getPixel():
        '''
        pixel is a list containing the image coordinate of a pixel i.e.
        pixel = [col, row]
        '''
        # create a ray from the eye position and goes through the pixel
        ray = self.create_ray(pixel[1], pixel[0])

        # set the default color to the background color
        color = self.render.bgcolor

        nearest_isect = self.get_nearest_object_intersection(ray)

        if nearest_isect.is_valid_intersection():  # valid intersection
            color = self.ambient[:3] * nearest_isect.material.ambient[:3]  # ambient color is used when the point is in shadow
            # get a list of light sources that are visible from the nearest intersection point
            visible_lights = self.get_visible_lights(nearest_isect)
            nearest_isect.n = GT.normalize(nearest_isect.n)  # ensure that the returned normals are normalized
            if len(visible_lights) > 0:  # light-shadow
                '''
                Compute the color based on the material found in nearest_isect.material
                and the light sources visible from nearest_isect.p position.
                '''
                for light in visible_lights:
                    color += self.blinn_phong_shading_per_light(-ray.viewDirection, light, nearest_isect)

        # At this point color should be a floating-point numpy array of 3 elements
        # and is the final color of the pixel.
        self.render.setPixel(pixel, color)

    self.render.save()
Ejemplo n.º 29
0
  def create_ray(self,row,col):
      ''' 
      Create ray (i.e. origin and direction) from a pixel specified by (row, col).
      The ray originates at the camera's eye and goes through the point in space
      that corresponds to the image pixel coordinate (row, col). Take a look at
      the Camera class to see the variables that can be used here.
      '''
      ray = Ray() # construct an empty ray
      '''
      The ray origin is set in this starter code. You need to compute and set
      the ray.viewDirection variable inside the solution block below.
      Note: GeomTransform.py implements a function called normalize for 
      normalizing vectors (with appropriate check for division by zero). 
      For a vector v, you can get the normalized vector as:
      normalized_v = GT.normalize(v)
      '''
      cam = self.render.camera # use a local variable to save some typing
      ray.eyePoint = cam.pointFrom # origin of the ray

      #TODO ====== BEGIN SOLUTION ======

      # get the camera coordinates
      cam_x = col*(cam.right-cam.left)/cam.imageWidth + cam.left
      cam_y = row*-(cam.top-cam.bottom)/cam.imageHeight + cam.top
      cam_z = cam.near

      xa = cam.cameraXAxis
      ya = cam.cameraYAxis
      za = cam.cameraZAxis
      R = [ [xa[0], ya[0], za[0], 0],
            [xa[1], ya[1], za[1], 0],
            [xa[2], ya[2], za[2], 0],
            [0, 0, 0, 1] ]
      T = [ [1, 0, 0, -cam.pointFrom[0]], 
            [0, 1, 0, -cam.pointFrom[1]],
            [0, 0, 1, -cam.pointFrom[2]],
            [0, 0, 0, 1] ]

      cam_to_world = np.linalg.inv(np.dot(np.transpose(R),T))

      world_pixels = np.dot( cam_to_world, [cam_x, cam_y, cam_z, 1] )
      #world_pixels = world_pixels/world_pixels[3]
      ray.viewDirection = GT.normalize(world_pixels[0:3] - ray.eyePoint)

      # ===== END SOLUTION =====
      return ray    
Ejemplo n.º 30
0
    def test_rand_eye_intersection_distance_correctness(self, NTEST = 100):    
        ''' Shoot rays from random eye position towards the center. The intersection
        point should be radius distance from the center. '''
        np.random.seed(9125)
        for testNo in range(NTEST):
            # generate a random point outside of the range [-5, 5]
            sgn = 1 if np.random.rand(1) <= .5 else -1
            
            eye = (np.random.rand(1, 3).flatten() + 5) * sgn # generate a random point
            viewDir = GT.normalize(-eye) # direction towards the center

            #print(sgn, eye, viewDir)
            
            ray = Ray(eye, viewDir)
                        
            result = self.sphere.intersect(ray)
            distance = np.linalg.norm(result.p - self.center)
            nptest.assert_almost_equal(distance, self.radius)
Ejemplo n.º 31
0
    def test_rand_eye_intersection_distance_correctness(self, NTEST = 100):
        ''' Shoot rays from random eye position towards the center. The intersection
        point should be radius distance from the center. '''
        np.random.seed(9125)
        for testNo in range(NTEST):
            # generate a random point outside of the range [-5, 5]
            sgn = 1 if np.random.rand(1) <= .5 else -1

            eye = (np.random.rand(1, 3).flatten() + 5) * sgn # generate a random point
            viewDir = GT.normalize(-eye) # direction towards the center

            #print(sgn, eye, viewDir)

            ray = Ray(eye, viewDir)

            result = self.sphere.intersect(ray)
            distance = np.linalg.norm(result.p - self.center)
            nptest.assert_almost_equal(distance, self.radius)
Ejemplo n.º 32
0
    def setUp(self):
        '''
        Create a scene with a camera placed at [0, 0, 4] and looking at the
        world origin.
        '''
        scene = Scene()
        self.scene = scene

        #Camera
        camera = Camera({
            'from': np.array([1., 4., -2.]),
            'to': np.array([1., -2., 4]),
            'up': GT.normalize(np.array([-1., 1., 0.])),
            'fov': 45,
            'width': 10001,
            'height': 10001
        })  # choose very high dimension to avoid rounding error
        render = Render({'camera': camera})
        scene.render = render
Ejemplo n.º 33
0
    def test_rand_eye_intersection_distance_from_eye_correctness(self, NTEST = 100):    
        ''' Shoot rays from random eye position towards the center. Test whether
        the distance to the intersection point from the eye position is distance
        to the center minus the radius.'''
        np.random.seed(9125) # uses the same random sequence as point to center distance test
        for testNo in range(NTEST):
            # generate a random point outside of the range [-5, 5]
            sgn = 1 if np.random.rand(1) <= .5 else -1
            
            eye = (np.random.rand(1, 3).flatten() + 5) * sgn # generate a random point
            viewDir = GT.normalize(-eye) # direction towards the center

            #print(sgn, eye, viewDir)
            
            ray = Ray(eye, viewDir)
                        
            result = self.sphere.intersect(ray)
            isect_pt_from_eye_distance = np.linalg.norm(result.p - eye) # for sanity check
            eye_distance_from_center = np.linalg.norm(eye - self.center)
            #print(isect_pt_from_eye_distance, result.t)
            nptest.assert_almost_equal(result.t, isect_pt_from_eye_distance) # sanity check
            nptest.assert_almost_equal(result.t, eye_distance_from_center - self.radius) # sanity check
Ejemplo n.º 34
0
    def test_rand_eye_intersection_distance_from_eye_correctness(self, NTEST = 100):
        ''' Shoot rays from random eye position towards the center. Test whether
        the distance to the intersection point from the eye position is distance
        to the center minus the radius.'''
        np.random.seed(9125) # uses the same random sequence as point to center distance test
        for testNo in range(NTEST):
            # generate a random point outside of the range [-5, 5]
            sgn = 1 if np.random.rand(1) <= .5 else -1

            eye = (np.random.rand(1, 3).flatten() + 5) * sgn # generate a random point
            viewDir = GT.normalize(-eye) # direction towards the center

            #print(sgn, eye, viewDir)

            ray = Ray(eye, viewDir)

            result = self.sphere.intersect(ray)
            isect_pt_from_eye_distance = np.linalg.norm(result.p - eye) # for sanity check
            eye_distance_from_center = np.linalg.norm(eye - self.center)
            #print(isect_pt_from_eye_distance, result.t)
            nptest.assert_almost_equal(result.t, isect_pt_from_eye_distance) # sanity check
            nptest.assert_almost_equal(result.t, eye_distance_from_center - self.radius) # sanity check
Ejemplo n.º 35
0
  def get_visible_lights(self, isect):
      ''' 
      isect is variable of type IntersectionResult. isect.p contains the 
      intersection point. This function should return a python list containing
      all the lights that are "visible" from isect.p position. A light source
      is visible if there are no objects between the point and the light source.
      All light sources are of type Light (Light class is defined in HelperClasses.py).
      The light sources in the scene is stored in the variable Scene.lights
      (accessed using self.lights). Your returned list should be a subset
      of self.lights
      '''
     
      #you need to loop over the lights and return those that are visible from the position in result
      visibleLights = []
      #TODO ====== BEGIN SOLUTION =====

      # loop over self.lights, build a ray from isect.p to the light's location
      # and then check that get_nearest_object_intersection with that ray is the light
      for light in self.lights:
        ray = Ray(isect.p, GT.normalize(light.pointFrom - isect.p))
        nearest_isect = self.get_nearest_object_intersection(ray)
        # find time of intersection with the light
        # note that p_intersect = ray_start + t*ray_dir
        # so t = (p_intersect - ray_start)/ray_dir
        if (ray.viewDirection[0] != 0):
          light_t = (light.pointFrom[0] - isect.p[0])/ray.viewDirection[0] 
        elif (ray.viewDirection[1] != 0):
          light_t = (light.pointFrom[1] - isect.p[1])/ray.viewDirection[1]
        else:
          light_t = (light.pointFrom[2] - isect.p[2])/ray.viewDirection[2]  
        # consider the light if it is closer than the first object you hit
        if light_t <= nearest_isect.t:
          visibleLights.append(light)

      # ===== END SOLUTION HERE =====
      return visibleLights
Ejemplo n.º 36
0
 def test_ray_originates_on_sphere_goes_inside(self):
     test_intersection_with_result(self.sphere, origin=[1, 0, 0],
                                   direction=[-1, 0, 0],
                                   isect_pt = [-self.radius, 0, 0],
                                   isect_normal=GT.normalize([-self.radius, 0, 0]),
                                   isect_dist = 2. * self.radius)
Ejemplo n.º 37
0
  def intersect(self, ray):
      '''
      input: ray in the same coordinate system as the sphere
      output: IntersectionResult, contains the intersection point, normal,
              distance from the eye position and material (see Ray.py)
      Let, the ray be P = P0 + tv, where P0 = eye, v = ray direction
      We want to find t.
      sphere with center Pc and radius r:
      (P - Pc)^T (P - Pc) = r^2
      We need to consider the different situations that can arise with ray-sphere
      intersection.
      NOTE 1: If the eye position is inside the sphere it SHOULD return the
      ray-sphere intersection along the view direction. Because the point *is
      visible* to the eye as far as ray-sphere intersection is concerned.
      The color used for rendering that point will be considered during the
      lighting and shading stage. In general there's no reason why there can't
      be a viewer and light sources inside a sphere.
      NOTE 2: If the ray origin is on the surface of the sphere then the nearest
      intersection should be ignored otherwise we'll have problems where
      the surface point cannot 'see' the light because of self intersection.
      '''
      '''
      Implement intersection between the ray and the current object and
      return IntersectionResult variable (isect) which will store the
      intersection point, the normal at the intersection and material of the
      object at the intersection point.
      '''
      isect = IntersectionResult() # by default isect corresponds to no intersection

      global EPS_DISTANCE # use this for testing if a variable is close to 0
      # ===== BEGIN SOLUTION HERE =====
      # Vector from view point to the center of the shere
      eye2center = self.center - ray.eyePoint
      dotProduct = np.dot(eye2center, ray.viewDirection)
      projectionVector = [x * (dotProduct / np.linalg.norm(ray.viewDirection)) for x in ray.viewDirection]
      projectionPoint = ray.eyePoint + projectionVector

      # Sphere center is behind the eye
      if  dotProduct < EPS_DISTANCE:
          norm = np.linalg.norm(eye2center)
          if norm >= self.radius - EPS_DISTANCE:
              # No intersection
              return isect
          elif norm != self.radius:
              # Eye is inside the sphere
              d = math.sqrt(math.pow(self.radius, 2) - math.pow(np.linalg.norm(projectionPoint - self.center), 2))
              isect.t = d - np.linalg.norm(projectionPoint - ray.eyePoint)
              isect.p = ray.eyePoint + [x * isect.t for x in ray.viewDirection]
              isect.n = GT.normalize(isect.p - self.center)
              isect.material = self.material
      else:
          if np.linalg.norm(self.center - projectionPoint) > self.radius - EPS_DISTANCE:
              return isect
          else:
              d = math.sqrt(math.pow(self.radius, 2) - math.pow(np.linalg.norm(projectionPoint - self.center), 2))

              if np.linalg.norm(eye2center) > self.radius:
                  isect.t = np.linalg.norm(projectionPoint - ray.eyePoint) - d
              else:
                  isect.t = np.linalg.norm(projectionPoint - ray.eyePoint) + d

              isect.p = ray.eyePoint + [x * isect.t for x in ray.viewDirection]
              isect.n = GT.normalize(isect.p - self.center)
              isect.material = self.material
      # ===== END SOLUTION HERE =====
      return isect
Ejemplo n.º 38
0
 def test_ray_originates_on_sphere_goes_inside(self):
     test_intersection_with_result(self.sphere, origin=[1, 0, 0], 
                                   direction=[-1, 0, 0],
                                   isect_pt = [-self.radius, 0, 0], 
                                   isect_normal=GT.normalize([-self.radius, 0, 0]),
                                   isect_dist = 2. * self.radius)
Ejemplo n.º 39
0
 def test_ray_originates_on_sphere_goes_inside_v2(self):
     test_intersection_with_result(self.sphere, origin=[1, 0, 0], 
                                   direction=[-1, 1, 0],
                                   isect_pt = [0, 1, 0], 
                                   isect_normal=GT.normalize([0, 1, 0]),
                                   isect_dist = np.sqrt(2.))
Ejemplo n.º 40
0
    def draw_volume(self): 

        ##########################
        eye_dir = GT.normalize(self.view.eye);
        
        # 1. ANGLE-AXIS SOL BEGIN
        '''
        get_angle_axis_matrix will return a rotation matrix after accounting for
        degenerate cases when axis of rotation is 0 i.e. angle 0 or 180 degs.
        In the case of 180 degs rotation is performed about the default rotation axis (last argument).
        Note that the 2nd rotation is necessary to prevent the quads from rotating.
        If the quads rotate then for certain view directions the quads will not
        cover the entire texture.
        '''
        M, angle_deg, axis_of_rotation = get_angle_axis_matrix([0, 0, 1], eye_dir, [0, 1, 0])
        new_up = np.dot(M[:3,:3], [0, 1, 0])
        # orientation independent of view.up
        world_up_proj_on_quad = np.array([0, 1, 0]) - eye_dir[1] * eye_dir
        Mup, angle_deg, axis_of_rotation = get_angle_axis_matrix(new_up, world_up_proj_on_quad, eye_dir)
        M_angleaxis = np.dot(Mup, M)        
        # ANGLE-AXIS SOL END
        
        # 2. Direct matrix computation BEGIN
        # directly compute the same matrix as above
        quad_right = GT.normalize(np.cross([0, 1, 0], eye_dir))
        if np.linalg.norm(quad_right) < 1e-6: # eye_dir is parallel to [0, 1, 0]
            quad_right = [1, 0, 0]
        quad_up = GT.normalize(np.cross(eye_dir, quad_right))
        M_direct = np.eye(4)
        M_direct[:3,2] = eye_dir
        M_direct[:3,0] = quad_right
        M_direct[:3,1] = quad_up
        # Direct matrix computation END
        
        # 3. Y-X rotation based solution BEGIN
        # rotation about the y-axis
        degY = np.rad2deg(np.arctan2(eye_dir[0], eye_dir[2])) # eye.z -> x and eye.x -> y
        # rotation about x-axis
        zx = np.linalg.norm([eye_dir[0], eye_dir[2]]) # length of the projection of unit dir vector on the xz plane
        degX = -np.rad2deg(np.arctan2(eye_dir[1], zx))
        
        # Construct rotation matrix
        mX = GT.rotateX(degX)
        mY = GT.rotateY(degY)
        
        M_RotYX = np.dot(mY, mX)
        # Y-X rotation based solution END
        
        # compare all 3 solutions which should produce the same matrix except 
        # when viewing from top/bottom the orientation of the quad may differ by 90 degs
        np.testing.assert_array_almost_equal(M_direct[:3,:3], M_angleaxis[:3,:3], decimal=6)
        np.testing.assert_array_almost_equal(M_direct[:3,:3], M_RotYX[:3,:3], decimal=6)
        M = M_direct
        
        

        glMatrixMode(GL_MODELVIEW) # switch back to modelview matrix
        glPushMatrix()

        glMultMatrixd(M.T)

        glPushAttrib(GL_ALL_ATTRIB_BITS)
        glColor3f(1, 0, 0)
        glutWireCube(1.0)
        glPopAttrib()
        glPopMatrix()
        
        ''' TO DO: have the quads (slices) face the viewer '''
        
        glEnable(GL_BLEND)
        # glBlendFunc can be set during init but it's set here just to have all relevant things in one place
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        
        
        #glDisable(GL_CULL_FACE)
        glEnable(GL_TEXTURE_3D)
        
         # Yet another solution: using inverse lookat matrix
#        # compute view matrix
#        M = GT.lookAtMatrix(self.view.eye, self.view.eye + self.view.lookat, self.view.up).getA()
#        # compute inverse view matrix        
#        Minv = np.linalg.inv(M)
#        # Minv is the view inverse and transforms the geometry to the camera's origin
#        #  The slices will move and translate with the camera
#        # GT.translate applies translation so that geometry is in front of the camera  
#        Tform = np.dot(Minv, GT.translate([0., 0., -2.]).getA())

        '''
        Draw all the NUMSLICES quads with corresponding texture coordinates. 
        '''
    
        # the z-coordinate represents the depth of each slice in the bounding box
        for z in np.linspace(-0.5, 0.5, self.NUMSLICES):
            # the 1st row corresponds to the x-coordinates and 2nd row y-coordinates of the slices.
            quad_coord = np.array([[0, 1, 1, 0], [0, 0, 1, 1]]) - 0.5
            
            # add row for z-coord            
            quad_coord = np.vstack((quad_coord,np.array([z,z,z,z]))) 
            # add row for homogeneous coord
            quad_coord = np.vstack((quad_coord,np.array([1,1,1,1])))

            # Apply rotation to slice coordinates            
            quad_coord = np.dot(M, quad_coord)

            glBegin(GL_QUADS)
            for i in range(4):
                glTexCoord3f(quad_coord[0][i] + .5 , quad_coord[1][i] + .5 , quad_coord[2][i] + .5) 
                glVertex3f(quad_coord[0][i], quad_coord[1][i], quad_coord[2][i])
            glEnd()
            
          
        glDisable(GL_TEXTURE_3D)
        

        glDisable(GL_BLEND)
Ejemplo n.º 41
0
 def test_ray_originates_on_sphere_goes_inside_v2(self):
     test_intersection_with_result(self.sphere, origin=[1, 0, 0],
                                   direction=[-1, 1, 0],
                                   isect_pt = [0, 1, 0],
                                   isect_normal=GT.normalize([0, 1, 0]),
                                   isect_dist = np.sqrt(2.))
Ejemplo n.º 42
0
    def intersect(self, ray):
        '''
      input: ray in the same coordinate system as the sphere
      output: IntersectionResult, contains the intersection point, normal,
              distance from the eye position and material (see Ray.py)
      Let, the ray be P = P0 + tv, where P0 = eye, v = ray direction
      We want to find t.
      sphere with center Pc and radius r:
      (P - Pc)^T (P - Pc) = r^2
      We need to consider the different situations that can arise with ray-sphere
      intersection.
      NOTE 1: If the eye position is inside the sphere it SHOULD return the
      ray-sphere intersection along the view direction. Because the point *is
      visible* to the eye as far as ray-sphere intersection is concerned.
      The color used for rendering that point will be considered during the
      lighting and shading stage. In general there's no reason why there can't
      be a viewer and light sources inside a sphere.
      NOTE 2: If the ray origin is on the surface of the sphere then the nearest
      intersection should be ignored otherwise we'll have problems where
      the surface point cannot 'see' the light because of self intersection.
      '''
        '''
      Implement intersection between the ray and the current object and
      return IntersectionResult variable (isect) which will store the
      intersection point, the normal at the intersection and material of the
      object at the intersection point.
      '''
        isect = IntersectionResult(
        )  # by default isect corresponds to no intersection

        global EPS_DISTANCE  # use this for testing if a variable is close to 0
        # TODO ===== BEGIN SOLUTION HERE =====

        # IF THE RAY IS INSIDE THE SPHERE (i.e. eye < center + radius)
        # do stuff
        # ELSE IF The ray is on the sphere
        # OTHERWISE it's a normal ray intersection from the outside

        # Derivation of formulas which comes down to quadratic to find distances
        # Pn = (P0 - Pc) + tv
        # D = P0 - Pc
        # Pn^T Pn = r^2
        # (D + tv)^T (D+tv) = r^2
        # (D^2 - r^2) + 2Dv*t + t^2 * v^2 = r^2
        # E = D^2 - r^2
        # t = (- 2D*v +/- sqrt(2Dv^2 - 4v^2(E)))/2(v^2)

        dir_vec = ray.viewDirection
        p = ray.eyePoint
        v2 = np.dot(dir_vec, dir_vec)
        Dv = 2 * np.dot(dir_vec, p - self.center)
        c0 = np.dot(p - self.center, p - self.center) - (self.radius**2)
        delta = Dv * Dv - 4 * v2 * c0
        if delta < -EPS_DISTANCE:
            return isect

        delta = np.fabs(delta)
        x1 = (-Dv - math.sqrt(delta)) / (2 * v2)
        x2 = (-Dv + math.sqrt(delta)) / (2 * v2)
        x = min(x1, x2)
        if x < -EPS_DISTANCE:
            if x1 > 0:
                x = x1
            if x2 > 0:
                x = x2

        if np.fabs(x) < EPS_DISTANCE:
            if x1 > EPS_DISTANCE:
                x = x1
            if x2 > EPS_DISTANCE:
                x = x2

        # ray casts such as intersection is behind the sphere
        if (x < -EPS_DISTANCE) or np.fabs(x) < EPS_DISTANCE:
            return isect
        else:
            # we have a valid intersection
            isect.t = x
            isect.material = self.material
            isect.p = ray.eyePoint + x * ray.viewDirection
            isect.n = GT.normalize(isect.p - self.center)

        # ===== END SOLUTION HERE =====
        return isect