大气渲染全攻略

  最近在写一个大型一点的Demo,就相当于毕业设计了吧,目前正在把以前学的东西逐个集成,昨天终于完成了大气渲染的这一部分,虽然以前也做个一个的,但是那只是一个实验性质的,不过还是相当有效,这次很快就完成了,我想做个比较完善的天空,加入云,云也应该加入光照计算,夜晚星空这些我也正在考虑,我把目前大气渲染的一些细节写出来,方便大家以后如果要使用的话可以用作参考。

  先贴张效果图。

                        

  从视觉的角度来说应该是正确的,不过右图的太阳之中感觉太明亮了,这可能和Mie散射的参数设置有关,具体的参数设置,大家可以自己尝试。

技术细节:

  光学深度(Optical Depth): 描述了光强度通过某一种介质的衰减程度。

  描述方程:t(Pa, Pb, waveLength) = 4*PI*K(waveLength)[exp( -h/H0 )]在Pa到Pb间的积分(ps:公式不好输入,具体可以参考GPU Gems2或相关论文)

  方程解释:h 是一个相对高度, H0表示大气层中平均密度所在的考度(因为大气层越往上越稀薄)大气层中最高位置被压缩至1.一般我们大气层的平均密度所在高度为0.25, 也就是大气层厚度X0.25所在的高度。Pa, Pb表示大气层中任意两点,当然要是被光照的一面。

  

  其实呢,我们可以讲这个光学深度值预计算出来,想想地球,内层是地壳,外层是大气层,我们以高度h代表一张查找表的U值,以弧度theta代表V值(球体是圆的,我们也可以假设是太阳围绕地球旋转),具体如何计算大家画画图用初中的几何知识就可以得出结果了,这里要注意,我们的h的增量不是线性增长的,和星球半径和大气层厚度有关。(积分用数值逼近法计算,因为是预计算的,因此可以比较精确,一般采用100次分割就不错了)。

 

