diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasedPixelRenderer.java b/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasedPixelRenderer.java
new file mode 100644
index 0000000..7eec5da
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasedPixelRenderer.java
@@ -0,0 +1,82 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import javax.vecmath.Color3f;
+
+import de.mfo.jsurf.rendering.RenderingInterruptedException;
+
+class AntiAliasedPixelRenderer extends PixelRenderStrategy {
+ private final Color3f[] internalColorBuffer;
+ private final float thresholdSqr;
+ private final AntiAliasingPattern aap;
+ private final ColumnSubstitutorPairProvider cspProvider;
+
+ public AntiAliasedPixelRenderer(DrawcallStaticData dcsd, Color3f[] internalColorBuffer, PolynomialTracer polyTracer, ColumnSubstitutorPairProvider cspProvider) {
+ super(dcsd, polyTracer);
+ this.internalColorBuffer = internalColorBuffer;
+ this.thresholdSqr = dcsd.antiAliasingThreshold * dcsd.antiAliasingThreshold;
+ this.aap = dcsd.antiAliasingPattern;
+ this.cspProvider = cspProvider;
+ }
+
+ @Override
+ public void renderPixel(int x, int y, PixelStep step, ColumnSubstitutorPair csp) {
+ internalColorBuffer[ step.internalBufferIndex ] = tracePolynomial( csp.scs, csp.gcs, step.u, step.v );
+ if( x == 0 || y == 0 )
+ return;
+
+ Color3f urColor = internalColorBuffer[ step.internalBufferIndex ];
+ Color3f ulColor = internalColorBuffer[ step.internalBufferIndex - 1 ];
+ Color3f lrColor = internalColorBuffer[ step.internalBufferIndex - step.width ];
+ Color3f llColor = internalColorBuffer[ step.internalBufferIndex - step.width - 1 ];
+
+ boolean doSuperSampling =
+ aap != AntiAliasingPattern.OG_2x2 &&
+ (thresholdSqr == 0 ||
+ areTooDifferent( ulColor, urColor ) ||
+ areTooDifferent( ulColor, llColor ) ||
+ areTooDifferent( ulColor, lrColor ) ||
+ areTooDifferent( urColor, llColor ) ||
+ areTooDifferent( urColor, lrColor ) ||
+ areTooDifferent( llColor, lrColor ));
+
+ // first average pixel-corner colors. Weight depends on whether more samples will be taken
+ Color3f accumulator = new Color3f( ulColor );
+ accumulator.add( urColor );
+ accumulator.add( llColor );
+ accumulator.add( lrColor );
+ accumulator.scale( doSuperSampling ? aap.cornerWeight : 0.25f);
+
+ if (doSuperSampling)
+ sampleExceptCorners(step, accumulator);
+
+ accumulator.clamp( 0f, 1f );
+
+ colorBuffer[ step.colorBufferIndex ] = accumulator.get().getRGB();
+ }
+
+ private void sampleExceptCorners( PixelStep step, Color3f accumulator )
+ {
+ for( AntiAliasingPattern.SamplingPoint sp : aap )
+ {
+ if( Thread.currentThread().isInterrupted() )
+ throw new RenderingInterruptedException();
+
+ // corners already accumulated above
+ if (sp.isCorner)
+ continue;
+
+ double v = step.vOld + sp.v * step.v_incr;
+ double u = step.uOld + sp.u * step.u_incr;
+ ColumnSubstitutorPair csp = cspProvider.get( v );
+ Color3f sample = tracePolynomial( csp.scs, csp.gcs, u, v );
+ accumulator.scaleAdd( sp.weight, sample, accumulator );
+ }
+ }
+
+ private boolean areTooDifferent(Color3f c1, Color3f c2) {
+ float x = c1.x - c2.x;
+ float y = c1.y - c2.y;
+ float z = c1.z - c2.z;
+ return (x * x) + (y * y) + (z * z) >= thresholdSqr;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasingPattern.java b/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasingPattern.java
index 21a47e8..60f09ff 100644
--- a/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasingPattern.java
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/AntiAliasingPattern.java
@@ -21,33 +21,36 @@
public enum AntiAliasingPattern implements Iterable< AntiAliasingPattern.SamplingPoint >
{
- OG_1x1( getOGSSPattern( 1 ) ),
- OG_2x2( getOGSSPattern( 2 ) ),
- OG_3x3( getOGSSPattern( 3 ) ),
- OG_4x4( getOGSSPattern( 4 ) ),
- OG_5x5( getOGSSPattern( 5 ) ),
- OG_6x6( getOGSSPattern( 6 ) ),
- OG_7x7( getOGSSPattern( 7 ) ),
- OG_8x8( getOGSSPattern( 8 ) ),
- RG_2x2( getRGSSPattern() ),
- QUINCUNX( getQuincunxPattern() );
+ OG_1x1( getOGSSPattern( 1 ), 1 ),
+ OG_2x2( getOGSSPattern( 2 ), 2 ),
+ OG_3x3( getOGSSPattern( 3 ), 3 ),
+ OG_4x4( getOGSSPattern( 4 ), 4 ),
+ OG_5x5( getOGSSPattern( 5 ), 5 ),
+ OG_6x6( getOGSSPattern( 6 ), 6 ),
+ OG_7x7( getOGSSPattern( 7 ), 7 ),
+ OG_8x8( getOGSSPattern( 8 ), 8 ),
+ RG_2x2( getRGSSPattern(), 2 ),
+ QUINCUNX( getQuincunxPattern(), 3 );
private final SamplingPoint[] points;
+ public final int vSteps;
+
+ public final float cornerWeight;
public static class SamplingPoint
{
- private float u, v, weight;
+ public final float u;
+ public final float v;
+ public final float weight;
+ public final boolean isCorner;
private SamplingPoint( float u, float v, float weight )
{
this.u = u;
this.v = v;
this.weight = weight;
+ this.isCorner = (u == 0.0 || u == 1.0) && (v == 0.0 || v == 1.0);
}
-
- public float getU() { return u;}
- public float getV() { return v; }
- public float getWeight() { return weight; }
}
private class SamplingPointIterator implements Iterator< SamplingPoint >
@@ -86,7 +89,19 @@ public void remove()
}
}
- private AntiAliasingPattern( SamplingPoint[] points ) { this.points = points; }
+ private AntiAliasingPattern( SamplingPoint[] points, int vSteps ) {
+ this.points = points;
+ this.cornerWeight = findFirstCorner().weight;
+ this.vSteps = vSteps;
+ }
+
+ SamplingPoint findFirstCorner() {
+ for (SamplingPoint sp : points)
+ if (sp.isCorner)
+ return sp;
+ return points[0];
+ }
+
public Iterator< SamplingPoint > iterator() { return new SamplingPointIterator( this.points ); }
private static SamplingPoint[] getOGSSPattern( int size )
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/BasicPixelRenderer.java b/src/main/java/de/mfo/jsurf/rendering/cpu/BasicPixelRenderer.java
new file mode 100644
index 0000000..c5ce6bc
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/BasicPixelRenderer.java
@@ -0,0 +1,11 @@
+package de.mfo.jsurf.rendering.cpu;
+
+class BasicPixelRenderer extends PixelRenderStrategy {
+ public BasicPixelRenderer(DrawcallStaticData dcsd, PolynomialTracer polyTracer) {
+ super(dcsd, polyTracer);
+ }
+
+ @Override public void renderPixel(int x, int y, PixelStep step, ColumnSubstitutorPair csp) {
+ colorBuffer[ step.colorBufferIndex ] = tracePolynomial(csp.scs, csp.gcs, step.u, step.v ).get().getRGB();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/ColorBufferPool.java b/src/main/java/de/mfo/jsurf/rendering/cpu/ColorBufferPool.java
new file mode 100644
index 0000000..4b52a94
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/ColorBufferPool.java
@@ -0,0 +1,115 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.vecmath.Color3f;
+
+/**
+ * The buffers returned from this class will not have the exact size requested, but the
+ * size of the minimum power of two higher (or equal) to it.
+ * For example, if asked for a buffer for 457 colors, it will return one of size 512.
+ *
+ * This will waste memory, but allows to reuse the buffers for different parts, and also
+ * when the rendering size changes. It also keeps them nicely aligned in memory, which
+ * could produce more speed improvements.
+ *
+ * @author Sebastian
+ *
+ */
+public class ColorBufferPool {
+
+ // Should be an array of lists, but Java does not allow arrays of generic types
+ private List> bufferPool;
+ private boolean dontPool = false;
+
+ // Usage statistics
+ private int createdBuffers = 0;
+ private int requestedBuffers = 0;
+ private int totalBufferSize = 0;
+
+ // the biggest buffer it can hold is of size 2^32 == 2^16 x 2^16, an area of 65536 x 65536
+ private static final int MAX_POWER_OF_TWO = 32;
+
+ public static ColorBufferPool createDummyPool() {
+ ColorBufferPool pool = new ColorBufferPool();
+ pool.dontPool = true;
+ return pool;
+ }
+
+ public ColorBufferPool() {
+ bufferPool = new ArrayList>(MAX_POWER_OF_TWO);
+ for (int i = 0 ; i < MAX_POWER_OF_TWO ; i++) {
+ bufferPool.add(new ArrayList());
+ }
+ }
+
+ public Color3f[] getBuffer(int size) {
+ size = powOf2Roundup(size);
+ int index = highestOneBit(size);
+ requestedBuffers++;
+
+ List bucket = bufferPool.get(index);
+ synchronized (bucket) {
+ if (dontPool || bucket.isEmpty()) {
+ createdBuffers++;
+ totalBufferSize += size;
+ return new Color3f[size];
+ } else
+ return bucket.remove(bucket.size() - 1);
+ }
+ }
+
+ /** Index of the highest bit set in the binary representation of an integer */
+ public static int highestOneBit(int x) {
+ int index = 0;
+ if ((x & 0xFFFF0000) != 0) {
+ index += 16;
+ x >>= 16;
+ }
+ if ((x & 0xFF00) != 0) {
+ index += 8;
+ x >>= 8;
+ }
+ if ((x & 0xF0) != 0) {
+ index += 4;
+ x >>= 4;
+ }
+ if ((x & 0x0C) != 0) {
+ index += 2;
+ x >>= 2;
+ }
+ if ((x & 2) != 0)
+ index++;
+
+ return index;
+ }
+
+ // https://stackoverflow.com/questions/364985/algorithm-for-finding-the-smallest-power-of-two-thats-greater-or-equal-to-a-giv
+ /** Minimum power of two bigger or equal to value */
+ private int powOf2Roundup(int x) {
+ if (x < 0)
+ return 0;
+
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x+1;
+ }
+
+ public void releaseBuffer(Color3f[] buffer) {
+ int size = buffer.length;
+ int index = highestOneBit(size);
+ List bucket = bufferPool.get(index);
+ synchronized (bucket) {
+ bucket.add(buffer);
+ }
+ }
+
+ public String getPoolStatistics() {
+ return "Requests / created: " + requestedBuffers + "/" + createdBuffers + ". Total size: " + totalBufferSize;
+ }
+}
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPair.java b/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPair.java
new file mode 100644
index 0000000..f96524d
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPair.java
@@ -0,0 +1,16 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import de.mfo.jsurf.algebra.ColumnSubstitutor;
+import de.mfo.jsurf.algebra.ColumnSubstitutorForGradient;
+
+class ColumnSubstitutorPair
+{
+ public final ColumnSubstitutor scs;
+ public final ColumnSubstitutorForGradient gcs;
+
+ ColumnSubstitutorPair( ColumnSubstitutor scs, ColumnSubstitutorForGradient gcs )
+ {
+ this.scs = scs;
+ this.gcs = gcs;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPairProvider.java b/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPairProvider.java
new file mode 100644
index 0000000..df39029
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/ColumnSubstitutorPairProvider.java
@@ -0,0 +1,30 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import de.mfo.jsurf.algebra.RowSubstitutor;
+import de.mfo.jsurf.algebra.RowSubstitutorForGradient;
+
+class ColumnSubstitutorPairProvider {
+ private final RowSubstitutor surfaceRowSubstitutor;
+ private final RowSubstitutorForGradient gradientRowSubstitutor;
+
+ private final ColumnSubstitutorPair[] csps;
+ private final double vMult;
+ private final double vInc;
+
+ public ColumnSubstitutorPairProvider(DrawcallStaticData dcsd, PixelStep step) {
+ this.surfaceRowSubstitutor = dcsd.surfaceRowSubstitutor;
+ this.gradientRowSubstitutor = dcsd.gradientRowSubstitutor;
+ this.vInc = -step.v_start;
+ this.vMult = (double)dcsd.antiAliasingPattern.vSteps / step.v_incr;
+ this.csps = new ColumnSubstitutorPair[(dcsd.antiAliasingPattern.vSteps + 1) * (step.height + 1)];
+ }
+
+ public ColumnSubstitutorPair get(double v) {
+ int index = (int)((v + vInc) * vMult);
+
+ if (csps[index] == null)
+ csps[index] = new ColumnSubstitutorPair(surfaceRowSubstitutor.setV( v ), gradientRowSubstitutor.setV( v ));
+
+ return csps[index];
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/DrawcallStaticData.java b/src/main/java/de/mfo/jsurf/rendering/cpu/DrawcallStaticData.java
index 18d7eb9..a3d6995 100644
--- a/src/main/java/de/mfo/jsurf/rendering/cpu/DrawcallStaticData.java
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/DrawcallStaticData.java
@@ -22,7 +22,7 @@
import javax.vecmath.*;
-class DrawcallStaticData
+public class DrawcallStaticData
{
int[] colorBuffer;
int width;
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/PixelRenderStrategy.java b/src/main/java/de/mfo/jsurf/rendering/cpu/PixelRenderStrategy.java
new file mode 100644
index 0000000..60e43fe
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/PixelRenderStrategy.java
@@ -0,0 +1,67 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import javax.vecmath.Color3f;
+import javax.vecmath.Vector3d;
+
+import de.mfo.jsurf.algebra.ColumnSubstitutor;
+import de.mfo.jsurf.algebra.ColumnSubstitutorForGradient;
+import de.mfo.jsurf.algebra.UnivariatePolynomialVector3d;
+
+public abstract class PixelRenderStrategy {
+ private final PolynomialTracer polyTracer;
+ private final RayCreator rayCreator;
+ private final Shader frontShader;
+ private final Shader backShader;
+ private final Color3f backgroundColor;
+
+ protected final int[] colorBuffer;
+
+ public PixelRenderStrategy(DrawcallStaticData dcsd, PolynomialTracer polyTracer) {
+ this.polyTracer = polyTracer;
+ this.frontShader = new Shader(dcsd.frontAmbientColor, dcsd.lightSources, dcsd.frontLightProducts);
+ this.backShader = new Shader(dcsd.backAmbientColor, dcsd.lightSources, dcsd.backLightProducts);
+ this.backgroundColor = dcsd.backgroundColor;
+ this.rayCreator = dcsd.rayCreator;
+ this.colorBuffer = dcsd.colorBuffer;
+ }
+
+ public abstract void renderPixel(int x, int y, PixelStep step, ColumnSubstitutorPair csp);
+
+ protected Color3f tracePolynomial( ColumnSubstitutor scs, ColumnSubstitutorForGradient gcs, double u, double v )
+ {
+ double hit = polyTracer.findClosestHit(scs, gcs, u, v);
+
+ if (Double.isNaN(hit))
+ return backgroundColor;
+
+ UnivariatePolynomialVector3d gradientPolys = gcs.setU( u );
+ Vector3d n_surfaceSpace = gradientPolys.setT( hit );
+ Vector3d n_cameraSpace = rayCreator.surfaceSpaceNormalToCameraSpaceNormal( n_surfaceSpace );
+
+ Ray ray = rayCreator.createCameraSpaceRay( u, v );
+ return shade( ray, hit, n_cameraSpace );
+ }
+
+ /**
+ * Calculates the shading in camera space
+ */
+ protected Color3f shade( Ray ray, double hit, Vector3d cameraSpaceNormal )
+ {
+ // normalize only if point is not singular
+ float nLength = (float) cameraSpaceNormal.length();
+ if( nLength != 0.0f )
+ cameraSpaceNormal.scale( 1.0f / nLength );
+
+ Vector3d view = new Vector3d(-ray.d.x, -ray.d.y, -ray.d.z);
+ // TODO: not normalizing the view does not seem to affect the rendered result, maybe it can be avoided
+ view.normalize();
+
+ Shader shader = frontShader;
+ if( cameraSpaceNormal.dot( view ) <= 0.0f ) {
+ shader = backShader;
+ cameraSpaceNormal.negate();
+ }
+
+ return shader.shade(ray.at(hit), view, cameraSpaceNormal);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/PixelStep.java b/src/main/java/de/mfo/jsurf/rendering/cpu/PixelStep.java
new file mode 100644
index 0000000..bee3480
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/PixelStep.java
@@ -0,0 +1,66 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import javax.vecmath.Vector2d;
+
+/**
+ * Holds information needed for stepping through the pixels in the image
+ */
+class PixelStep {
+ private final double u_start;
+ final double v_start;
+ final double u_incr;
+ final double v_incr;
+
+ public final int width;
+ public final int height;
+
+ public double v;
+ public double u;
+ public double vOld;
+ public double uOld;
+
+ public int internalBufferIndex;
+ public int colorBufferIndex;
+ private final int colorBufferVStep;
+
+ public PixelStep(DrawcallStaticData dcsd, int xStart, int yStart, int xEnd, int yEnd) {
+ RayCreator rayCreator = dcsd.rayCreator;
+ Vector2d uInterval = rayCreator.getUInterval();
+ Vector2d vInterval = rayCreator.getVInterval();
+ double displace = (dcsd.antiAliasingPattern == AntiAliasingPattern.OG_1x1) ? 0 : 0.5;
+ this.u_start = rayCreator.transformU( ( xStart - displace ) / ( dcsd.width - 1.0 ) );
+ this.v_start = rayCreator.transformV( ( yStart - displace ) / ( dcsd.height - 1.0 ) );
+ this.u_incr = ( uInterval.y - uInterval.x ) / ( dcsd.width - 1.0 );
+ this.v_incr = ( vInterval.y - vInterval.x ) / ( dcsd.height - 1.0 );
+
+ this.width = xEnd - xStart + 2;
+ this.height = yEnd - yStart + 2;
+ this.colorBufferIndex = (yStart - 1) * dcsd.width + xStart - 1;
+ this.colorBufferVStep = dcsd.width - width;
+ reset();
+ }
+
+ private void reset() {
+ vOld = 0;
+ v = v_start;
+
+ uOld = 0;
+ u = u_start;
+ }
+
+ public void stepU() {
+ uOld = u;
+ u += u_incr;
+ colorBufferIndex++;
+ internalBufferIndex++;
+ }
+
+ public void stepV() {
+ vOld = v;
+ v += v_incr;
+
+ uOld = 0;
+ u = u_start;
+ colorBufferIndex += colorBufferVStep;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/PolynomialTracer.java b/src/main/java/de/mfo/jsurf/rendering/cpu/PolynomialTracer.java
new file mode 100644
index 0000000..bd8da7d
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/PolynomialTracer.java
@@ -0,0 +1,54 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import java.util.List;
+
+import javax.vecmath.Vector2d;
+
+import de.mfo.jsurf.algebra.ColumnSubstitutor;
+import de.mfo.jsurf.algebra.ColumnSubstitutorForGradient;
+import de.mfo.jsurf.algebra.RealRootFinder;
+import de.mfo.jsurf.algebra.UnivariatePolynomial;
+import de.mfo.jsurf.rendering.cpu.clipping.Clipper;
+
+public class PolynomialTracer {
+ private final RealRootFinder realRootFinder;
+ private final RayCreator rayCreator;
+ private final Clipper rayClipper;
+
+ public PolynomialTracer(DrawcallStaticData dcds) {
+ this.rayClipper = dcds.rayClipper;
+ this.rayCreator = dcds.rayCreator;
+ this.realRootFinder = dcds.realRootFinder;
+ }
+
+ /**
+ * Returns the closest valid distance at which there was a hit. Double.NaN if no hit.
+ */
+ public double findClosestHit( ColumnSubstitutor scs, ColumnSubstitutorForGradient gcs, double u, double v )
+ {
+ Ray clippingRay = rayCreator.createClippingSpaceRay( u, v );
+
+ List< Vector2d > intervals = rayClipper.clipRay( clippingRay );
+ if( !intervals.isEmpty() )
+ {
+ UnivariatePolynomial surfacePoly = scs.setU( u );
+ for( Vector2d interval : intervals )
+ {
+ // adjust interval, so that it does not start before the eye point
+ double eyeLocation = rayCreator.getEyeLocationOnRay();
+ if( interval.x < eyeLocation && eyeLocation < interval.y )
+ interval.x = Math.max( interval.x, eyeLocation );
+
+ // intersect ray with surface and shade pixel
+ double hit = realRootFinder.findFirstRootIn( surfacePoly, interval.x, interval.y );
+ Ray surfaceRay = rayCreator.createSurfaceSpaceRay( u, v );
+ if( !java.lang.Double.isNaN( hit ) && rayClipper.clipPoint( surfaceRay.at( hit ), true ) )
+ {
+ return hit;
+ }
+ }
+ }
+ return Double.NaN;
+ }
+
+}
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/RenderingTask.java b/src/main/java/de/mfo/jsurf/rendering/cpu/RenderingTask.java
index 46a4741..9aff7e0 100644
--- a/src/main/java/de/mfo/jsurf/rendering/cpu/RenderingTask.java
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/RenderingTask.java
@@ -16,430 +16,69 @@
package de.mfo.jsurf.rendering.cpu;
-import de.mfo.jsurf.algebra.*;
-import de.mfo.jsurf.debug.*;
-import de.mfo.jsurf.rendering.*;
+import java.util.concurrent.Callable;
+import javax.vecmath.Color3f;
-import javax.vecmath.*;
-import java.util.concurrent.*;
-import java.util.*;
+import de.mfo.jsurf.rendering.RenderingInterruptedException;
public class RenderingTask implements Callable
{
+ static ColorBufferPool bufferPool = new ColorBufferPool();
+
// initialized by the constructor
- private int xStart;
- private int yStart;
- private int xEnd;
- private int yEnd;
- private DrawcallStaticData dcsd;
+ private final DrawcallStaticData dcsd;
+ private final PixelStep step;
+ private final ColumnSubstitutorPairProvider cspProvider;
+ private final PolynomialTracer polyTracer;
public RenderingTask( DrawcallStaticData dcsd, int xStart, int yStart, int xEnd, int yEnd )
{
this.dcsd = dcsd;
- this.xStart = xStart;
- this.yStart = yStart;
- this.xEnd = xEnd;
- this.yEnd = yEnd;
+ this.step = new PixelStep(dcsd, xStart, yStart, xEnd, yEnd);
+ this.cspProvider = new ColumnSubstitutorPairProvider(dcsd, step);
+ this.polyTracer = new PolynomialTracer(dcsd);
}
- public Boolean call()
- {
- try
- {
- render();
+ public Boolean call() {
+ Color3f[] colorBuffer = null;
+ try {
+ PixelRenderStrategy pixelRenderer;
+ if (useAntiAliasing()) {
+ colorBuffer = bufferPool.getBuffer(step.width * step.height);
+ pixelRenderer = new AntiAliasedPixelRenderer(dcsd, colorBuffer, polyTracer, cspProvider);
+ } else {
+ pixelRenderer = new BasicPixelRenderer(dcsd, polyTracer);
+ }
+ renderImage(pixelRenderer);
return true;
- }
- catch( RenderingInterruptedException rie )
- {
- // rendering interrupted .. that's ok
- //System.err.println( "... interrupted" );
- }
- catch( Throwable t )
- {
+ } catch( RenderingInterruptedException rie ) { // rendering interrupted .. that's ok
+ } catch( Throwable t ) {
t.printStackTrace();
+ } finally {
+ if (colorBuffer != null)
+ bufferPool.releaseBuffer(colorBuffer);
}
- finally
- {
- //Thread.interrupted(); // clear the interruption flag
- }
-
return false;
}
- private class ColumnSubstitutorPair
- {
- ColumnSubstitutorPair( ColumnSubstitutor scs, ColumnSubstitutorForGradient gcs )
- {
- this.scs = scs;
- this.gcs = gcs;
- }
-
- ColumnSubstitutor scs;
- ColumnSubstitutorForGradient gcs;
+ private boolean useAntiAliasing() {
+ return dcsd.antiAliasingPattern != AntiAliasingPattern.OG_1x1;
}
- protected void render()
- throws RenderingInterruptedException
- {
- switch( dcsd.antiAliasingPattern )
- {
- case OG_1x1:
- {
- // no antialising -> sample pixel center
- int internal_width = xEnd - xStart + 1;
- int internal_height = yEnd - yStart + 1;
- double u_start = dcsd.rayCreator.transformU( xStart / ( dcsd.width - 1.0 ) );
- double v_start = dcsd.rayCreator.transformV( yStart / ( dcsd.height - 1.0 ) );
- double u_incr = ( dcsd.rayCreator.getUInterval().y - dcsd.rayCreator.getUInterval().x ) / ( dcsd.width - 1.0 );
- double v_incr = ( dcsd.rayCreator.getVInterval().y - dcsd.rayCreator.getVInterval().x ) / ( dcsd.height - 1.0 );
- for( int y = 0; y < internal_height; y++ )
- {
- double v = v_start + y * v_incr;
- ColumnSubstitutor scs = dcsd.surfaceRowSubstitutor.setV( v );
- ColumnSubstitutorForGradient gcs = dcsd.gradientRowSubstitutor.setV( v );
-
- for( int x = 0; x < internal_width; x++ )
- {
- if( Thread.currentThread().isInterrupted() )
- throw new RenderingInterruptedException();
- double u = u_start + x * u_incr;
- dcsd.colorBuffer[ dcsd.width * ( yStart + y ) + xStart + x ] = tracePolynomial( scs, gcs, u, v ).get().getRGB();
- //dcsd.colorBuffer[ dcsd.width * y + x ] = traceRay( u, v ).get().getRGB();
- }
- }
- break;
- }
- default:
- {
- // all other antialiasing modes
- // first sample canvas at pixel corners and cast primary rays
- int internal_width = xEnd - xStart + 2;
- int internal_height = yEnd - yStart + 2;
- Color3f[] internalColorBuffer = new Color3f[ internal_width * internal_height ];
-
- ColumnSubstitutor scs = null;
- ColumnSubstitutorForGradient gcs = null;
- HashMap< java.lang.Double, ColumnSubstitutorPair > csp_hm = new HashMap< java.lang.Double, ColumnSubstitutorPair >();
- double u_start = dcsd.rayCreator.transformU( ( xStart - 0.5 ) / ( dcsd.width - 1.0 ) );
- double v_start = dcsd.rayCreator.transformV( ( yStart - 0.5 ) / ( dcsd.height - 1.0 ) );
- double u_incr = ( dcsd.rayCreator.getUInterval().y - dcsd.rayCreator.getUInterval().x ) / ( dcsd.width - 1.0 );
- double v_incr = ( dcsd.rayCreator.getVInterval().y - dcsd.rayCreator.getVInterval().x ) / ( dcsd.height - 1.0 );
- double v = 0.0;
- for( int y = 0; y < internal_height; ++y )
- {
- csp_hm.clear(); csp_hm.put( v, new ColumnSubstitutorPair( scs, gcs ) );
-
- v = v_start + y * v_incr;
- scs = dcsd.surfaceRowSubstitutor.setV( v );
- gcs = dcsd.gradientRowSubstitutor.setV( v );
-
- csp_hm.put( v, new ColumnSubstitutorPair( scs, gcs ) );
-
- for( int x = 0; x < internal_width; ++x )
- {
- if( Thread.currentThread().isInterrupted() )
- throw new RenderingInterruptedException();
-
- // current position on viewing plane
- double u = u_start + x * u_incr;
- // trace rays corresponding to (u,v)-coordinates on viewing plane
-
- internalColorBuffer[ y * internal_width + x ] = tracePolynomial( scs, gcs, u, v );
- if( x > 0 && y > 0 )
- {
- Color3f ulColor = internalColorBuffer[ y * internal_width + x - 1 ];
- Color3f urColor = internalColorBuffer[ y * internal_width + x ];
- Color3f llColor = internalColorBuffer[ ( y - 1 ) * internal_width + x - 1];
- Color3f lrColor = internalColorBuffer[ ( y - 1 ) * internal_width + x ];
-
- dcsd.colorBuffer[ ( yStart + y - 1 ) * dcsd.width + ( xStart + x - 1 ) ] = antiAliasPixel( u - u_incr, v - v_incr, u_incr, v_incr, dcsd.antiAliasingPattern, ulColor, urColor, llColor, lrColor, csp_hm ).get().getRGB();
- }
- }
- }
- }
- }
- }
-
- private Color3f antiAliasPixel( double ll_u, double ll_v, double u_incr, double v_incr, AntiAliasingPattern aap, Color3f ulColor, Color3f urColor, Color3f llColor, Color3f lrColor, HashMap< java.lang.Double, ColumnSubstitutorPair > csp_hm )
- {
- // first average pixel-corner colors
- Color3f finalColor;
-
- // adaptive supersampling
- float thresholdSqr = dcsd.antiAliasingThreshold * dcsd.antiAliasingThreshold;
- if( aap != AntiAliasingPattern.OG_2x2 && ( colorDiffSqr( ulColor, urColor ) >= thresholdSqr ||
- colorDiffSqr( ulColor, llColor ) >= thresholdSqr ||
- colorDiffSqr( ulColor, lrColor ) >= thresholdSqr ||
- colorDiffSqr( urColor, llColor ) >= thresholdSqr ||
- colorDiffSqr( urColor, lrColor ) >= thresholdSqr ||
- colorDiffSqr( llColor, lrColor ) >= thresholdSqr ) )
- {
- // anti-alias pixel with advanced sampling pattern
- finalColor = new Color3f();
- for( AntiAliasingPattern.SamplingPoint sp : aap )
- {
- if( Thread.currentThread().isInterrupted() )
- throw new RenderingInterruptedException();
-
- Color3f ss_color;
- if( sp.getU() == 0.0 && sp.getV() == 0.0 )
- ss_color = llColor;
- else if( sp.getU() == 0.0 && sp.getV() == 1.0 )
- ss_color = ulColor;
- else if( sp.getU() == 1.0 && sp.getV() == 1.0 )
- ss_color = urColor;
- else if( sp.getU() == 1.0 && sp.getV() == 0.0 )
- ss_color = lrColor;
- else
- {
- // color of this sample point is not known -> calculate
- double v = ll_v + sp.getV() * v_incr;
- double u = ll_u + sp.getU() * u_incr;
- ColumnSubstitutorPair csp = csp_hm.get( v );
- if( csp == null )
- {
- csp = new ColumnSubstitutorPair( dcsd.surfaceRowSubstitutor.setV( v ), dcsd.gradientRowSubstitutor.setV( v ) );
- csp_hm.put( v, csp );
- }
- ss_color = tracePolynomial( csp.scs, csp.gcs, u, v );
- }
- finalColor.scaleAdd( sp.getWeight(), ss_color, finalColor );
-
- if( false )
- return new Color3f( 0, 0, 0 ); // paint pixels, that are supposed to be anti-aliased in black
- }
- }
- else
- {
- finalColor = new Color3f( ulColor );
- finalColor.add( urColor );
- finalColor.add( llColor );
- finalColor.add( lrColor );
- finalColor.scale( 0.25f );
- }
-
- // clamp color, because floating point operations may yield values outside [0,1]
- finalColor.clamp( 0f, 1f );
- return finalColor;
- }
-
- private Color3f tracePolynomial( ColumnSubstitutor scs, ColumnSubstitutorForGradient gcs, double u, double v )
- {
- // create rays
- Ray ray = dcsd.rayCreator.createCameraSpaceRay( u, v );
- Ray clippingRay = dcsd.rayCreator.createClippingSpaceRay( u, v );
- Ray surfaceRay = dcsd.rayCreator.createSurfaceSpaceRay( u, v );
-
- Point3d eye = ray.at( dcsd.rayCreator.getEyeLocationOnRay() );
- UnivariatePolynomialVector3d gradientPolys = null;
-
- // optimize rays and root-finder parameters
- //optimizeRays( ray, clippingRay, surfaceRay );
-
- // clip ray
- List< Vector2d > intervals = dcsd.rayClipper.clipRay( clippingRay );
- if( !intervals.isEmpty() )
- {
- UnivariatePolynomial surfacePoly = scs.setU( u );
- for( Vector2d interval : intervals )
- {
- // adjust interval, so that it does not start before the eye point
- double eyeLocation = dcsd.rayCreator.getEyeLocationOnRay();
- if( interval.x < eyeLocation && eyeLocation < interval.y )
- interval.x = Math.max( interval.x, eyeLocation );
-
- // intersect ray with surface and shade pixel
- //double[] hits = hits = dcsd.realRootFinder.findAllRootsIn( surfacePoly, interval.x, interval.y );
- double[] hits = { dcsd.realRootFinder.findFirstRootIn( surfacePoly, interval.x, interval.y ) };
- if( java.lang.Double.isNaN( hits[ 0 ] ))
- hits = new double[ 0 ];
- for( double hit : hits )
- {
- if( dcsd.rayClipper.clipPoint( surfaceRay.at( hit ), true ) )
- {
- if( gradientPolys == null )
- gradientPolys = gcs.setU( u );
- Vector3d n_surfaceSpace = gradientPolys.setT( hit );
- Vector3d n_cameraSpace = dcsd.rayCreator.surfaceSpaceNormalToCameraSpaceNormal( n_surfaceSpace );
-
- return shade( ray.at( hit ), n_cameraSpace, eye );
- }
- }
- }
- }
- return dcsd.backgroundColor;
- }
-
-// private Color3f traceRay( double u, double v )
-// {
-// // create rays
-// Ray ray = dcsd.rayCreator.createCameraSpaceRay( u, v );
-// Ray clippingRay = dcsd.rayCreator.createClippingSpaceRay( u, v );
-// Ray surfaceRay = dcsd.rayCreator.createSurfaceSpaceRay( u, v );
-//
-// Point3d eye = Helper.interpolate1D( ray.o, ray.d, dcsd.rayCreator.getEyeLocationOnRay() );
-// UnivariatePolynomialVector3d gradientPolys = null;
-//
-// // optimize rays and root-finder parameters
-// //optimizeRays( ray, clippingRay, surfaceRay );
-//
-// //System.out.println( u + "," + v + ":("+surfaceRay.o.x+","+surfaceRay.o.y+","+surfaceRay.o.z+")"+"("+surfaceRay.d.x+","+surfaceRay.d.y+","+surfaceRay.d.z+")t" );
-//
-// // clip ray
-// List< Vector2d > intervals = dcsd.rayClipper.clipRay( clippingRay );
-// for( Vector2d interval : intervals )
-// {
-// // adjust interval, so that it does not start before the eye point
-// double eyeLocation = dcsd.rayCreator.getEyeLocationOnRay();
-//
-// if( interval.x < eyeLocation && eyeLocation < interval.y )
-// interval.x = Math.max( interval.x, eyeLocation );
-//
-// // intersect ray with surface and shade pixel
-// double[] hit = new double[ 1 ];
-// if( intersect( surfaceRay, interval.x, interval.y, hit ) )
-// if( dcsd.rayClipper.clipPoint( surfaceRay.at( hit[ 0 ] ), true ) )
-// {
-// if( gradientPolys == null )
-// gradientPolys = gcs.setU( u );
-// Vector3d n_surfaceSpace = gradientPolys.setT( hit );
-// Vector3d n_cameraSpace = dcsd.rayCreator.surfaceSpaceNormalToCameraSpaceNormal( n_surfaceSpace );
-//
-// return shade( ray.at( hit ), n_cameraSpace, eye );
-// }
-// return shade( ray, surfaceRay, hit[ 0 ], eye );
-// }
-// return dcsd.backgroundColor;
-// }
-
- private float colorDiffSqr( Color3f c1, Color3f c2 )
- {
- Vector3f diff = new Vector3f( c1 );
- diff.sub( c2 );
- return diff.dot( diff );
- }
-
- protected boolean intersectPolynomial( UnivariatePolynomial p, double rayStart, double rayEnd, double[] hit )
- {
- //System.out.println( p );
- hit[ 0 ] = dcsd.realRootFinder.findFirstRootIn( p, rayStart, rayEnd );
- return !java.lang.Double.isNaN( hit[ 0 ] );
- }
-
- protected boolean intersect( Ray r, double rayStart, double rayEnd, double[] hit )
- {
- UnivariatePolynomial x = new UnivariatePolynomial( r.o.x, r.d.x );
- UnivariatePolynomial y = new UnivariatePolynomial( r.o.y, r.d.y );
- UnivariatePolynomial z = new UnivariatePolynomial( r.o.z, r.d.z );
-
- UnivariatePolynomial p = dcsd.coefficientCalculator.calculateCoefficients( x, y, z );
- p = p.shrink();
-
- hit[ 0 ] = ( float ) dcsd.realRootFinder.findFirstRootIn( p, rayStart, rayEnd );
- return !java.lang.Double.isNaN( hit[ 0 ] );
- }
-
- boolean blowUpChooseMaterial( Point3d p )
- {
- double R;
- if( dcsd.rayClipper instanceof de.mfo.jsurf.rendering.cpu.clipping.ClipBlowUpSurface )
- R = ( ( de.mfo.jsurf.rendering.cpu.clipping.ClipBlowUpSurface ) dcsd.rayClipper ).get_R();
- else
- R = 1.0;
-
- double u = p.x;
- double tmp = Math.sqrt( p.y*p.y + p.z*p.z );
- double v = R + tmp;
- double dist = u * u + v * v;
- if( dist > 1.0 )
- v = R - tmp; // choose the solution inside the disc
- return ( 3.0 * dist ) % 2.0 < 1.0;
- }
-
- /**
- * Calculates the shading in camera space
- * @param p The hit point on the surface in camera space.
- * @param n The surface normal at the hit point in camera space.
- * @param eye The eye point in camera space.
- * @return
- */
- protected Color3f shade( Point3d p, Vector3d n, Point3d eye )
- {
- // normalize only if point is not singular
- float nLength = (float) n.length();
- if( nLength != 0.0f )
- n.scale( 1.0f / nLength );
-
- // compute view vector
- Vector3d v = new Vector3d( eye );
- v.sub( p );
- v.normalize();
-/*
- // special coloring for blowup-visualization
- if( n.dot( v ) < 0.0f )
- n.negate();
- if( blowUpChooseMaterial( dcsd.rayCreator.cameraSpaceToSurfaceSpace( p ) ) )
- {
- return shadeWithMaterial( p, v, n, dcsd.frontAmbientColor, dcsd.frontLightProducts );
- }
- else
- {
- return shadeWithMaterial( p, v, n, dcsd.backAmbientColor, dcsd.backLightProducts );
- }
-*/
- // compute, which material to use
- if( n.dot( v ) > 0.0f )
- {
- return shadeWithMaterial( p, v, n, dcsd.frontAmbientColor, dcsd.frontLightProducts );
- }
- else
- {
- n.negate();
- return shadeWithMaterial( p, v, n, dcsd.backAmbientColor, dcsd.backLightProducts );
- }
- }
-
- /**
- * Shades a point with the same algorithm used by the
- * {@link surf raytracer}.
- * @param hitPoint Intersection point.
- * @param v View vector (from intersection point to eye).
- * @param n Surface normal.
- * @param material Surface material.
- * @return
- */
- protected Color3f shadeWithMaterial( Point3d hitPoint, Vector3d v, Vector3d n, Color3f ambientColor, LightProducts[] lightProducts )
- {
- Vector3d l = new Vector3d();
- Vector3d h = new Vector3d();
-
- Color3f color = new Color3f( ambientColor );
-
- for( int i = 0; i < dcsd.lightSources.length; i++ )
- {
- LightSource lightSource = dcsd.lightSources[i];
-
- l.sub( lightSource.getPosition(), hitPoint );
- l.normalize();
-
- float lambertTerm = (float) n.dot( l );
- if( lambertTerm > 0.0f )
- {
- // compute diffuse color component
- color.scaleAdd( lambertTerm, lightProducts[i].getDiffuseProduct(), color );
-
- // compute specular color component
- h.add( l, v );
- h.normalize();
-
- color.scaleAdd( ( float ) Math.pow( Math.max( 0.0f, n.dot( h ) ), lightProducts[i].getMaterial().getShininess() ), lightProducts[i].getSpecularProduct(), color );
- }
- }
-
- color.clampMax( 1.0f );
-
- return color;
- }
+ private void renderImage(PixelRenderStrategy pixelRenderer) {
+ for( int y = 0; y < step.height; ++y )
+ {
+ ColumnSubstitutorPair csp = cspProvider.get(step.v);
+
+ for( int x = 0; x < step.width; ++x )
+ {
+ if( Thread.currentThread().isInterrupted() )
+ throw new RenderingInterruptedException();
+
+ pixelRenderer.renderPixel(x, y, step, csp);
+ step.stepU();
+ }
+ step.stepV();
+ }
+ }
}
-
-
diff --git a/src/main/java/de/mfo/jsurf/rendering/cpu/Shader.java b/src/main/java/de/mfo/jsurf/rendering/cpu/Shader.java
new file mode 100644
index 0000000..d37f4cb
--- /dev/null
+++ b/src/main/java/de/mfo/jsurf/rendering/cpu/Shader.java
@@ -0,0 +1,62 @@
+package de.mfo.jsurf.rendering.cpu;
+
+import javax.vecmath.Color3f;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3d;
+
+import de.mfo.jsurf.rendering.LightProducts;
+import de.mfo.jsurf.rendering.LightSource;
+
+public class Shader {
+ private final Color3f ambientColor;
+ private final LightSource[] lightSources;
+ private final LightProducts[] lightProducts;
+
+ public Shader(Color3f ambientColor, LightSource[] lightSources, LightProducts[] lightProducts) {
+ this.ambientColor = ambientColor;
+ this.lightSources = lightSources;
+ this.lightProducts = lightProducts;
+ }
+
+ /**
+ * Shades a point with the same algorithm used by the
+ * {@link surf raytracer}.
+ * @param hitPoint Intersection point.
+ * @param view View vector (from intersection point to eye).
+ * @param normal Surface normal.
+ * @return
+ */
+ protected Color3f shade( Point3d hitPoint, Vector3d view, Vector3d normal)
+ {
+ Vector3d lightDirection = new Vector3d();
+ Vector3d h = new Vector3d();
+
+ Color3f color = new Color3f( ambientColor );
+
+ for( int i = 0; i < lightSources.length; i++ )
+ {
+ LightSource lightSource = lightSources[i];
+ LightProducts lightProducts = this.lightProducts[i];
+
+ lightDirection.sub( lightSource.getPosition(), hitPoint );
+ lightDirection.normalize();
+
+ float lambertTerm = (float) normal.dot( lightDirection );
+ if( lambertTerm > 0.0f )
+ {
+ // compute diffuse color component
+ color.scaleAdd( lambertTerm, lightProducts.getDiffuseProduct(), color );
+
+ // compute specular color component
+ h.add( lightDirection, view );
+ h.normalize();
+ double specularTerm = Math.pow( Math.max( 0.0f, normal.dot(h) ), lightProducts.getMaterial().getShininess() );
+ color.scaleAdd( (float)specularTerm, lightProducts.getSpecularProduct(), color );
+ }
+ }
+
+ color.clampMax( 1.0f );
+
+ return color;
+ }
+}