/// <summary> /// Reads back a lobe texture histogram into an array /// </summary> /// <param name="_texHistogram_CPU"></param> /// <param name="_scatteringOrder"></param> /// <returns></returns> public static double[,] HistogramTexture2Array(Texture2D _texHistogram_CPU, int _scatteringOrder) { int scattMin = _scatteringOrder - 1; // Because scattering order 1 is actually stored in first slice of the texture array int scattMax = scattMin + 1; // To simulate a single scattering order // int scattMax = MAX_SCATTERING_ORDER; // To simulate all scattering orders accumulated int W = _texHistogram_CPU.Width; int H = _texHistogram_CPU.Height; double[,] histogramData = new double[W, H]; for (int scatteringOrder = scattMin; scatteringOrder < scattMax; scatteringOrder++) { PixelsBuffer Content = _texHistogram_CPU.Map(0, scatteringOrder); using (BinaryReader R = Content.OpenStreamRead()) for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { histogramData[X, Y] += W * H * R.ReadSingle(); } } Content.CloseStream(); _texHistogram_CPU.UnMap(0, scatteringOrder); } return(histogramData); }
private void FFT_CPUInOut(Complex[,] _input, Complex[,] _output, float _sign) { try { ////////////////////////////////////////////////////////////////////////// // Load initial content PixelsBuffer loadingBuffer = m_texBufferCPU.MapWrite(0, 0); System.IO.BinaryWriter W = loadingBuffer.OpenStreamWrite(); for (int Y = 0; Y < m_size; Y++) { for (int X = 0; X < m_size; X++) { W.Write((float)_input[X, Y].r); W.Write((float)_input[X, Y].i); } } loadingBuffer.CloseStream(); m_texBufferCPU.UnMap(loadingBuffer); m_texBufferIn.CopyFrom(m_texBufferCPU); ////////////////////////////////////////////////////////////////////////// // Apply multiple shader passes FFT_GPUInOut(_sign); ////////////////////////////////////////////////////////////////////////// // Read back content m_texBufferCPU.CopyFrom(m_texBufferOut); PixelsBuffer resultBuffer = m_texBufferCPU.MapRead(0, 0); System.IO.BinaryReader R = resultBuffer.OpenStreamRead(); for (int Y = 0; Y < m_size; Y++) { for (int X = 0; X < m_size; X++) { _output[X, Y].Set(R.ReadSingle(), R.ReadSingle()); } } resultBuffer.CloseStream(); m_texBufferCPU.UnMap(resultBuffer); } catch (Exception _e) { throw new Exception("An error occurred while performing the FFT!", _e); } }
/// <summary> /// Builds the surface texture from an actual image file /// </summary> /// <param name="_textureFileName"></param> /// <param name="_pixelSize">Size of a pixel, assuming the maximum height is 1</param> public unsafe void BuildSurfaceFromTexture( string _textureFileName, float _pixelSize ) { if ( m_Tex_Heightfield != null ) m_Tex_Heightfield.Dispose(); // We will create a new one so dispose of the old one... // Read the bitmap int W, H; float4[,] Content = null; using ( Bitmap BM = Bitmap.FromFile( _textureFileName ) as Bitmap ) { W = BM.Width; H = BM.Height; Content = new float4[W,H]; BitmapData LockedBitmap = BM.LockBits( new Rectangle( 0, 0, W, H ), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb ); byte R, G, B, A; for ( int Y=0; Y < H; Y++ ) { byte* pScanline = (byte*) LockedBitmap.Scan0.ToPointer() + Y*LockedBitmap.Stride; for ( int X=0; X < W; X++ ) { // Read in shitty order B = *pScanline++; G = *pScanline++; R = *pScanline++; A = *pScanline++; // Use this if you really need RGBA data // Content[X,Y].Set( R / 255.0f, G / 255.0f, B / 255.0f, A / 255.0f ); // But assuming it's a height field, we only store one component into alpha Content[X,Y].Set( 0, 0, 0, R / 255.0f ); // Use Red as height } } BM.UnlockBits( LockedBitmap ); } // Build normal (shitty version) float Hx0, Hx1, Hy0, Hy1; float3 dNx = new float3( 2.0f * _pixelSize, 0, 0 ); float3 dNy = new float3( 0, 2.0f * _pixelSize, 0 ); float3 N; for ( int Y=0; Y < H; Y++ ) { int pY = (Y+H-1) % H; int nY = (Y+1) % H; for ( int X=0; X < W; X++ ) { int pX = (X+W-1) % W; int nX = (X+1) % W; Hx0 = Content[pX,Y].w; Hx1 = Content[nX,Y].w; Hy0 = Content[X,pY].w; Hy1 = Content[X,nY].w; dNx.z = Hx1 - Hx0; dNy.z = Hy0 - Hy1; // Assuming +Y is upward N = dNx.Cross( dNy ); N = N.Normalized; Content[X,Y].x = N.x; Content[X,Y].y = N.y; Content[X,Y].z = N.z; } } // Build the texture from the array PixelsBuffer Buf = new PixelsBuffer( W*H*16 ); using ( BinaryWriter Writer = Buf.OpenStreamWrite() ) for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { float4 pixel = Content[X,Y]; Writer.Write( pixel.x ); Writer.Write( pixel.y ); Writer.Write( pixel.z ); Writer.Write( pixel.w ); } Buf.CloseStream(); m_Tex_Heightfield = new Texture2D( m_Device, W, H, 1, 1, PIXEL_FORMAT.RGBA32_FLOAT, false, false, new PixelsBuffer[] { Buf } ); }
/// <summary> /// Builds a heightfield whose heights are distributed according to the following probability (a.k.a. the normal distribution with sigma=1 and µ=0): /// p(height) = exp( -0.5*height^2 ) / sqrt(2PI) /// /// From "2015 Heitz - Generating Procedural Beckmann Surfaces" /// </summary> /// <param name="_roughness"></param> /// <remarks>Only isotropic roughness is supported</remarks> public void BuildBeckmannSurfaceTexture( float _roughness ) { m_internalChange = true; // Shouldn't happen but modifying the slider value may trigger a call to this function again, this flag prevents it // Mirror current roughness floatTrackbarControlBeckmannRoughness.Value = _roughness; // Precompute stuff that resemble a lot to the Box-Muller algorithm to generate normal distribution random values WMath.SimpleRNG.SetSeed( 521288629, 362436069 ); for ( int i=0; i < m_SB_Beckmann.m.Length; i++ ) { double U0 = WMath.SimpleRNG.GetUniform(); double U1 = WMath.SimpleRNG.GetUniform(); double U2 = WMath.SimpleRNG.GetUniform(); m_SB_Beckmann.m[i].m_phase = (float) (2.0 * Math.PI * U0); // Phase double theta = 2.0 * Math.PI * U1; double radius = Math.Sqrt( -Math.Log( U2 ) ); m_SB_Beckmann.m[i].m_frequencyX = (float) (radius * Math.Cos( theta ) * _roughness); // Frequency in X direction m_SB_Beckmann.m[i].m_frequencyY = (float) (radius * Math.Sin( theta ) * _roughness); // Frequency in Y direction } m_SB_Beckmann.Write(); m_SB_Beckmann.SetInput( 0 ); #if true if ( m_Tex_Heightfield == null ) m_Tex_Heightfield = new Texture2D( m_Device, HEIGHTFIELD_SIZE, HEIGHTFIELD_SIZE, 1, 1, PIXEL_FORMAT.RGBA32_FLOAT, false, true, null ); // Run the CS if ( m_Shader_ComputeBeckmannSurface.Use() ) { m_Tex_Heightfield.SetCSUAV( 0 ); float size = floatTrackbarControlBeckmannSizeFactor.Value * HEIGHTFIELD_SIZE; // m_CB_ComputeBeckmann.m._Position_Size.Set( -128.0f, -128.0f, 256.0f, 256.0f ); m_CB_ComputeBeckmann.m._Position_Size.Set( -0.5f * size, -0.5f * size, size, size ); m_CB_ComputeBeckmann.m._HeightFieldResolution = HEIGHTFIELD_SIZE; m_CB_ComputeBeckmann.m._SamplesCount = (uint) m_SB_Beckmann.m.Length; m_CB_ComputeBeckmann.UpdateData(); m_Shader_ComputeBeckmannSurface.Dispatch( HEIGHTFIELD_SIZE >> 4, HEIGHTFIELD_SIZE >> 4, 1 ); m_Tex_Heightfield.RemoveFromLastAssignedSlotUAV(); } #else // CPU version PixelsBuffer Content = new PixelsBuffer( HEIGHTFIELD_SIZE*HEIGHTFIELD_SIZE*System.Runtime.InteropServices.Marshal.SizeOf(typeof(float)) ); double scale = Math.Sqrt( 2.0 / N ); // Generate heights float range = 128.0f; float2 pos; float height; float minHeight = float.MaxValue, maxHeight = -float.MaxValue; double accum; using ( BinaryWriter W = Content.OpenStreamWrite() ) { for ( int Y=0; Y < HEIGHTFIELD_SIZE; Y++ ) { pos.y = range * (2.0f * Y / (HEIGHTFIELD_SIZE-1) - 1.0f); for ( int X=0; X < HEIGHTFIELD_SIZE; X++ ) { pos.x = range * (2.0f * X / (HEIGHTFIELD_SIZE-1) - 1.0f); // height = (float) WMath.SimpleRNG.GetNormal(); // height = (float) GenerateNormalDistributionHeight(); accum = 0.0; for ( int i=0; i < N; i++ ) { accum += Math.Cos( m_phi[i] + pos.x * m_fx[i] + pos.y * m_fy[i] ); } height = (float) (scale * accum); minHeight = Math.Min( minHeight, height ); maxHeight = Math.Max( maxHeight, height ); W.Write( height ); } } } Content.CloseStream(); m_Tex_Heightfield = new Texture2D( m_Device, HEIGHTFIELD_SIZE, HEIGHTFIELD_SIZE, 1, 1, PIXEL_FORMAT.R32_FLOAT, false, false, new PixelsBuffer[] { Content } ); #endif m_internalChange = false; }