Irrlicht Engine logo

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

 

 

 

 

 


Kopírování obsahu bez souhlasu autorů je zakázáno.
Copyright 2007 Irrlicht3d.cz