Renderer(int line) { startLine = line; if (!scene.antiAliasing) { sampled = false; samples = Math.max(scene.raysPerPixel, DEBUG_samples); // Force it to be a perfect square samples = (int) Math.sqrt(samples); samples = samples * samples; imageSamples = new Double3D[samples]; for (int i = 0; i < samples; i++) imageSamples[i] = new Double3D(0, 0, 0); // Make samples are in the range [0,1] Sample.multiJitter(imageSamples, samples); // Make samples are in the range [-2,2] Sample.cubicSplineFilter(imageSamples, samples); // Scale image samples to [-.5,.5] and adjust to worldCoords this should be +-.5 pixels // since we're in the middle of a pixel to begin with for (int i = 0; i < samples; i++) { imageSamples[i].x = widthRatio / 2 * (imageSamples[i].x / 4.0); imageSamples[i].y = heightRatio / 2 * (imageSamples[i].y / 4.0); // System.out.println("Jitter by: " + imageSamples[i] + " Pixel width: " + widthRatio + " // Pixel height: " + heightRatio ); } imageSamples[0].x++; imageSamples[0].x--; } }
Renderer() { xMin = scene.camera.viewportLeft + 0.5; xMax = scene.camera.viewportRight; yMin = scene.camera.viewportBottom + 0.5; yMax = scene.camera.viewportTop; if (scene.antiAliasing) { samples = Math.max(scene.raysPerPixel, DEBUG_samples); // Force it to be a perfect square samples = (int) Math.sqrt(samples); samples = samples * samples; imageSamples = new Double3D[samples]; for (int i = 0; i < samples; i++) imageSamples[i] = new Double3D(0, 0, 0); Sample.multiJitter(imageSamples, samples); // Samples are in the range [-2,2] Sample.cubicSplineFilter(imageSamples, samples); // Scale image samples to [-1,1] for (int i = 0; i < samples; i++) { imageSamples[i].x = widthRatio * imageSamples[i].x / 2.0; imageSamples[i].y = heightRatio * imageSamples[i].y / 2.0; } } }
// 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; }
private void updateSubImageImpl( TextureData data, int newTarget, int mipmapLevel, int dstx, int dsty, int srcx, int srcy, int width, int height) throws GLException { GL gl = GLU.getCurrentGL(); data.setHaveEXTABGR(gl.isExtensionAvailable("GL_EXT_abgr")); data.setHaveGL12(gl.isExtensionAvailable("GL_VERSION_1_2")); Buffer buffer = data.getBuffer(); if (buffer == null && data.getMipmapData() == null) { // Assume user just wanted to get the Texture object allocated return; } int rowlen = data.getRowLength(); int dataWidth = data.getWidth(); int dataHeight = data.getHeight(); if (data.getMipmapData() != null) { // Compute the width, height and row length at the specified mipmap level // Note we do not support specification of the row length for // mipmapped textures at this point for (int i = 0; i < mipmapLevel; i++) { width = Math.max(width / 2, 1); height = Math.max(height / 2, 1); dataWidth = Math.max(dataWidth / 2, 1); dataHeight = Math.max(dataHeight / 2, 1); } rowlen = 0; buffer = data.getMipmapData()[mipmapLevel]; } // Clip incoming rectangles to what is available both on this // texture and in the incoming TextureData if (srcx < 0) { width += srcx; srcx = 0; } if (srcy < 0) { height += srcy; srcy = 0; } // NOTE: not sure whether the following two are the correct thing to do if (dstx < 0) { width += dstx; dstx = 0; } if (dsty < 0) { height += dsty; dsty = 0; } if (srcx + width > dataWidth) { width = dataWidth - srcx; } if (srcy + height > dataHeight) { height = dataHeight - srcy; } if (dstx + width > texWidth) { width = texWidth - dstx; } if (dsty + height > texHeight) { height = texHeight - dsty; } checkCompressedTextureExtensions(data); if (data.isDataCompressed()) { gl.glCompressedTexSubImage2D( newTarget, mipmapLevel, dstx, dsty, width, height, data.getInternalFormat(), buffer.remaining(), buffer); } else { int[] align = new int[1]; int[] rowLength = new int[1]; int[] skipRows = new int[1]; int[] skipPixels = new int[1]; gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment gl.glGetIntegerv(GL.GL_UNPACK_ROW_LENGTH, rowLength, 0); // save row length gl.glGetIntegerv(GL.GL_UNPACK_SKIP_ROWS, skipRows, 0); // save skipped rows gl.glGetIntegerv(GL.GL_UNPACK_SKIP_PIXELS, skipPixels, 0); // save skipped pixels gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment()); if (DEBUG && VERBOSE) { System.out.println("Row length = " + rowlen); System.out.println("skip pixels = " + srcx); System.out.println("skip rows = " + srcy); System.out.println("dstx = " + dstx); System.out.println("dsty = " + dsty); System.out.println("width = " + width); System.out.println("height = " + height); } gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowlen); gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, srcy); gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, srcx); gl.glTexSubImage2D( newTarget, mipmapLevel, dstx, dsty, width, height, data.getPixelFormat(), data.getPixelType(), buffer); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowLength[0]); // restore row length gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, skipRows[0]); // restore skipped rows gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, skipPixels[0]); // restore skipped pixels } }
/** * Updates the content area of the specified target of this texture using the data in the given * image. In general this is intended for construction of cube maps. * * @throws GLException if no OpenGL context was current or if any OpenGL-related errors occurred */ public void updateImage(TextureData data, int target) throws GLException { GL gl = GLU.getCurrentGL(); imgWidth = data.getWidth(); imgHeight = data.getHeight(); aspectRatio = (float) imgWidth / (float) imgHeight; mustFlipVertically = data.getMustFlipVertically(); int texTarget = 0; int texParamTarget = this.target; // See whether we have automatic mipmap generation support boolean haveAutoMipmapGeneration = (gl.isExtensionAvailable("GL_VERSION_1_4") || gl.isExtensionAvailable("GL_SGIS_generate_mipmap")); // Indicate to the TextureData what functionality is available data.setHaveEXTABGR(gl.isExtensionAvailable("GL_EXT_abgr")); data.setHaveGL12(gl.isExtensionAvailable("GL_VERSION_1_2")); // Note that automatic mipmap generation doesn't work for // GL_ARB_texture_rectangle if ((!isPowerOfTwo(imgWidth) || !isPowerOfTwo(imgHeight)) && !haveNPOT(gl)) { haveAutoMipmapGeneration = false; } boolean expandingCompressedTexture = false; if (data.getMipmap() && !haveAutoMipmapGeneration) { // GLU always scales the texture's dimensions to be powers of // two. It also doesn't really matter exactly what the texture // width and height are because the texture coords are always // between 0.0 and 1.0. imgWidth = nextPowerOfTwo(imgWidth); imgHeight = nextPowerOfTwo(imgHeight); texWidth = imgWidth; texHeight = imgHeight; texTarget = GL.GL_TEXTURE_2D; } else if ((isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) || haveNPOT(gl)) { if (DEBUG) { if (isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) { System.err.println("Power-of-two texture"); } else { System.err.println("Using GL_ARB_texture_non_power_of_two"); } } texWidth = imgWidth; texHeight = imgHeight; texTarget = GL.GL_TEXTURE_2D; } else if (haveTexRect(gl) && !data.isDataCompressed()) { // GL_ARB_texture_rectangle does not work for compressed textures if (DEBUG) { System.err.println("Using GL_ARB_texture_rectangle"); } texWidth = imgWidth; texHeight = imgHeight; texTarget = GL.GL_TEXTURE_RECTANGLE_ARB; } else { // If we receive non-power-of-two compressed texture data and // don't have true hardware support for compressed textures, we // can fake this support by producing an empty "compressed" // texture image, using glCompressedTexImage2D with that to // allocate the texture, and glCompressedTexSubImage2D with the // incoming data. if (data.isDataCompressed()) { if (data.getMipmapData() != null) { // We don't currently support expanding of compressed, // mipmapped non-power-of-two textures to the nearest power // of two; the obvious port of the non-mipmapped code didn't // work throw new GLException( "Mipmapped non-power-of-two compressed textures only supported on OpenGL 2.0 hardware (GL_ARB_texture_non_power_of_two)"); } expandingCompressedTexture = true; } if (DEBUG) { System.err.println("Expanding texture to power-of-two dimensions"); } if (data.getBorder() != 0) { throw new RuntimeException( "Scaling up a non-power-of-two texture which has a border won't work"); } texWidth = nextPowerOfTwo(imgWidth); texHeight = nextPowerOfTwo(imgHeight); texTarget = GL.GL_TEXTURE_2D; } texParamTarget = texTarget; setImageSize(imgWidth, imgHeight, texTarget); if (target != 0) { // Allow user to override auto detection and skip bind step (for // cubemap construction) texTarget = target; if (this.target == 0) { throw new GLException("Override of target failed; no target specified yet"); } texParamTarget = this.target; gl.glBindTexture(texParamTarget, texID); } else { gl.glBindTexture(texTarget, texID); } if (data.getMipmap() && !haveAutoMipmapGeneration) { int[] align = new int[1]; gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment()); if (data.isDataCompressed()) { throw new GLException("May not request mipmap generation for compressed textures"); } try { GLU glu = new GLU(); glu.gluBuild2DMipmaps( texTarget, data.getInternalFormat(), data.getWidth(), data.getHeight(), data.getPixelFormat(), data.getPixelType(), data.getBuffer()); } finally { gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment } } else { checkCompressedTextureExtensions(data); Buffer[] mipmapData = data.getMipmapData(); if (mipmapData != null) { int width = texWidth; int height = texHeight; for (int i = 0; i < mipmapData.length; i++) { if (data.isDataCompressed()) { // Need to use glCompressedTexImage2D directly to allocate and fill this image // Avoid spurious memory allocation when possible gl.glCompressedTexImage2D( texTarget, i, data.getInternalFormat(), width, height, data.getBorder(), mipmapData[i].remaining(), mipmapData[i]); } else { // Allocate texture image at this level gl.glTexImage2D( texTarget, i, data.getInternalFormat(), width, height, data.getBorder(), data.getPixelFormat(), data.getPixelType(), null); updateSubImageImpl(data, texTarget, i, 0, 0, 0, 0, data.getWidth(), data.getHeight()); } width = Math.max(width / 2, 1); height = Math.max(height / 2, 1); } } else { if (data.isDataCompressed()) { if (!expandingCompressedTexture) { // Need to use glCompressedTexImage2D directly to allocate and fill this image // Avoid spurious memory allocation when possible gl.glCompressedTexImage2D( texTarget, 0, data.getInternalFormat(), texWidth, texHeight, data.getBorder(), data.getBuffer().capacity(), data.getBuffer()); } else { ByteBuffer buf = DDSImage.allocateBlankBuffer(texWidth, texHeight, data.getInternalFormat()); gl.glCompressedTexImage2D( texTarget, 0, data.getInternalFormat(), texWidth, texHeight, data.getBorder(), buf.capacity(), buf); updateSubImageImpl(data, texTarget, 0, 0, 0, 0, 0, data.getWidth(), data.getHeight()); } } else { if (data.getMipmap() && haveAutoMipmapGeneration) { // For now, only use hardware mipmapping for uncompressed 2D // textures where the user hasn't explicitly specified // mipmap data; don't know about interactions between // GL_GENERATE_MIPMAP and glCompressedTexImage2D gl.glTexParameteri(texParamTarget, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE); usingAutoMipmapGeneration = true; } gl.glTexImage2D( texTarget, 0, data.getInternalFormat(), texWidth, texHeight, data.getBorder(), data.getPixelFormat(), data.getPixelType(), null); updateSubImageImpl(data, texTarget, 0, 0, 0, 0, 0, data.getWidth(), data.getHeight()); } } } int minFilter = (data.getMipmap() ? GL.GL_LINEAR_MIPMAP_LINEAR : GL.GL_LINEAR); int magFilter = GL.GL_LINEAR; int wrapMode = (gl.isExtensionAvailable("GL_VERSION_1_2") ? GL.GL_CLAMP_TO_EDGE : GL.GL_CLAMP); // REMIND: figure out what to do for GL_TEXTURE_RECTANGLE_ARB if (texTarget != GL.GL_TEXTURE_RECTANGLE_ARB) { gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_MIN_FILTER, minFilter); gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_MAG_FILTER, magFilter); gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_S, wrapMode); gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_T, wrapMode); if (this.target == GL.GL_TEXTURE_CUBE_MAP) { gl.glTexParameteri(texParamTarget, GL.GL_TEXTURE_WRAP_R, wrapMode); } } // Don't overwrite target if we're loading e.g. faces of a cube // map if ((this.target == 0) || (this.target == GL.GL_TEXTURE_2D) || (this.target == GL.GL_TEXTURE_RECTANGLE_ARB)) { this.target = texTarget; } // This estimate will be wrong for cube maps estimatedMemorySize = data.getEstimatedMemorySize(); }