c++ - Refracción en Raytracing?
graphics 3d (1)
He estado trabajando en mi raytracer otra vez. Agregué soporte de reflexión y multihilo. Actualmente estoy trabajando para agregar refracciones, pero solo funciona la mitad.
Como puede ver, hay una esfera central (sin resalte especular), una esfera reflectante (a la derecha) y una esfera refractiva (izquierda). Estoy bastante feliz con las reflexiones, se ve muy bien. Para las refracciones su tipo de trabajo ... la luz se refracta y todas las sombras de las esferas son visibles en la esfera (índice de refracción 1.4), pero hay un anillo negro exterior.
EDITAR: Aparentemente el anillo negro se hace más grande, y por lo tanto la esfera más pequeña, cuando aumente el índice de refracción de la esfera. Por el contrario, al disminuir el índice de refracción, la esfera se hace más grande y el anillo negro más pequeño ... hasta que, con el índice de refracción establecido en uno, el anillo desaparece por completo. IOR = 1.9 IOR = 1.1 IOR = 1.00001 Y, curiosamente, en IOR = 1, la esfera pierde su transparencia y se vuelve blanca.
Creo que cubrí la reflexión interna total y no es el problema aquí.
Ahora el código: estoy usando el operator |
para el producto de puntos, por lo que (vec|vec)
es un producto de puntos y el operator ~
invierte los vectores. Los objetos, tanto las luces como las esferas se almacenan en Object **objects;
. Función de raytrace
Colour raytrace(const Ray &r, const int &depth)
{
//first find the nearest intersection of a ray with an object
Colour finalColour = skyBlue *(r.getDirection()|Vector(0,0,-1)) * SKY_FACTOR;
double t, t_min = INFINITY;
int index_nearObj = -1;
for(int i = 0; i < objSize; i++)
{
if(!dynamic_cast<Light *>(objects[i]))//skip light src
{
t = objects[i]->findParam(r);
if(t > 0 && t < t_min)
{
t_min = t;
index_nearObj = i;
}
}
}
//no intersection
if(index_nearObj < 0)
return finalColour;
Vector intersect = r.getOrigin() + r.getDirection()*t_min;
Vector normal = objects[index_nearObj]->NormalAtIntersect(intersect);
Colour objectColor = objects[index_nearObj]->getColor();
Ray rRefl, rRefr; //reflected and refracted Ray
Colour refl = finalColour, refr = finalColour; //reflected and refracted colours
double reflectance = 0, transmittance = 0;
if(objects[index_nearObj]->isReflective() && depth < MAX_TRACE_DEPTH)
{
//handle reflection
rRefl = objects[index_nearObj]->calcReflectingRay(r, intersect, normal);
refl = raytrace(rRefl, depth + 1);
reflectance = 1;
}
if(objects[index_nearObj]->isRefractive() && depth < MAX_TRACE_DEPTH)
{
//handle transmission
rRefr = objects[index_nearObj]->calcRefractingRay(r, intersect, normal, reflectance, transmittance);
refr = raytrace(rRefr, depth + 1);
}
Ray rShadow; //shadow ray
bool shadowed;
double t_light = -1;
Colour localColour;
Vector tmpv;
//get material properties
double ka = 0.2; //ambient coefficient
double kd; //diffuse coefficient
double ks; //specular coefficient
Colour ambient = ka * objectColor; //ambient component
Colour diffuse, specular;
double brightness;
localColour = ambient;
//look if the object is in shadow or light
//do this by casting a ray from the obj and
// check if there is an intersection with another obj
for(int i = 0; i < objSize; i++)
{
if(dynamic_cast<Light *>(objects[i])) //if object is a light
{
//for each light
shadowed = false;
//create Ray to light
tmpv = objects[i]->getPosition() - intersect;
rShadow = Ray(intersect + (!tmpv) * BIAS, tmpv);
t_light = objects[i]->findParam(rShadow);
if(t_light < 0) //no imtersect, which is quite impossible
continue;
//then we check if that Ray intersects one object that is not a light
for(int j = 0; j < objSize; j++)
{
if(!dynamic_cast<Light *>(objects[j]) && j != index_nearObj)//if obj is not a light
{
t = objects[j]->findParam(rShadow);
//if it is smaller we know the light is behind the object
//--> shadowed by this light
if (t >= 0 && t < t_light)
{
// Set the flag and stop the cycle
shadowed = true;
break;
}
}
}
if(!shadowed)
{
rRefl = objects[index_nearObj]->calcReflectingRay(rShadow, intersect, normal);
//reflected ray from ligh src, for ks
kd = maximum(0.0, (normal|rShadow.getDirection()));
if(objects[index_nearObj]->getShiny() <= 0)
ks = 0;
else
ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getShiny());
diffuse = kd * objectColor;// * objects[i]->getColour();
specular = ks * objects[i]->getColor();
brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT);
localColour += brightness * (diffuse + specular);
}
}
}
finalColour = localColour + (transmittance * refr + reflectance * refl);
return finalColour;
}
Ahora, la función que calcula el rayo refractado, usé varios sitios diferentes para el recurso, y cada uno tenía algoritmos similares. Esto es lo mejor que pude hacer hasta ahora. Puede ser solo un pequeño detalle que no estoy viendo ...
Ray Sphere::calcRefractingRay(const Ray &r, const Vector &intersection,Vector &normal, double & refl, double &trans)const
{
double n1, n2, n;
double cosI = (r.getDirection()|normal);
if(cosI > 0.0)
{
n1 = 1.0;
n2 = getRefrIndex();
normal = ~normal;//invert
}
else
{
n1 = getRefrIndex();
n2 = 1.0;
cosI = -cosI;
}
n = n1/n2;
double sinT2 = n*n * (1.0 - cosI * cosI);
double cosT = sqrt(1.0 - sinT2);
//fresnel equations
double rn = (n1 * cosI - n2 * cosT)/(n1 * cosI + n2 * cosT);
double rt = (n2 * cosI - n1 * cosT)/(n2 * cosI + n2 * cosT);
rn *= rn;
rt *= rt;
refl = (rn + rt)*0.5;
trans = 1.0 - refl;
if(n == 1.0)
return r;
if(cosT*cosT < 0.0)//tot inner refl
{
refl = 1;
trans = 0;
return calcReflectingRay(r, intersection, normal);
}
Vector dir = n * r.getDirection() + (n * cosI - cosT)*normal;
return Ray(intersection + dir * BIAS, dir);
}
EDITAR: También he cambiado el índice de refracción alrededor. Desde
if(cosI > 0.0)
{
n1 = 1.0;
n2 = getRefrIndex();
normal = ~normal;
}
else
{
n1 = getRefrIndex();
n2 = 1.0;
cosI = -cosI;
}
a
if(cosI > 0.0)
{
n1 = getRefrIndex();
n2 = 1.0;
normal = ~normal;
}
else
{
n1 = 1.0;
n2 = getRefrIndex();
cosI = -cosI;
}
¡Entonces obtengo esto, y casi lo mismo (todavía al revés) con un índice de refracción en 1! Y el cálculo de la reflexión:
Ray Sphere::calcReflectingRay(const Ray &r, const Vector &intersection, const Vector &normal)const
{
Vector rdir = r.getDirection();
Vector dir = rdir - 2 * (rdir|normal) * normal;
return Ray(intersection + dir*BIAS, dir);
//the Ray constructor automatically normalizes directions
}
Así que mi pregunta es: ¿Cómo arreglo el círculo negro exterior? ¿Qué versión es correcta?
La ayuda es muy apreciada :)
Esto se compila en Linux usando g ++ 4.8.2.
Advertencia: lo siguiente es una conjetura, no una certeza. Tendría que mirar el código con más detalle para estar seguro de lo que está sucediendo y por qué.
Dicho esto, me parece que su código original es básicamente simular una lente cóncava en lugar de convexa.
Una lente convexa es básicamente una lente de aumento, que enfoca los rayos de luz de un área relativamente pequeña en un plano:
Esto también muestra por qué el código corregido muestra una imagen al revés. Los rayos de luz que vienen de la parte superior en un lado se proyectan hacia la parte inferior en el otro (y viceversa).
Sin embargo, volviendo a la lente cóncava: una lente cóncava es una lente reductora que muestra un gran ángulo de imagen desde el frente de la lente:
Si mira la esquina inferior derecha aquí, muestra lo que sospecho que es el problema: especialmente con un alto índice de refracción, los rayos de luz que intentan entrar en la lente se intersecan con el borde de la lente. Para todos los ángulos más anchos que eso, normalmente verás un anillo negro, porque el borde frontal de la lente actúa como un tono para evitar que la luz entre.
Aumentar el índice de refracción aumenta el ancho de ese anillo negro, porque la luz se curva más, por lo que una porción más grande en los bordes se interseca con el borde exterior de la lente.
En caso de que te importe cómo evitan esto con cosas como las lentes de cámara de gran angular, la ruta habitual es usar una lente de menisco, al menos para el elemento frontal:
Esto no es una panacea, pero al menos evita que los rayos de luz entrantes se crucen con el borde exterior del elemento de lente frontal. Dependiendo de la amplitud del ángulo que debe cubrir el lente, a menudo será un menisco un poco menos radical que este (y en algunos casos será un plano-cóncavo), pero se entiende la idea general.
Advertencia final: por supuesto, todos estos elementos están dibujados a mano, y están destinados únicamente a dar una idea general, no (por ejemplo) reflejan el diseño de ninguna lente en particular, un elemento con un índice de refracción en particular, etc.