Desarrollé un rastreador de rayos que utiliza el modelo de iluminación phong / blinn phong estándar. Ahora lo estoy modificando para admitir renderizado basado físicamente, así que estoy implementando varios modelos BRDF. En este momento estoy enfocado en el modelo Oren-Nayar y Torrance-Sparrow. Cada uno de estos se basa en coordenadas esféricas utilizadas para expresar el incidente y la dirección saliente de la luz.
Mi pregunta es: ¿de qué manera es el correcto convertir wi y wo de coordenadas cartesianas a coordenadas esféricas?
Estoy aplicando la fórmula estándar que se informa aquí https://en.wikipedia.org/wiki/Spherical_coordinate_system#Coordinate_system_conversions, pero no estoy seguro de estar haciendo lo correcto, porque mi vector no tiene cola en el origen de la sistema de coordenadas cartesianas, pero se centran en el punto de intersección del rayo con el objeto.
Aquí puedes encontrar mi implementación actual:
https://github.com/chicio/Multispectral-Ray-tracing/tree/brdf/RayTracing/RayTracer/Objects/BRDF
https://github.com/chicio/Multispectral-Ray-tracing/blob/brdf/RayTracing/RayTracer/Math/Vector3D.cpp
¿Alguien puede ayudarme a dar una explicación de la forma correcta de convertir el vector wi y wo de coordenadas cartesianas a esféricas?
ACTUALIZAR
Copio aquí la parte relevante del código:
cálculo de coordenadas esféricas
float Vector3D::sphericalTheta() const {
float sphericalTheta = acosf(Utils::clamp(y, -1.f, 1.f));
return sphericalTheta;
}
float Vector3D::sphericalPhi() const {
float phi = atan2f(z, x);
return (phi < 0.f) ? phi + 2.f * M_PI : phi;
}
Oren Nayar
OrenNayar::OrenNayar(Spectrum<constant::spectrumSamples> reflectanceSpectrum, float degree) : reflectanceSpectrum{reflectanceSpectrum} {
float sigma = Utils::degreeToRadian(degree);
float sigmaPowerTwo = sigma * sigma;
A = 1.0f - (sigmaPowerTwo / 2.0f * (sigmaPowerTwo + 0.33f));
B = 0.45f * sigmaPowerTwo / (sigmaPowerTwo + 0.09f);
};
Spectrum<constant::spectrumSamples> OrenNayar::f(const Vector3D& wi, const Vector3D& wo, const Intersection* intersection) const {
float thetaI = wi.sphericalTheta();
float phiI = wi.sphericalPhi();
float thetaO = wo.sphericalTheta();
float phiO = wo.sphericalPhi();
float alpha = std::fmaxf(thetaI, thetaO);
float beta = std::fminf(thetaI, thetaO);
Spectrum<constant::spectrumSamples> orenNayar = reflectanceSpectrum * constant::inversePi * (A + B * std::fmaxf(0, cosf(phiI - phiO) * sinf(alpha) * tanf(beta)));
return orenNayar;
}
Torrance-Sparrow
float TorranceSparrow::G(const Vector3D& wi, const Vector3D& wo, const Vector3D& wh, const Intersection* intersection) const {
Vector3D normal = intersection->normal;
normal.normalize();
float normalDotWh = fabsf(normal.dot(wh));
float normalDotWo = fabsf(normal.dot(wo));
float normalDotWi = fabsf(normal.dot(wi));
float woDotWh = fabsf(wo.dot(wh));
float G = fminf(1.0f, std::fminf((2.0f * normalDotWh * normalDotWo)/woDotWh, (2.0f * normalDotWh * normalDotWi)/woDotWh));
return G;
}
float TorranceSparrow::D(const Vector3D& wh, const Intersection* intersection) const {
Vector3D normal = intersection->normal;
normal.normalize();
float cosThetaH = fabsf(wh.dot(normal));
float Dd = (exponent + 2) * constant::inverseTwoPi * powf(cosThetaH, exponent);
return Dd;
}
Spectrum<constant::spectrumSamples> TorranceSparrow::f(const Vector3D& wi, const Vector3D& wo, const Intersection* intersection) const {
Vector3D normal = intersection->normal;
normal.normalize();
float thetaI = wi.sphericalTheta();
float thetaO = wo.sphericalTheta();
float cosThetaO = fabsf(cosf(thetaO));
float cosThetaI = fabsf(cosf(thetaI));
if(cosThetaI == 0 || cosThetaO == 0) {
return reflectanceSpectrum * 0.0f;
}
Vector3D wh = (wi + wo);
wh.normalize();
float cosThetaH = wi.dot(wh);
float F = Fresnel::dieletricFresnel(cosThetaH, refractiveIndex);
float g = G(wi, wo, wh, intersection);
float d = D(wh, intersection);
printf("f %f g %f d %f \n", F, g, d);
printf("result %f \n", ((d * g * F) / (4.0f * cosThetaI * cosThetaO)));
Spectrum<constant::spectrumSamples> torranceSparrow = reflectanceSpectrum * ((d * g * F) / (4.0f * cosThetaI * cosThetaO));
return torranceSparrow;
}
ACTUALIZACIÓN 2
Después de algunas búsquedas, encontré esta implementación de Oren-Nayar BRDF .
En la implementación anterior, theta para wi y wo se obtiene simplemente haciendo arccos (wo.dotProduct (Normal)) y arccos (wi.dotProduct (Normal)). Esto me parece razonable, ya que podemos usar la normalidad del punto de intersección como la dirección cenital de nuestro sistema de coordenadas esféricas y hacer el cálculo. El cálculo de gamma = cos (phi_wi - phi_wo) hace algún tipo de proyección de wi y wo en lo que llama "espacio tangente". Suponiendo que todo sea correcto en esta implementación, ¿puedo usar las fórmulas | Ver - Normal x (View.dotProduct (Normal)) | y | Light - Normal x (Light.dotProduct (Normal)) | para obtener la coordenada phi (en lugar de usar arctan ("algo"))?