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 紋理貼圖