大气渲染全攻略

  最近在写一个大型一点的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次分割就不错了)。

 

代码
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.6254);
float  Krg = 1.0f/pow(0.5254);
float  Krb = 1.0f/pow(0.4704);

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, 00) );
        
        float4 opticalDepthEP 
= tex2Dlod(s0, float4( Viewheight,       radEye, 00) );
        float4 opticalDepthCS 
= tex2Dlod(s0, float4( currentPosHeight, radSun, 00) );
        
        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的那一部分,就像碟状的那种,之所以选这种是因为我感觉更加合理,不过我想就算是个半球也没有什么问题。

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

 

 

posted @ 2010-02-06 15:48  ttthinks  阅读(1601)  评论(0编辑  收藏  举报