tylermorganwall / rayrender

A pathtracer for R. Build and render complex scenes and 3D data visualizations directly from R
http://www.rayrender.net
622 stars 43 forks source link

Checker patterns don't render properly on x-z plane when y = 0 #12

Closed brodieG closed 4 years ago

brodieG commented 4 years ago

Sorry, hopefully I'm not making you regret releasing this package. Here is another one:

library(rayrender)

# OK, not exactly on x-z plane with y==0

render_scene(
  xz_rect(
    y=-.0001,
    material=diffuse(color='white', checkercolor='black', checkerperiod=.25)
  ),
  width=100, height=100, samples=20,
  lookfrom=c(0, 1, 1)
)

# OK, not exactly on x-z plane with y==0

render_scene(
  xz_rect(
    angle=c(1, 0, 0),
    material=diffuse(color='white', checkercolor='black', checkerperiod=.25)
  ),
  width=100, height=100, samples=20,
  lookfrom=c(0, 1, 1)
)

# OK, no checker

render_scene(
  xz_rect(
    material=diffuse(color='white')
  ),
  width=100, height=100, samples=20,
  lookfrom=c(0, 1, 1)
)

# NOT OK, checker on x-z plane at y==0

render_scene(
  xz_rect(
    material=diffuse(color='white', checkercolor='black', checkerperiod=.25)
  ),
  width=100, height=100, samples=20,
  lookfrom=c(0, 1, 1)
)

Created on 2019-11-16 by the reprex package (v0.3.0)

tylermorganwall commented 4 years ago

This is due to the way procedural texturing works for checker patterns: rayrender takes the sine of the absolute world coordinate of the intersection point and colors the surface one color or the other, depending on the sign. Planes that exactly match up with the zero nodes are ill-defined.

Float sines  = sin(invperiod*p.x()*M_PI) * sin(invperiod*p.y()*M_PI) * sin(invperiod*p.z()*M_PI);
if(sines < 0) {
   return(odd->value(u,v,p));
} else {
   return(even->value(u,v,p));
}

A potential solution involves checking for these zeros and ignoring them.

Float sinx  = sin(invperiod*p.x()*M_PI);
sinx = sinx == 0 ? 1 : sinx;
Float siny  = sin(invperiod*p.y()*M_PI);
siny = siny == 0 ? 1 : siny;
Float sinz  = sin(invperiod*p.z()*M_PI);
sinz = sinz == 0 ? 1 : sinz;
if(sinx * siny * sinz < 0) {
   return(odd->value(u,v,p));
} else {
   return(even->value(u,v,p));
}

This solution originally didn't work well due to the non-robust comparison to zero, but I just fixed by re-projecting the hit point to the plane after calculating the intersection.

bool yz_rect::hit(const ray& r, Float t_min, Float t_max, hit_record& rec, random_gen& rng) {
  Float t = (k-r.origin().x()) / r.direction().x();
  if(t < t_min || t > t_max) {
    return(false);
  }
  Float z = r.origin().z() + t*r.direction().z();
  Float y = r.origin().y() + t*r.direction().y();
  if(z < z0 || z > z1 || y < y0 || y > y1) {
    return(false);
  }
  rec.u = (y-y0)/(y1-y0);
  rec.v = (z-z0)/(z1-z0);
  rec.t = t;
  rec.mat_ptr = mp;
  rec.p = r.point_at_parameter(t);
  rec.p.e[0] = k; //This is new
  rec.normal = vec3(1,0,0); 
  return(true);
}

I'm going to test this solution to see how robust it is, but this might be workable..

tylermorganwall commented 4 years ago

Fixed in cf78fba108ceb095b71a52ea5f26546faa373fac