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; + } +}