經過OSG實現對模型的日照模擬

1. 加載模型

經過OpenSceneGraph加載一個傾斜攝影的場景模型數據:html

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>

using namespace std;

int main()
{
    string osgPath = "D:/Data/Dayanta_OSGB/Data/MultiFoderReader.osgb";
    osg::Node * node = osgDB::readNodeFile(osgPath);
    osgViewer::Viewer viewer;
    viewer.setSceneData(node);
    viewer.setUpViewInWindow(100, 100, 800, 600);
    return viewer.run();
}

運行結果顯示的場景以下:

想要對模型進行日照模擬,就須要用到光照和陰影技術。注意此時模型上的部分陰影是紋理上自帶的。node

2. 光照

osgViewer的默認場景中是有燈光的,調整上述的場景的視角,某些地方是全黑的,並且場景效果偏暗。這裏須要設置本身須要的環境反射和漫反射。ios

1) 環境反射

環境反射是針對環境光而言的,在環境反射中,環境光照射物體是各方面均勻、強度相等的,所以環境光不用設置位置和方向,只須要指定顏色。算法

2) 漫反射

漫反射是針對平行光和點光源光而言的。太陽光照就是平行光,因爲太陽距離地球很遠,陽光到達地球的時能夠認爲是平行的。平行光能夠用一個方向和一個顏色來定義。固然,對於像燈泡那樣的點光源光,還須要指定光源的位置。函數

3) 日照方向

(1) 太陽高度角和太陽方位角

對於太陽光照來講,其方向並非隨便設置的。這裏須要引入太陽高度角和太陽方位角兩個概念,經過這兩個角度,能夠肯定日照的方向。post

太陽高度角指的就是太陽光的入射方向和地平面之間的夾角;而太陽方位角略微複雜點,指的是太陽光線在地平面上的投影與當地子午線的夾角,可近似地看做是豎立在地面上的直線在陽光下的陰影與正南方向的夾角。其中方位角以正南方向爲0,由南向東向北爲負,有南向西向北爲正。例如太陽在正東方,則其方位角爲-90度;在正東北方時,方位角爲-135度;在正西方時,方位角是90度,在正西北方爲135度;固然在正北方時方位角能夠表示爲正負180度。學習

(2) 計算過程

根據上述定義,對於空間某一點的日照光線,能夠有以下示意圖。

令太陽光線長度L1=1,有以下推算過程:spa

α是太陽高度角,則日照方向Z長度L3=sin(α);
L1在地平面(XY)平面的長度L2 = cos(α);
β是太陽方位角,則日照方向X長度L5 = L2cos(β);
同時日照方向Y長度L4 = L2
sin(β)。.net

所以,對於太陽高度角α和太陽方位角β,日照光線的單位向量n(x,y,z)爲:

X = cos(α)cos(β);
Y = cos(α)
sin(β);
Z = sin(α);

4) 改進實現

在OSG中是經過設置光照節點加入到場景節點中來實現光照的。這裏把太陽高度角設置成45度,太陽方位角度設置成315度。經過上述轉換,獲得光照方向。有一點要注意的是osg::Light沒有顯式的設置平行光的接口,請教大牛才知道只須要在setPosition()函數中設置w份量爲0就能夠了。關於這一點我也確實有點不理解。

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Light>

using namespace std;
using namespace osg;

//添加燈光節點
void AddLight(osg::ref_ptr<osg::Group> group)
{
    double solarAltitude = 45.0;
    double solarAzimuth = 315.0;

    //開啓光照
    osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
    stateset = group->getOrCreateStateSet();
    stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);                // 啓用光照
    stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);              // 啓用指定光源

    //建立一個Light對象
    osg::ref_ptr<osg::Light> light = new osg::Light();
    light->setLightNum(0);

    //設置方向:平行光
    osg::Vec3 arrayvector(0.0f, 0.0f, -1.0f);
    double fAltitude = osg::DegreesToRadians(solarAltitude);                //光源高度角
    double fAzimuth = osg::DegreesToRadians(solarAzimuth);          //光源方位角
    arrayvector[0] = cos(fAltitude)*cos(fAzimuth);
    arrayvector[1] = cos(fAltitude)*sin(fAzimuth);
    arrayvector[2] = sin(fAltitude);
    light->setDirection(arrayvector);

    //平行光位置任意,可是w份量要爲0
    osg::Vec4 lightpos(arrayvector[0], arrayvector[1], arrayvector[2], 0.0f);
    light->setPosition(lightpos);   

    //設置環境光的顏色
    light->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

    //設置散射光顏色
    light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));     

    //    //設置恆衰減指數
    //    light->setConstantAttenuation(1.0f);
    //    //設置線形衰減指數
    //    light->setLinearAttenuation(0.0f);
    //    //設置二次方衰減指數
    //    light->setQuadraticAttenuation(0.0f);

    //建立光源
    osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
    lightSource->setLight(light);

    group->addChild(lightSource);
}


