/// <summary> /// Random direction around the [0,0,1] vector. /// Normal distribution is used, using required variance. /// </summary> /// <param name="rnd">Random generator to be used.</param> /// <param name="variance">Variance of the deviation angle in radians.</param> /// <returns></returns> public static Vector3d RandomDirectionNormal(RandomJames rnd, double variance) { // result: [0,0,1] * rotX(deviation) * rotZ(orientation) double deviation = rnd.Normal(0.0, variance); double orientation = rnd.RandomDouble(0.0, Math.PI); Matrix4d mat = Matrix4d.CreateRotationX(deviation) * Matrix4d.CreateRotationZ(orientation); return(new Vector3d(mat.Row2)); // [0,0,1] * mat }
/// <summary> /// Random direction around the givent center vector. /// Normal distribution is used, using required variance. /// </summary> /// <param name="rnd">Random generator to be used.</param> /// <param name="dir">Central direction.</param> /// <param name="variance">Variance of the deviation angle in radians.</param> /// <returns></returns> public static Vector3d RandomDirectionNormal(RandomJames rnd, Vector3d dir, double variance) { // Matrix3d fromz: [0,0,1] -> dir // Vector4d delta: [0,0,1] * rotX(deviation) * rotZ(orientation) // result: delta * fromz dir.Normalize(); Vector3d axis1, axis2; GetAxes(ref dir, out axis1, out axis2); Matrix4d fromz = new Matrix4d(new Vector4d(axis1), new Vector4d(axis2), new Vector4d(dir), Vector4d.UnitW); //fromz.Transpose(); double deviation = rnd.Normal(0.0, variance); double orientation = rnd.RandomDouble(0.0, Math.PI); Matrix4d mat = Matrix4d.CreateRotationX(deviation) * Matrix4d.CreateRotationZ(orientation) * fromz; return(new Vector3d(mat.Row2)); // [0,0,1] * mat }
/// <summary> /// Computes one image sample. Internal integration support. /// </summary> /// <param name="x">Horizontal coordinate.</param> /// <param name="y">Vertical coordinate.</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="color">Computed sample color.</param> /// <returns>Hash-value used for adaptive subsampling.</returns> public virtual long GetSample( double x, double y, int rank, int total, RandomJames rnd, double[] color ) { Vector3d p0, p1; int bands = color.Length; if ( !scene.Camera.GetRay( x, y, rank, total, rnd, out p0, out p1 ) ) { Array.Clear( color, 0, bands ); // invalid ray -> black color return 1L; } LinkedList<Intersection> intersections = scene.Intersectable.Intersect( p0, p1 ); Intersection.countRays++; Intersection i = Intersection.FirstIntersection( intersections, ref p1 ); if ( i == null ) // no intersection -> background color { Array.Copy( scene.BackgroundColor, color, bands ); return 0L; } // there was at least one intersection i.Complete(); // hash code for adaptive supersampling: long hash = i.Solid.GetHashCode(); // apply all the textures fist.. if ( i.Textures != null ) foreach ( ITexture tex in i.Textures ) hash = hash * HASH_TEXTURE + tex.Apply( i, rank, total, rnd ); // terminate if light sources are missing if ( scene.Sources == null || scene.Sources.Count < 1 ) { Array.Copy( i.SurfaceColor, color, bands ); return hash; } // .. else apply the reflectance model for each source p1 = -p1; p1.Normalize(); i.Material = (IMaterial)i.Material.Clone(); i.Material.Color = i.SurfaceColor; Array.Clear( color, 0, bands ); foreach ( ILightSource source in scene.Sources ) { Vector3d dir; double[] intensity = source.GetIntensity( i, rank, total, rnd, out dir ); if ( intensity != null ) { double[] reflection = i.ReflectanceModel.ColorReflection( i, dir, p1, ReflectionComponent.ALL ); if ( reflection != null ) { for ( int b = 0; b < bands; b++ ) color[ b ] += intensity[ b ] * reflection[ b ]; hash = hash * HASH_LIGHT + source.GetHashCode(); } } } return hash; }
/// <summary> /// Computes one image sample. Internal integration support. /// </summary> /// <param name="x">Horizontal coordinate.</param> /// <param name="y">Vertical coordinate.</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="color">Computed sample color.</param> /// <returns>Hash-value used for adaptive subsampling.</returns> public override long GetSample( double x, double y, int rank, int total, RandomJames rnd, double[] color ) { // initial color = black Array.Clear( color, 0, color.Length ); Vector3d p0, p1; if ( !scene.Camera.GetRay( x, y, rank, total, rnd, out p0, out p1 ) ) return 11L; long hash = shade( 0, 1.0, ref p0, ref p1, rank, total, rnd, color ); return hash; }
/// <summary> /// Recursive shading function - computes color contribution of the given ray (shot from the /// origin 'p0' into direction vector 'p1''). Recursion is stopped /// by a hybrid method: 'importance' and 'level' are checked. /// Internal integration support. /// </summary> /// <param name="level">Current recursion depth.</param> /// <param name="importance">Importance of the current ray.</param> /// <param name="p0">Ray origin.</param> /// <param name="p1">Ray direction vector.</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="color">Result color.</param> /// <returns>Hash-value (ray sub-signature) used for adaptive subsampling.</returns> protected virtual long shade( int level, double importance, ref Vector3d p0, ref Vector3d p1, int rank, int total, RandomJames rnd, double[] color ) { int bands = color.Length; LinkedList<Intersection> intersections = scene.Intersectable.Intersect( p0, p1 ); Intersection.countRays++; Intersection i = Intersection.FirstIntersection( intersections, ref p1 ); int b; if ( i == null ) // no intersection -> background color { Array.Copy( scene.BackgroundColor, color, bands ); return 1L; } // there was at least one intersection i.Complete(); // hash code for adaptive supersampling: long hash = i.Solid.GetHashCode(); // apply all the textures fist.. if ( i.Textures != null ) foreach ( ITexture tex in i.Textures ) hash = hash * HASH_TEXTURE + tex.Apply( i, rank, total, rnd ); p1 = -p1; // viewing vector p1.Normalize(); if ( scene.Sources == null || scene.Sources.Count < 1 ) // no light sources at all Array.Copy( i.SurfaceColor, color, bands ); else { // apply the reflectance model for each source i.Material = (IMaterial)i.Material.Clone(); i.Material.Color = i.SurfaceColor; Array.Clear( color, 0, bands ); foreach ( ILightSource source in scene.Sources ) { Vector3d dir; double[] intensity = source.GetIntensity( i, rank, total, rnd, out dir ); if ( intensity != null ) { if ( DoShadows && !dir.Equals( Vector3d.Zero ) ) { intersections = scene.Intersectable.Intersect( i.CoordWorld, dir ); Intersection.countRays++; Intersection si = Intersection.FirstIntersection( intersections, ref dir ); // Better shadow testing: intersection between 0.0 and 1.0 kills the lighting if ( si != null && !si.Far( 1.0, ref dir ) ) continue; } double[] reflection = i.ReflectanceModel.ColorReflection( i, dir, p1, ReflectionComponent.ALL ); if ( reflection != null ) { for ( b = 0; b < bands; b++ ) color[ b ] += intensity[ b ] * reflection[ b ]; hash = hash * HASH_LIGHT + source.GetHashCode(); } } } } // check the recursion depth: if ( level++ >= MaxLevel || !DoReflections && !DoRefractions ) return hash; // no further recursion Vector3d r; double maxK; double[] comp = new double[ bands ]; double newImportance; if ( DoReflections ) // trying to shoot a reflected ray.. { Geometry.SpecularReflection( ref i.Normal, ref p1, out r ); double[] ks = i.ReflectanceModel.ColorReflection( i, p1, r, ReflectionComponent.SPECULAR_REFLECTION ); if ( ks != null ) { maxK = ks[ 0 ]; for ( b = 1; b < bands; b++ ) if ( ks[ b ] > maxK ) maxK = ks[ b ]; newImportance = importance * maxK; if ( newImportance >= MinImportance ) // do compute the reflected ray { hash += HASH_REFLECT * shade( level, newImportance, ref i.CoordWorld, ref r, rank, total, rnd, comp ); for ( b = 0; b < bands; b++ ) color[ b ] += ks[ b ] * comp[ b ]; } } } if ( DoRefractions ) // trying to shoot a refracted ray.. { maxK = i.Material.Kt; // simple solution, no influence of reflectance model yet newImportance = importance * maxK; if ( newImportance < MinImportance ) return hash; // refracted ray: if ( (r = Geometry.SpecularRefraction( i.Normal, i.Material.n, p1 )) == null ) return hash; hash += HASH_REFRACT * shade( level, newImportance, ref i.CoordWorld, ref r, rank, total, rnd, comp ); for ( b = 0; b < bands; b++ ) color[ b ] += maxK * comp[ b ]; } return hash; }
/// <summary> /// Renders the given rectangle into the given raster image. /// Has to be re-entrant since this code is started in multiple parallel threads. /// </summary> /// <param name="image">Pre-initialized raster image.</param> /// <param name="sel">Selector for this working thread.</param> /// <param name="rnd">Thread-specific random generator.</param> public virtual void RenderRectangle( Bitmap image, int x1, int y1, int x2, int y2, ThreadSelector sel, RandomJames rnd ) { bool lead = sel( 0L ); if ( lead && ProgressData != null ) lock ( ProgressData ) { ProgressData.Finished = 0.0f; ProgressData.Message = ""; } double[] color = new double[ 3 ]; // pixel color // run several phases of image rendering: int cell = 32; // cell size while ( cell > 1 && cell > Adaptive ) cell >>= 1; int initCell = cell; int x, y; bool xParity, yParity; float total = (x2 - x1) * (y2 - y1); long counter = 0L; long units = 0; do // do one phase { for ( y = y1, yParity = false; y < y2; // one image row y += cell, yParity = !yParity ) for ( x = x1, xParity = false; x < x2; // one image cell x += cell, xParity = !xParity ) if ( cell == initCell || xParity || yParity ) // process the cell { if ( !sel( counter++ ) ) continue; // determine sample color .. RenderPixel( x, y, color, rnd ); if ( Gamma <= 0.001 ) for ( int b = 0; b < color.Length; b++ ) color[ b ] = Arith.Clamp( color[ b ], 0.0, 1.0 ); // .. and render it: Color c = Color.FromArgb( (int)(color[ 0 ] * 255.0), (int)(color[ 1 ] * 255.0), (int)(color[ 2 ] * 255.0) ); lock ( image ) { if ( cell == 1 ) image.SetPixel( x, y, c ); else { int xMax = x + cell; if ( xMax > x2 ) xMax = x2; int yMax = y + cell; if ( yMax > y2 ) yMax = y2; for ( int iy = y; iy < yMax; iy++ ) for ( int ix = x; ix < xMax; ix++ ) image.SetPixel( ix, iy, c ); } } if ( (++units & 63L) == 0L && ProgressData != null ) lock ( ProgressData ) { if ( !ProgressData.Continue ) return; if ( lead ) { ProgressData.Finished = counter / total; ProgressData.Sync( image ); } } } } while ( (cell >>= 1) > 0 ); // do one phase }
/// <summary> /// Ray-generator. Internal integration support. /// </summary> /// <param name="x">Origin position within a viewport (horizontal coordinate).</param> /// <param name="y">Origin position within a viewport (vertical coordinate).</param> /// <param name="rank">Rank of this ray, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of rays (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="p0">Ray origin.</param> /// <param name="p1">Ray direction vector.</param> /// <returns>True if the ray (viewport position) is valid.</returns> public bool GetRay( double x, double y, int rank, int total, RandomJames rnd, out Vector3d p0, out Vector3d p1 ) { return GetRay( x, y, out p0, out p1 ); }
/// <summary> /// Renders the single pixel of an image. /// </summary> /// <param name="x">Horizontal coordinate.</param> /// <param name="y">Vertical coordinate.</param> /// <param name="color">Computed pixel color.</param> /// <param name="rnd">Shared random generator.</param> public virtual void RenderPixel( int x, int y, double[] color, RandomJames rnd ) { ImageFunction.GetSample( x + 0.5, y + 0.5, color ); // gamma-encoding: if ( Gamma > 0.001 ) { // gamma-encoding and clamping double g = 1.0 / Gamma; for ( int b = 0; b < color.Length; b++ ) color[ b ] = Arith.Clamp( Math.Pow( color[ b ], g ), 0.0, 1.0 ); } // else: no gamma, no clamping (for HDRI) }
/// <summary> /// Renders the given rectangle into the given raster image. /// </summary> /// <param name="image">Pre-initialized raster image.</param> /// <param name="rnd">Shared random generator.</param> public virtual void RenderRectangle( Bitmap image, int x1, int y1, int x2, int y2, RandomJames rnd ) { RenderRectangle( image, x1, y1, x2, y2, ( n ) => true, rnd ); }
public SamplingState( RectangleLightSource src, RandomJames _rnd ) { source = src; rnd = _rnd; permU = new RandomJames.Permutation(); permV = new RandomJames.Permutation(); rank = total = 0; }
/// <summary> /// Returns intensity (incl. color) of the source contribution to the given scene point. /// Internal integration support. /// </summary> /// <param name="intersection">Scene point (only world coordinates and normal vector are needed).</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="dir">Direction to the source is set here (zero vector for omnidirectional source).</param> /// <returns>Intensity vector in current color space or null if the point is not lit.</returns> public double[] GetIntensity( Intersection intersection, int rank, int total, RandomJames rnd, out Vector3d dir ) { return GetIntensity( intersection, out dir ); }
/// <summary> /// Returns intensity (incl. color) of the source contribution to the given scene point. /// Internal integration support. /// </summary> /// <param name="intersection">Scene point (only world coordinates and normal vector are needed).</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <param name="dir">Direction to the source is set here (zero vector for omnidirectional source). Not normalized!</param> /// <returns>Intensity vector in current color space or null if the point is not lit.</returns> public override double[] GetIntensity( Intersection intersection, int rank, int total, RandomJames rnd, out Vector3d dir ) { if ( rnd == null ) return GetIntensity( intersection, out dir ); SamplingState ss; lock ( states ) { if ( !states.TryGetValue( rnd.GetHashCode(), out ss ) ) { ss = new SamplingState( this, rnd ); states.Add( rnd.GetHashCode(), ss ); } } // generate a [new] sample: ss.generateSample( rank, total ); dir = ss.sample - intersection.CoordWorld; if ( Vector3d.Dot( dir, intersection.Normal ) <= 0.0 ) return null; if ( Dim == null || Dim.Length < 3 ) return intensity; double dist = dir.Length; double dimCoef = 1.0 / (Dim[0] + dist * (Dim[1] + dist * Dim[2])); int bands = intensity.Length; double[] result = new double[ bands ]; for ( int i = 0; i < bands; i++ ) result[ i ] = intensity[ i ] * dimCoef; return result; }
/// <summary> /// Apply the relevant value-modulation in the given Intersection instance. /// Internal integration support. /// </summary> /// <param name="inter">Data object to modify.</param> /// <param name="rank">Rank of this sample, 0 <= rank < total (for integration).</param> /// <param name="total">Total number of samples (for integration).</param> /// <param name="rnd">Global (per-thread) instance of the random generator.</param> /// <returns>Hash value (texture signature) for adaptive subsampling.</returns> public long Apply( Intersection inter, int rank, int total, RandomJames rnd ) { return Apply( inter ); }
/// <summary> /// Renders the single pixel of an image. /// </summary> /// <param name="x">Horizontal coordinate.</param> /// <param name="y">Vertical coordinate.</param> /// <param name="color">Computed pixel color.</param> /// <param name="rnd">Shared random generator.</param> public override void RenderPixel( int x, int y, double[] color, RandomJames rnd ) { Debug.Assert( color != null ); Debug.Assert( rnd != null ); int bands = color.Length; int b; Array.Clear( color, 0, bands ); double[] tmp = new double[ bands ]; int i, j, ord; double step = 1.0 / superXY; double amplitude = Jittering * step; double origin = 0.5 * (step - amplitude); double x0, y0; for ( j = ord = 0, y0 = y + origin; j++ < superXY; y0 += step ) for ( i = 0, x0 = x + origin; i++ < superXY; x0 += step ) { ImageFunction.GetSample( x0 + amplitude * rnd.UniformNumber(), y0 + amplitude * rnd.UniformNumber(), ord++, Supersampling, rnd, tmp ); for ( b = 0; b < bands; b++ ) color[ b ] += tmp[ b ]; } double mul = step / superXY; if ( Gamma > 0.001 ) { // gamma-encoding and clamping double g = 1.0 / Gamma; for ( b = 0; b < bands; b++ ) color[ b ] = Arith.Clamp( Math.Pow( color[ b ] * mul, g ), 0.0, 1.0 ); } else // no gamma, no clamping (for HDRI) for ( b = 0; b < bands; b++ ) color[ b ] *= mul; }