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