官方代码($sdk)\examples\10.Shaders
这个例子是在irr里使用DX和OpenGL的Shader,使用Shader创建新的材质类型。
同固定管线渲染一样,使用Shader也需要用到很多矩阵,但是这些矩阵信息不再是irr自动提供,而是通过继承IShaderConstantSetCallBack接口类提供矩阵信息给Shader。在该例中使用了一些简单的顶点Shader,它会根据摄像机的位置计算顶点的颜色。实现这功能,需要为Shader提供转换法线的逆世界矩阵、转换坐标的剪切矩阵、摄像机位置和物体在世界矩阵中的位置。这些数据将在自己继承的IShaderConstantSetCallBack接口类的OnSetConstanes()方法中使用。OnSetConstanes()会在每次设置材质的时候被调用。它的参数IMaterialRendererServices接口类的setVertexShaderConstant()方法用来设置Shader所需的数据。下面看如何继承IShaderConstantSetCallBack接口类。
classMyShaderCallBack : public video::IShaderConstantSetCallBack
{
public:
//参数services:指向一个接口,它提供设置Shader常量的方法
//参数userData:创建Shader时能指定用户数据
virtualvoid OnSetConstants(video::IMaterialRendererServices* services,
s32userData)
{
video::IVideoDriver*driver = services->getVideoDriver();
//设置逆向世界矩阵
//如果使用高级Shader(用户可以在程序开始时进行选择),在此必须设置其名
称。
core::matrix4invWorld = driver->getTransform(video::ETS_WORLD);
invWorld.makeInverse();
if(UseHighLevelShaders)
services->setVertexShaderConstant("mInvWorld",invWorld.pointer(), 16);
else
services->setVertexShaderConstant(invWorld.pointer(),0, 4);
//设置剪切矩阵
core::matrix4worldViewProj;
worldViewProj= driver->getTransform(video::ETS_PROJECTION);
worldViewProj*= driver->getTransform(video::ETS_VIEW);
worldViewProj*= driver->getTransform(video::ETS_WORLD);
if(UseHighLevelShaders)
services->setVertexShaderConstant("mWorldViewProj",worldViewProj.pointer(), 16);
else
services->setVertexShaderConstant(worldViewProj.pointer(),4, 4);
//设置摄像机位置
core::vector3dfpos = device->getSceneManager()->
getActiveCamera()->getAbsolutePosition();
if(UseHighLevelShaders)
services->setVertexShaderConstant("mLightPos",reinterpret_cast<f32*>(&pos), 3);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos),8, 1);
//设置灯光颜色
video::SColorfcol(0.0f,1.0f,1.0f,0.0f);
if(UseHighLevelShaders)
services->setVertexShaderConstant("mLightColor",
reinterpret_cast<f32*>(&col),4);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&col),9, 1);
//设置世界转换矩阵
core::matrix4world = driver->getTransform(video::ETS_WORLD);
world= world.getTransposed();
if(UseHighLevelShaders)
{
services->setVertexShaderConstant("mTransWorld",world.pointer(), 16);
//设置纹理,它可以同时使用int和float类型的setPixelShaderConstant接口(只有使用OpenGL的时候需要它)
s32TextureLayerID = 0;
if(UseHighLevelShaders)
services->setPixelShaderConstant("myTexture",&TextureLayerID, 1);
}
else
services->setVertexShaderConstant(world.pointer(),10, 4);
}
};
这个例子启动时除了3D驱动选择菜单还比以往多增加了一个选择菜单,用于选择是否使用高级Shader和是否使用Cg。新增加的菜单代码如下
if(driverType == video::EDT_DIRECT3D9 ||
driverType == video::EDT_OPENGL)
{
chari;
printf("Pleasepress 'y' if you want to use high level shaders.\n");
std::cin>> i;
if(i == 'y')
{
UseHighLevelShaders= true;
printf("Pleasepress 'y' if you want to use Cg shaders.\n");
std::cin>> i;
if(i == 'y')
UseCgShaders= true;
}
}
确认是否支持Cg
if(UseCgShaders && !driver->queryFeature(video::EVDF_CG))
{
printf("Warning:No Cg support, disabling.\n");
UseCgShaders=false;
}
似乎从CUDA出来后Cg就不再热了。不管怎么说,irr引擎支持是好事。
下面设置使用的Shader文件名。如果使用DX,需要使用顶点着色器和像素着色器程序,如果使用OpenGL,则需要使用顶点着色器和ARB片段着色器程序。在SDK的media文件夹下,已经有为不同驱动设备写好的相应文件,分别是d3d8.ps,d3d8.vs, d3d9.ps, d3d9.vs,opengl.ps and opengl.vs。
switch(driverType)
{
//设置DX8对应的Shader文件
casevideo::EDT_DIRECT3D8:
psFileName= "../../media/d3d8.psh";
vsFileName= "../../media/d3d8.vsh";
break;
//设置DX9对应的Shader文件
casevideo::EDT_DIRECT3D9:
if(UseHighLevelShaders)
{
//Cg能处理HLSL
psFileName= "../../media/d3d9.hlsl";
vsFileName= psFileName; // 两种Shader在相同文件里
}
else
{
psFileName= "../../media/d3d9.psh";
vsFileName= "../../media/d3d9.vsh";
}
break;
//设置OpenGL对应的Shader文件
casevideo::EDT_OPENGL:
if(UseHighLevelShaders)
{
if(!UseCgShaders)
{
psFileName= "../../media/opengl.frag";
vsFileName= "../../media/opengl.vert";
}
else
{
//Cg使用HLSL psFileName= "../../media/d3d9.hlsl";
vsFileName= psFileName; // both shaders are in the same file
}
}
else
{
psFileName= "../../media/opengl.psh";
vsFileName= "../../media/opengl.vsh";
}
break;
}
检测硬件是否支持相应的Shader,如果不支持,就将Shader文件名设为空,后面就不会再进行该类Shader显示。
if(!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
{
device->getLogger()->log("WARNING:Pixel shaders disabled "\
"becauseof missing driver/hardware support.");
psFileName= "";
}
if(!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
{
device->getLogger()->log("WARNING:Vertex shaders disabled "\
"becauseof missing driver/hardware support.");
vsFileName= "";
}
在irr中改变一个材质,可以通过修改材质结构中的材质类型值(如Video::EMT_SOLID)来达到相应的效果,这个值是一个32位数值。要创建一个新材质,就需要irr来创建一个新的材质类型值,然后用它做参数进行修改就行了。接下来就是创建新的Shader材质。先通过irr驱动获得IGPUProgrammingServices指针,通过这个指针的addShaderMaterialFromFiles()方法获得一个新的材质类型值。s32addShaderMaterialFromFiles(const io::path&vertexShaderProgramFileName, const io::path&pixelShaderProgramFileName,IShaderConstantSetCallBack* callback =0,E_MATERIAL_TYPE baseMaterial = video::EMT_SOLID,s32 userData = 0),第一个参数是顶点Shader的文件,第二个参数是像素Shader的文件,第三个参数IShaderConstantSetCallBack类的指针,本例一开始写的那个MyShaderCallBack类就是继承它,如果不想设置,这里也可设为空指针0。第四个参数是告诉irr哪个材质类型时候作为基层材质。第四个参数是个用户自定义参数,该方法调用IShaderConstantSetCallBack类的OnSetConstants时会把这个参数传进去。返回值就是新创建的材质类型值。如果创建失败会返回-1。
//获取IGPUProgrammingServices指针
video::IGPUProgrammingServices*gpu = driver->getGPUProgrammingServices();
//例子里为了说明第四个参数的用途,创建了两个不同基层材质类型的材质,一个使用EMT_SOLID (不透明的)另一个使用EMT_TRANSPARENT_ADD_COLOR(透明+颜色)。
s32newMaterialType1 = 0;
s32newMaterialType2 = 0;
if(gpu)
{
//创建自己写的Shader回调函数类
MyShaderCallBack*mc = new MyShaderCallBack();
//根据用户选择创建高级Shader或低级Shader
if(UseHighLevelShaders)
{
//创建高级Shader
constvideo::E_GPU_SHADING_LANGUAGE shadingLanguage =UseCgShaders ?video::EGSL_CG:video::EGSL_DEFAULT;
//从hlsl、glsl、cg创建高级Shader材质
newMaterialType1= gpu->addHighLevelShaderMaterialFromFiles(
vsFileName,"vertexMain", video::EVST_VS_1_1,
psFileName,"pixelMain", video::EPST_PS_1_1,
mc,video::EMT_SOLID, 0, shadingLanguage);
newMaterialType2= gpu->addHighLevelShaderMaterialFromFiles(
vsFileName,"vertexMain", video::EVST_VS_1_1,
psFileName,"pixelMain", video::EPST_PS_1_1,
mc,video::EMT_TRANSPARENT_ADD_COLOR, 0 , shadingLanguage);
}
else
{
//从asm 或 arb_asm创建低级Shader材质
newMaterialType1= gpu->addShaderMaterialFromFiles(vsFileName,
psFileName,mc, video::EMT_SOLID);
newMaterialType2= gpu->addShaderMaterialFromFiles(vsFileName,
psFileName,mc, video::EMT_TRANSPARENT_ADD_COLOR);
}
mc->drop();
}
接下来例子中的代码是前面其他例子里经常出现的内容。创建测试用的立方体,并把刚创建的材质设置上去。为了好区别不同立方体用的材质类型,在立方体上添加了文字场景节点。为使这些立方体看起来更加有趣,同时更容易看出不同材质的区别,在立方体上加了一个旋转器。设置材质使用的是场景节点的setMaterialType方法,参数就是前面创建的材质类型值newMaterialType1、newMaterialType2。但因setMaterialType接受的参数必须是video::E_MATERIAL_TYPE枚举类型,这里需要进行强制类型转换。
一共创建了3个立方体,第一个使用的是newMaterialType1材质,它使用了顶点Shader和像素Shader,基础材质类型是不透明;第二个使用的是newMaterialType2材质,它使用了顶点Shader和像素Shader,基础材质类型是透明+颜色;第三个没有使用Shader材质,是默认材质。
后面为了使场景更好看,创建了天空盒。但在这里多出了一句driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,false),这句在前面的例子里从来没有出现过,从方法名setTextureCreationFlag能知道它是设置irr驱动创建纹理的方法的,会opengl或dx的,一看到参数中的MIP_MAPS就知道这句是干什么用的了。这句是关闭了Mipmap纹理创建。简单的说,Mipmap纹理就是从大到小不同规格的同一图像,每一个规格,刚好是上一个规格的一半。当这个图像离用户距离近的时候,就是用较大的图像给用户看;当图像离用户远的时候,就是使用较小的图像给用户看。这样做能够减少每一帧对纹理进行缩放的计算量,同时如果各个规格的纹理是专门处理过的,在显示时看到的效果将比直接缩放效果要好的多。因天空盒不需要使用Mipmap,这里就关闭了这个纹理创建标志。
最后的代码更没什么好说的了,就是那么再熟悉不过的帧循环。
在这个例子中,所有Shader都是写到文件中的,其实也可以把Shader写到cpp文件中,但这是就不是使用IGPUProgrammingServices中的addShaderMaterialFromFiles(),而是使用
addShaderMaterial(),填写的参数也不再是文件名,而是Shader函数代码的函数名。想要更详细的了解irr的材质,应该仔细看看SMaterial.h头文件。