private Vector3 CastRay(Ray ray, int depth = 0) { (float i1, float i2)minIntersection = (float.MaxValue, float.MaxValue); IIntersectable minObj = null; // find closest intersection if it exists foreach (IIntersectable obj in objects) { (float i1, float i2)intersection = obj.Intersect(ray); if (intersection.i1 > 0 && intersection.i1 < minIntersection.i1) { minIntersection = intersection; minObj = obj; } } if (minObj == null) { if (depth == 0) { return(BackgroundColour); } else { return(new Vector3(0)); } } Vector3 iPoint = ray.ToPoint(minIntersection.i1); Vector3 iNormal = minObj.CalcNormal(iPoint, true); Vector3 pointCol = minObj.Colour * minObj.Ambient; foreach (Light light in lights) { // cast shadow ray for each object. if collide, shadow Vector3 il = (light.Pos - iPoint).Normalized(); Ray ilRay = new Ray(iPoint + iNormal, il); bool shadow = false; foreach (IIntersectable obj in objects) { if (obj.Intersect(ilRay).i1 > 0) { shadow = true; break; } } // calculate actual lighting if not in shadow // based on https://en.wikipedia.org/wiki/Phong_reflection_model and https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model if (!shadow) { float angleCoeff = Math.Max(0f, Vector3.Dot(il, iNormal)); Vector3 diffuse = minObj.Colour * angleCoeff * light.Brightness * minObj.Diffuse; Vector3 lv = il - ray.Dir; Vector3 h = lv / lv.Length; float nh = Math.Max(0f, Vector3.Dot(iNormal, h)); Vector3 specular = minObj.SpecularColour * (float)Math.Pow(nh, minObj.Shininess) * minObj.Specular; pointCol += diffuse * specular; } // handle reflective materials if (minObj.Reflectivity > 0 && depth < 5) { Vector3 rVec = (il - 2 * Vector3.Dot(il, iNormal) * iNormal).Normalized(); Ray subRay = new Ray(iPoint + iNormal, rVec); Vector3 rColour = CastRay(subRay, depth + 1); pointCol += rColour * minObj.Reflectivity; } } return(pointCol); }