ExpandedBlockStart.gif 代码
void  CLUTs::CalculateOpticalDepth( int  samples,  float  R,  float  r,  float  havg,  float  Kr,  float  Kg,  float  Kb,  float  M)
{
    
/* *********************************
    原理:采用数值逼近法求的光学深度的积分值
    参数值: u 高度,压缩量(0,1)区间
             v cos角度, 通过0.5*(1-cos(theta)) 压缩至(0,1)
    *********************************
*/

    
float  sARadius  =  R  /  (R  -  r);  //  Scale Atmosphere Radius
     float  sPRadius  =  r  /  (R  -  r);  //  Scale Planet     Radius

    
for  ( int  u = 0 ; u < uSize; u ++ )
    {
        
for  ( int  v = 0 ; v < vSize; v ++ )
        {
            p[u][v].a 
=   0 ;
            
// 高度
             float  h  =   1.0f   /  (uSize - 1 *  u;
            
// 方向角度(弧度制)
             float  theta  =   3.1415926535   /  (vSize - 1 *  v;

            
//  可以简化
             float  A  =  sPRadius + h;
            
float  L   =   - A * cos(theta)  +  sqrt(sARadius * sARadius  -  A * A * sin(theta) * sin(theta));
            
float  ds  =  L  /  samples; 

            
for  ( int  i = 0 ; i < samples; i ++ )
            {
                
float  L   =  ds * (i + 0.5 );
                
float  hi  =  sqrt(A * +  L * +   2 * A * L * cos(theta))  -  sPRadius;
                
if  (hi  <   0 )
                {
                    
break ;
                }
                p[u][v].a 
+=  exp(  - hi  /  havg);
            }

            p[u][v].a 
*=  ds;

            
//  Rayleigh散射
            p[u][v].a  *=   0.012 ;
            p[u][v].r 
=  p[u][v].a  *   4   *   3.1415926535 ; //  * Kr;
            p[u][v].g  =  p[u][v].a  *   4   *   3.1415926535 ; //  * Kg;
            p[u][v].b  =  p[u][v].a  *   4   *   3.1415926535 ; //  * Kb;

            
//  Mie散射
            p[u][v].a  *=   4   *   3.1415926535 ;

        }
    }
}

渲染图:

              大小是512x512的,这种图可以直接使用。

分析一下,相同高度下角度越大我们的像素越亮,基本到中央90度的位置是最亮的,随着高度的增加,我们的最亮的地方也逐渐向下移动,因为光学深度和光线在大气层中通过的距离有关,角度越大距离则越大(因为我们不是在球体的中心,而是非常靠近顶层,我们只是在球体表面,其实可以使用数学软件计算公式并画出曲线图,这对程序问题的发现有很重大的意义,也可以便于分析计算结果(可惜,以前貌似大一学过Mathmatics,不过全部忘完了)。

   RayLeigh散射和Mie散射:RayLeigh散射导致天空颜色的改变,Mie散射导致天空有时看上去朦朦胧胧的,RayLeigh散射和光线波长有关,和光线波长的四次方成反比,这也是出现大气颜色的一个关键,记住了,光学深度出来后不要忘记乘以1/pow(waveLength, 4);

 

  第二个方程就是外向散射方程(GPU Gems2中文版如是说)这个方程的推导过程可以看NishiTa的论文,有个假定条件:光线是平行的!!!这样就可以大大简化方程的复杂度。推导我不写了(在电脑上输入公式就是一种杯具...)具体在《Display of the Earth Taking into Account Atmosphereric Scattering》上。

 

 Shader代码

float4x4 matWorldViewProj;
float3 eyePos;

#define  PI 3.1415926535858


float   Krr  =   1.0f / pow( 0.625 4 );
float   Krg  =   1.0f / pow( 0.525 4 );
float   Krb  =   1.0f / pow( 0.470 4 );

float   Km   =   1.0f ;

float   Luminance;
float   OpticalScale;
float   RayLeighDensity;
float   MieDensity;

float3 lightDir 
=  float3( 0 , 0 , 1 );

float   scale  =   0.005 ;

float   Viewheight  =   0.10 ;

float  Exposure  =   1.5 ;


#define  SAMPLES 5

sampler s0 : register(s0);

float  PhaseFunction( float  g,  float  costheta)
{
    
return   1.5 * ( 1 - g * g) / ( 2 + g * g)  *  ( 1   +  costheta * costheta)  /  pow( 1   +  g * -   2 * g * costheta,  1.5 );
}


void  vs_main(float4 inPos : POSITION,
             
out  float4 outPos : POSITION,
             
out  float4 outColor : TEXCOORD0,
             
out  float3 eyeVec   : TEXCOORD1)
{
    float4 Pos 
=  float4(inPos.xyz  +  eyePos,  1.0f );
    outPos 
=  mul(Pos, matWorldViewProj);
    
    float3 rayVec 
=  inPos  /  SAMPLES;
    
float  rayVecLength  =  length(rayVec);
    
    float3 StartPos 
=  eyePos;
    
float  cosAngle  =  dot(normalize(rayVec), normalize(float3(inPos.x,  0 , inPos.z)));
    
float  sinAngle  =  sqrt( 1   -  sqrt(cosAngle * cosAngle));
    float4 OutScatter 
=   0 ;
    
    
float  radEye  =  acos(dot(normalize(rayVec), float3( 0 , 1 , 0 )))   /  PI;
    
float  radSun  =  acos(dot(normalize(lightDir), float3( 0 , 1 , 0 ))) /  PI;
    
for ( int  i = 0 ; i < SAMPLES; i ++ )
    {
        float3 currentPos 
=  StartPos  +  (i + 0.5 ) * rayVec;
        
float  currentPosHeight  =  (currentPos.y  -  eyePos.y) * scale; // (i+0.5)*rayVecLength*(sinAngle)*scale + Viewheight;
        float4 opticalDepthCP  =  tex2Dlod(s0, float4( currentPosHeight, radEye,  0 0 ) );
        
        float4 opticalDepthEP 
=  tex2Dlod(s0, float4( Viewheight,       radEye,  0 0 ) );
        float4 opticalDepthCS 
=  tex2Dlod(s0, float4( currentPosHeight, radSun,  0 0 ) );
        
        float4 opticalDepthCE 
=  opticalDepthEP  -  opticalDepthCP;
        float4 Attenuation 
=  exp(  - (opticalDepthCE  +  opticalDepthCS)  *  OpticalScale  *  float4(Krr, Krg, Krb,  1.0f ));

        OutScatter 
+=  Attenuation  *  exp( - 4 * currentPosHeight) ;

    }
    OutScatter 
*=  rayVecLength;
    
    
    outColor 
=  OutScatter  *  Luminance  *  scale;
    eyeVec 
=  normalize(rayVec);

}

void  ps_main(float4 inColor : TEXCOORD0,
             float3 eyeVec  : TEXCOORD1, 
             
out  float4 outColor : COLOR)
{

    
float  costheta  =  dot(normalize(eyeVec),  - normalize(lightDir)); 
    
float  Fr  =   0.75 * ( 1   +  costheta  *  costheta);
    
    
float  Fm  =  PhaseFunction(  - 0.9922f , costheta);
    
    
    float4 RColor 
=  inColor  *  float4(Krr, Krg, Krb,  1 );
    float4 MColor 
=  inColor.a  *  Km;
    
    float4 color 
=  MColor * Fm * MieDensity  +  RColor * RayLeighDensity ;
    

     outColor 
=  color;
}
Technique T0
{
    pass p0
    {
        VertexShader 
=  compile vs_3_0 vs_main();
        PixelShader  
=  compile ps_3_0 ps_main();

    }
}

这里用了顶点纹理,以前没用过,还不知道顶点纹理只能用tex2Dlod()。注意参数,Mie散射的phase Function的g值影响到了太阳光斑的大小(没说耀斑),也就是日晕的大小,我设置成-0.9922f的样子感觉还不错,最麻烦的其实还是如何调节参数,我整整调了两天才感觉比较合理,当然,还加入了HDR效果,不过是个简单的ToneMap。加了之后感觉颜色更加协调,如果只是一个简单的Explosure的话你会发现日落时黄色和红色不太分明。

 

天空模型的问题:不是严格意义上的半球,而只是一个半径是8000的球的最顶端的0.025的那一部分,就像碟状的那种,之所以选这种是因为我感觉更加合理,不过我想就算是个半球也没有什么问题。

  我把天空封装成一个完整的类就把代码发上来,希望这篇文章对大家有所帮助。

 

 

转载于:https://www.cnblogs.com/ttthink/archive/2010/02/06/1665002.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值