📄 bucketrenderer.java
字号:
package org.sunflow.core.renderer;
import org.sunflow.core.BucketOrder;
import org.sunflow.core.Display;
import org.sunflow.core.Filter;
import org.sunflow.core.ImageSampler;
import org.sunflow.core.Instance;
import org.sunflow.core.IntersectionState;
import org.sunflow.core.Options;
import org.sunflow.core.Scene;
import org.sunflow.core.Shader;
import org.sunflow.core.ShadingState;
import org.sunflow.core.bucket.BucketOrderFactory;
import org.sunflow.core.filter.BoxFilter;
import org.sunflow.core.filter.FilterFactory;
import org.sunflow.image.Bitmap;
import org.sunflow.image.Color;
import org.sunflow.math.MathUtils;
import org.sunflow.math.QMC;
import org.sunflow.system.Timer;
import org.sunflow.system.UI;
import org.sunflow.system.UI.Module;
public class BucketRenderer implements ImageSampler {
private Scene scene;
private Display display;
// resolution
private int imageWidth;
private int imageHeight;
// bucketing
private String bucketOrderName;
private BucketOrder bucketOrder;
private int bucketSize;
private int bucketCounter;
private int[] bucketCoords;
private boolean dumpBuckets;
// anti-aliasing
private int minAADepth;
private int maxAADepth;
private int superSampling;
private float contrastThreshold;
private boolean jitter;
private boolean displayAA;
// derived quantities
private double invSuperSampling;
private int subPixelSize;
private int minStepSize;
private int maxStepSize;
private int[] sigma;
private float thresh;
private boolean useJitter;
// filtering
private String filterName;
private Filter filter;
private int fs;
private float fhs;
public BucketRenderer() {
bucketSize = 32;
bucketOrderName = "hilbert";
displayAA = false;
contrastThreshold = 0.1f;
filterName = "box";
jitter = false; // off by default
dumpBuckets = false; // for debugging only - not user settable
}
public boolean 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.getBoolean("aa.display", displayAA);
jitter = options.getBoolean("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(Module.BCKT, "Unrecognized filter type: \"%s\" - defaulting to box", filterName);
filter = new BoxFilter(1);
filterName = "box";
}
fhs = filter.getSize() * 0.5f;
fs = (int) Math.ceil(subPixelSize * (fhs - 0.5f));
// prepare QMC sampling
sigma = QMC.generateSigmaTable(subPixelSize << 7);
UI.printInfo(Module.BCKT, "Bucket renderer settings:");
UI.printInfo(Module.BCKT, " * Resolution: %dx%d", imageWidth, imageHeight);
UI.printInfo(Module.BCKT, " * Bucket size: %d", bucketSize);
UI.printInfo(Module.BCKT, " * Number of buckets: %dx%d", numBucketsX, numBucketsY);
if (minAADepth != maxAADepth)
UI.printInfo(Module.BCKT, " * Anti-aliasing: %s -> %s (adaptive)", aaDepthToString(minAADepth), aaDepthToString(maxAADepth));
else
UI.printInfo(Module.BCKT, " * Anti-aliasing: %s (fixed)", aaDepthToString(minAADepth));
UI.printInfo(Module.BCKT, " * Rays per sample: %d", superSampling);
UI.printInfo(Module.BCKT, " * Subpixel jitter: %s", useJitter ? "on" : (jitter ? "auto-off" : "off"));
UI.printInfo(Module.BCKT, " * Contrast threshold: %.2f", contrastThreshold);
UI.printInfo(Module.BCKT, " * Filter type: %s", filterName);
UI.printInfo(Module.BCKT, " * Filter size: %.2f pixels", filter.getSize());
return true;
}
private String aaDepthToString(int depth) {
int pixelAA = (depth) < 0 ? -(1 << (-depth)) : (1 << depth);
return String.format("%s%d sample%s", depth < 0 ? "1/" : "", pixelAA * pixelAA, depth == 0 ? "" : "s");
}
public void render(Display display) {
this.display = display;
display.imageBegin(imageWidth, imageHeight, bucketSize);
// set members variables
bucketCounter = 0;
// start task
UI.taskStart("Rendering", 0, bucketCoords.length);
Timer timer = new Timer();
timer.start();
Thread[] renderThreads = new Thread[scene.getThreads()];
for (int i = 0; i < renderThreads.length; i++) {
renderThreads[i] = new BucketThread(i);
renderThreads[i].setPriority(scene.getThreadPriority());
renderThreads[i].start();
}
for (int i = 0; i < renderThreads.length; i++) {
try {
renderThreads[i].join();
} catch (InterruptedException e) {
UI.printError(Module.BCKT, "Bucket processing thread %d of %d was interrupted", i + 1, renderThreads.length);
}
}
UI.taskStop();
timer.end();
UI.printInfo(Module.BCKT, "Render time: %s", timer.toString());
display.imageEnd();
}
private class BucketThread extends Thread {
private int threadID;
BucketThread(int threadID) {
this.threadID = threadID;
}
public void run() {
IntersectionState istate = new IntersectionState();
while (true) {
int bx, by;
synchronized (BucketRenderer.this) {
if (bucketCounter >= bucketCoords.length)
return;
UI.taskUpdate(bucketCounter);
bx = bucketCoords[bucketCounter + 0];
by = bucketCoords[bucketCounter + 1];
bucketCounter += 2;
}
renderBucket(display, bx, by, threadID, istate);
if (UI.taskCanceled())
return;
}
}
}
private void renderBucket(Display 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];
// 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++) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -