|
| 1 | +#include <fstream> |
| 2 | +#include "Vector.hpp" |
| 3 | +#include "Renderer.hpp" |
| 4 | +#include "Scene.hpp" |
| 5 | +#include <optional> |
| 6 | + |
| 7 | +inline float deg2rad(const float °) |
| 8 | +{ return deg * M_PI/180.0; } |
| 9 | + |
| 10 | +// Compute reflection direction |
| 11 | +Vector3f reflect(const Vector3f &I, const Vector3f &N) |
| 12 | +{ |
| 13 | + return I - 2 * dotProduct(I, N) * N; |
| 14 | +} |
| 15 | + |
| 16 | +// [comment] |
| 17 | +// Compute refraction direction using Snell's law |
| 18 | +// |
| 19 | +// We need to handle with care the two possible situations: |
| 20 | +// |
| 21 | +// - When the ray is inside the object |
| 22 | +// |
| 23 | +// - When the ray is outside. |
| 24 | +// |
| 25 | +// If the ray is outside, you need to make cosi positive cosi = -N.I |
| 26 | +// |
| 27 | +// If the ray is inside, you need to invert the refractive indices and negate the normal N |
| 28 | +// [/comment] |
| 29 | +Vector3f refract(const Vector3f &I, const Vector3f &N, const float &ior) |
| 30 | +{ |
| 31 | + float cosi = clamp(-1, 1, dotProduct(I, N)); |
| 32 | + float etai = 1, etat = ior; |
| 33 | + Vector3f n = N; |
| 34 | + if (cosi < 0) { cosi = -cosi; } else { std::swap(etai, etat); n= -N; } |
| 35 | + float eta = etai / etat; |
| 36 | + float k = 1 - eta * eta * (1 - cosi * cosi); |
| 37 | + return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n; |
| 38 | +} |
| 39 | + |
| 40 | +// [comment] |
| 41 | +// Compute Fresnel equation |
| 42 | +// |
| 43 | +// \param I is the incident view direction |
| 44 | +// |
| 45 | +// \param N is the normal at the intersection point |
| 46 | +// |
| 47 | +// \param ior is the material refractive index |
| 48 | +// [/comment] |
| 49 | +float fresnel(const Vector3f &I, const Vector3f &N, const float &ior) |
| 50 | +{ |
| 51 | + float cosi = clamp(-1, 1, dotProduct(I, N)); |
| 52 | + float etai = 1, etat = ior; |
| 53 | + if (cosi > 0) { std::swap(etai, etat); } |
| 54 | + // Compute sini using Snell's law |
| 55 | + float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi)); |
| 56 | + // Total internal reflection |
| 57 | + if (sint >= 1) { |
| 58 | + return 1; |
| 59 | + } |
| 60 | + else { |
| 61 | + float cost = sqrtf(std::max(0.f, 1 - sint * sint)); |
| 62 | + cosi = fabsf(cosi); |
| 63 | + float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost)); |
| 64 | + float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost)); |
| 65 | + return (Rs * Rs + Rp * Rp) / 2; |
| 66 | + } |
| 67 | + // As a consequence of the conservation of energy, transmittance is given by: |
| 68 | + // kt = 1 - kr; |
| 69 | +} |
| 70 | + |
| 71 | +// [comment] |
| 72 | +// Returns true if the ray intersects an object, false otherwise. |
| 73 | +// |
| 74 | +// \param orig is the ray origin |
| 75 | +// \param dir is the ray direction |
| 76 | +// \param objects is the list of objects the scene contains |
| 77 | +// \param[out] tNear contains the distance to the cloesest intersected object. |
| 78 | +// \param[out] index stores the index of the intersect triangle if the interesected object is a mesh. |
| 79 | +// \param[out] uv stores the u and v barycentric coordinates of the intersected point |
| 80 | +// \param[out] *hitObject stores the pointer to the intersected object (used to retrieve material information, etc.) |
| 81 | +// \param isShadowRay is it a shadow ray. We can return from the function sooner as soon as we have found a hit. |
| 82 | +// [/comment] |
| 83 | +std::optional<hit_payload> trace( |
| 84 | + const Vector3f &orig, const Vector3f &dir, |
| 85 | + const std::vector<std::unique_ptr<Object> > &objects) |
| 86 | +{ |
| 87 | + float tNear = kInfinity; |
| 88 | + std::optional<hit_payload> payload; |
| 89 | + for (const auto & object : objects) |
| 90 | + { |
| 91 | + float tNearK = kInfinity; |
| 92 | + uint32_t indexK; |
| 93 | + Vector2f uvK; |
| 94 | + if (object->intersect(orig, dir, tNearK, indexK, uvK) && tNearK < tNear) |
| 95 | + { |
| 96 | + payload.emplace(); |
| 97 | + payload->hit_obj = object.get(); |
| 98 | + payload->tNear = tNearK; |
| 99 | + payload->index = indexK; |
| 100 | + payload->uv = uvK; |
| 101 | + tNear = tNearK; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + return payload; |
| 106 | +} |
| 107 | + |
| 108 | +// [comment] |
| 109 | +// Implementation of the Whitted-style light transport algorithm (E [S*] (D|G) L) |
| 110 | +// |
| 111 | +// This function is the function that compute the color at the intersection point |
| 112 | +// of a ray defined by a position and a direction. Note that thus function is recursive (it calls itself). |
| 113 | +// |
| 114 | +// If the material of the intersected object is either reflective or reflective and refractive, |
| 115 | +// then we compute the reflection/refraction direction and cast two new rays into the scene |
| 116 | +// by calling the castRay() function recursively. When the surface is transparent, we mix |
| 117 | +// the reflection and refraction color using the result of the fresnel equations (it computes |
| 118 | +// the amount of reflection and refraction depending on the surface normal, incident view direction |
| 119 | +// and surface refractive index). |
| 120 | +// |
| 121 | +// If the surface is diffuse/glossy we use the Phong illumation model to compute the color |
| 122 | +// at the intersection point. |
| 123 | +// [/comment] |
| 124 | +Vector3f castRay( |
| 125 | + const Vector3f &orig, const Vector3f &dir, const Scene& scene, |
| 126 | + int depth) |
| 127 | +{ |
| 128 | + if (depth > scene.maxDepth) { |
| 129 | + return Vector3f(0.0,0.0,0.0); |
| 130 | + } |
| 131 | + |
| 132 | + Vector3f hitColor = scene.backgroundColor; |
| 133 | + if (auto payload = trace(orig, dir, scene.get_objects()); payload) |
| 134 | + { |
| 135 | + Vector3f hitPoint = orig + dir * payload->tNear; |
| 136 | + Vector3f N; // normal |
| 137 | + Vector2f st; // st coordinates |
| 138 | + payload->hit_obj->getSurfaceProperties(hitPoint, dir, payload->index, payload->uv, N, st); |
| 139 | + switch (payload->hit_obj->materialType) { |
| 140 | + case REFLECTION_AND_REFRACTION: |
| 141 | + { |
| 142 | + Vector3f reflectionDirection = normalize(reflect(dir, N)); |
| 143 | + Vector3f refractionDirection = normalize(refract(dir, N, payload->hit_obj->ior)); |
| 144 | + Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ? |
| 145 | + hitPoint - N * scene.epsilon : |
| 146 | + hitPoint + N * scene.epsilon; |
| 147 | + Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ? |
| 148 | + hitPoint - N * scene.epsilon : |
| 149 | + hitPoint + N * scene.epsilon; |
| 150 | + Vector3f reflectionColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1); |
| 151 | + Vector3f refractionColor = castRay(refractionRayOrig, refractionDirection, scene, depth + 1); |
| 152 | + float kr = fresnel(dir, N, payload->hit_obj->ior); |
| 153 | + hitColor = reflectionColor * kr + refractionColor * (1 - kr); |
| 154 | + break; |
| 155 | + } |
| 156 | + case REFLECTION: |
| 157 | + { |
| 158 | + float kr = fresnel(dir, N, payload->hit_obj->ior); |
| 159 | + Vector3f reflectionDirection = reflect(dir, N); |
| 160 | + Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ? |
| 161 | + hitPoint + N * scene.epsilon : |
| 162 | + hitPoint - N * scene.epsilon; |
| 163 | + hitColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1) * kr; |
| 164 | + break; |
| 165 | + } |
| 166 | + default: |
| 167 | + { |
| 168 | + // [comment] |
| 169 | + // We use the Phong illumation model int the default case. The phong model |
| 170 | + // is composed of a diffuse and a specular reflection component. |
| 171 | + // [/comment] |
| 172 | + Vector3f lightAmt = 0, specularColor = 0; |
| 173 | + Vector3f shadowPointOrig = (dotProduct(dir, N) < 0) ? |
| 174 | + hitPoint + N * scene.epsilon : |
| 175 | + hitPoint - N * scene.epsilon; |
| 176 | + // [comment] |
| 177 | + // Loop over all lights in the scene and sum their contribution up |
| 178 | + // We also apply the lambert cosine law |
| 179 | + // [/comment] |
| 180 | + for (auto& light : scene.get_lights()) { |
| 181 | + Vector3f lightDir = light->position - hitPoint; |
| 182 | + // square of the distance between hitPoint and the light |
| 183 | + float lightDistance2 = dotProduct(lightDir, lightDir); |
| 184 | + lightDir = normalize(lightDir); |
| 185 | + float LdotN = std::max(0.f, dotProduct(lightDir, N)); |
| 186 | + // is the point in shadow, and is the nearest occluding object closer to the object than the light itself? |
| 187 | + auto shadow_res = trace(shadowPointOrig, lightDir, scene.get_objects()); |
| 188 | + bool inShadow = shadow_res && (shadow_res->tNear * shadow_res->tNear < lightDistance2); |
| 189 | + |
| 190 | + lightAmt += inShadow ? 0 : light->intensity * LdotN; |
| 191 | + Vector3f reflectionDirection = reflect(-lightDir, N); |
| 192 | + |
| 193 | + specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, dir)), |
| 194 | + payload->hit_obj->specularExponent) * light->intensity; |
| 195 | + } |
| 196 | + |
| 197 | + hitColor = lightAmt * payload->hit_obj->evalDiffuseColor(st) * payload->hit_obj->Kd + specularColor * payload->hit_obj->Ks; |
| 198 | + break; |
| 199 | + } |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + return hitColor; |
| 204 | +} |
| 205 | + |
| 206 | +// [comment] |
| 207 | +// The main render function. This where we iterate over all pixels in the image, generate |
| 208 | +// primary rays and cast these rays into the scene. The content of the framebuffer is |
| 209 | +// saved to a file. |
| 210 | +// [/comment] |
| 211 | +void Renderer::Render(const Scene& scene) |
| 212 | +{ |
| 213 | + std::vector<Vector3f> framebuffer(scene.width * scene.height); |
| 214 | + |
| 215 | + float scale = std::tan(deg2rad(scene.fov * 0.5f)); |
| 216 | + float imageAspectRatio = scene.width / (float)scene.height; |
| 217 | + |
| 218 | + // Use this variable as the eye position to start your rays. |
| 219 | + Vector3f eye_pos(0); |
| 220 | + int m = 0; |
| 221 | + for (int j = 0; j < scene.height; ++j) |
| 222 | + { |
| 223 | + for (int i = 0; i < scene.width; ++i) |
| 224 | + { |
| 225 | + // generate primary ray direction |
| 226 | + float x; |
| 227 | + float y; |
| 228 | + // TODO: Find the x and y positions of the current pixel to get the direction |
| 229 | + // vector that passes through it. |
| 230 | + // Also, don't forget to multiply both of them with the variable *scale*, and |
| 231 | + // x (horizontal) variable with the *imageAspectRatio* |
| 232 | + |
| 233 | + Vector3f dir = Vector3f(x, y, -1); // Don't forget to normalize this direction! |
| 234 | + framebuffer[m++] = castRay(eye_pos, dir, scene, 0); |
| 235 | + } |
| 236 | + UpdateProgress(j / (float)scene.height); |
| 237 | + } |
| 238 | + |
| 239 | + // save framebuffer to file |
| 240 | + FILE* fp = fopen("binary.ppm", "wb"); |
| 241 | + (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height); |
| 242 | + for (auto i = 0; i < scene.height * scene.width; ++i) { |
| 243 | + static unsigned char color[3]; |
| 244 | + color[0] = (char)(255 * clamp(0, 1, framebuffer[i].x)); |
| 245 | + color[1] = (char)(255 * clamp(0, 1, framebuffer[i].y)); |
| 246 | + color[2] = (char)(255 * clamp(0, 1, framebuffer[i].z)); |
| 247 | + fwrite(color, 1, 3, fp); |
| 248 | + } |
| 249 | + fclose(fp); |
| 250 | +} |
0 commit comments