int main()
{
    //根節點
    osg::ref_ptr<osg::Group> root = new osg::Group; 
    root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);        //默認去掉光照

    //
    string osgPath = "C:/Data/baoli/Production_3/Data/MultiFoderReader.osgb";
    osg::Node * node = osgDB::readNodeFile(osgPath);
    root->addChild(node);

    //
    AddLight(root);

    //
    osgViewer::Viewer viewer;
    viewer.setSceneData(root);
    viewer.setUpViewInWindow(100, 100, 800, 600);
    return viewer.run();
}

最終運行結果是模型總體有了亮度,可是因爲紋理的效果,光照的明暗效果的效果沒有顯現出來。可是若是是白模,將會看到很明顯的明暗效果。

3. 陰影

在OSG中已經實現了生成陰影的組件osgShadow。其具體調用方式也比較簡單,首先將節點和燈光加入到ShadowedScene對象,而後標明投射者和被投射者,最後選擇一種陰影渲染算法應用到場景就能夠了。

注意這裏的陰影渲染算法應該選用ShadowMap,由於我這裏的投射者和被投射者都是同一個物體,不少例子裏面用的ShadowTexture算法是不支持自投影的。

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Light>

#include <osgShadow/ShadowedScene>
#include <osgShadow/ShadowMap>

using namespace std;
using namespace osg;

//添加燈光節點
void AddLight(osg::ref_ptr<osg::Group> group)
{
    double solarAltitude = 45.0;
    double solarAzimuth = 315.0;

    //開啓光照
    osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
    stateset = group->getOrCreateStateSet();
    stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);                // 啓用光照
    stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);              // 啓用指定光源

    //建立一個Light對象
    osg::ref_ptr<osg::Light> light = new osg::Light();
    light->setLightNum(0);

    //設置方向:平行光
    osg::Vec3 arrayvector(0.0f, 0.0f, -1.0f);
    double fAltitude = osg::DegreesToRadians(solarAltitude);                //光源高度角
    double fAzimuth = osg::DegreesToRadians(solarAzimuth);          //光源方位角
    arrayvector[0] = cos(fAltitude)*cos(fAzimuth);
    arrayvector[1] = cos(fAltitude)*sin(fAzimuth);
    arrayvector[2] = sin(fAltitude);
    light->setDirection(arrayvector);

    //平行光位置任意,可是w份量要爲0
    osg::Vec4 lightpos(arrayvector[0], arrayvector[1], arrayvector[2], 0.0f);
    light->setPosition(lightpos);   

    //設置環境光的顏色
    light->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

    //設置散射光顏色
    light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));     

    //    //設置恆衰減指數
    //    light->setConstantAttenuation(1.0f);
    //    //設置線形衰減指數
    //    light->setLinearAttenuation(0.0f);
    //    //設置二次方衰減指數
    //    light->setQuadraticAttenuation(0.0f);

    //建立光源
    osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
    lightSource->setLight(light);

    group->addChild(lightSource);
}


