private void generateFixedSamples(Vector3[] samples, Color[] colors) { for (int i = 0; i < samples.Length; i++) { double randX = (double)i / (double)samples.Length; double randY = QMC.halton(0, i); int x = 0; while (randX >= colHistogram[x] && x < colHistogram.Length - 1) { x++; } float[] rowHistogram = imageHistogram[x]; int y = 0; while (randY >= rowHistogram[y] && y < rowHistogram.Length - 1) { y++; } // sample from (x, y) float u = (float)((x == 0) ? (randX / colHistogram[0]) : ((randX - colHistogram[x - 1]) / (colHistogram[x] - colHistogram[x - 1]))); float v = (float)((y == 0) ? (randY / rowHistogram[0]) : ((randY - rowHistogram[y - 1]) / (rowHistogram[y] - rowHistogram[y - 1]))); float px = ((x == 0) ? colHistogram[0] : (colHistogram[x] - colHistogram[x - 1])); float py = ((y == 0) ? rowHistogram[0] : (rowHistogram[y] - rowHistogram[y - 1])); float su = (x + u) / colHistogram.Length; float sv = (y + v) / rowHistogram.Length; float invP = (float)Math.Sin(sv * Math.PI) * jacobian / (numSamples * px * py); samples[i] = getDirection(su, sv); basis.transform(samples[i]); colors[i] = texture.getPixel(su, sv).mul(invP); } }
private ShadingState(ShadingState previous, IntersectionState istate, Ray r, int i, int d) { this.r = r; this.istate = istate; this.i = i; this.d = d; this.instance = istate.instance; // local copy this.primitiveID = istate.id; this.hitU = istate.u; this.hitV = istate.v; if (previous == null) { diffuseDepth = 0; reflectionDepth = 0; refractionDepth = 0; } else { diffuseDepth = previous.diffuseDepth; reflectionDepth = previous.reflectionDepth; refractionDepth = previous.refractionDepth; this.server = previous.server; this.map = previous.map; this.rx = previous.rx; this.ry = previous.ry; this.i += previous.i; this.d += previous.d; } behind = false; cosND = float.NaN; includeLights = includeSpecular = true; qmcD0I = QMC.halton(this.d, this.i); qmcD1I = QMC.halton(this.d + 1, this.i); result = null; }
private int progressiveRenderNext(IntersectionState istate) { int TASK_SIZE = 16; SmallBucket first = smallBucketQueue.Count > 0 ? smallBucketQueue.Dequeue() : null; if (first == null) { return(0); } int ds = first.size / TASK_SIZE; bool useMask = smallBucketQueue.Count != 0; int mask = 2 * first.size / TASK_SIZE - 1; int pixels = 0; for (int i = 0, y = first.y; i < TASK_SIZE && y < imageHeight; i++, y += ds) { for (int j = 0, x = first.x; j < TASK_SIZE && x < imageWidth; j++, x += ds) { // check to see if this is a pixel from a higher level tile if (useMask && (x & mask) == 0 && (y & mask) == 0) { continue; } int instance = (x & (sigma.Length - 1)) * sigma.Length + sigma[y & (sigma.Length - 1)]; double time = QMC.halton(1, instance); double lensU = QMC.halton(2, instance); double lensV = QMC.halton(3, instance); ShadingState state = scene.getRadiance(istate, x, imageHeight - 1 - y, lensU, lensV, time, instance); Color c = state != null?state.getResult() : Color.BLACK; pixels++; // fill region display.imageFill(x, y, Math.Min(ds, imageWidth - x), Math.Min(ds, imageHeight - y), c); } } if (first.size >= 2 * TASK_SIZE) { // generate child buckets int size = (int)((uint)first.size >> 1);//>>> for (int i = 0; i < 2; i++) { if (first.y + i * size < imageHeight) { for (int j = 0; j < 2; j++) { if (first.x + j * size < imageWidth) { SmallBucket b = new SmallBucket(); b.x = first.x + j * size; b.y = first.y + i * size; b.size = size; b.constrast = 1.0f / size; smallBucketQueue.Enqueue(b); } } } } } return(pixels); }
private void computeSubPixel(ImageSample sample, IntersectionState istate) { float x = sample.rx; float y = sample.ry; double q0 = QMC.halton(1, sample.i); double q1 = QMC.halton(2, sample.i); double q2 = QMC.halton(3, sample.i); if (superSampling > 1) { // multiple sampling sample.add(scene.getRadiance(istate, x, y, q1, q2, q0, sample.i)); for (int i = 1; i < superSampling; i++) { double time = QMC.mod1(q0 + i * invSuperSampling); double lensU = QMC.mod1(q1 + QMC.halton(0, i)); double lensV = QMC.mod1(q2 + QMC.halton(1, i)); sample.add(scene.getRadiance(istate, x, y, lensU, lensV, time, sample.i + i)); } sample.scale((float)invSuperSampling); } else { // single sample sample.set(scene.getRadiance(istate, x, y, q1, q2, q0, sample.i)); } }
public bool prepare(Options options, Scene scene, int w, int h) { this.scene = scene; imageWidth = w; imageHeight = h; // prepare table used by deterministic anti-aliasing sigma = QMC.generateSigmaTable(1 << 7); return(true); }
private void renderBucket(IDisplay display, int bx, int by, int threadID, IntersectionState istate, ShadingCache cache) { // pixel sized extents int x0 = bx * bucketSize; int y0 = by * bucketSize; int bw = Math.Min(bucketSize, imageWidth - x0); int bh = Math.Min(bucketSize, imageHeight - y0); // prepare bucket display.imagePrepare(x0, y0, bw, bh, threadID); Color[] bucketRGB = new Color[bw * bh]; float[] bucketAlpha = new float[bw * bh]; for (int y = 0, i = 0, cy = imageHeight - 1 - y0; y < bh; y++, cy--) { for (int x = 0, cx = x0; x < bw; x++, i++, cx++) { // sample pixel Color c = Color.black(); float a = 0; int instance = ((cx & ((1 << QMC.MAX_SIGMA_ORDER) - 1)) << QMC.MAX_SIGMA_ORDER) + QMC.sigma(cy & ((1 << QMC.MAX_SIGMA_ORDER) - 1), QMC.MAX_SIGMA_ORDER); double jitterX = QMC.halton(0, instance); double jitterY = QMC.halton(1, instance); double jitterT = QMC.halton(2, instance); double jitterU = QMC.halton(3, instance); double jitterV = QMC.halton(4, instance); for (int s = 0; s < numSamples; s++) { float rx = cx + 0.5f + (float)warpCubic(QMC.mod1(jitterX + s * invNumSamples)); float ry = cy + 0.5f + (float)warpCubic(QMC.mod1(jitterY + QMC.halton(0, s))); double time = QMC.mod1(jitterT + QMC.halton(1, s)); double lensU = QMC.mod1(jitterU + QMC.halton(2, s)); double lensV = QMC.mod1(jitterV + QMC.halton(3, s)); ShadingState state = scene.getRadiance(istate, rx, ry, lensU, lensV, time, instance + s, 5, cache); if (state != null) { c.add(state.getResult()); a++; } } bucketRGB[i] = c.mul(invNumSamples); bucketAlpha[i] = a * invNumSamples; if (cache != null) { cache.reset(); } } } // update pixels display.imageUpdate(x0, y0, bw, bh, bucketRGB, bucketAlpha); }
/** * Get a QMC sample from a finite sequence of n elements. This provides * better stratification than the infinite version, but does not allow for * adaptive sampling. * * @param j sample number (starts from 0) * @param dim dimension to sample * @param n number of samples * @return pseudo-random value in [0,1) */ public double getRandom(int j, int dim, int n) { switch (dim) { case 0: return(QMC.mod1(qmcD0I + (double)j / (double)n)); case 1: return(QMC.mod1(qmcD1I + QMC.halton(0, j))); default: return(QMC.mod1(QMC.halton(d + dim, i) + QMC.halton(dim - 1, j))); } }
public void Run() { ByteUtil.InitByteUtil(); IntersectionState istate = new IntersectionState(); for (int i = start; i < end; i++) { lock (lockObj) { UI.taskUpdate(server.photonCounter); server.photonCounter++; if (UI.taskCanceled()) { return; } } int qmcI = i + seed; double rand = QMC.halton(0, qmcI) * histogram[histogram.Length - 1]; int j = 0; while (rand >= histogram[j] && j < histogram.Length) { j++; } // make sure we didn't pick a zero-probability light if (j == histogram.Length) { continue; } double randX1 = (j == 0) ? rand / histogram[0] : (rand - histogram[j]) / (histogram[j] - histogram[j - 1]); double randY1 = QMC.halton(1, qmcI); double randX2 = QMC.halton(2, qmcI); double randY2 = QMC.halton(3, qmcI); Point3 pt = new Point3(); Vector3 dir = new Vector3(); Color power = new Color(); server.lights[j].getPhoton(randX1, randY1, randX2, randY2, pt, dir, power); power.mul(scale); Ray r = new Ray(pt, dir); server.scene.trace(r, istate); if (istate.hit()) { server.shadePhoton(ShadingState.createPhotonState(r, istate, qmcI, map, server), power); } } }
private ShadingState(ShadingState previous, IntersectionState istate, Ray r, int i, int d) { this.r = r; this.istate = istate; this.i = i; this.d = d; time = istate.time; instance = istate.instance; // local copy primitiveID = istate.id; hitU = istate.u; hitV = istate.v; hitW = istate.w; // get matrices for current time o2w = instance.getObjectToWorld(time); w2o = instance.getWorldToObject(time); if (previous == null) { diffuseDepth = 0; reflectionDepth = 0; refractionDepth = 0; } else { diffuseDepth = previous.diffuseDepth; reflectionDepth = previous.reflectionDepth; refractionDepth = previous.refractionDepth; server = previous.server; map = previous.map; rx = previous.rx; ry = previous.ry; this.i += previous.i; this.d += previous.d; } behind = false; cosND = float.NaN; includeLights = includeSpecular = true; qmcD0I = QMC.halton(this.d, this.i); qmcD1I = QMC.halton(this.d + 1, this.i); result = null; bias = 0.001f; }
public bool update(ParameterList pl, SunflowAPI api) { updateBasis(pl.getVector("center", null), pl.getVector("up", null)); numSamples = pl.getInt("samples", numSamples); string filename = pl.getstring("texture", null); if (filename != null) { texture = TextureCache.getTexture(api.resolveTextureFilename(filename), true); } // no texture provided if (texture == null) { return(false); } Bitmap b = texture.getBitmap(); if (b == null) { return(false); } // rebuild histograms if this is a new texture if (filename != null) { imageHistogram = new float[b.Width][]; for (int i = 0; i < imageHistogram.Length; i++) { imageHistogram[i] = new float[b.Height]; } colHistogram = new float[b.Width]; float du = 1.0f / b.Width; float dv = 1.0f / b.Height; for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { float u = (x + 0.5f) * du; float v = (y + 0.5f) * dv; Color c = texture.getPixel(u, v); // box filter the image // c.add(texture.getPixel(u + du, v)); // c.add(texture.getPixel(u + du, v+ dv)); // c.add(texture.getPixel(u, v + dv)); // c.mul(0.25f); imageHistogram[x][y] = c.getLuminance() * (float)Math.Sin(Math.PI * v); if (y > 0) { imageHistogram[x][y] += imageHistogram[x][y - 1]; } } colHistogram[x] = imageHistogram[x][b.Height - 1]; if (x > 0) { colHistogram[x] += colHistogram[x - 1]; } for (int y = 0; y < b.Height; y++) { imageHistogram[x][y] /= imageHistogram[x][b.Height - 1]; } } for (int x = 0; x < b.Width; x++) { colHistogram[x] /= colHistogram[b.Width - 1]; } jacobian = (float)(2 * Math.PI * Math.PI) / (b.Width * b.Height); } // take fixed samples if (pl.getbool("fixed", samples != null)) { // Bitmap loc = new Bitmap(filename); samples = new Vector3[numSamples]; colors = new Color[numSamples]; for (int i = 0; i < numSamples; i++) { double randX = (double)i / (double)numSamples; double randY = QMC.halton(0, i); int x = 0; while (randX >= colHistogram[x] && x < colHistogram.Length - 1) { x++; } float[] rowHistogram = imageHistogram[x]; int y = 0; while (randY >= rowHistogram[y] && y < rowHistogram.Length - 1) { y++; } // sample from (x, y) float u = (float)((x == 0) ? (randX / colHistogram[0]) : ((randX - colHistogram[x - 1]) / (colHistogram[x] - colHistogram[x - 1]))); float v = (float)((y == 0) ? (randY / rowHistogram[0]) : ((randY - rowHistogram[y - 1]) / (rowHistogram[y] - rowHistogram[y - 1]))); float px = ((x == 0) ? colHistogram[0] : (colHistogram[x] - colHistogram[x - 1])); float py = ((y == 0) ? rowHistogram[0] : (rowHistogram[y] - rowHistogram[y - 1])); float su = (x + u) / colHistogram.Length; float sv = (y + v) / rowHistogram.Length; float invP = (float)Math.Sin(sv * Math.PI) * jacobian / (numSamples * px * py); samples[i] = getDirection(su, sv); basis.transform(samples[i]); colors[i] = texture.getPixel(su, sv).mul(invP); // loc.setPixel(x, y, Color.YELLOW.copy().mul(1e6f)); } // loc.save("samples.hdr"); } else { // turn off samples = null; colors = null; } return(true); }
public bool prepare(Options options, Scene scene, int w, int h) { this.scene = scene; imageWidth = w; imageHeight = h; // fetch options bucketSize = options.getInt("bucket.size", bucketSize); bucketOrderName = options.getstring("bucket.order", bucketOrderName); minAADepth = options.getInt("aa.min", minAADepth); maxAADepth = options.getInt("aa.max", maxAADepth); superSampling = options.getInt("aa.samples", superSampling); displayAA = options.getbool("aa.display", displayAA); jitter = options.getbool("aa.jitter", jitter); contrastThreshold = options.getFloat("aa.contrast", contrastThreshold); // limit bucket size and compute number of buckets in each direction bucketSize = MathUtils.clamp(bucketSize, 16, 512); int numBucketsX = (imageWidth + bucketSize - 1) / bucketSize; int numBucketsY = (imageHeight + bucketSize - 1) / bucketSize; bucketOrder = BucketOrderFactory.create(bucketOrderName); bucketCoords = bucketOrder.getBucketSequence(numBucketsX, numBucketsY); // validate AA options minAADepth = MathUtils.clamp(minAADepth, -4, 5); maxAADepth = MathUtils.clamp(maxAADepth, minAADepth, 5); superSampling = MathUtils.clamp(superSampling, 1, 256); invSuperSampling = 1.0 / superSampling; // compute AA stepping sizes subPixelSize = (maxAADepth > 0) ? (1 << maxAADepth) : 1; minStepSize = maxAADepth >= 0 ? 1 : 1 << (-maxAADepth); if (minAADepth == maxAADepth) { maxStepSize = minStepSize; } else { maxStepSize = minAADepth > 0 ? 1 << minAADepth : subPixelSize << (-minAADepth); } useJitter = jitter && maxAADepth > 0; // compute anti-aliasing contrast thresholds contrastThreshold = MathUtils.clamp(contrastThreshold, 0, 1); thresh = contrastThreshold * (float)Math.Pow(2.0f, minAADepth); // read filter settings from scene filterName = options.getstring("filter", filterName); filter = FilterFactory.get(filterName); // adjust filter if (filter == null) { UI.printWarning(UI.Module.BCKT, "Unrecognized filter type: \"{0}\" - defaulting to box", filterName); filter = new BoxFilter(1); filterName = "box"; } fhs = filter.getSize() * 0.5f; fs = (int)Math.Ceiling(subPixelSize * (fhs - 0.5f)); // prepare QMC sampling sigma = QMC.generateSigmaTable(subPixelSize << 7); UI.printInfo(UI.Module.BCKT, "Bucket renderer settings:"); UI.printInfo(UI.Module.BCKT, " * Resolution: {0}x{1}", imageWidth, imageHeight); UI.printInfo(UI.Module.BCKT, " * Bucket size: {0}", bucketSize); UI.printInfo(UI.Module.BCKT, " * Number of buckets: {0}x{1}", numBucketsX, numBucketsY); if (minAADepth != maxAADepth) { UI.printInfo(UI.Module.BCKT, " * Anti-aliasing: {0} -> {1} (adaptive)", aaDepthTostring(minAADepth), aaDepthTostring(maxAADepth)); } else { UI.printInfo(UI.Module.BCKT, " * Anti-aliasing: {0} (fixed)", aaDepthTostring(minAADepth)); } UI.printInfo(UI.Module.BCKT, " * Rays per sample: {0}", superSampling); UI.printInfo(UI.Module.BCKT, " * Subpixel jitter: {0}", useJitter ? "on" : (jitter ? "auto-off" : "off")); UI.printInfo(UI.Module.BCKT, " * Contrast threshold: {0}", contrastThreshold); UI.printInfo(UI.Module.BCKT, " * Filter type: {0}", filterName); UI.printInfo(UI.Module.BCKT, " * Filter size: {0} pixels", filter.getSize()); return(true); }
private void renderBucket(IDisplay display, int bx, int by, int threadID, IntersectionState istate) { // pixel sized extents int x0 = bx * bucketSize; int y0 = by * bucketSize; int bw = Math.Min(bucketSize, imageWidth - x0); int bh = Math.Min(bucketSize, imageHeight - y0); // prepare bucket display.imagePrepare(x0, y0, bw, bh, threadID); Color[] bucketRGB = new Color[bw * bh]; float[] bucketAlpha = new float[bw * bh]; // subpixel extents int sx0 = x0 * subPixelSize - fs; int sy0 = y0 * subPixelSize - fs; int sbw = bw * subPixelSize + fs * 2; int sbh = bh * subPixelSize + fs * 2; // round up to align with maximum step size sbw = (sbw + (maxStepSize - 1)) & (~(maxStepSize - 1)); sbh = (sbh + (maxStepSize - 1)) & (~(maxStepSize - 1)); // extra padding as needed if (maxStepSize > 1) { sbw++; sbh++; } // allocate bucket memory ImageSample[] samples = new ImageSample[sbw * sbh]; // allocate samples and compute jitter offsets float invSubPixelSize = 1.0f / subPixelSize; for (int y = 0, index = 0; y < sbh; y++) { for (int x = 0; x < sbw; x++, index++) { int sx = sx0 + x; int sy = sy0 + y; int j = sx & (sigmaLength - 1); int k = sy & (sigmaLength - 1); int i = (j << sigmaOrder) + QMC.sigma(k, sigmaOrder); float dx = useJitter ? (float)QMC.halton(0, k) : 0.5f; float dy = useJitter ? (float)QMC.halton(0, j) : 0.5f; float rx = (sx + dx) * invSubPixelSize; float ry = (sy + dy) * invSubPixelSize; ry = imageHeight - ry; samples[index] = new ImageSample(rx, ry, i); } } for (int x = 0; x < sbw - 1; x += maxStepSize) { for (int y = 0; y < sbh - 1; y += maxStepSize) { refineSamples(samples, sbw, x, y, maxStepSize, thresh, istate); } } if (dumpBuckets) { UI.printInfo(UI.Module.BCKT, "Dumping bucket [{0}, {1}] to file ...", bx, by); GenericBitmap bitmap = new GenericBitmap(sbw, sbh); for (int y = sbh - 1, index = 0; y >= 0; y--) { for (int x = 0; x < sbw; x++, index++) { bitmap.writePixel(x, y, samples[index].c, samples[index].alpha); } } bitmap.save(string.Format("bucket_{0}_{1}.png", bx, by)); } if (displayAA) { // color coded image of what is visible float invArea = invSubPixelSize * invSubPixelSize; for (int y = 0, index = 0; y < bh; y++) { for (int x = 0; x < bw; x++, index++) { int sampled = 0; for (int i = 0; i < subPixelSize; i++) { for (int j = 0; j < subPixelSize; j++) { int sx = x * subPixelSize + fs + i; int sy = y * subPixelSize + fs + j; int s = sx + sy * sbw; sampled += samples[s].sampled() ? 1 : 0; } } bucketRGB[index] = new Color(sampled * invArea); bucketAlpha[index] = 1.0f; } } } else { // filter samples into pixels float cy = imageHeight - (y0 + 0.5f); for (int y = 0, index = 0; y < bh; y++, cy--) { float cx = x0 + 0.5f; for (int x = 0; x < bw; x++, index++, cx++) { Color c = Color.black(); float a = 0.0f; float weight = 0.0f; for (int j = -fs, sy = y * subPixelSize; j <= fs; j++, sy++) { for (int i = -fs, sx = x * subPixelSize, s = sx + sy * sbw; i <= fs; i++, sx++, s++) { float dx = samples[s].rx - cx; if (Math.Abs(dx) > fhs) { continue; } float dy = samples[s].ry - cy; if (Math.Abs(dy) > fhs) { continue; } float f = filter.get(dx, dy); c.madd(f, samples[s].c); a += f * samples[s].alpha; weight += f; } } float invWeight = 1.0f / weight; c.mul(invWeight); a *= invWeight; bucketRGB[index] = c; bucketAlpha[index] = a; } } } // update pixels display.imageUpdate(x0, y0, bw, bh, bucketRGB, bucketAlpha); }