OSG與Shader的結合使用
- 2019 年 10 月 3 日
- 筆記
[toc]
1. 概述
以往在OpenGL中學習渲染管線的時候,是依次按照申請數據、傳送緩衝區、頂點著色器、片元著色器這幾個步驟編程的。OSG是OpenGL的一些頂層的封裝,使用shader的時候看不到這些步驟了,所以有點不習慣。這裡我總結了兩個最簡單的例子。
2. 固定管線著色
OSG一個最簡單的示例是展示自帶的數據glider.osg:
#include <iostream> #include <Windows.h> #include <osgViewer/Viewer> #include <osgDB/ReadFile> using namespace std; int main() { osg::ref_ptr<osg::Group> root= new osg::Group(); string osgPath = "D:/Work/OSGBuild/OpenSceneGraph-Data/glider.osg"; osg::Node * node = osgDB::readNodeFile(osgPath); root->addChild(node); osgViewer::Viewer viewer; viewer.setSceneData(root); viewer.setUpViewInWindow(100, 100, 800, 600); return viewer.run(); }
顯示的結果是一個簡單的滑翔機: 用文本的方式打開glider.osg這個數據,裡面記錄的是其頂點資訊:
這個數據應該是通過固定管線渲染出來的,那麼可以為這個場景加入Shader:
#include <iostream> #include <Windows.h> #include <osgViewer/Viewer> #include <osgDB/ReadFile> using namespace std; //設置紋理著色 static void ColorShader(osg::ref_ptr<osg::Node> node) { const char * vertexShader = { "void main(void ){n" " gl_FrontColor = gl_Color;n" " gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;n" "}n" }; const char * fragShader = { "void main(void){n" " gl_FragColor = gl_Color;n" "}n" }; osg::StateSet * ss = node->getOrCreateStateSet(); osg::ref_ptr<osg::Program> program = new osg::Program(); program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragShader)); program->addShader(new osg::Shader(osg::Shader::VERTEX, vertexShader)); ss->setAttributeAndModes(program, osg::StateAttribute::ON); } int main() { osg::ref_ptr<osg::Group> root= new osg::Group(); string osgPath = "D:/Work/OSGBuild/OpenSceneGraph-Data/glider.osg"; osg::Node * node = osgDB::readNodeFile(osgPath); root->addChild(node); ColorShader(node); osgViewer::Viewer viewer; viewer.setSceneData(root); viewer.setUpViewInWindow(100, 100, 800, 600); return viewer.run(); }
這段著色器程式碼是什麼意思呢?其實很簡單,當使用固定管線的glColor函數後,該顏色值就以作為內置gl_Color變數傳入頂點著色器, 頂點著色器計算通過gl_FontColor和gl_BackColor保存正面和反面的值;而繼續傳入到片元著色器之後,gl_Color則會變成一個由FontColor和BackColor插值計算出來的變數。最終gl_FragColor接受到的就是固定管線渲染得到的值。運行的結果如下: 最終的結果與之前的結果有所差異,這是osgViewer的默認場景中是有燈光效果的,可編程管線的渲染效果覆蓋了固定管線的效果。可以在之前固定管線渲染的例子中加入一句程式碼
root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
去除光照效果,兩者的渲染效果就完全一致了。
3. 紋理著色
另一個例子是通過OSG載入一個帶紋理的OSGB模型:
#include <iostream> #include <Windows.h> #include <osgViewer/Viewer> #include <osgDB/ReadFile> using namespace std; int main() { osg::ref_ptr<osg::Group> root= new osg::Group(); string osgPath = "D:/Data/scene/Dayanta_OSGB/Data/MultiFoderReader.osgb"; osg::Node * node = osgDB::readNodeFile(osgPath); root->addChild(node); osgViewer::Viewer viewer; viewer.setSceneData(root); viewer.setUpViewInWindow(100, 100, 800, 600); return viewer.run(); }
運行結果會發現某些視角下場景發暗,這同樣也是由於場景中的默認光線造成的: 採取同樣的方式,通過shader覆蓋固定管線的渲染效果:
//設置紋理著色 static void TextureShader(osg::ref_ptr<osg::Node> node) { const char * vertexShader = { "void main(void ){n" " gl_TexCoord[0] = gl_MultiTexCoord0;n" " gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;n" "}n" }; const char * fragShader = { "uniform sampler2D baseTexture;n" "void main(void){n" " vec2 coord = gl_TexCoord[0].xy;n" " vec4 C = texture2D(baseTexture, coord)n;" " gl_FragColor = C;n" "}n" }; osg::StateSet * ss = node->getOrCreateStateSet(); osg::ref_ptr<osg::Program> program = new osg::Program(); program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragShader)); program->addShader(new osg::Shader(osg::Shader::VERTEX, vertexShader)); ss->setAttributeAndModes(program, osg::StateAttribute::ON); } int main() { osg::ref_ptr<osg::Group> root= new osg::Group(); string osgPath = "D:/Data/scene/Dayanta_OSGB/Data/MultiFoderReader.osgb"; osg::Node * node = osgDB::readNodeFile(osgPath); root->addChild(node); TextureShader(node); osgViewer::Viewer viewer; viewer.setSceneData(root); viewer.setUpViewInWindow(100, 100, 800, 600); return viewer.run(); }
這段shader程式碼也比較簡單,在頂點著色器中,gl_MultiTexCoord0表示在啟用多重紋理時的0號紋理單元的坐標頂點,將其保存在預先定義的紋理坐標gl_TexCoord[0]中。gl_TexCoord[0]經過插值後傳入片元著色器,通過自定義的紋理單元變數sampler2D baseTexture,使用texture2D函數獲取像素值。最終的渲染效果如下:
4. 參考
[1].GLSL下幾個簡單的Shader [2].GLSL 紋理貼圖