Tutorial 10: Shadery
Přeložil: Ondřej Hübsch
Tento tutoriál ukazuje jak použít shadery pro D3D8, D3D9 a OpenGL v enginu a jak vytvořit materiály, které používají shadery. Dále ukazuje jak vypnout
generaci mipmap při načítání textur, a jak používat text scene node.
Neukazuje ale, jak shadery fungují. Doporučuji Vám přečíst si dokumentaci D3D nebo OpenGL, najít tutoriál, nebo si o tomto tématu přečíst knihu.
Výsledný program vypadá takto:
|
| Začínáme! |
Na začátku, jako vždy, potřebujeme načíst všechny hlavičkové soubory,
a provést věci, které děláme s každým tutoriálem (aplikací):
#include <irrlicht.h> #include <iostream>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
|
Protože chceme použít pár zajímavých shaderů v těchto tutoriálech,
musíme je nastavit, aby mohli ukazovat zajímavé efekty. V tomto příkladu
budeme používat jednoduchý vertex shader, který bude počítat barvu vertexu, který je na pozici kamery.
Budeme potřebovat následující data: Invertovanou world matici pro transformaci normál, ořezávací matici pro
transformaci pozice, pozici kamery a world pozici objektu pro kalkulaci úhlu světla a barvu světla.
Abychom byli schopni říci shaderu všechna tato data každý snímek, musíme odvodit třídu z rozhraní
IShaderConstantSetCallBack a přepsat pouze metodu, která se nazývá OnSetConstants(). Tato metoda
bude volána neustále, pokud je nastaven patřičný materiál.
Metoda setVertexShaderConstant() z rozhraní IMaterialRendererServices je používána pro nastavení dat, která shader potřebuje.
Jestliže si uživatel vybere používání High Level Shader Language jako je například HLSL narozdíl od Assembleru v tomto příkladu,
musí nastavit jméno promněné jako parametr namísto registrování indexu.
IrrlichtDevice* device = 0; bool UseHighLevelShaders = 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.M[0], 16); else services->setVertexShaderConstant(&invWorld.M[0], 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.M[0], 16); else services->setVertexShaderConstant(&worldViewProj.M[0], 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.M[0], 16); else services->setVertexShaderConstant(&world.M[0], 10, 4); } }; |
Následujících pár řádek zapne engine, stejně jako ve většině předchozích tutoriálů.
Navíc se ale zeptáme uživatele, jestli hodlá použít high lever shadery (pouze pokud má odpovídající zařízení, které tuto možnost podporuje).
int main() { // let user select driver type
video::E_DRIVER_TYPE driverType = video::EDT_DIRECTX9;
printf("Please select the driver you want for this example:\n"\ " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\ " (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\ " (f) NullDevice\n (otherKey) exit\n\n");
char i; std::cin >> i;
switch(i) { case 'a': driverType = video::EDT_DIRECT3D9;break; case 'b': driverType = video::EDT_DIRECT3D8;break; case 'c': driverType = video::EDT_OPENGL; break; case 'd': driverType = video::EDT_SOFTWARE; break; case 'e': driverType = video::EDT_SOFTWARE2;break; case 'f': driverType = video::EDT_NULL; break; default: return 1; }
// ask the user if we should use high level shaders for this example if (driverType == video::EDT_DIRECT3D9 || driverType == video::EDT_OPENGL)
{ printf("Please press 'y' if you want to use high level shaders.\n"); std::cin >> i; if (i == 'y') UseHighLevelShaders = true; }
// create device
device = createDevice(driverType, core::dimension2d<s32>(640, 480));
if (device == 0) { printf("\nWas not able to create driver.\n"\ "Please restart and select another driver.\n"); getch(); return 1; }
video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); gui::IGUIEnvironment* gui = device->getGUIEnvironment(); |
Nyní následují více zajímavější části. Jestiže používáme Direct3D,
chceme načíst vertex a pixel shader programy, jestliže používáme OpenGL,
chceme načíst ARB fragmenty a vertex programy.
Napsal jsem odpovídající programy (naleznete je níže) - d3d8.ps, d3d8.vs, d3d9.ps, d3d9.vs, opengl.ps a opengl.vs.
Nyní potřebujeme pouze správné jména souborů. To je zařízeno dále v následujícím switch-u.
Zapamatujte si, není důležité zapisovat shadery do textových souborů jako v tomto případě,
můžete je totiž zapsat jako string do cpp souboru a později použít addShaderMaterial() namísto
addShaderMaterialFromFiles().
c8* vsFileName = 0; // filename for the vertex shader c8* psFileName = 0; // 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) { 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) { psFileName = "../../media/opengl.frag"; vsFileName = "../../media/opengl.vert"; } else { psFileName = "../../media/opengl.psh"; vsFileName = "../../media/opengl.vsh"; } break; }
|
Dále, potřebujeme ověřit, jestli má uživatel dostatečný hardware a
vybrat render který umí spustit shader, který chceme.
Jestliže ne, jednoduše nastavíme řetězec se jménem souboru na 0.
Není to důležité, ale v tomto případě velmi užitečné:
Například, jestliže hardware umí spustit vertex shader, ale již neumí pixel shader,
jednoduše vytvoříme nový materiál, který používá pouze vertex shader, nikoliv pixel shader.
Jinak, pokud řekneme enginu aby vytvořil materiál a engine zjistí, že hardware
není schopen jej vykreslit kompletně, engine odmítne a nevytvoří žádný materiál.
Takže, v tomto tutoriálu, by jste měli vidět minimálně vertex shader v akci, pokud Vaše karta neumí pixel shadering.
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 = 0; } 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 = 0; } |
Nyní, se pustíme do vytvoření materiálů.
Možná, že již víte z předchozích příkladů, že typ materiálu v irrlichtu je
jednoduše nastaven změněním hodnoty MaterialType v struktuře SMaterial.
Tato hodnota je jednoduše 32 bitová, jako video::EMT_SOLID, takže jediné co potřebujeme je,
aby pro nás engine vytvořil novou hodnotu, kterou budeme moci nastavit. Pro to musíme získat
ukazatel na IGPUProgrammingServices a zavolat metodu addShaderMaterialFromFiles(), která navrací novou, 32-bitovou hodnotu. Toť vše.
Paramtery této metody jsou následující: První je jméno souboru který obsahuje kód vertex a pixel shaderu.
Jestliže používáte addShaderMaterial(), nepotřebujete žádná jména souborů, jediné co musíte je dát kód shaderu
jako řetězec. Následující parametr je ukazatel na IShaderConstantSetCallBack, třídu, kterou jsme si
napsali na začátku tohoto tutoriálu. Jestliže nechcete nastavovat konstanty, nastavte tento parametr na 0.
Poslední parametr říká enginu, jaký materiál má použít jako základní.
Pro ukázku si vytvoříme dva materiály s odlišným základním materiálem, jeden se EMT_SOLID,
druhý s EMT_TRANSPARENT_ADD_COLOR.
// 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) { // create material from high level shaders (hlsl or glsl)
newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles( vsFileName, "vertexMain", video::EVST_VS_1_1, psFileName, "pixelMain", video::EPST_PS_1_1, mc, video::EMT_SOLID);
newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles( vsFileName, "vertexMain", video::EVST_VS_1_1, psFileName, "pixelMain", video::EPST_PS_1_1, mc, video::EMT_TRANSPARENT_ADD_COLOR); } 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(); }
|
Nyní je čas na otestování materiálů.
Vytvoříme testovací krychli a nastavíme jí vytvořený materiál. Dále přidáme textový scene node ke krychli a
rotační animátor, který ji udělá zajímavější a důležitější.
// 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(); |
Stejné to je pro druhou krychli, pouze s odlišným materiálem.
// 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->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(); |
Poté přidáme další krychli, která nepoužívá shadery, čistě pro porovnání.
// 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);
|
Jako poslední do scény přidáme skybox a uživatelsky ovladatelnou kameru. Pro skybox textury vypneme
generaci mipmap, jelikož pro ni mipmapy nepotřebujeme.
// 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(0, 100.0f, 100.0f); cam->setPosition(core::vector3df(-100,50,100)); cam->setTarget(core::vector3df(0,0,0)); device->getCursorControl()->setVisible(false); |
Nyní vše vykreslíme. A to je, vážení, vše!
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;
|
Nyní vše zkompilujeme a doufám že si užijete hodně srandy s psaním shaderů :).
|
| Soubory shaderů |
Soubory obsahují shadery které můžete naleznout ve složce media (v SDK)Vypadají takto:
| D3D9.HLSL |
// part of the Irrlicht Engine Shader example.
// These simple Direct3D9 pixel and vertex shaders // will be loaded by the shaders
// example. Please note that these example shaders don't do // anything really useful.
// They only demonstrate that shaders can be used in Irrlicht.
//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
float4x4 mWorldViewProj; // World * View * Projection transformation
float4x4 mInvWorld; // Inverted world matrix
float4x4 mTransWorld; // Transposed world matrix
float3 mLightPos; // Light position
float4 mLightColor; // Light color
// Vertex shader output structure
struct VS_OUTPUT
{
float4 Position : POSITION; // vertex position
float4 Diffuse : COLOR0; // vertex diffuse color
float2 TexCoord : TEXCOORD0; // tex coords
};
VS_OUTPUT vertexMain( in float4 vPosition : POSITION,
in float3 vNormal : NORMAL,
float2 texCoord : TEXCOORD0 )
{
VS_OUTPUT Output;
// transform position to clip space
Output.Position = mul(vPosition, mWorldViewProj);
// transform normal
float3 normal = mul(vNormal, mInvWorld);
// renormalize normal
normal = normalize(normal);
// position in world coodinates
float3 worldpos = mul(mTransWorld, vPosition);
// calculate light vector, vtxpos - lightpos
float3 lightVector = worldpos - mLightPos;
// normalize light vector
lightVector = normalize(lightVector);
// calculate light color
float3 tmp = dot(-lightVector, normal);
tmp = lit(tmp.x, tmp.y, 1.0);
tmp = mLightColor * tmp.y;
Output.Diffuse = float4(tmp.x, tmp.y, tmp.z, 0);
Output.TexCoord = texCoord;
return Output;
}
// Pixel shader output structure
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // Pixel color
};
sampler2D tex0;
PS_OUTPUT pixelMain( float2 TexCoord : TEXCOORD0,
float4 Position : POSITION,
float4 Diffuse : COLOR0 )
{
PS_OUTPUT Output;
float4 col = tex2D( tex0, TexCoord ); // sample color map
// multiply with diffuse and do other senseless operations
Output.RGBColor = Diffuse * col;
Output.RGBColor *= 4.0;
return Output;
} |
| D3D9.VSH |
; part of the Irrlicht Engine Shader example.
; This Direct3D9 vertex shader will be loaded by the engine.
; Please note that these example shaders don't do anything really useful.
; They only demonstrate that shaders can be used in Irrlicht.
vs.1.1
dcl_position v0; ; declare position
dcl_normal v1; ; declare normal
dcl_color v2; ; declare color
dcl_texcoord0 v3; ; declare texture coordinate
; transpose and transform position to clip space
mul r0, v0.x, c4
mad r0, v0.y, c5, r0
mad r0, v0.z, c6, r0
add oPos, c7, r0
; transform normal
dp3 r1.x, v1, c0
dp3 r1.y, v1, c1
dp3 r1.z, v1, c2
; renormalize normal
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w
; calculate light vector
m4x4 r6, v0, c10 ; vertex into world position
add r2, c8, -r6 ; vtxpos - lightpos
; normalize light vector
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w
; calculate light color
dp3 r3, r1, r2 ; dp3 with negative light vector
lit r5, r3 ; clamp to zero if r3 < 0, r5 has diffuce component in r5.y
mul oD0, r5.y, c9 ; ouput diffuse color
mov oT0, v3 ; store texture coordinates |
| D3D9.PSH |
; part of the Irrlicht Engine Shader example.
; This simple Direct3D9 pixel shader will be loaded by the engine.
; Please note that these example shaders don't do anything really useful.
; They only demonstrate that shaders can be used in Irrlicht.
ps.1.1
tex t0 ; sample color map
add r0, v0, v0 ; mulitply with color
mul t0, t0, r0 ; mulitply with color
add r0, t0, t0 ; make it brighter and store result
|
| D3D8.VSH |
; part of the Irrlicht Engine Shader example.
; This Direct3D9 vertex shader will be loaded by the engine.
; Please note that these example shaders don't do anything really useful.
; They only demonstrate that shaders can be used in Irrlicht.
vs.1.1
; transpose and transform position to clip space
mul r0, v0.x, c4
mad r0, v0.y, c5, r0
mad r0, v0.z, c6, r0
add oPos, c7, r0
; transform normal
dp3 r1.x, v1, c0
dp3 r1.y, v1, c1
dp3 r1.z, v1, c2
; renormalize normal
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w
; calculate light vector
m4x4 r6, v0, c10 ; vertex into world position
add r2, c8, -r6 ; vtxpos - lightpos
; normalize light vector
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w
; calculate light color
dp3 r3, r1, r2 ; dp3 with negative light vector
lit r5, r3 ; clamp to zero if r3 < 0, r5 has diffuce component in r5.y
mul oD0, r5.y, c9 ; ouput diffuse color
mov oT0, v3 ; store texture coordinates |
| D3D8.PSH |
; part of the Irrlicht Engine Shader example.
; This simple Direct3D9 pixel shader will be loaded by the engine.
; Please note that these example shaders don't do anything really useful.
; They only demonstrate that shaders can be used in Irrlicht.
ps.1.1
tex t0 ; sample color map
mul_x2 t0, t0, v0 ; mulitply with color
add r0, t0, t0 ; make it brighter and store result |
| OPENGL.VSH |
!!ARBvp1.0
# part of the Irrlicht Engine Shader example.
# Please note that these example shaders don't do anything really useful.
# They only demonstrate that shaders can be used in Irrlicht.
#input
ATTRIB InPos = vertex.position;
ATTRIB InColor = vertex.color;
ATTRIB InNormal = vertex.normal;
ATTRIB InTexCoord = vertex.texcoord;
#output
OUTPUT OutPos = result.position;
OUTPUT OutColor = result.color;
OUTPUT OutTexCoord = result.texcoord;
PARAM MVP[4] = { state.matrix.mvp }; # modelViewProjection matrix.
TEMP Temp;
TEMP TempColor;
TEMP TempNormal;
TEMP TempPos;
#transform position to clip space
DP4 Temp.x, MVP[0], InPos;
DP4 Temp.y, MVP[1], InPos;
DP4 Temp.z, MVP[2], InPos;
DP4 Temp.w, MVP[3], InPos;
#transform normal
DP3 TempNormal.x, InNormal.x, program.local[0];
DP3 TempNormal.y, InNormal.y, program.local[1];
DP3 TempNormal.z, InNormal.z, program.local[2];
#renormalize normal
DP3 TempNormal.w, TempNormal, TempNormal;
RSQ TempNormal.w, TempNormal.w;
MUL TempNormal, TempNormal, TempNormal.w;
# calculate light vector
DP4 TempPos.x, InPos, program.local[10]; # vertex into world position
DP4 TempPos.y, InPos, program.local[11];
DP4 TempPos.z, InPos, program.local[12];
DP4 TempPos.w, InPos, program.local[13];
ADD TempPos, program.local[8], -TempPos; # vtxpos - lightpos
# normalize light vector
DP3 TempPos.w, TempPos, TempPos;
RSQ TempPos.w, TempPos.w;
MUL TempPos, TempPos, TempPos.w;
# calculate light color
DP3 TempColor, TempNormal, TempPos; # dp3 with negative light vector
LIT OutColor, TempColor; # clamp to zero if r3 < 0
MUL OutColor, TempColor.y, program.local[9]; # ouput diffuse color
MOV OutColor.w, 1.0; # we want alpha to be always 1
MOV OutTexCoord, InTexCoord; # store texture coordinate
MOV OutPos, Temp;
END |
| OPENGL.PSH |
!!ARBfp1.0
# part of the Irrlicht Engine Shader example.
# Please note that these example shaders don't do anything really useful.
# They only demonstrate that shaders can be used in Irrlicht.
#Input
ATTRIB inTexCoord = fragment.texcoord; # texture coordinates
ATTRIB inColor = fragment.color.primary; # interpolated diffuse color
#Output
OUTPUT outColor = result.color;
TEMP texelColor;
TEMP tmp;
TXP texelColor, inTexCoord, texture, 2D;
ADD tmp, inColor, inColor; # mulitply with color
MUL texelColor, texelColor, tmp; # mulitply with color
ADD outColor, texelColor, texelColor; # make it brighter and store result
END |
|
|