boolean transmissionDirection(Ray ray, HitRecord hit, Ray transmission) { double n = transmission.r.prevR.n; double nt = transmission.r.n; Double3D N = hit.normal; Double3D D = ray.dir; double cosine = -D.dot(N); if (n > nt) { // We're inside, so reverse the normal N = N.sMult(-1); cosine = -D.dot(N); } double nRatio = n / nt; double cosinePSq = 1.0 - nRatio * nRatio * (1.0f - cosine * cosine); // check for total internal refraction here if (cosinePSq < 0.0f) return false; // total internal refraction else { // D - N(N.D) // Double3D pOne = D.minus( N.sMult(N.dot(D)) ).sMult(nRatio); double inside = nRatio * cosine - Math.sqrt(cosinePSq); Double3D temp = D.sMult(nRatio).plus(N.sMult(inside)).getUnit(); transmission.dir.x = temp.x; transmission.dir.y = temp.y; transmission.dir.z = temp.z; } return true; }
@Override public void run() { // Always tracing from the origin here. Double3D origin = new Double3D(0.0, 0.0, 0.0); black.ka = new DoubleColor(0, 0, 0, 1); black.kd = new DoubleColor(0, 0, 0, 1); black.ks = new DoubleColor(0, 0, 0, 1); black.shiny = 0; black.refractiveIndex = 1; black.reflectivity = new DoubleColor(0, 0, 0, 1); black.refractivity = new DoubleColor(0, 0, 0, 1); white.ka = new DoubleColor(1, 1, 1, 1); white.kd = new DoubleColor(0, 0, 0, 1); white.ks = new DoubleColor(0, 0, 0, 1); white.shiny = 0; white.refractiveIndex = 1; white.reflectivity = new DoubleColor(0, 0, 0, 1); white.refractivity = new DoubleColor(0, 0, 0, 1); // Work though viewport (pixel) coordinates // Start in the middle of pixel 0 double worldY = scene.camera.windowBottom + (yMin + startLine) * heightRatio; for (curY = (int) yMin + startLine; curY < yMax; curY = curY + THREADS) { worldY += THREADS * heightRatio; // Start in the middle of pixel 0 double worldX = scene.camera.windowLeft + (xMin) * widthRatio; for (curX = (int) xMin; curX < xMax; curX++) { worldX += widthRatio; Double3D dir = new Double3D(worldX, worldY, -scene.camera.near); Ray ray = new Ray(origin, dir.getUnit()); viewPort[curX][curY] = trace(ray); // We haven't sampled this pixel yet. if (!scene.antiAliasing) sampled = false; } // for y } // for x System.out.println("Thread " + startLine + " finished."); }
// iPoint is the point of intersection with the surface. DoubleColor shade(Ray ray, HitRecord hit, MaterialCell material, boolean background) { DoubleColor color = new DoubleColor(0.0, 0.0, 0.0, 1.0); // Add ambient light only once color.plus( new DoubleColor( (double) (lights[0].ambient[0] * material.ka.r), (double) (lights[0].ambient[1] * material.ka.g), (double) (lights[0].ambient[2] * material.ka.b), (double) (lights[0].ambient[3] * material.ka.a))); // Assign material color? // Local light or directional? If directional then we need to see if it's shining on the // object if (!background) { double d = 2; // L.distanceTo(hit.hitP); for (int i = 0; i < lights.length; i++) { if (lights[i].lightSwitch == 1) { Double3D L = new Double3D( (double) lights[i].position[0], (double) lights[i].position[1], (double) lights[i].position[2]); L = L.minus(hit.hitP).getUnit(); Ray shadowRay = new Ray(hit.hitP, L); // trace shadow ray to light source // Turn shadows on and shadowRay hit nothing if (!scene.shadows || shadowTrace(shadowRay)) { double LdN = Math.max(0, hit.normal.dot(L)); if (LdN > 0) { // -2(-L.N)N + -L Double3D R = hit.normal.sMult(-2 * hit.normal.dot(L.sMult(-1))).plus(L.sMult(-1)); double RdV = Math.max(0, -R.dot(ray.dir)); // If the light is free add the diffuse light // Intensity (Kd * (LdN) + Ks *(RdV)^(shiny)/(r + k) color.plus( new DoubleColor( (double) (lights[i].diffuse[0] * LdN + lights[i].specular[0] * Math.pow(RdV, material.shiny)) / d, (double) (lights[i].diffuse[1] * LdN + lights[i].specular[1] * Math.pow(RdV, material.shiny)) / d, (double) (lights[i].diffuse[2] * LdN + lights[i].specular[2] * Math.pow(RdV, material.shiny)) / d, 1.0)); // */ } // if(LdN > 0) } // if(!scene.shadows || shadowTrace(shadowRay)) } // if(lights[i].lightSwitch == 1){ } // for // Shiny Phong // If IdN > 0 then we find a reflection // If IdN < 0 then we need -normal if (scene.reflections && (material.reflectivity.r > 0 || material.reflectivity.g > 0 || material.reflectivity.b > 0)) { depth++; // R = I - 2 * (I.N)N Double3D R = new Double3D(); Double3D I = ray.dir; // .sMult(-1.0); Double3D N = hit.normal; // double IdN = I.dot(N); // if (IdN > 0){ // N = N.sMult(-1.0); // IdN = -I.dot(N); // }//*/ R = I.plus(N.sMult(-2.0 * I.dot(N))); Ray reflect = new Ray(hit.hitP, R); DoubleColor reflection = trace(reflect); // Scale by distance? // reflection.scale( 1 / reflect.origin().distanceTo(hit.hitP)); reflection.r = reflection.r * material.reflectivity.r; reflection.g = reflection.g * material.reflectivity.g; reflection.b = reflection.b * material.reflectivity.b; color.plus(reflection); depth--; } if (scene.refractions && (material.refractivity.r > 0 || material.refractivity.g > 0 || material.refractivity.b > 0)) // */ { depth++; Ray refract = new Ray(hit.hitP, ray.dir); if (hit.index == ray.r.objectNum) // Hit the object we're already in { // Pop the n off the stack refract.r = ray.r; // Swap the refraction indices double temp = refract.r.n; refract.r.n = refract.r.prevR.n; refract.r.prevR.n = temp; } else // Otherwise we hit a new object push this n onto the stack and get mat index { refract.r.prevR = ray.r; refract.r.n = material.refractiveIndex; refract.r.objectNum = hit.index; } if (transmissionDirection(ray, hit, refract)) { DoubleColor refraction = trace(refract); refraction.r = refraction.r * material.refractivity.r; refraction.g = refraction.g * material.refractivity.g; refraction.b = refraction.b * material.refractivity.b; // Scale for distance? color.plus(refraction); } depth--; } } return color; }
// All rays we deal with here are in world coordinates. // Should take the refractive index of the material it is currently in. DoubleColor trace(Ray ray) { DoubleColor color = new DoubleColor(0.0, 0.0, 0.0, 1.0); HitRecord hit = new HitRecord(); if (depth > Math.max(DEBUG_recursion, scene.maxRecursiveDepth)) return color; double tMin = 0.0001; double tMax = 10000000; // Spheres only for now for (int i = 0; i < numObjects; i++) // Did I hit the bounding sphere for an object? if (spheres[i].hit(ray, tMin, tMax, 0, hit)) if (scene.spheresOnly) { for (PMesh.SurfCell s = shapes[i].surfHead; s != null; s = s.next) for (PMesh.PolyCell poly = s.polyHead; poly != null; poly = poly.next) // Triangles only for now if (poly.numVerts == 3) { Double3D v[] = new Double3D[3]; int j = 0; for (PMesh.VertListCell vert = poly.vert; vert != null; vert = vert.next) v[j++] = shapes[i].vertArray.get(vert.vert).viewPos; // Increment j in the line post access // Check for a hit on this polygon if (Triangle.hit(v[0], v[1], v[2], ray, tMin, tMax, 0, hit)) { tMax = hit.t; hit.normal = poly.viewNorm; hit.matIndex = s.material; hit.index = i; } } else System.out.println( "Need to intersect polygon with " + poly.numVerts + " vertices."); } else { tMax = hit.t; hit.matIndex = i; // May cause an error if object 10 and it only has 3 materials. hit.index = i; } if (hit.index >= 0) // If it intersects then multi-sample { if (!sampled && depth == 0) { // Only sample once per ray from the main loop sampled = true; Double3D dir = ray.dir; DoubleColor antiAlias = trace(ray); for (int i = 0; i < samples; i++) { // Double3D sample = new Double3D(dir.x + imageSamples[i].x, dir.y + imageSamples[i].y, // dir.z).getUnit(); // ray.dir = sample; ray.dir.x = dir.x + imageSamples[i].x; ray.dir.y = dir.y + imageSamples[i].y; antiAlias.plus(trace(ray)); } antiAlias.scale(1.0 / (samples + 1.0)); color.plus(antiAlias); } else if (hit.matIndex < shapes[hit.index].materials.length) color = shade(ray, hit, shapes[hit.index].materials[hit.matIndex], false); else color = shade( ray, hit, shapes[hit.index].materials[shapes[hit.index].materials.length - 1], false); } else // We hit nothing check for intersection with the far clip plane for checker board // pattern. if (scene.checkerBackground) color = shade(ray, hit, checkerBackgroundHit(ray, hit), true); return color; }