16-08-2015, 08:34 PM
Tutorial 10: Shaders
Questo tutorial spiega come utilizzare gli shaders per D3D8, D3D9, OpenGL e Cg con Irrlicht e come creare nuovi materiali. Viene anche spiegato come disabilitare la generazione delle mipmaps durante il caricamento delle texture e come utilizzare i nodi di scena di tipo testo (etichette con del testo).
In questo tutorial non verrà spiegato come funzionano gli shaders. Se volete approfondire gli shaders consiglio di leggere la documentazione delle D3D, OpenGL e del linguaggio Cg, cercate tutorials o leggete un libro specifico.
Come negli altri tutorial, per prima cosa includiamo gli header ed indichiamo al linker la libreria giusta:
Poiché vogliamo utilizzare determinati shaders dobbiamo preimpostare alcuni dati per il loro utilizzo affinché possano elaborare correttamente i colori da produrre. In questo esempio useremo un semplice vertex shader che calcolerà il colore dei vertici in base alla posizione della camera. Per questo allo shader dovremo passare alcune informazioni: la 'matrice mondo' inversa per la trasformazione delle normali, la 'matrice di taglio' per la trasformazione della posizione, la posizione della camera, le coordinate mondo per la posizione dell'oggetto per il calcolo dell'angolo della luce e il colore della luce. Per fornire ad ogni frame tutti questi dati allo shader dobbiamo derivare una classe ad hoc dall'interfaccia IShaderConstantSetCallBack e sovrascrivere il solo metodo OnSetConstants(). Questo metodo verrà richiamato ogni volta che viene impostato il materiale. Il metodo setVertexShaderConstant() dell'interfaccia IMaterialRendererServices è utilizzato per passare le costanti come parametro allo shader. Se l'utente decidesse di utilizzare un High Level shader language come HLSL invece di uno di tipo Assembler (non più usati dalle DX9 in poi), dovrete impostare la variabile del nome indicandolo direttamente come parametro tra apici anziché passare il suo indirizzo.
Le prossime linee di codice avviano l'engine come nella maggior parte dei tutorial fino a qui visti. In più stavolta chiederemo all'utente se vuole usare gli shaders di tipo HLSL nel caso il driver selezionato li supporti.
Ora la parte più interessante. Se utilizziamo le Direct3D, dovremo caricare i rispettivi vertex e pixel shader, se usiamo le OpenGL caricheremo l'ARB fragment ed i vertex script. Ho scritto i corrispondenti programmi nei files d3d8.ps, d3d8.vs, d3d9.ps, d3d9.vs, opengl.ps e opengl.vs. Dobbiamo solo indicare i nomi giusti. Lo facciamo nel comando switch che segue. Notare, non è necessario scrivere gli shaders dentro un file di testo, come nell'esempio. E' sempre possibile scrivere gli shaders direttamente come stringhe dentro il sorgente cpp e poi passarle tramite addShaderMaterial() invece del addShaderMaterialFromFiles().
Andiamo a verificare se l'hardware ed il driver selezionato sono in grado di eseguire gli shader che stiamo usando. Se non lo fossero andremo a svuotare la stringa del nome file dello shader. Nel nostro caso non sarebbe necessario ma comunque è utile per l'esempio in se: infatti potrebbe capitare che il nostro hardware sia capace solo di gestire i vertex shaders ma non i pixel shaders, allora dovremmo creare un materiale ad hoc che utilizzi solo i vertex shader e nessun pixel shader, in ogni caso se avessimo comunque creato questi materiali e l'engine avesse visto che l'hardware non poteva processarli, l'engine stesso non avrebbe creato alcun materiale. In questo esempio dovremmo vedere di certo il vertex shader in azione anche senza il pixel shader (oramai l'hardware odierno li gestisce entrambi).
Ora creiamo i materiali. Come forse avete capito dai precedenti tutorial, un tipo di materiale in Irrlicht viene impostato semplicemente cambiando il valore MaterialType nella struct SMaterial. Si tratta di un valore da 32 bit value, come video::EMT_SOLID. A noi serve quindi andare a creare un nuovo valore da immettere nell'engine per il nostro nuovo materiale. Per farlo andiamo a prenderci un puntatore dal IGPUProgrammingServices e richiamiamo addShaderMaterialFromFiles(), che ci ritorna il valore come sequenza di 32 bit. E' tutto.
I parametri da passare a questo metodo sono i seguenti: Primo, il nome del file che contiene il codice di ciascun shader. Se invece volete usare addShaderMaterial(), non vi serve il nome del file, potete scrivere il codice direttamente in una stringa e passarla come parametro. Il parametro che segue è un puntatore alla interfaccia IShaderConstantSetCallBack che avevamo derivato all'inizio del tutorial. Se non volete passare alcuna costante allo shader, azzerate il parametro. L'ultimo parametro dice all'engine quale materiale deve usare come materiale base.
Per dimostrarlo, creiamo due materiali ciascuno con un differente materiale base, uno con EMT_SOLID l'altro con EMT_TRANSPARENT_ADD_COLOR.
Ora è tempo di testare i nostri materiali. Creiamoci dei cubi ed impostiamo i materiali appena creati. In più aggiungiamo un nodo di scena di tipo text sui cubi (delle etichette) ed un rotation animator per renderli più interessanti e dare un po' di dinamicità.
Facciamo lo stesso per il secondo cubo ma ovviamente usiamo il secondo materiale.
Quindi aggiungiamo un terzo cubo senza alcun shader, per poterli comparare.
Per ultimo, aggiungiamo una skybox ed una camera controllata dall'utente. Per la texture della skybox, andiamo a disabilitare la generazione della mipmap, perché non ci serve.
Ora disegniamo. Questo è tutto.
Compiliamo e lanciamo il programma, spero vi siate divertiti con il vostro nuovo piccolo programma e la scrittura degli shader .
Versione pdf scaricabile da QUI
Questo tutorial spiega come utilizzare gli shaders per D3D8, D3D9, OpenGL e Cg con Irrlicht e come creare nuovi materiali. Viene anche spiegato come disabilitare la generazione delle mipmaps durante il caricamento delle texture e come utilizzare i nodi di scena di tipo testo (etichette con del testo).
In questo tutorial non verrà spiegato come funzionano gli shaders. Se volete approfondire gli shaders consiglio di leggere la documentazione delle D3D, OpenGL e del linguaggio Cg, cercate tutorials o leggete un libro specifico.
Come negli altri tutorial, per prima cosa includiamo gli header ed indichiamo al linker la libreria giusta:
Codice PHP:
#include <irrlicht.h>
#include <iostream>
#include "driverChoice.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
Codice PHP:
IrrlichtDevice* device = 0;
bool UseHighLevelShaders = false;
bool UseCgShaders = false;
class MyShaderCallBack : public video::IShaderConstantSetCallBack
{
public:
virtual void OnSetConstants(video::IMaterialRendererServices* services,
s32 userData)
{
video::IVideoDriver* driver = services->getVideoDriver();
// set inverted world matrix
// if we are using highlevel shaders (the user can select this when
// starting the program), we must set the constants by name.
core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
invWorld.makeInverse();
if (UseHighLevelShaders)
services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);
else
services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
// set clip matrix
core::matrix4 worldViewProj;
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);
// set camera position
core::vector3df pos = device->getSceneManager()->
getActiveCamera()->getAbsolutePosition();
if (UseHighLevelShaders)
services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&pos), 3);
else
services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);
// set light color
video::SColorf col(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);
// set transposed world matrix
core::matrix4 world = driver->getTransform(video::ETS_WORLD);
world = world.getTransposed();
if (UseHighLevelShaders)
{
services->setVertexShaderConstant("mTransWorld", world.pointer(), 16);
// set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver).
s32 TextureLayerID = 0;
if (UseHighLevelShaders)
services->setPixelShaderConstant("myTexture", &TextureLayerID, 1);
}
else
services->setVertexShaderConstant(world.pointer(), 10, 4);
}
};
Codice PHP:
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// ask the user if we should use high level shaders for this example
if (driverType == video::EDT_DIRECT3D9 ||
driverType == video::EDT_OPENGL)
{
char i;
printf("Please press 'y' if you want to use high level shaders.\n");
std::cin >> i;
if (i == 'y')
{
UseHighLevelShaders = true;
printf("Please press 'y' if you want to use Cg shaders.\n");
std::cin >> i;
if (i == 'y')
UseCgShaders = true;
}
}
// create device
device = createDevice(driverType, core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
// Make sure we don't try Cg without support for it
if (UseCgShaders && !driver->queryFeature(video::EVDF_CG))
{
printf("Warning: No Cg support, disabling.\n");
UseCgShaders=false;
}
Codice PHP:
io::path vsFileName; // filename for the vertex shader
io::path psFileName; // filename for the pixel shader
switch(driverType)
{
case video::EDT_DIRECT3D8:
psFileName = "../../media/d3d8.psh";
vsFileName = "../../media/d3d8.vsh";
break;
case video::EDT_DIRECT3D9:
if (UseHighLevelShaders)
{
// Cg can also handle this syntax
psFileName = "../../media/d3d9.hlsl";
vsFileName = psFileName; // both shaders are in the same file
}
else
{
psFileName = "../../media/d3d9.psh";
vsFileName = "../../media/d3d9.vsh";
}
break;
case video::EDT_OPENGL:
if (UseHighLevelShaders)
{
if (!UseCgShaders)
{
psFileName = "../../media/opengl.frag";
vsFileName = "../../media/opengl.vert";
}
else
{
// Use HLSL syntax for Cg
psFileName = "../../media/d3d9.hlsl";
vsFileName = psFileName; // both shaders are in the same file
}
}
else
{
psFileName = "../../media/opengl.psh";
vsFileName = "../../media/opengl.vsh";
}
break;
}
Codice PHP:
if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
{
device->getLogger()->log("WARNING: Pixel shaders disabled "\
"because of 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 "\
"because of missing driver/hardware support.");
vsFileName = "";
}
I parametri da passare a questo metodo sono i seguenti: Primo, il nome del file che contiene il codice di ciascun shader. Se invece volete usare addShaderMaterial(), non vi serve il nome del file, potete scrivere il codice direttamente in una stringa e passarla come parametro. Il parametro che segue è un puntatore alla interfaccia IShaderConstantSetCallBack che avevamo derivato all'inizio del tutorial. Se non volete passare alcuna costante allo shader, azzerate il parametro. L'ultimo parametro dice all'engine quale materiale deve usare come materiale base.
Per dimostrarlo, creiamo due materiali ciascuno con un differente materiale base, uno con EMT_SOLID l'altro con EMT_TRANSPARENT_ADD_COLOR.
Codice PHP:
// create materials
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
s32 newMaterialType1 = 0;
s32 newMaterialType2 = 0;
if (gpu)
{
MyShaderCallBack* mc = new MyShaderCallBack();
// create the shaders depending on if the user wanted high level
// or low level shaders:
if (UseHighLevelShaders)
{
// Choose the desired shader type. Default is the native
// shader type for the driver, for Cg pass the special
// enum value EGSL_CG
const video::E_GPU_SHADING_LANGUAGE shadingLanguage =
UseCgShaders ? video::EGSL_CG:video::EGSL_DEFAULT;
// create material from high level shaders (hlsl, glsl or cg)
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
{
// create material from low level shaders (asm or arb_asm)
newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mc, video::EMT_SOLID);
newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
psFileName, mc, video::EMT_TRANSPARENT_ADD_COLOR);
}
mc->drop();
}
Codice PHP:
// create test scene node 1, with the new created material type 1
scene::ISceneNode* node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,0,0));
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
smgr->addTextSceneNode(gui->getBuiltInFont(),
L"PS & VS & EMT_SOLID",
video::SColor(255,255,255,255), node);
scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
core::vector3df(0,0.3f,0));
node->addAnimator(anim);
anim->drop();
Codice PHP:
// create test scene node 2, with the new created material type 2
node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,-10,50));
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialFlag(video::EMF_BLEND_OPERATION, true);
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
smgr->addTextSceneNode(gui->getBuiltInFont(),
L"PS & VS & EMT_TRANSPARENT",
video::SColor(255,255,255,255), node);
anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
node->addAnimator(anim);
anim->drop();
Codice PHP:
// add a scene node with no shader
node = smgr->addCubeSceneNode(50);
node->setPosition(core::vector3df(0,50,25));
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
video::SColor(255,255,255,255), node);
Codice PHP:
// add a nice skybox
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
// add a camera and disable the mouse cursor
scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
cam->setPosition(core::vector3df(-100,50,100));
cam->setTarget(core::vector3df(0,0,0));
device->getCursorControl()->setVisible(false);
Codice PHP:
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,0,0,0));
smgr->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
Versione pdf scaricabile da QUI