int main()
{
    //根節點
    osg::ref_ptr<osg::Group> root = new osg::Group; 
    root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);        //默認去掉光照

    //標識陰影接收對象                                                                                                                      
    const int ReceivesShadowTraversalMask = 0x1;
    //標識陰影投影對象
    const int CastsShadowTraversalMask = 0x2;

    //陰影節點
    osg::ref_ptr<osgShadow::ShadowedScene> shadowedScene = new osgShadow::ShadowedScene();
    shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask);
    shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask);   
    root->addChild(shadowedScene);
    
    //場景節點
    string osgPath = "C:/Data/baoli/Production_3/Data/MultiFoderReader.osgb";
    osg::Node * node = osgDB::readNodeFile(osgPath);
    shadowedScene->addChild(node);
    
    //設置投射者
    node->setNodeMask(CastsShadowTraversalMask);               //只須要設置投射體,那麼默認狀況下全部的物體都是被投物體

    //ShadowMap陰影算法
    osg::ref_ptr<osgShadow::ShadowMap> sm = new osgShadow::ShadowMap;
    shadowedScene->setShadowTechnique(sm.get());

    //
    AddLight(shadowedScene);

    //
    osgViewer::Viewer viewer;
    viewer.setSceneData(root);
    viewer.setUpViewInWindow(100, 100, 800, 600);
    return viewer.run();
}

最後的實現效果以下,能夠看到很明顯的陰影效果:

4. 太陽高度角與太陽方位角的計算

到這裏光照和陰影的效果就已經徹底實現了,可是我這裏模擬的是太陽日照的效果,那麼一個新的問題又產生了。前面說根據太陽高度角與太陽方位角計算光照的方向。那麼太陽高度角與太陽方位角又是怎麼計算出來的呢?這裏推薦一篇寫的不錯的文章:太陽高度角方位角計算。惋惜這篇文章的圖片已失效,我這裏就把四個計算公式再貼一下:

1) 太陽高度角計算公式

2) 太陽方位角計算公式

3) 太陽赤緯計算公式

4) 時角計算公式

5) 真太陽時

那篇文章中其餘的公式都很清晰,可是關於真太陽時的描述其實我以爲沒有講清楚,看的是一頭霧水。後來我也在網上查閱一些資料,使人可笑的是這個真太陽時關聯的最多的倒是算命算生辰八字。那我就經過這個一步步來說這個真太陽時是怎麼來的。

咱們知道,古代是經過日晷等方式來計時的,例如午時就是影子最短的時候。可是因爲日照到達地球的差別,烏魯木齊和北京的午時確定不是同一時刻。古代的人沒有那個技術條件,將各地的時間統一塊兒來,都是各地用各自的地方時來計時。因此算生辰八字和計算日照同樣,都須要當地最精確的太陽光照形成的時間,這個時間就是真太陽時。

可是咱們如今都是有行政時間的,不管在北京或者烏魯木齊,用的都是東經120度的中國北京時間。而在世界上是分24個時區的,每15度就是一個時區。那麼能夠算算北京時間12點整在烏魯木齊的真太陽時是多少。

經查閱烏魯木齊的經度大約爲87.68,那麼時差爲(87.68- 120.0)/15.0=-2.154667,也就是負2小時9分鐘16.8秒,所以可算得烏魯木齊的地方時就是9時50分43.2秒。那麼這個算出來的地方時是否是就是真太陽時呢?

其實也不是的。這個時間實際上是平太陽時。平太陽時假設地球繞太陽是標準的圓形,一年中天天都是均勻的。可是地球繞日運行的軌道是橢圓的,則地球相對於太陽的自轉並非均勻的,天天並不都是24小時,有時候少有時候多。這個時間差別就是真太陽時差。

在查閱真太陽時差的時候發現資料真的挺少,並且各有說法。有的說真太陽時差每一年都不同,是根據天文信息計算出來的,每一年都會發布一次;而在維基百科上面給出了天天的真太陽時差的模擬計算公式;更多的是給了一張表,按照表的日期取值就好了[什麼是真太陽時]。我這裏只能採信第三種,例如5月29日的真太陽時差是+2分22秒,那麼將上面計算的平太陽時加上這個時差,爲9時53分5.2秒。即5月29日北京時間烏魯木齊的真太陽時爲9時53分5.2秒。

5. 參考文獻

  1. Shadows
  2. 太陽高度角方位角計算
  3. 什麼是真太陽時
  4. (轉載)關於太陽(衛星)天頂角,太陽高度角,太陽方位角的整理
  5. DEM-地貌暈渲圖的生成原理
  6. OSG 學習第四天:光照
相關文章
相關標籤/搜索