/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "raytracer/raytracer_core.h" #include "raytracer/raytracer_job.h" #include "scene3d/mobject.h" #include "scene3d/mlight.h" #include "scene3d/mcamera.h" #include "scene3d/scene.h" #include "rand/rand.h" #include "platform.h" using namespace GS; using namespace GS::Core; using namespace GS::Raytrace; //------------------------------------------------------------------------------ float Raytracer::Fresnel(const Vector4 &v, const Vector4 &np, float eta) { float const r0 = Math::Pow(1.0f - eta, 2.0f) / Math::Pow(1.0f + eta, 2.0f); // Light vector and normal are assumed to be normalized. return Types::Clamp (r0 + (1.0f - r0) * Math::Pow(1 - Types::Abs(v.Dot(np)), 5.0f), 0.0f, 1.0f); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ float Raytracer::ShadowFeel(const Vector4 &s, const Vector4 &d, float l, int r) { float k_shadow = 1; if (configuration.trace_transparency) { if (!r) return k_shadow; // Get closest hit. Trace trace; scene_shadow_tree.RaytraceScene(trace, s, d, l); statistics.ray_count++; statistics.tri_test += trace.tri_test; if (trace.has_i && (trace.i_t > 0)) { // Check opacity. float opacity = SampleMaterialOpacity(trace); // Early exit on fully opaque hit. if (opacity == 1) return 0; k_shadow = 1 - opacity; // Recurse. Vector4 offset_pi = trace.s + trace.d * (trace.i_t + Units::Mm(1)); k_shadow *= ShadowFeel(offset_pi, trace.d, l - Vector4::Dist(trace.s, offset_pi), --r); } } else { // Any hit within range will do. Trace trace(false); scene_shadow_tree.RaytraceScene(trace, s, d, l); if (trace.has_i && (trace.i_t > 0)) return 0; // Occluded. } return k_shadow; } void Raytracer::ComputeRadiance(Trace &trace, Color &o, Bounce &bounce) { bool use_fixed_function = trace.m->shader.IsEmpty(); bool blend_additive = trace.m->blendop == Material::Blend_Add; // Evaluate material alpha. float alpha; if (use_fixed_function) alpha = SampleMaterialOpacity(trace); else alpha = SampleMaterialSink(trace, ShaderTree::SinkOpacity).x; alpha *= trace.o->opacity; // Compute direct lighting, if the material does not care about the alpha test, or if the material cares about it and its alpha is up to the threshold. if (!(trace.m->renderword & Material::Render_AlphaTest) || alpha > trace.m->athreshold) { // Evaluate material glossiness. float glossiness; if (use_fixed_function) glossiness = trace.m->glossiness; else glossiness = SampleMaterialSink(trace, ShaderTree::SinkGlossiness).x; // Evaluate light contribution. Color l_diff(0, 0, 0), l_spec(0, 0, 0); Vector4 offset_pi = trace.pi + trace.n * Units::Mm(1.f); for (uint n = 0; n < lgt.GetCount(); ++n) if (S3D::MLight *l = lgt[n].l) { Core::Light *light = (Core::Light *)l; float k_shadow = 1.f; if ((light->shadow != Core::Light::Shadow_None) && configuration.trace_shadow) { Vector4 d; switch (light->model) { default: case Core::Light::Model_Point: d = light->GetMatrix().GetRow(3) - offset_pi; break; case Core::Light::Model_Linear: d = light->GetMatrix().GetRow(2).Reversed() * light->clip_distance; break; } if (d.Dot(trace.n) > 0) { float l = d.Len(); d /= l; k_shadow = ShadowFeel(offset_pi, d, l, configuration.trace_shadow_transparency_max_recursion); if (!k_shadow) continue; } } // Compute contribution. float k_d, k_s; if (light->SampleEnergy(trace.pi, trace.n, &k_d, &k_s, &trace.d, glossiness)) { l_diff += light->diffuse_color * light->diffuse_intensity * k_d * k_shadow; l_spec += light->specular_color * light->specular_intensity * k_s * k_shadow; } } // Compute indirect lighting. Color l_indirect(0, 0, 0), ambient(0, 0, 0); if (configuration.trace_gi && bounce.indirect) { bounce.indirect--; Spread &mc = monte_carlo[Random::Rand(32)]; Matrix3 nm(Matrix3::FromOrthonormalBasis(trace.n)); Color l; // divide by the number of bounce, to avoid full bounce each time. int count_spread = mc.spread.GetCount(); if (configuration.indirect_gi_bounce - bounce.indirect != 0) count_spread /= configuration.indirect_gi_bounce - bounce.indirect + 1; count_spread = Types::Max(count_spread, 1); for (int n = 0; n < count_spread; ++n) { Bounce ibounce; ibounce.indirect = bounce.indirect; ibounce.reflection = 0; ibounce.refraction = 0; Raytrace(RayGrid(offset_pi, mc.spread[n] * nm), l, ibounce); l_indirect += l; } l_indirect /= (float)count_spread; } else ambient = (configuration.gi_use_ambient || !configuration.trace_gi) ? scene->ambient_color * scene->ambient_intensity : Vector4(0.f, 0.f, 0.f); // Compute ambient occlusion. float ambient_occlusion = 1.0f; if ((alpha >= 1.0f) && configuration.ao_activate) { Spread &mc = monte_carlo[Random::Rand(32)]; Matrix3 nm(Matrix3::FromOrthonormalBasis(trace.n)); float countouch = 0.0f; float lengthmax = configuration.ao_length; float divlengthmaxsq = 1.0f / lengthmax; for (uint n = 0; n < mc.spread.GetCount(); ++n) { // Create the direction vector from the normal of the point with a bit of random. Trace traceOcclusion; Vector4 start(trace.pi + mc.spread[n] * nm * Units::Mm(1.f)); /* nVector DirVect(mc.spread[n] * nm); scene_tree.RaytraceScene(traceOcclusion, start, DirVect, lengthmax); // check the raytrace pass if the alpha of the map and continue to raytrace then float alphaOcclusion = 0.0f; float current_length = 0.0f; while(alphaOcclusion < 1.0f && current_length < lengthmax && traceOcclusion.has_i && (traceOcclusion.i_t > 0.0f)) { current_length += traceOcclusion.i_t; // Compute intersection point and fetch material. traceOcclusion.pi = traceOcclusion.s + traceOcclusion.d * traceOcclusion.i_t; traceOcclusion.m = traceOcclusion.g->material_table[traceOcclusion.g->pol[traceOcclusion.ip].material]; bool use_fixed_functionOcclusion = trace.m->shader_tree == NULL ? true : false; // Evaluate material alpha. float TempAlphaOcclusion = 0.0f; if (use_fixed_functionOcclusion) TempAlphaOcclusion = SampleMaterialOpacity(traceOcclusion); else TempAlphaOcclusion = SampleMaterialSink(traceOcclusion, nShaderTree::SinkOpacity).x; alphaOcclusion += TempAlphaOcclusion*traceOcclusion.o->opacity; if(alphaOcclusion < 1.0f && current_length < lengthmax) scene_tree.RaytraceScene(traceOcclusion, traceOcclusion.pi + DirVect* Mm(1), DirVect, lengthmax - current_length); } if(alphaOcclusion > 1.0f) alphaOcclusion = 1.0f; if (alphaOcclusion > 0) countouch += (1.0f - Types::Clamp(current_length * divlengthmaxsq, 0.0f, 1.0f))* alphaOcclusion; */ scene_tree.RaytraceScene(traceOcclusion, start, mc.spread[n] * nm, lengthmax); if (traceOcclusion.has_i && (traceOcclusion.i_t > 0.0f)) countouch += 1.0f - Types::Clamp(traceOcclusion.i_t * divlengthmaxsq, 0.0f, 1.0f); } if (countouch > 0.0f) ambient_occlusion = 1.0f - countouch / mc.spread.GetCount(); ambient_occlusion = Types::Clamp(ambient_occlusion); } // Sample attributes. Color diffuse, specular, self; if (use_fixed_function) { // Gather attributes. diffuse = SampleMaterialAttribute(trace, Channel_Diffuse); specular = SampleMaterialAttribute(trace, Channel_Specular); self = SampleMaterialAttribute(trace, Channel_SelfIllum); // Vertex color. if (trace.m->GetChannelStage(Channel_Light)) { Color color = SampleMaterialAttribute(trace, Channel_Light); diffuse *= color; specular *= color; } else if (trace.m->renderword & Material::Render_VertexColor) { Color color = SampleGeometryAttribute(trace, GeometryVertexColor); diffuse *= color; specular *= color; } // Environment mapping. if (trace.m->GetChannelStage(Channel_Reflection)) { Color color = SampleMaterialAttribute(trace, Channel_Reflection); switch (trace.m->GetChannelStage(Channel_Reflection)->op) { case Material::Operator_Multiply: diffuse *= color; break; case Material::Operator_Default: case Material::Operator_Add: diffuse += color; break; } } } else { diffuse = SampleMaterialSink(trace, ShaderTree::SinkDiffuse); specular = SampleMaterialSink(trace, ShaderTree::SinkSpecular); self = SampleMaterialSink(trace, ShaderTree::SinkConstant); } // Final color. o = ((diffuse * (l_diff + l_indirect + ambient* ambient_occlusion)) + specular * l_spec + self) /** alpha*/; // Don't multiply the alpha, because there is real raytracing for the refraction after. } else { alpha = 0; o.Set(0, 0, 0); } // Apply fog. if (scene->fog_far > 0) { float kfog = Types::Clamp((trace.td - scene->fog_near) / (scene->fog_far - scene->fog_near)); o = o * (1.f - kfog) + scene->fog_color * kfog; } // Trace reflected and transmitted rays as required. float krefl = alpha; float eta = trace.m->irefraction; if (trace.ir == trace.m->irefraction) eta = 1.0f; if (((alpha < 1) || blend_additive) && bounce.refraction) { float n = trace.ir / eta; if (configuration.fresnel_activate) krefl = Fresnel(trace.d, trace.n.FaceForward(trace.d), n); float c1 = -trace.n.FaceForward(trace.d).Dot(trace.d); float w = n * Types::Abs(c1); float c2 = Math::Sqrt(1 + (w - n) * (w + n)); Vector4 rtransmit = (trace.d * n) + trace.n.FaceForward(trace.d) * (w - c2); rtransmit = rtransmit.Normalized(); Vector4 offset_pi = trace.pi + rtransmit * Units::Mm(1.f); if (c2 < 0) krefl = 1.0f; // Full reflection, we are inside the matter and by an angle where it is physically impossible (as Snell-Descartes law) to have refraction. if ((1.0f - krefl) > 0.0f) { bounce.refraction--; Color b; float save_ir = trace.ir; trace.ir = eta; Raytrace(RayGrid(offset_pi, rtransmit), b, bounce, &trace); trace.ir = save_ir; if (blend_additive) o += b; else o = o * krefl + b * (1 - krefl); bounce.refraction++; } } // Reflection. float material_reflection = SampleMaterialSink(trace, ShaderTree::SinkReflection).x; if (configuration.trace_reflection && material_reflection && bounce.reflection) { if (krefl > 0.0f) { bounce.reflection--; Vector4 nf = trace.n.FaceForward(trace.d).Normalized(); float c1 = -nf.Dot(trace.d); Vector4 rreflect = trace.d + (nf * 2.f * Types::Abs(c1)); Vector4 offset_pi = trace.pi + rreflect * Units::Mm(1.f) ; Color b; float save_ir = trace.ir; trace.ir = eta; Raytrace(RayGrid(offset_pi, rreflect), b, bounce, &trace); trace.ir = save_ir; o += b * (krefl * material_reflection); bounce.reflection++; } } // Note: Isn't doing this here getting rid of HDR informations? o = o.Clamped(Vector4(0, 0, 0), Vector4(1, 1, 1)); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Raytracer::PrimaryRay(const RayGrid &ray, Color &o) { Bounce bounce; bounce.indirect = configuration.indirect_gi_bounce; bounce.reflection = configuration.trace_reflection_max_recursion; bounce.refraction = configuration.trace_refraction_max_recursion; Raytrace(ray, o, bounce); } void Raytracer::Raytrace(const RayGrid &ray, Color &o, Bounce &bounce, Trace *previous_trace) { Trace trace; if (previous_trace) { trace.ir = previous_trace->ir; trace.td = previous_trace->td; } scene_tree.RaytraceScene(trace, ray.p[0], ray.d[0]); statistics.ray_count++; statistics.tri_test += trace.tri_test; // Shade result. if (trace.has_i) { // Compute intersection point. trace.pi = trace.s + trace.d * trace.i_t; // Compute intersection normal. Vector4 normal_sink = SampleMaterialSink(trace, ShaderTree::SinkNormal); trace.o->GetMatrix().ApplyRotation(&trace.n, &normal_sink); trace.n.Normalize(); if (trace.backface) trace.n = trace.n.Reversed(); // Integrate the newly traveled distance. trace.td += trace.i_t; // Gather radiance. ComputeRadiance(trace, o, bounce); } else o = scene->background_color; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Raytracer::Render(Picture &output, uint w, uint h) { if (!w || !h) return false; uint logical_h = h; viewport.Set((float)w, (float)h); if (configuration.interlaced) { if (h & 1) __ERR__(__LOG_E__ << "Interlaced frame height must be a multiple of 2.", false) if (configuration.interlaced_trace_half_frame) h /= 2; } Camera *camera = scene->current_camera; if (!camera) return false; // Create destination picture. output.AllocAs(w, h); // Allocate output hdr buffer. Array hdr(w * h); if (!hdr) __ERR__(__LOG_E__<< "Failed to allocate floating point frame buffer.\n", false) // Reset statistics. render_clock = scene->GetClock()->Getf(); statistics.Reset(); // Progress structure. Progress progress; progress.start_clock = Platform::Get().GetClock(); progress.instance = this; progress.progress = 0; progress.buffer = hdr; progress.w = 0; progress.h = 0; progress.done = false; // Create virtual screen. Benchmark bench(true); scene_tree.ResetStats(); Vector4 screen[4], wscreen[4]; float hw, hh, ar = ((camera->aspect_ratio == -1.f) ? 1.f : camera->aspect_ratio); if (camera->aspect_ratio_ref_yaxis) { hw = ((float)w / (float)logical_h) / ar; hh = 1; } else { hw = 1; hh = ((float)logical_h / ar) / (float)w; } screen[0].Set(-hw, hh, camera->zoom_factor); screen[1].Set(hw, hh, camera->zoom_factor); screen[2].Set(hw, -hh, camera->zoom_factor); screen[3].Set(-hw, -hh, camera->zoom_factor); camera->GetMatrix().Apply(wscreen, screen, 4); // Interpolate across world screen and trace. Vector4 dt_l, pt_l, dt_r, pt_r; dt_l = (wscreen[3] - wscreen[0]) / (float)logical_h; pt_l = wscreen[0]; dt_r = (wscreen[2] - wscreen[1]) / (float)logical_h; pt_r = wscreen[1]; // Interlace. if (configuration.interlaced && configuration.interlaced_trace_half_frame) { if (!interlace_even) { pt_l += dt_l; pt_r += dt_r; } dt_l *= 2.f; dt_r *= 2.f; } const Vector4 &s = camera->GetMatrix().GetRow(3); // Rendering. abort = false; progress.description = "Rendering (1/2)"; #define __JobTileSize 32 // Split rendering in tiles. AutoList job_list; ASync::JobGroup group; for (uint y = 0; y < h; y += __JobTileSize) for (uint x = 0; x < w; x += __JobTileSize) { RaytraceJob *job = new RaytraceJob; job_list.Add(job); job->core = this; job->start_height = y; job->end_height = y + __JobTileSize < h ? y + __JobTileSize : h; job->start_width = x; job->end_width = x + __JobTileSize < w ? x + __JobTileSize : w; job->s = s; job->dt_l = dt_l; job->pt_l = pt_l; job->dt_r = dt_r; job->pt_r = pt_r; job->hdr = hdr; job->pitch = w; Platform::Get().job_manager->EnqueueJob(job, &group); } while (!Platform::Get().job_manager->JoinGroup(&group, false)) if (hook) { // progress.progress = 1.f - (float)group.GetJobCount() / job_list.GetCount(); hook->RaytracerProgress(progress); } job_list.Clear(); /* // Split anti-aliasing in tiles. progress.description = "Anti-aliasing (2/2)"; for (uint y = 1; y < (h - 1); y += __JobTileSize) for (uint x = 1; x < (w - 1); x += __JobTileSize) { nAntialiasJob *job = new nAntialiasJob; job_list.Add(job); job->core = this; job->start_height = y; job->end_height = y + __JobTileSize < (h - 1) ? y + __JobTileSize : (h - 1); job->start_width = x; job->end_width = x + __JobTileSize < (w - 1) ? x + __JobTileSize : (w - 1); job->s = s; job->dt_l = dt_l; job->pt_l = pt_l; job->dt_r = dt_r; job->pt_r = pt_r; job->hdr = hdr; job->pitch = w; Platform::Get().job_manager->EnqueueJob(job, &group); } // Join anti-aliasing job group. while (!Platform::Get().job_manager->JoinGroup(&group, false)) if (hook) { // progress.progress = 1.f - (float)group.GetJobCount() / job_list.GetCount(); hook->RaytracerProgress(progress); } job_list.Clear(); */ bench.Stop(); __LOG__ << "Raytracing done. Took " << bench.GetMs() << " ms. Ray/s = " << (scene_tree.ray_count * 1000) / bench.GetMs() << "\n"; // HDR conversion to standard 32 bit RGBA. #pragma omp parallel { #pragma omp for schedule(dynamic) nowait for (uint v = 0; v < h; ++v) { uint *o_rgb = ((uint *)output.GetData()) + w * v; Color *o_hdr = hdr + w * v; for (uint u = 0; u < w; ++u) o_rgb[u] = ((uint)(Types::Clamp(o_hdr[u].w) * 255) << 24) + ((uint)(Types::Clamp(o_hdr[u].x) * 255) << 16) + ((uint)(Types::Clamp(o_hdr[u].y) * 255) << 8) + ((uint)(Types::Clamp(o_hdr[u].z) * 255)); } } // ... // Backup current frame if interlaced and wait for the next half-frame. if (configuration.interlaced) { if (interlace_half_frame.isValid()) { // If the frame is valid compose to output. if ((interlace_half_frame.GetWidth() != w) || (interlace_half_frame.GetHeight() != h)) __LOG_E__ << "Unexpected frame dimension change during interlaced sequence rendering.\n"; else { Picture half_frame(output); if (output.AllocAs(w, logical_h)) { // Select even and odd frames based on current parity. Picture *even = interlace_even ? &half_frame : &interlace_half_frame, *odd = interlace_even ? &interlace_half_frame : &half_frame; // Compose. uint *p_even = (uint *)even->GetData(), *p_odd = (uint *)odd->GetData(), *p_output = (uint *)output.GetData(); if (configuration.interlaced_trace_half_frame) for (uint v = 0; v < h; ++v) { Memory::Copy(p_output, p_even, w * 4); p_even += w; p_output += w; Memory::Copy(p_output, p_odd, w * 4); p_odd += w; p_output += w; } else { if (interlace_even) p_even += w; else p_odd += w; for (uint v = 0; v < h; ++v) { Memory::Copy(p_output, p_even, w * 4); p_even += w * 2; p_output += w; Memory::Copy(p_output, p_odd, w * 4); p_odd += w * 2; p_output += w; } } } } // Drop buffer, it has been committed to output. interlace_half_frame.Free(); } else { // Buffer the current output and drop it. No save is to be done yet. interlace_half_frame.Clone(output); output.Free(); } } // Done, switch interlace parity. interlace_even = !interlace_even; viewport.Set(1, 1); return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Raytracer::StartInterlacedSequence() { interlace_even = configuration.interlace_even; interlace_half_frame.Free(); } void Raytracer::Abort() { abort = true; } void Raytracer::SetConfiguration(const Configuration &config) { configuration = config; for (int n = 0; n < 32; ++n) monte_carlo[n].Initialize(configuration.gi_sample, configuration.gi_sample, Units::Deg(configuration.ao_angle)); // 64 evaluations per ray. } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Raytracer::SetScene(const GS::S3D::Scene *s) { if (!gf) __ERR__(__LOG_E__ << "No graphic resource factory to set raytracer scene.\n", false) Free(); // Grab scene and shadow scene. scene = s; if (!scene_tree.SetScene(*gf, s) || !scene_shadow_tree.SetScene(*gf, s, true)) return false; // Grab lights, reset caches. SharedList lights; s->GetItemListByType(lights); if (!lgt.Allocate(lights.GetCount())) __ERR__(__LOG_E__ << "Failed to allocate raytracer light array.\n", false) uint lgt_count = 0; ListForeachPtr(S3D::MLight *, l, lights) { lgt[lgt_count].l = l->isActive() ? l : NULL; lgt[lgt_count].g = NULL; lgt_count++; } return true; } void Raytracer::Free() { scene_tree.Free(); scene_shadow_tree.Free(); lgt.Free(); } //------------------------------------------------------------------------------ Raytracer::Raytracer(ResourceFactory *f) : gf(f) { SetConfiguration(configuration); viewport.Set(1, 1); hook = NULL; }