private void HandleObjectSelection(Vector2 position) { float scale = (float)Math.Tan(MathHelper.DegreesToRadians(_camera.fov * 0.5f)); float imageAspectRatio = (float)settings.width / settings.height; float x = (2.0f * (position.X + 0.5f) / settings.width - 1.0f) * imageAspectRatio * scale; float y = (1.0f - 2.0f * (position.Y + 0.5f) / settings.height) * scale; RayTracer.NearestIntersection(settings.scene.meshes, new Ray(_camera.position, new Vector3(x, -y, 1.0f)), out float distance, out int?indexOfNearest); if (indexOfNearest.HasValue) { SelectObject(indexOfNearest.Value); } else { DeselectObject(); } }
private static void TracePhoton(int depth, Scene scene, Ray photonRay, bool causticTracing = false) { RayTracer.NearestIntersection(scene.meshes, photonRay, out float distance, out int?indexOfNearest); if (!indexOfNearest.HasValue) { return; } Vector3 intersection = photonRay.At(distance); Vector3 normal = scene.meshes[indexOfNearest.Value].Normal(intersection); int index = indexOfNearest.Value; Material material = scene.materials[index]; _photon.L = photonRay.direction; //Incident direction _photon.position = intersection; //World space position of the photon hit //Initialize photon if (depth == Config.MAX_PHOTON_DEPTH && !_shadowPhoton) { _caustic = false; _photon.power = Vector3.One; } else if (_shadowPhoton) //Shadow photon (indirect illumination) { if (material.materialType == MaterialType.Diffuse) { _photon.power = new Vector3(-Config.SHADOW_STRENGTH); //TODO: This might not work with diffuse spheres right now as the shadow photon will stop inside the sphere (?) _globalPhotonMap[index].Add(new[] { _photon.position.X, _photon.position.Y, _photon.position.Z }, _photon); //Store photon } else { photonRay.origin = intersection + (photonRay.direction * Renderer.EPSILON); TracePhoton(0, scene, photonRay); } _shadowPhoton = false; return; } if (depth == 0) { return; } switch (material.materialType) { case MaterialType.Diffuse: { if (_caustic) { _causticPhotonMap[index].Add(new[] { _photon.position.X, _photon.position.Y, _photon.position.Z }, _photon); //Store photon _caustic = false; } else if (causticTracing) { _caustic = false; return; } else { _photon.power *= 1.0f / (float)Math.Sqrt(Config.MAX_PHOTON_DEPTH - depth + 1.0f); //TODO: This might not work with diffuse spheres right now as the shadow photon will stop inside the sphere (?) _globalPhotonMap[index].Add(new[] { _photon.position.X, _photon.position.Y, _photon.position.Z }, _photon); //Store photon //TODO: Randomly absorb or bounce (Russian roulette)? //Bounce the photon Vector3 newNormal = normal; if (Vector3.Dot(normal, photonRay.direction) > 0.0f) { newNormal = -normal; } var direction = MathHelper.RandomOnHemisphereWithStaticSeed(newNormal); Ray newRay = new Ray(intersection + direction * Renderer.EPSILON, direction); TracePhoton(depth - 1, scene, newRay); } break; } case MaterialType.Reflection: { if (causticTracing) { _caustic = true; } else { //Shadow photon ray (currently only doing it on reflection and refraction objects, since the scene only consists of those) _shadowPhoton = true; Vector3 shadowRayOrigin = intersection + (photonRay.direction * Renderer.EPSILON); Ray shadowRay = new Ray(shadowRayOrigin, photonRay.direction); TracePhoton(0, scene, shadowRay); _shadowPhoton = false; } bool outside = Vector3.Dot(photonRay.direction, normal) < 0.0f; Vector3 bias = Renderer.EPSILON * normal; Vector3 reflectionRayOrigin = outside ? intersection + bias : intersection - bias; Vector3 reflectionDirection = Renderer.Reflect(photonRay.direction, normal).Normalized(); Ray reflectionRay = new Ray(reflectionRayOrigin, reflectionDirection); TracePhoton(depth - 1, scene, reflectionRay, causticTracing); break; } case MaterialType.Reflection_Refraction: { if (causticTracing) { _caustic = true; } else { //Shadow photon ray (currently only doing it on reflection and refraction objects, since the scene only consists of those) _shadowPhoton = true; Vector3 shadowRayOrigin = intersection + (photonRay.direction * Renderer.EPSILON); Ray shadowRay = new Ray(shadowRayOrigin, photonRay.direction); TracePhoton(0, scene, shadowRay); _shadowPhoton = false; } var ior = material.ior; bool outside = Vector3.Dot(photonRay.direction, normal) < 0.0f; Vector3 bias = Renderer.EPSILON * normal; float kr = Renderer.Fresnel(photonRay.direction, normal, ior); //Compute refraction if it is not a case of total internal reflection if (kr < 1.0f) { Vector3 refractionRayOrigin = outside ? intersection - bias : intersection + bias; Vector3 refractionDirection = Renderer.Refract(photonRay.direction, normal, ior).Normalized(); Ray refractionRay = new Ray(refractionRayOrigin, refractionDirection); TracePhoton(depth - 1, scene, refractionRay, causticTracing); } else { Vector3 reflectionRayOrigin = outside ? intersection + bias : intersection - bias; Vector3 reflectionDirection = Renderer.Reflect(photonRay.direction, normal).Normalized(); Ray reflectionRay = new Ray(reflectionRayOrigin, reflectionDirection); TracePhoton(depth - 1, scene, reflectionRay, causticTracing); } break; } } }
public static void Render(RenderSettings settings, Camera camera) { if (settings.traceMethod == TraceMethod.PhotonTracing) { if (Config.RENDER_PHOTON_MAP) { screen.UpdateSurface(PhotonMapping.PhotonMapRender(settings, camera)); return; } } Bitmap bitmap = new Bitmap(settings.width, settings.height); for (int j = 0; j < settings.height; ++j) { for (int i = 0; i < settings.width; ++i) { Vector3 colorVector = settings.backgroundColor; float vx = i / (float)settings.width; float vy = j / (float)settings.height; Ray ray = camera.RayThroughScreen(vx, vy); switch (settings.traceMethod) { case TraceMethod.WhittedRayTracing: { colorVector = RayTracer.Trace(settings.maxDepth, settings.scene, ray, settings.backgroundColor); break; } case TraceMethod.PathTracing: { int sampleCount = Config.PATH_TRACING_SAMPLES; //TODO: Unsure if this should be here or if I should start random sampling loop from 0 instead of 1 colorVector = PathTracer.Trace(settings.maxDepth, settings.scene, ray, settings.backgroundColor); for (int k = 1; k < sampleCount; k++) { var offsetXMin = -(0.5f / settings.width); var offsetXMax = (0.5f / settings.width); var offsetYMin = -(0.5f / settings.height); var offsetYMax = (0.5f / settings.height); var x = vx + MathHelper.RandomRange(offsetXMin, offsetXMax); var y = vy + MathHelper.RandomRange(offsetYMin, offsetYMax); ray = camera.RayThroughScreen(x, y); colorVector += PathTracer.Trace(settings.maxDepth, settings.scene, ray, settings.backgroundColor); } colorVector /= sampleCount; break; } case TraceMethod.PhotonTracing: { colorVector = PhotonTracer.Trace(settings.maxDepth, settings.scene, ray, settings.backgroundColor); break; } } if (settings.showUI) { //Render UI Vector3?uiColor = RenderUI(settings, i, j); if (uiColor.HasValue) { colorVector += uiColor.Value; } } float red = MathHelper.Clamp(colorVector.X, 0.0f, 1.0f); float green = MathHelper.Clamp(colorVector.Y, 0.0f, 1.0f); float blue = MathHelper.Clamp(colorVector.Z, 0.0f, 1.0f); Color color = Color.FromArgb(255, (int)(red * 255), (int)(green * 255), (int)(blue * 255)); bitmap.SetPixel(i, j, color); } } screen.UpdateSurface(bitmap); }