/// <summary> /// Builds a 3d noise texture /// </summary> /// <param name="parameters">Builder parameters</param> /// <returns>Returns a <see cref="Texture3dData"/> object containing the generated noise texture</returns> /// <exception cref="System.ArgumentNullException">Thrown if parameters is null</exception> /// <exception cref="System.IO.InvalidDataException">Thrown if parameters are invalid</exception> public static unsafe Texture3dData Build( Noise3dTextureBuilderParameters parameters ) { Arguments.CheckNotNull( parameters, "parameters" ); parameters.Validate( ); Texture3dData data = new Texture3dData( parameters.Width, parameters.Height, parameters.Depth, parameters.Format ); fixed( byte* voxels = data.Bytes ) { GenerateNoise( data, parameters, voxels ); } return data; }
/// <summary> /// Writes a 3d texture to a stream /// </summary> /// <param name="textureDataArray">Texture data array</param> /// <param name="stream">Stream to write to</param> public static void WriteTextureToStream( Texture3dData[] textureDataArray, Stream stream ) { Arguments.CheckNotNullAndContainsNoNulls( textureDataArray, "textureDataArray" ); Arguments.CheckNotNull( stream, "stream" ); if ( textureDataArray.Length == 0 ) { throw new ArgumentException( "Texture data array was empty - must contain at least one element", "textureDataArray" ); } using ( BinaryWriter writer = new BinaryWriter( stream ) ) { writer.Write( TextureFileFormatVersion1.TextureFileIdentifier ); TextureFileFormatVersion1.Group headerGroup = new TextureFileFormatVersion1.Group( ); headerGroup.GroupId = GroupIdentifier.TextureHeaderGroup; long startOfHeaderGroup = BeginGroup( writer, headerGroup ); // Write the header TextureFileFormatVersion1.Header header = new TextureFileFormatVersion1.Header( ); header.Dimensions = 3; header.Format = textureDataArray[ 0 ].Format; header.TextureDataEntries = textureDataArray.Length; header.Write( writer ); // Write texture data TextureFileFormatVersion1.Group textureDataGroup = new TextureFileFormatVersion1.Group( ); textureDataGroup.GroupId = GroupIdentifier.Texture3dDataGroup; for ( int textureDataIndex = 0; textureDataIndex < textureDataArray.Length; ++textureDataIndex ) { // Write the texture group long startOfTextureGroup = BeginGroup( writer, textureDataGroup ); Texture3dData textureData = textureDataArray[ textureDataIndex ]; // Write texture data writer.Write( textureData.Width ); writer.Write( textureData.Height ); writer.Write( textureData.Depth ); writer.Write( textureData.Bytes ); // Update the texture group EndGroup( writer, startOfTextureGroup ); } // Update group with correct size EndGroup( writer, startOfHeaderGroup ); } }
/// <summary> /// Loads 3d texture data from the specified stream /// </summary> private static Texture3dData[] Load3dTextureData( BinaryReader reader, TextureFileFormatVersion1.Header header ) { Texture3dData[] textureDataArray = new Texture3dData[ header.TextureDataEntries ]; for ( int textureDataCount = 0; textureDataCount < header.TextureDataEntries; ++textureDataCount ) { TextureFileFormatVersion1.Group textureGroup = TextureFileFormatVersion1.Group.Read( reader ); if ( textureGroup.GroupId != GroupIdentifier.Texture3dDataGroup ) { throw new FileLoadException( "Expected texture group" ); } int width = reader.ReadInt32( ); int height = reader.ReadInt32( ); int depth = reader.ReadInt32( ); Texture3dData texData = new Texture3dData( ); texData.Create( width, height, depth, header.Format ); reader.Read( texData.Bytes, 0, texData.Bytes.Length ); textureDataArray[ textureDataCount ] = texData; } return textureDataArray; }
// // Discrepancies in atmospheric modelling papers: // - HG: // - Neilsen: HG(t,g) = (1-g2)/(4pi(1 + g2 - 2cos(t))^1.5) // - Wolfram: HG(t,g) = (1-g2)/(1 + g2 - 2.g.cos(t))^1.5 // - O'Neil (adapted HG): HG(t,g) = 3(1-g2)/2(2+g2) . (1+t2)/(1+g2 - 2gt)^1.5 // - O'Neil and Wolfram work well. Neilsen doesn't (denominator can evalute to < 0 before raising to 3/2 power) // - Scattering coefficients: (# is wavelength) // - Neilsen Rayleigh coefficient: 8pi^2(n2-1)^2/3N#^4 // - Neilsen Mie coefficient: 0.434c.pi.4pi^2/#^2K // - K=non-wavelength dependent fudge factor for Bm: ~0.67 // - c=concentration factor(0.6544T-0.6510).10e-16 (T=turbidity. ~555nm) // // // // // Realtime rendering of atmospheric scattering for flight simulators: // http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/2554/pdf/imm2554.pdf // // Display of The Earth Taking into Account Atmospheric Scattering: (Nishita) // http://nis-lab.is.s.u-tokyo.ac.jp/~nis/abs_sig.html#sig93 // // Implementation of Nishita's paper: // http://www.gamedev.net/reference/articles/article2093.asp // // A practical analytical model of daylight: // http://www.cs.utah.edu/vissim/papers/sunsky/sunsky.pdf // // Discussion about atmospheric shaders: // http://www.gamedev.net/community/forums/topic.asp?topic_id=335023 // // Paper that this model is based on: // http://www.vis.uni-stuttgart.de/~schafhts/HomePage/pubs/wscg07-schafhitzel.pdf // // Heyney-Greenstein on Mathworld: // http://scienceworld.wolfram.com/physics/Heyney-GreensteinPhaseFunction.html // /// <summary> /// Builds a 3D lookup texture /// </summary> /// <param name="model">The atmosphere model</param> /// <param name="parameters">Parameters for the build process</param> /// <param name="progress">Optional progress object</param> /// <returns>Returns a 3d texture data. Returns null if cancel was flagged in the progress object</returns> public unsafe AtmosphereBuildOutputs Build( AtmosphereBuildModel model, AtmosphereBuildParameters parameters, AtmosphereBuildProgress progress ) { Texture2dData opticalDepthTexture = new Texture2dData( ); Texture3dData scatteringTexture = new Texture3dData( ); scatteringTexture.Create( parameters.ViewAngleSamples, parameters.SunAngleSamples, parameters.HeightSamples, TextureFormat.R8G8B8A8 ); SetupModelAndParameters( parameters, model ); progress = progress ?? new AtmosphereBuildProgress( ); fixed ( byte* voxels = scatteringTexture.Bytes ) { if ( !BuildScatteringTexture( parameters, voxels, progress ) ) { return null; } } if ( progress.Cancel ) { return null; } opticalDepthTexture.Create( parameters.OpticalDepthResolution, parameters.OpticalDepthResolution, TextureFormat.R8G8B8 ); fixed ( byte* pixels = opticalDepthTexture.Bytes ) { if ( !BuildOpticalDepthTexture( parameters, pixels, progress ) ) { return null; } } return new AtmosphereBuildOutputs( scatteringTexture, opticalDepthTexture ); }
/// <summary> /// Generates a toroidally tiled noise /// </summary> private static unsafe void GenerateNoise( Texture3dData data, Noise3dTextureBuilderParameters parameters, byte* voxels ) { SimpleNoise3d sNoise = new SimpleNoise3d(); Noise noise = new Noise( ); float nW = parameters.NoiseWidth; float nH = parameters.NoiseHeight; float nD = parameters.NoiseDepth; float nX0 = parameters.NoiseX; float nY0 = parameters.NoiseY; float nZ0 = parameters.NoiseZ; float nXInc = nW / data.Width; float nYInc = nH / data.Height; float nZInc = nD / data.Depth; byte* voxel = voxels; int bytesPerVoxel = TextureFormatInfo.GetSizeInBytes( parameters.Format ); if ( bytesPerVoxel != 3 && bytesPerVoxel != 4 ) { throw new InvalidDataException( string.Format( "Unexpected voxel stride of {0} - expected either 3 or 4 from format {1}", bytesPerVoxel, parameters.Format ) ); } // Check hacked channel expectations... bool hasAlpha = TextureFormatInfo.HasAlphaChannel( parameters.Format ); float nZ = nZ0; float maxNoise = float.MinValue; float minNoise = float.MaxValue; for ( int z = 0; z < parameters.Depth; ++z, nZ += nZInc ) { float nY = nY0; for ( int y = 0; y < parameters.Height; ++y, nY += nYInc ) { float nX = nX0; for ( int x = 0; x < parameters.Width; ++x, nX += nXInc ) { switch ( parameters.GenerationType ) { case NoiseGenerationType.Grayscale : { float n = ( 1.0f + sNoise.GetTilingNoise( nX, nY, nZ, nW, nH, nD ) ) / 2; maxNoise = Math.Max( maxNoise, n ); minNoise = Math.Min( minNoise, n ); // float n = 0.5f + noise.GetNoise( nX, nY, nZ ); voxel[ 0 ] = NoiseToByte( n ); voxel[ 1 ] = voxel[ 0 ]; voxel[ 2 ] = voxel[ 0 ]; if ( hasAlpha ) { voxel[ 3 ] = voxel[ 0 ]; } break; } case NoiseGenerationType.MultiChannel : { float r = noise.GetNoise( nX, nY, nZ ); float g = noise.GetNoise( nX + nW, nY, nZ ); float b = noise.GetNoise( nX, nY + nH, nZ ); voxel[ 0 ] = NoiseToByte( r ); voxel[ 1 ] = NoiseToByte( g ); voxel[ 2 ] = NoiseToByte( b ); if ( hasAlpha ) { float a = noise.GetNoise( nX, nY, nZ + parameters.NoiseDepth ); voxel[ 3 ] = NoiseToByte( a ); } break; } case NoiseGenerationType.TilingGrayscale : { float n = GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD ); voxel[ 0 ] = NoiseToByte( n ); voxel[ 1 ] = voxel[ 0 ]; voxel[ 2 ] = voxel[ 0 ]; if ( hasAlpha ) { voxel[ 3 ] = voxel[ 0 ]; } break; } case NoiseGenerationType.TilingMultiChannel : { float r = GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD ); float g = GetTiledNoise( noise, nX + nW, nY, nZ, nX0 + nW, nY0, nZ0, nW, nH, nD ); float b = GetTiledNoise( noise, nX, nY + nH, nZ, nX0, nY0 + nH, nZ0, nW, nH, nD ); voxel[ 0 ] = NoiseToByte( r ); voxel[ 1 ] = NoiseToByte( g ); voxel[ 2 ] = NoiseToByte( b ); if ( hasAlpha ) { float a = GetTiledNoise( noise, nX, nY, nZ + nD, nX0, nY0, nZ0 + nD, nW, nH, nD ); voxel[ 3 ] = NoiseToByte( a ); } break; } case NoiseGenerationType.BigNoise : { float real = 0.5f + GetTiledNoise( noise, nX, nY, nZ, nX0, nY0, nZ0, nW, nH, nD ); float imag = 0.5f + GetTiledNoise( noise, nX + nW, nY + nH, nZ + nD, nX0, nY0, nZ0, nW, nH, nD ); // maxNoise = Math.Max( maxNoise, real ); // minNoise = Math.Min( minNoise, real ); double a = Math.PI + Math.PI * real; voxel[ 0 ] = NoiseToByte( real ); voxel[ 1 ] = NoiseToByte( imag ); voxel[ 2 ] = ( byte )Utils.Clamp( ( Math.Sin( a ) + 1 ) * 127.5, 0, 255 ); voxel[ 3 ] = ( byte )Utils.Clamp( ( Math.Cos( a ) + 1 ) * 127.5, 0, 255 ); break; } default : throw new NotSupportedException( string.Format( "Noise generation type \"{0}\" is not supported yet", parameters.GenerationType ) ); } voxel += bytesPerVoxel; } } } }
/// <summary> /// Sets up the build outputs /// </summary> /// <param name="scatteringTexture">Scattering texture data</param> /// <param name="opticalDepthTexture">Optical depth texture data</param> public AtmosphereBuildOutputs( Texture3dData scatteringTexture, Texture2dData opticalDepthTexture ) { m_ScatteringTexture = scatteringTexture; m_OpticalDepthTexture = opticalDepthTexture; }
/// <summary> /// Creates this texture from a texture data object /// </summary> public unsafe void Create( Texture3dData data ) { m_Width = data.Width; m_Height = data.Height; m_Depth = data.Depth; m_Format = data.Format; m_Handle = OpenGlTextureHandle.CreateHandle( ); OpenGlTexture2dBuilder.TextureInfo info = OpenGlTexture2dBuilder.CheckTextureFormat( m_Format ); int target = Gl.GL_TEXTURE_3D; int border = 0; Gl.glEnable( target ); Gl.glBindTexture( target, m_Handle ); fixed ( void* bytes = data.Bytes ) { Gl.glTexImage3D( target, 0, info.GlInternalFormat, m_Width, m_Height, m_Depth, border, info.GlFormat, info.GlType, new IntPtr( bytes ) ); } }