/******* Fonction à écrire par les etudiants ******/ Color trace_ray (Ray ray_) { /** * \todo : recursive raytracing * * La fonction trace_ray() renvoie une couleur obtenue par la somme de l'éclairage direct (couleur calculée par la fonction * compute_direct_lighting()) et des couleurs provenant des reflets et transparences éventuels aux points d'intersection. * Dans la première partie du TP, seul l'éclairage direct sera calculé. Dans la seconde partie, les reflets et transparences seront rajoutés. * * Pour la première étape, la fonction trace_ray() ne calculant que les rayons primaires, l'intersection * entre le rayon et la scène doit être calculée (fonction intersect_scene() du module \ref RayAPI). * S'il n'y a pas d'intersection, une couleur blanche (triplet RGB [1, 1, 1], élément neutre de la multiplication des couleurs) * devra être retournée. * S'il y a une intersection, la couleur retournée sera la couleur résultante de l'éclairage direct du point d'intersection par les * sources lumineuses de la scène et calculée par la fonction compute_direct_lighting() à écrire dans la suite. * * Pour la deuxième étape, à partir des fonctions définies dans le module \ref RayAPI et permettant d'accéder aux informations de * profondeur et d'importance sur le rayon, définir un cas d'arêt dela récursivité et renvoyer si ce cas est vérifié la couleur * résultante de l'éclairage direct. Si la récursivité n'est pas terminée, en utilisant les fonctions définies dans le module \ref LightAPI, * calculer la couleur réfléchie. Pour cela, il faut tester si le matériau est réflechissant et, si c'est le cas, calculer le rayon * réfléchi et le coefficient de réflexion (une couleur). La couleur calculée en lançant le rayon réfléchi devra alors être multipliée par ce coefficient avant d'être ajoutée * à la couleur renvoyée par trace_ray(). * * Pour la troisème étape et de façon très similaire à la réflexion, utiliser les fonctions définies dans le module \ref LightAPI pour calculer la couleur réfractée. * Pour cela, il faut tester si le matériau est transparent et, si c'est le cas, calculer le rayon réfracté et le coefficient de * transparence (une couleur). La couleur calculée en lançant le rayon réfracté devra alors être multipliée par ce coefficient avant * d'être ajoutée à la couleur renvoyée par trace_ray(). * */ Color l = init_color (0.075f, 0.075f, 0.075f); Isect isect_; int isInter = intersect_scene (&ray_, &isect_ ); if (isInter!=0){ l = compute_direct_lighting (ray_, isect_); } if (ray_depth(ray_)>10 || ray_importance(ray_)<0.01f) return (l); //reflection if(isect_has_reflection(isect_)){ Ray refl_ray; Color refl_col = reflect(ray_, isect_, &refl_ray); l = l+refl_col*(trace_ray(refl_ray)); } //refraction if(isect_has_refraction(isect_)){ Ray rafr_ray; Color rafr_col = refract(ray_, isect_, &rafr_ray); if(color_is_black(rafr_col)==0) l = l+rafr_col*(trace_ray(rafr_ray)); } return l; }
t_colour refract(t_env *e, int depth, t_colour colour) { t_env *refract_env; if (depth > e->maxdepth) return (colour); refract_env = copy_env(e); if (e->hit_type == FACE) { set_refract_ray_object(e, refract_env); intersect_scene(refract_env); colour = find_colour_struct(refract_env, depth); } else if (e->hit_type == PRIMITIVE) { set_refract_ray_prim(e, refract_env); intersect_scene(refract_env); colour = find_colour_struct(refract_env, depth); } free(refract_env); return (colour); }
// ray方向からの放射輝度を求める Color radiance(const Ray &ray, const int depth, bool interpolation = true) { double t; // レイからシーンの交差位置までの距離 int id; // 交差したシーン内オブジェクトのID Vec normal; // 交差位置の法線 if (!intersect_scene(ray, &t, &id, &normal)) return BackgroundColor; const Rectangle &obj = recs[id]; const Vec hitpoint = ray.org + t * ray.dir; // 交差位置 // シーンを構成するジオメトリは全て完全拡散面と仮定 switch (DIFFUSE) { case DIFFUSE: { const Vec v = hitpoint - obj.p0; const double a_len = Dot(v, Normalize(obj.a)); const double b_len = Dot(v, Normalize(obj.b)); double da = obj.a_num * a_len / obj.a_len; double db = obj.b_num * b_len / obj.b_len; int ia = int(da); if (ia >= obj.a_num) ia --; int ib = int(db); if (ib >= obj.b_num) ib --; // バイキュービック補間 if (interpolation) { Color c[4][4]; int ia = int(da - 0.5); int ib = int(db - 0.5); for (int i = 0; i < 4; i ++) { for (int j = 0; j < 4; j ++) { c[i][j] = obj.sample(ia + i - 1, ib + j - 1); } } int ia0 = int(da - 0.5); int ib0 = int(db - 0.5); double dx = da - ia0 - 0.5; double dy = db - ib0 - 0.5; if (dx < 0.0) dx = 0.0; if (dx >= 1.0) dx = 1.0; if (dy < 0.0) dy = 0.0; if (dy >= 1.0) dy = 1.0; return PI * bicubicInterpolate(c, dx, dy); } // 完全拡散面なのでPI倍 return PI * obj.patch[ia * obj.b_num + ib]; } break; } }
// 直接光を計算する Color direct_radiance(const Vec &v0, const Vec &normal, const int id, const Vec &light_pos) { const Vec light_normal = Normalize(light_pos - spheres[LightID].position); const Vec light_dir = Normalize(light_pos - v0); const double dist2 = (light_pos - v0).LengthSquared(); const double dot0 = Dot(normal, light_dir); const double dot1 = Dot(light_normal, -1.0 * light_dir); if (dot0 >= 0 && dot1 >= 0) { const double G = dot0 * dot1 / dist2; double t; // レイからシーンの交差位置までの距離 int id_; // 交差したシーン内オブジェクトのID intersect_scene(Ray(v0, light_dir), &t, &id_); if (fabs(sqrt(dist2) - t) < 1e-3) { return Multiply(spheres[id].color, spheres[LightID].emission) * (1.0 / PI) * G / (1.0 / (4.0 * PI * pow(spheres[LightID].radius, 2.0))); } } return Color(); }
// ray方向からの放射輝度を求める Color radiance(const Ray &ray, const int depth, PhotonMap *photon_map, IrradianceCache *cache, const double gather_radius, const int gahter_max_photon_num, const int final_gather, const int direct_light_samples, bool precompute = false) { double t; // レイからシーンの交差位置までの距離 int id; // 交差したシーン内オブジェクトのID if (!intersect_scene(ray, &t, &id)) return BackgroundColor; const Sphere &obj = spheres[id]; const Vec hitpoint = ray.org + t * ray.dir; // 交差位置 const Vec normal = Normalize(hitpoint - obj.position); // 交差位置の法線 const Vec orienting_normal = Dot(normal, ray.dir) < 0.0 ? normal : (-1.0 * normal); // 交差位置の法線(物体からのレイの入出を考慮) // 色の反射率最大のものを得る。ロシアンルーレットで使う。 // ロシアンルーレットの閾値は任意だが色の反射率等を使うとより良い。 double russian_roulette_probability = std::max(obj.color.x, std::max(obj.color.y, obj.color.z)); // 一定以上レイを追跡したらロシアンルーレットを実行し追跡を打ち切るかどうかを判断する if (depth > MaxDepth) { if (rand01() >= russian_roulette_probability) return obj.emission; } else russian_roulette_probability = 1.0; // ロシアンルーレット実行しなかった switch (obj.ref_type) { case DIFFUSE: { if (id == LightID) return obj.emission; // イラディアンスキャッシュをつかって放射輝度推定する IrradianceCache::ResultIrradianceQueue iqueue; const double threashold = 10.0; // 大きいほど精度向上 const double Rmax = 100.0; const double radius = (1.0 / threashold) * Rmax; IrradianceCache::IrradianceQuery query(hitpoint, orienting_normal, radius * radius, threashold); cache->SearchCachedPoints(&iqueue, query); Color accum; if (iqueue.size() == 0 || precompute) { // 条件をみたすキャッシュ点見つからず // hitpointのイラディアンスを直接計算 // 計算方法は何でもいい。今回はフォトンマップを使う double R0 = 0; // orienting_normalの方向を基準とした正規直交基底(w, u, v)を作る。この基底に対する半球内で次のレイを飛ばす。 Vec w, u, v; w = orienting_normal; if (fabs(w.x) > 0.1) u = Normalize(Cross(Vec(0.0, 1.0, 0.0), w)); else u = Normalize(Cross(Vec(1.0, 0.0, 0.0), w)); v = Cross(w, u); // ファイナルギャザリング for (int fg = 0; fg < final_gather; fg ++) { // コサイン項を使った重点的サンプリング const double r1 = 2 * PI * rand01(); const double r2 = rand01(), r2s = sqrt(r2); Vec dir = Normalize((u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1.0 - r2))); Ray ray_(hitpoint, dir); double t_; // レイからシーンの交差位置までの距離 int id_; // 交差したシーン内オブジェクトのID if (intersect_scene(ray_, &t_, &id_)) { R0 += 1.0 / t_; const Sphere &obj_ = spheres[id_]; const Vec hitpoint_ = ray_.org + t_ * ray_.dir; // 交差位置 const Vec normal_ = Normalize(hitpoint_ - obj_.position); // 交差位置の法線 const Vec orienting_normal_ = Dot(normal_, ray_.dir) < 0.0 ? normal_ : (-1.0 * normal_); // 交差位置の法線(物体からのレイの入出を考慮) // フォトンマップをつかって放射輝度推定する PhotonMap::ResultQueue pqueue; // k近傍探索。gather_radius半径内のフォトンを最大gather_max_photon_num個集めてくる PhotonMap::Query query(hitpoint_, orienting_normal_, gather_radius, gahter_max_photon_num); photon_map->SearchKNN(&pqueue, query); Color accumulated_flux; double max_distance2 = -1; // キューからフォトンを取り出しvectorに格納する std::vector<PhotonMap::ElementForQueue> photons; photons.reserve(pqueue.size()); for (;!pqueue.empty();) { PhotonMap::ElementForQueue p = pqueue.top(); pqueue.pop(); photons.push_back(p); max_distance2 = std::max(max_distance2, p.distance2); } // 円錐フィルタを使用して放射輝度推定する const double max_distance = sqrt(max_distance2); const double k = 1.1; for (int i = 0; i < photons.size(); i ++) { const double weight = 1.0 - (sqrt(photons[i].distance2) / (k * max_distance)); // 円錐フィルタの重み const Color value = Multiply(obj_.color, photons[i].point->power) / PI; // Diffuse面のBRDF = 1.0 / πであったのでこれをかける accumulated_flux = accumulated_flux + weight * value; } accumulated_flux = accumulated_flux / (1.0 - 2.0 / (3.0 * k)); // 円錐フィルタの係数 if (max_distance2 > 0.0) { accum = accum + accumulated_flux / (PI * max_distance2) / russian_roulette_probability / final_gather; } } } R0 = 1.0 / (R0 / final_gather); // イラディアンス追加 cache->AddPointToTree(Irradiance(hitpoint, accum * PI, orienting_normal, R0)); // イラディアンスからray方向への放射輝度推定 accum = Multiply(obj.color, accum); } else { // イラディアンスキャッシュをつかって放射輝度推定する std::vector<IrradianceCache::ElementForIrradianceQueue> irrs; irrs.reserve(iqueue.size()); double weight = 0; for (;!iqueue.empty();) { IrradianceCache::ElementForIrradianceQueue i = iqueue.top(); iqueue.pop(); irrs.push_back(i); weight += i.weight; } for (int i = 0; i < irrs.size(); i ++) { accum = accum + irrs[i].weight * irrs[i].point->irradiance / weight / PI; // イラディアンス -> ラディアンスの変換のためにPIでわる(完全拡散面仮定) } // イラディアンスからray方向への放射輝度推定 accum = Multiply(obj.color, accum); } // 直接光のサンプリング // ファイナルギャザリングで求まるのは(今回は)間接光のみなので直接光は別にサンプリングして求めてやる for (int i = 0; i < direct_light_samples; i ++) { accum = accum + direct_radiance_sample(hitpoint, orienting_normal, id) / russian_roulette_probability / direct_light_samples; } return accum; } break; } return Color(); }
// フォトン追跡法によりフォトンマップ構築 void create_photon_map(const int shoot_photon_num, PhotonMap *photon_map) { std::cout << "Shooting photons... (" << shoot_photon_num << " photons)" << std::endl; for (int i = 0; i < shoot_photon_num; i ++) { // 光源からフォトンを発射する // 光源上の一点をサンプリングする const double r1 = 2 * PI * rand01(); const double r2 = 1.0 - 2.0 * rand01() ; const Vec light_pos = spheres[LightID].position + ((spheres[LightID].radius + EPS) * Vec(sqrt(1.0 - r2*r2) * cos(r1), sqrt(1.0 - r2*r2) * sin(r1), r2)); const Vec normal = Normalize(light_pos - spheres[LightID].position); // 光源上の点から半球サンプリングする Vec w, u, v; w = normal; if (fabs(w.x) > 0.1) u = Normalize(Cross(Vec(0.0, 1.0, 0.0), w)); else u = Normalize(Cross(Vec(1.0, 0.0, 0.0), w)); v = Cross(w, u); // コサイン項に比例させる。フォトンが運ぶのが放射輝度ではなく放射束であるため。 const double u1 = 2 * PI * rand01(); const double u2 = rand01(), u2s = sqrt(u2); Vec light_dir = Normalize((u * cos(u1) * u2s + v * sin(u1) * u2s + w * sqrt(1.0 - u2))); Ray now_ray(light_pos, light_dir); // emissionの値は放射輝度だが、フォトンが運ぶのは放射束なので変換する必要がある。 // L(放射輝度)= dΦ/(cosθdωdA)なので、光源の放射束はΦ = ∫∫L・cosθdωdAになる。今回は球光源で完全拡散光源であることから // 球上の任意の場所、任意の方向に等しい放射輝度Leを持つ。(これがemissionの値)よって、 // Φ = Le・∫∫cosθdωdAで、Le・∫dA∫cosθdωとなり、∫dAは球の面積なので4πr^2、∫cosθdωは立体角の積分なのでπとなる。 // よって、Φ = Le・4πr^2・πとなる。この値を光源から発射するフォトン数で割ってやれば一つのフォトンが運ぶ放射束が求まる。 Color now_flux = spheres[LightID].emission * 4.0 * PI * pow(spheres[LightID].radius, 2.0) * PI / shoot_photon_num; // フォトンがシーンを飛ぶ bool trace_end = false; for (;!trace_end;) { // 放射束が0.0なフォトンを追跡してもしょうがないので打ち切る if (std::max(now_flux.x, std::max(now_flux.y, now_flux.z)) <= 0.0) break; double t; // レイからシーンの交差位置までの距離 int id; // 交差したシーン内オブジェクトのID if (!intersect_scene(now_ray, &t, &id)) break; const Sphere &obj = spheres[id]; const Vec hitpoint = now_ray.org + t * now_ray.dir; // 交差位置 const Vec normal = Normalize(hitpoint - obj.position); // 交差位置の法線 const Vec orienting_normal = Dot(normal, now_ray.dir) < 0.0 ? normal : (-1.0 * normal); // 交差位置の法線(物体からのレイの入出を考慮) switch (obj.ref_type) { case DIFFUSE: { // 拡散面なのでフォトンをフォトンマップに格納する photon_map->AddPoint(Photon(hitpoint, now_flux, now_ray.dir)); // 反射するかどうかをロシアンルーレットで決める // 例によって確率は任意。今回はフォトンマップ本に従ってRGBの反射率の平均を使う const double probability = (obj.color.x + obj.color.y + obj.color.z) / 3; if (probability > rand01()) { // 反射 // orienting_normalの方向を基準とした正規直交基底(w, u, v)を作る。この基底に対する半球内で次のレイを飛ばす。 Vec w, u, v; w = orienting_normal; if (fabs(w.x) > 0.1) u = Normalize(Cross(Vec(0.0, 1.0, 0.0), w)); else u = Normalize(Cross(Vec(1.0, 0.0, 0.0), w)); v = Cross(w, u); // コサイン項を使った重点的サンプリング const double r1 = 2 * PI * rand01(); const double r2 = rand01(), r2s = sqrt(r2); Vec dir = Normalize((u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1.0 - r2))); now_ray = Ray(hitpoint, dir); now_flux = Multiply(now_flux, obj.color) / probability; continue; } else { // 吸収(すなわちここで追跡終了) trace_end = true; continue; } } break; } } } std::cout << "Done. (" << photon_map->Size() << " photons are stored)" << std::endl; std::cout << "Creating KD-tree..." << std::endl; photon_map->CreateKDtree(); std::cout << "Done." << std::endl; }
RGB_float recursive_ray_trace(Vector ray, Point p, int step) { RGB_float color = background_clr; RGB_float reflected_color = {0,0,0}; RGB_float refracted_color = {0,0,0}; Spheres *closest_sph; Point *hit = new Point; closest_sph = intersect_scene(p, ray, scene, hit); //get the point color here //intersects a sphere Point *plane_hit = new Point; color = background_clr; if(chessboard_on && intersect_plane(p, ray, N_plane, p0, plane_hit)) { Vector eye_vec = get_vec(*plane_hit, eye_pos); Vector light_vec = get_vec(*plane_hit, p); normalize(&light_vec); normalize(&eye_vec); color = colorPlane(*plane_hit); Vector shadow_vec = get_vec(*plane_hit, light1); Spheres *sph = NULL; if(inShadow(*plane_hit, shadow_vec, scene, sph) && shadow_on) { color = clr_scale(color, .5); } } if(closest_sph != NULL) { Vector eye_vec = get_vec(*hit, eye_pos); Vector surf_norm = sphere_normal(*hit, closest_sph); Vector light_vec = get_vec(*hit, p); normalize(&light_vec); normalize(&surf_norm); normalize(&eye_vec); color = phong(*hit, eye_vec, surf_norm, closest_sph); if(step < step_max && reflection_on) { Vector reflect_vec = vec_minus(vec_scale(surf_norm, vec_dot(surf_norm, light_vec)*2), light_vec); step += 1; normalize(&reflect_vec); reflected_color = recursive_ray_trace(reflect_vec, *hit, step); reflected_color = clr_scale(reflected_color, closest_sph->reflectance); color = clr_add(color, reflected_color); } if(step < step_max && refraction_on) { Vector refracted_ray = getRefractedRay(1.51, closest_sph, surf_norm, light_vec); step += 1; normalize(&refracted_ray); refracted_ray.x = hit->x + refracted_ray.x; refracted_ray.y = hit->x + refracted_ray.y; refracted_ray.z = hit->x + refracted_ray.z; refracted_color = recursive_ray_trace(refracted_ray, *hit, step); color = clr_add(color, reflected_color); } return color; } else { return color; } }
void calc_form_factor(const int a_div_num, const int b_div_num, const int mc_sample) { const double n = sizeof(recs) / sizeof(Rectangle); for (int i = 0; i < int(n); i ++) { recs[i].divide_patchs(a_div_num, b_div_num); // とりあえず適当に分割 patch_num += recs[i].a_num * recs[i].b_num; } std::cout << "patch num: " << patch_num << " form factor num:" << patch_num * patch_num << std::endl; form_factor = new double[patch_num * patch_num]; memset(form_factor, 0.0, sizeof(double) * patch_num * patch_num); //#pragma omp parallel for schedule(dynamic, 1) num_threads(3) for (int i = 0; i < int(n); i ++) { srand(i * i * i + i); int patch_i = 0; for (int k = 0; k < i; k ++) patch_i += recs[k].a_num * recs[k].b_num; std::cout << i << std::endl; for (int ia = 0; ia < recs[i].a_num; ia ++) { std::cout << "*"; for (int ib = 0; ib < recs[i].b_num; ib ++) { // 面(i)上の、パッチ(ia, ib) // form_factor[patch_i * patch_num + patch_j] = F_ij const Vec normal_i = recs[i].normal; const double Ai = Cross(recs[i].a / recs[i].a_num, recs[i].b / recs[i].b_num).Length(); // パッチ(ia, ib)の面積 // 相手の面 int patch_j = 0; for (int j = 0; j < int(n); j ++) { const Vec normal_j = recs[j].normal; for (int ja = 0; ja < recs[j].a_num; ja ++) { for (int jb = 0; jb < recs[j].b_num; jb ++) { const double Aj = Cross(recs[j].a / recs[j].a_num, recs[j].b / recs[j].b_num).Length(); // パッチ(ja, jb)の面積 if (i != j) { // フォームファクター計算 // モンテカルロ積分 // 等間隔にサンプリング double F = 0; const int Ni = mc_sample, Nj = mc_sample; for (int ias = 0; ias < Ni; ias ++) { for (int ibs = 0; ibs < Ni; ibs ++) { for (int jas = 0; jas < Nj; jas ++) { for (int jbs = 0; jbs < Nj; jbs ++) { const double u0 = (double)(ias + 0.5) / Ni, u1 = (double)(ibs + 0.5) / Ni; const double u2 = (double)(jas + 0.5) / Nj, u3 = (double)(jbs + 0.5) / Nj; const Vec xi = recs[i].p0 + recs[i].a * ((double)(ia + u0) / recs[i].a_num) + recs[i].b * ((double)(ib + u1) / recs[i].b_num); const Vec xj = recs[j].p0 + recs[j].a * ((double)(ja + u2) / recs[j].a_num) + recs[j].b * ((double)(jb + u3) / recs[j].b_num); // V(x, y) const Vec ij = Normalize(xj - xi); double t; // レイからシーンの交差位置までの距離 int id; // 交差したシーン内オブジェクトのID Vec normal; // 交差位置の法線 if (intersect_scene(Ray(xi, ij), &t, &id, &normal) && id != j) { continue; } const double d0 = Dot(normal_i, ij); const double d1 = Dot(normal_j, -1.0 * ij); if (d0 > 0.0 && d1 > 0.0) { const double K = d0 * d1 / (PI * (xj - xi).LengthSquared()); const double pdf = (1.0 / Ai) * (1.0 / Aj); F += K / pdf; } } } } } F /= (Ni) * (Ni) * (Nj) * (Nj) * Ai; if (F > 1.0) F = 1.0; form_factor[patch_i * patch_num + patch_j] = F; } patch_j ++; } } } patch_i ++; } } } }
/************************************************************************ * This is the recursive ray tracer - you need to implement this! * You should decide what arguments to use. ************************************************************************/ vec3 recursive_ray_trace(vec3 eye, vec3 ray, int num, bool inobj) { // // do your thing here // if(num>step_max) return null_clr; vec3 hit; int isplane; void *sph = intersect_scene(eye, ray, scene, &hit, &isplane); vec3 color = null_clr; if(sph==NULL) { return background_clr; } vec3 lightvec = light1 - hit; vec3 lightvec_normal = normalize(lightvec); vec3 lighthit; int lightisplane; void * light_sph = intersect_scene(hit, lightvec_normal, scene, &lighthit, &lightisplane); vec3 surf_normal = isplane?vec3(0,1,0):sphere_normal(hit, (Spheres*)sph); if(light_sph==NULL) { color += phong(-1*ray, lightvec, surf_normal, sph, hit, isplane); } else { if(!shadow_on) { color += phong(-1*ray, lightvec, surf_normal, sph, hit, isplane); } else { color += get_shadow(-1*ray, lightvec, surf_normal, sph, hit, isplane); } } if(reflect_on) { vec3 reflect_vector = 2*dot(-1*ray, surf_normal)*surf_normal + ray; reflect_vector = normalize(reflect_vector); if(isplane) { color += ((struct plane*)sph)->reflectance * recursive_ray_trace(hit, reflect_vector,num+1, inobj); } else if(!isplane) color += ((Spheres *)sph)->reflectance * recursive_ray_trace(hit, reflect_vector, num+1, inobj); } if(refract_on) { vec3 outlightvector; if(refraction(hit, -1*ray, sph, isplane, inobj, &outlightvector)) { if(!isplane) { // printf("refraction point\n"); Spheres * refractsph = (Spheres *)sph; color += refractsph->refr*recursive_ray_trace(hit, outlightvector, num+1, !inobj); } } } if(diffuse_reflection_on && num<2) { int i; for (i=0;i<DIFFUSE_REFLECTION;i++) { float xtheta = 2.0 * static_cast <float> (rand()) / static_cast <float> (RAND_MAX) - 1.0; float ytheta = 2.0 * static_cast <float> (rand()) / static_cast <float> (RAND_MAX) - 1.0; float ztheta = 2.0 * static_cast <float> (rand()) / static_cast <float> (RAND_MAX) - 1.0; vec3 dfray; dfray = rotateX(xtheta*M_PI,surf_normal); dfray = rotateY(ytheta*M_PI,dfray); dfray = rotateZ(ztheta*M_PI,dfray); color += (0.1/DIFFUSE_REFLECTION)*recursive_ray_trace(hit, dfray, num+1, inobj); } } return color; }