I am working on an OpenGL ray-tracer, which is capable of loading obj files and ray-trace it. My application loads the obj file with assimp and then sends all of the triangle faces (with the primitive coordinates and te material coefficients as well) to the fragment shader by using shader storage objects. The basic structure is about to render the results to a quad from the fragment shader.
I have trouble with the ray-tracing part in the fragment shader, but first let's introduce it.
For diffuse light, Lambert's cosine law, for specular light Phong-Blinn model was used. In case of total reflection a weight
variable is used to make the reflected light has an effect on other objects as well. The weight is calculated with approximating the Fresnel equation by Schlick method. In the image below, you can see, that the plane works like a mirror reflecting the image of the cube above.
I would like to make the cube appear as a glass object (like a glass sphere), which has refracting and reflecting effects as well. Or at least refracts the light. In the image above you can see a refracting effect on cube, but it is not as good, as it should be. I searched examples how to implement it, but until now, I recognized fresnel euquation has to be used just like in the reflection part.
Here is fragment my shader:
vec3 Fresnel(vec3 F0, float cosTheta) {
return F0 + (vec3(1, 1, 1) - F0) * pow(1-cosTheta, 5);
}
float schlickApprox(float Ni, float cosTheta){
float F0=pow((1-Ni)/(1+Ni), 2);
return F0 + (1 - F0) * pow((1 - cosTheta), 5);
}
vec3 trace(Ray ray){
vec3 weight = vec3(1, 1, 1);
const float epsilon = 0.0001f;
vec3 outRadiance = vec3(0, 0, 0);
int maxdepth=5;
for (int i=0; i < maxdepth; i++){
Hit hit=traverseBvhTree(ray);
if (hit.t<0){ return weight * lights[0].La; }
vec4 textColor = texture(texture1, vec2(hit.u, hit.v));
Ray shadowRay;
shadowRay.orig = hit.orig + hit.normal * epsilon;
shadowRay.dir = normalize(lights[0].direction);
// Ambient Light
outRadiance+= materials[hit.mat].Ka.xyz * lights[0].La*textColor.xyz * weight;
// Diffuse light based on Lambert's cosine law
float cosTheta = dot(hit.normal, normalize(lights[0].direction));
if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
outRadiance +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * weight;
// Specular light based on Phong-Blinn model
vec3 halfway = normalize(-ray.dir + lights[0].direction);
float cosDelta = dot(hit.normal, halfway);
if (cosDelta > 0){
outRadiance +=weight * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
}
float fresnel=schlickApprox(materials[hit.mat].Ni, cosTheta);
// For refractive materials
if (materials[hit.mat].Ni < 3)
{
/*this is the under contruction part.*/
ray.orig = hit.orig - hit.normal*epsilon;
ray.dir = refract(ray.dir, hit.normal, materials[hit.mat].Ni);
}
// If the refraction index is more than 15, treat the material as mirror.
else if (materials[hit.mat].Ni >= 15) {
weight *= fresnel;
ray.orig=hit.orig+hit.normal*epsilon;
ray.dir=reflect(ray.dir, hit.normal);
}
}
return outRadiance;
}
Update 1
I updated the trace method in the shader. As far as I understand the physics of light, if there is a material, which reflects and refracts the light, I have to treat two cases according to this.
- In this case of reflection, I added a weight to the diffuse light calculation:
weight *= fresnel
- In case of refraction light, the weight is
weight*=1-fresnel
.
Moreover, I calculated the ray.orig
and ray.dir
connected to the cases and refraction calculation only happens when it is not the case of total internal reflection (fresnel
is smaller than 1).
The modified trace method:
vec3 trace(Ray ray){
vec3 weight = vec3(1, 1, 1);
const float epsilon = 0.0001f;
vec3 outRadiance = vec3(0, 0, 0);
int maxdepth=3;
for (int i=0; i < maxdepth; i++){
Hit hit=traverseBvhTree(ray);
if (hit.t<0){ return weight * lights[0].La; }
vec4 textColor = texture(texture1, vec2(hit.u, hit.v));
Ray shadowRay;
shadowRay.orig = hit.orig + hit.normal * epsilon;
shadowRay.dir = normalize(lights[0].direction);
// Ambient Light
outRadiance+= materials[hit.mat].Ka.xyz * lights[0].La*textColor.xyz * weight;
// Diffuse light based on Lambert's cosine law
float cosTheta = dot(hit.normal, normalize(lights[0].direction));
if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
outRadiance +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * weight;
// Specular light based on Phong-Blinn model
vec3 halfway = normalize(-ray.dir + lights[0].direction);
float cosDelta = dot(hit.normal, halfway);
if (cosDelta > 0){
outRadiance +=weight * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
}
float fresnel=schlickApprox(materials[hit.mat].Ni, cosTheta);
// For refractive/reflective materials
if (materials[hit.mat].Ni < 7)
{
bool outside = dot(ray.dir, hit.normal) < 0;
// compute refraction if it is not a case of total internal reflection
if (fresnel < 1) {
ray.orig = outside ? hit.orig-hit.normal*epsilon : hit.orig+hit.normal*epsilon;
ray.dir = refract(ray.dir, hit.normal,materials[hit.mat].Ni);
weight *= 1-fresnel;
continue;
}
// compute reflection
ray.orig= outside ? hit.orig+hit.normal*epsilon : hit.orig-hit.normal*epsilon;
ray.dir= reflect(ray.dir, hit.normal);
weight *= fresnel;
continue;
}
// If the refraction index is more than 15, treat the material as mirror: total reflection
else if (materials[hit.mat].Ni >= 7) {
weight *= fresnel;
ray.orig=hit.orig+hit.normal*epsilon;
ray.dir=reflect(ray.dir, hit.normal);
}
}
return outRadiance;
}
Here is a snapshot connected to the update. Slightly better, I guess.
Update 2:
I found an iterative algorithm which is using a stack to visualize refractive and reflected rays in opengl: it is on page 68.
I modified my frag shader according to this. Almost fine, except for the back faces, which are completely black. Some pictures attached.
Here is the trace method of my frag shader:
vec3 trace(Ray ray){
vec3 color;
float epsilon=0.001;
Stack stack[8];// max depth
int stackSize = 0;// current depth
int bounceCount = 0;
vec3 coeff = vec3(1, 1, 1);
bool continueLoop = true;
while (continueLoop){
Hit hit = traverseBvhTree(ray);
if (hit.t>0){
bounceCount++;
//----------------------------------------------------------------------------------------------------------------
Ray shadowRay;
shadowRay.orig = hit.orig + hit.normal * epsilon;
shadowRay.dir = normalize(lights[0].direction);
color+= materials[hit.mat].Ka.xyz * lights[0].La * coeff;
// Diffuse light
float cosTheta = dot(hit.normal, normalize(lights[0].direction));// Lambert-féle cosinus törvény alapján.
if (cosTheta>0 && traverseBvhTree(shadowRay).t<0) {
color +=lights[0].La * materials[hit.mat].Kd.xyz * cosTheta * coeff;
vec3 halfway = normalize(-ray.dir + lights[0].direction);
float cosDelta = dot(hit.normal, halfway);
// Specular light
if (cosDelta > 0){
color +=coeff * lights[0].Le * materials[hit.mat].Ks.xyz * pow(cosDelta, materials[hit.mat].shininess); }
}
//---------------------------------------------------------------------------------------------------------------
if (materials[hit.mat].indicator > 3.0 && bounceCount <=2){
float eta = 1.0/materials[hit.mat].Ni;
Ray refractedRay;
refractedRay.dir = dot(ray.dir, hit.normal) <= 0.0 ? refract(ray.dir, hit.normal, eta) : refract(ray.dir, -hit.normal, 1.0/eta);
bool totalInternalReflection = length(refractedRay.dir) < epsilon;
if(!totalInternalReflection){
refractedRay.orig = hit.orig + hit.normal*epsilon*sign(dot(ray.dir, hit.normal));
refractedRay.dir = normalize(refractedRay.dir);
stack[stackSize].coeff = coeff *(1 - schlickApprox(materials[hit.mat].Ni, dot(ray.dir, hit.normal)));
stack[stackSize].depth = bounceCount;
stack[stackSize++].ray = refractedRay;
}
else{
ray.dir = reflect(ray.dir, -hit.normal);
ray.orig = hit.orig - hit.normal*epsilon;
}
}
else if (materials[hit.mat].indicator == 0){
coeff *= schlickApprox(materials[hit.mat].Ni, dot(-ray.dir, hit.normal));
ray.orig=hit.orig+hit.normal*epsilon;
ray.dir=reflect(ray.dir, hit.normal);
}
else { //Diffuse Material
continueLoop=false;
}
}
else {
color+= coeff * lights[0].La;
continueLoop=false;
}
if (!continueLoop && stackSize > 0){
ray = stack[stackSize--].ray;
bounceCount = stack[stackSize].depth;
coeff = stack[stackSize].coeff;
continueLoop = true;
}
}
return color;
}