zoukankan      html  css  js  c++  java
  • ue4 lightmap格式解析

    lightmass分hq(pc和主机平台)和lq(移动平台),两者都存照明颜色、照明亮度、入射光线的簇的最大贡献方向。
    一、 ue4光照图
    1. lq分上下两部分,上部分是(光线颜色的线性空间值*照明亮度经log函数处理值)的归一化值,下半部分是入射光线的簇的最大贡献方向的归一化的值。采用24为rgb8的格式存储,默认会根据平台相关的压缩方式进行压缩以减小内存占用、存储空间、渲染时占用的gpu带宽。
    2. hq也分上下两部分,但上半部分存的是(归一化后的照明颜色)的gamma空间值,和亮度log函数处理的值的整数部分再归一化的值(A通道);下半部分存入射光线的簇的最大贡献方向值的归一化的值,和亮度log函数处理的值的小数部分的归一化值(A通道)。所以多一个通道,用rgb32存储,会用平台相关的压缩方式进行压缩,但压缩后还是比lq的大接近一倍。
    二、 光照图编码方式
    由于光照亮度的亮度范围应该是浮点数区间,所以最好的存储模式是浮点数格式,即exr,但是其存储、内存占用、带宽更大,同时有平台兼容性问题,所以各引擎一般在移动端用rgb8或rgba8图。
    把亮度从浮点数压缩到0-255的整数范围会造成精度的丢失,造成光照图质量降低。
    ue4用自己开发的编码分别对lq和hq进行编码压缩。
    1. lq亮度编码
    LQ亮度压缩的量化函数(函数1):

    y表示编码后的亮度,值域为 y∈[0,1]

    常数0.00390625为最小亮度

    函数图示:
    x取值范围是0-255,y取值范围是0-1。看图,x从0-1的过程中,y从0变到了0.5,可以看出占总定义域的1/255的x取值占了%50的值域。这说明,ue4 做了取舍,把更多的精度给了暗部,而损失了部分亮部的精度。由于lightmap烘焙的都是间接光亮度,所以这有一定的合理性。x的取值范围是0-255,实际却用不了这么多,所以后面会进行归一化处理,即将该函数结果乘亮度的结果进行归一化。
    2. hq亮度编码

    上式中y表示编码后的亮度,取值域为 y∈[0,1]

    常数0.01858136为最小亮度

    函数图示如下:
    观察该函数的曲线知道,和LQ一样,为暗部保留更多的细节。
    x为1时,y为0;x为2-0.01858时,y为1.
    所以,也会对该log函数的结果进行归一化,保证亮度范围的变化性和精度的高利用率。
    三、 整个过程
    1. 

    遍历各个sample,

    hq获得uvw和亮度的log值的min、max值;方向*亮度的值的最大最小值。

    static void GetLUVW( const float RGB[3], float& L, float& U, float& V, float& W )
    	{
    		float R = FMath::Max( 0.0f, RGB[0] );
    		float G = FMath::Max( 0.0f, RGB[1] );
    		float B = FMath::Max( 0.0f, RGB[2] );
    
    		L = 0.3f * R + 0.59f * G + 0.11f * B;
    		if( L < 1e-4f )
    		{
    			U = 1.0f;
    			V = 1.0f;
    			W = 1.0f;
    		}
    		else
    		{
    			U = R / L;
    			V = G / L;
    			W = B / L;
    		}
    	}
    

      

    {
    					// Complex
    					float L, U, V, W;
    					GetLUVW( SourceSample.Coefficients[0], L, U, V, W );
    
    					float LogL = FMath::Log2( L + LogBlackPoint );
    
    					MinCoefficient[0][0] = FMath::Min( MinCoefficient[0][0], U );
    					MaxCoefficient[0][0] = FMath::Max( MaxCoefficient[0][0], U );
    
    					MinCoefficient[0][1] = FMath::Min( MinCoefficient[0][1], V );
    					MaxCoefficient[0][1] = FMath::Max( MaxCoefficient[0][1], V );
    				
    					MinCoefficient[0][2] = FMath::Min( MinCoefficient[0][2], W );
    					MaxCoefficient[0][2] = FMath::Max( MaxCoefficient[0][2], W );
    				
    					MinCoefficient[0][3] = FMath::Min( MinCoefficient[0][3], LogL );
    					MaxCoefficient[0][3] = FMath::Max( MaxCoefficient[0][3], LogL );
    
    					// Dampen dark texel's contribution on the directionality min and max  抑制暗处的方向最大最小值的贡献
    					float DampenDirectionality = FMath::Clamp(L * 100, 0.0f, 1.0f);
    
    					for( int32 ColorIndex = 0; ColorIndex < 3; ColorIndex++ )
    					{
    						MinCoefficient[1][ColorIndex] = FMath::Min( MinCoefficient[1][ColorIndex], DampenDirectionality * SourceSample.Coefficients[1][ColorIndex] );
    						MaxCoefficient[1][ColorIndex] = FMath::Max( MaxCoefficient[1][ColorIndex], DampenDirectionality * SourceSample.Coefficients[1][ColorIndex] );
    					}
    				}
    

      

    lq获得u*亮度的log值的min、max,v*亮度的log值的min、max,w*亮度的log值的min、max值;方向*亮度的值的min、max值。

    {
    					// Simple
    					float L, U, V, W;
    					GetLUVW( SourceSample.Coefficients[2], L, U, V, W );
    
    					float LogL = FMath::Log2( L + SimpleLogBlackPoint ) / SimpleLogScale + 0.5f;
    
    					float LogR = LogL * U;
    					float LogG = LogL * V;
    					float LogB = LogL * W;
    
    					MinCoefficient[2][0] = FMath::Min( MinCoefficient[2][0], LogR );
    					MaxCoefficient[2][0] = FMath::Max( MaxCoefficient[2][0], LogR );
    
    					MinCoefficient[2][1] = FMath::Min( MinCoefficient[2][1], LogG );
    					MaxCoefficient[2][1] = FMath::Max( MaxCoefficient[2][1], LogG );
    				
    					MinCoefficient[2][2] = FMath::Min( MinCoefficient[2][2], LogB );
    					MaxCoefficient[2][2] = FMath::Max( MaxCoefficient[2][2], LogB );
    
    					// Dampen dark texel's contribution on the directionality min and max  抑制暗处的方向最大最小值的贡献
    float DampenDirectionality = FMath::Clamp(L * 100, 0.0f, 1.0f); for( int32 ColorIndex = 0; ColorIndex < 3; ColorIndex++ ) { MinCoefficient[3][ColorIndex] = FMath::Min( MinCoefficient[3][ColorIndex], DampenDirectionality * SourceSample.Coefficients[3][ColorIndex] ); MaxCoefficient[3][ColorIndex] = FMath::Max( MaxCoefficient[3][ColorIndex], DampenDirectionality * SourceSample.Coefficients[3][ColorIndex] ); } }

      

     2.  根据求得的max和min,获得归一化函数的scale和add值。y = kx+b的k和b值以及反向从y计算x的k和b值,为了压缩和解压缩:


    // Calculate the scale/bias for the light-map coefficients.
    		float CoefficientMultiply[LM_NUM_STORED_LIGHTMAP_COEF][4];
    		float CoefficientAdd[LM_NUM_STORED_LIGHTMAP_COEF][4];
    
    		for (int32 CoefficientIndex = 0; CoefficientIndex < LM_NUM_STORED_LIGHTMAP_COEF; CoefficientIndex++)
    		{
    			for (int32 ColorIndex = 0; ColorIndex < 4; ColorIndex++)
    			{
    				// Calculate scale and bias factors to pack into the desired range
    				// y = (x - Min) / (Max - Min)
    				// Mul = 1 / (Max - Min)
    				// Add = -Min / (Max - Min)
    				CoefficientMultiply[CoefficientIndex][ColorIndex] = 1.0f / FMath::Max<float>(MaxCoefficient[CoefficientIndex][ColorIndex] - MinCoefficient[CoefficientIndex][ColorIndex], DELTA);
    				CoefficientAdd[CoefficientIndex][ColorIndex] = -MinCoefficient[CoefficientIndex][ColorIndex] / FMath::Max<float>(MaxCoefficient[CoefficientIndex][ColorIndex] - MinCoefficient[CoefficientIndex][ColorIndex], DELTA);
    
    				// Output the values used to undo this packing
    				OutMultiply[CoefficientIndex][ColorIndex] = 1.0f / CoefficientMultiply[CoefficientIndex][ColorIndex];
    				OutAdd[CoefficientIndex][ColorIndex] = -CoefficientAdd[CoefficientIndex][ColorIndex] / CoefficientMultiply[CoefficientIndex][ColorIndex];
    			}
    		}
    

      

    3. lq的rgb需要大于0,防止除以0:
    4. 方向的反向的a通道的scale和Add值固定,为什么?这俩是sh相关的东西?

     

     5. 分配输出的压缩过的数据的数组,并设置其skyocclusion和aomaterialMask:

     

     

     

    const FLightSample& SourceSample = InLightSamples[SampleIndex];
    			FQuantizedLightSampleData& DestCoefficients = OutLightSamples[SampleIndex];
    #if ALLOW_LIGHTMAP_SAMPLE_DEBUGGING
    			if (SampleIndex == DebugSampleIndex)
    			{
    				int32 TempBreak = 0;
    			}
    #endif
    			DestCoefficients.Coverage = SourceSample.bIsMapped ? 255 : 0;
    
    			const FVector BentNormal(SourceSample.SkyOcclusion[0], SourceSample.SkyOcclusion[1], SourceSample.SkyOcclusion[2]);
    			const float BentNormalLength = BentNormal.Size();
    			const FVector NormalizedBentNormal = BentNormal.GetSafeNormal() * FVector(.5f) + FVector(.5f);
    
    			DestCoefficients.SkyOcclusion[0] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( NormalizedBentNormal[0] * 255.0f ), 0, 255 );
    			DestCoefficients.SkyOcclusion[1] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( NormalizedBentNormal[1] * 255.0f ), 0, 255 );
    			DestCoefficients.SkyOcclusion[2] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( NormalizedBentNormal[2] * 255.0f ), 0, 255 );
    			// Sqrt on length to allocate more precision near 0
    			DestCoefficients.SkyOcclusion[3] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( FMath::Sqrt(BentNormalLength) * 255.0f ), 0, 255 );
    
    			// Sqrt to allocate more precision near 0
    			DestCoefficients.AOMaterialMask = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( FMath::Sqrt(SourceSample.AOMaterialMask) * 255.0f ), 0, 255 ); 
    

      6.利用求出的scale和add归一化。归一化过程,hq的uvw归一化后转换到gamma空间再缩到0-255,l的log的函数的归一化后的整数部分再乘以255存到a,小数部分再乘255存到方向的a,方向的归一化存到方向图中。

     

    {
    				float L, U, V, W;
    				GetLUVW( SourceSample.Coefficients[0], L, U, V, W );
    
    				// LogLUVW encode color
    				float LogL = FMath::Log2( L + LogBlackPoint );
    
    				U = U * CoefficientMultiply[0][0] + CoefficientAdd[0][0];
    				V = V * CoefficientMultiply[0][1] + CoefficientAdd[0][1];
    				W = W * CoefficientMultiply[0][2] + CoefficientAdd[0][2];
    				LogL = LogL * CoefficientMultiply[0][3] + CoefficientAdd[0][3];
    
    				float Residual = LogL * 255.0f - FMath::RoundToFloat( LogL * 255.0f ) + 0.5f;
    
    				// U, V, W, LogL
    				// UVW stored in gamma space
    				DestCoefficients.Coefficients[0][0] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( FMath::Pow( U, 1.0f / 2.2f ) * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[0][1] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( FMath::Pow( V, 1.0f / 2.2f ) * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[0][2] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( FMath::Pow( W, 1.0f / 2.2f ) * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[0][3] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( LogL * 255.0f ), 0, 255 );
    				
    				float Dx = SourceSample.Coefficients[1][0] * CoefficientMultiply[1][0] + CoefficientAdd[1][0];
    				float Dy = SourceSample.Coefficients[1][1] * CoefficientMultiply[1][1] + CoefficientAdd[1][1];
    				float Dz = SourceSample.Coefficients[1][2] * CoefficientMultiply[1][2] + CoefficientAdd[1][2];
    
    				// Dx, Dy, Dz, Residual
    				DestCoefficients.Coefficients[1][0] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dx * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[1][1] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dy * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[1][2] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dz * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[1][3] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Residual * 255.0f ), 0, 255 );
    			}
    

      

     7. lq:l的log函数*颜色归一化,方向也归一化,方向一开始进行了l的抑制,是否会影响?:

    {
    				float L, U, V, W;
    				GetLUVW( SourceSample.Coefficients[2], L, U, V, W );
    
    				// LogRGB encode color
    				float LogL = FMath::Log2( L + SimpleLogBlackPoint ) / SimpleLogScale + 0.5f;
    
    				float LogR = LogL * U * CoefficientMultiply[2][0] + CoefficientAdd[2][0];
    				float LogG = LogL * V * CoefficientMultiply[2][1] + CoefficientAdd[2][1];
    				float LogB = LogL * W * CoefficientMultiply[2][2] + CoefficientAdd[2][2];
    				
    				// LogR, LogG, LogB
    				DestCoefficients.Coefficients[2][0] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( LogR * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[2][1] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( LogG * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[2][2] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( LogB * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[2][3] = 255;
    
    				float Dx = SourceSample.Coefficients[3][0] * CoefficientMultiply[3][0] + CoefficientAdd[3][0];
    				float Dy = SourceSample.Coefficients[3][1] * CoefficientMultiply[3][1] + CoefficientAdd[3][1];
    				float Dz = SourceSample.Coefficients[3][2] * CoefficientMultiply[3][2] + CoefficientAdd[3][2];
    
    				// Dx, Dy, Dz
    				DestCoefficients.Coefficients[3][0] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dx * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[3][1] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dy * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[3][2] = (uint8)FMath::Clamp<int32>( FMath::RoundToInt( Dz * 255.0f ), 0, 255 );
    				DestCoefficients.Coefficients[3][3] = 255;
    			}
    

      

     

    参考文档:https://zhuanlan.zhihu.com/p/68754084

  • 相关阅读:
    <Error>: CGContextRestoreGState
    Google 常用镜像收集
    NSCharacterSet 详解
    JAVA并发,CyclicBarrier
    JAVA并发,CountDownLatch使用
    JAVA并发,经典死锁案例-哲学家就餐
    Git-常用命令集合
    (转)《JAVA与模式》之模板方法模式
    JAVA并发,同步锁性能测试
    《转》JAVA并发编程:volatile关键字解析
  • 原文地址:https://www.cnblogs.com/Shaojunping/p/14435288.html
Copyright © 2011-2022 走看看