Xna 4.0 – Podstawowe shadery zgodne z WP7 – Environment Mapping

Aby wyjaśnić na czym polega efekt zacznę od dwóch zrzutów ekranu z jego implementacją na wygenerowanym torusie.

image image

Idea jest dobrze wyjaśniona na Wikipedii. Generalnie chodzi o skonstruowanie materiału, który odbija światło (i inne obiekty) jak na przykład powierzchnia metalu. Xna realizuje “kubusiowe” podejście :) czyli oparte na sześcianie i sześciu teksturach, które w zależności od kątów są wykorzystane do odbić.

Za pomocą Xna 4.0 proces przypisania takiego efektu do obiektu jest banalnie prostu.
Jedynym warunkiem na wejściu jest utworzenie (lub wgranie z pliku) obiektu, który ma określone koordynaty UV (do mapowania tekstury). W przypadku tego efektu jest to wymagane, sam się na tym przejechałem, kiedy nie chciałem mieć żadnego podstawowego mapowania tekstur pod spodem i określiłem tylko to co wydawało mi się konieczne do environment mappingu i w efekcie dostałem wyjątek wewnętrzny po stronie Xna i zwieszkę emulatora WP7 :)

Mając taki model jak na przykład wygenerowany przeze mnie torus powyżej określenie efektu odbywa się podobnie jak w poprzednim przykładzie:

EnvironmentMapEffect effect = new EnvironmentMapEffect(device);

effect.Texture = baseTexture; //wymagana tekstura w formacie Texture2D effect.EnvironmentMap = envMap; //wymagana tekstura w formacie TextureCube effect.EnvironmentMapSpecular = new Vector3(.4f, .4f, .4f);
//podbicie “mocy” oświetlenia, popatrzcie różnicę pomiędzy lewym
//a prawym zrzutem ekranu. Pierwszy był bez manipulacji wartością Specular.

Przypisanie takiego efektu (czego nie omówiłem wcześniej) do obiektu to prosta sprawa:

Effect effect = GetEnvMapEffect(GraphicsDevice);
//powyższa metoda w praktyce wykonuje powyższy kod i zwraca efekt
sphere.Meshes[0].MeshParts[0].Effect = effect;

Jeśli geometrię wygenerowana mamy samemu to zanim ją zaczniemy rysować należy wykonać metodę Apply() dla każdego przejścia (EffectPass) we wszystkich technikach (lub CurrentTechnique) danego efektu. We wszystkich przejściach i technikach to tak naprawdę na wyrost powiedziane. To się przydaje gdy efekt mamy zdefiniowany za pomocą Shadera napisanego w HLSL. W przypadku efektów zdefiniowanych w klasach i zgodnych z WP7 technikę będziemy mieli jedną i przejście jedno więc wystarczy na skróty:

effect.CurrentTechnique.Passes[0].Apply();

W przypadku klasy Model to wszystko dzieje się automatycznie gdy wykonamy Model.Draw() .

Jeśli jesteście ciekawi co taki environment mapping robi dokładniej to macie przykład takiego samego efektu właśnie napisanego w HLSL (zgodny z PC i Xbox 360):

#define PI 3.1415
//parametry wejściowe
float4x4 mWorldViewProj; // World * View * Projection
float3 mCameraPosition;
texture mTexture;
texture mEnvTexture;

float eyePositionW;

samplerCUBE mEnvTextureSample = sampler_state
{
texture = <mEnvTexture>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};

sampler gTextureSampler = sampler_state
{
texture = <mTexture>;
mipfilter = LINEAR;
};

struct VS_IN
{
float4 Position : POSITION;
float3 Normal : NORMAL;
};

// -----------------------------------------------------------------
struct VS_OUT {
float4 pos : POSITION;
float4 texNorm : TEXCOORD0;
float4 envCoord : TEXCOORD1;
};

float4 reflect(float4 I, float4 N)
{
return I - 2.0 * N * dot(N, I);
}

VS_OUT Vertex_Shader_Transform(
in float4 vPosition : POSITION,
in float4 vNormal :NORMAL,
in float4 vTexCoord : TEXCOORD0
)
{
VS_OUT outVal;
float4 normalized = normalize(vNormal);

    outVal.pos = mul( vPosition, mWorldViewProj ); outVal.texNorm = float4(0,0,0,0);
outVal.texNorm.x = asin(normalized.x)/(PI);
outVal.texNorm.y = asin(normalized.y)/(PI);
       

    float4 positionW = mul(mWorldViewProj, outVal.pos);
float4 N = mul(mWorldViewProj, vNormal);
N = normalize(N); float4 I = positionW-eyePositionW;
outVal.envCoord = reflect(I, N);

    return outVal;
}
// -----------------------------------------------------------------
float4 PixelShaderFunction(
float reflectionFactor : COLOR,
VS_OUT input,
uniform sampler TextureMap ) : COLOR0
{ float4 reflectedColor = texCUBE(mEnvTextureSample, input.envCoord);
float4 outColor = (reflectedColor);
outColor.a = 1.0;
return outColor;
}
// -----------------------------------------------------------------
technique Technique1
{
pass p0
{
VertexShader = compile vs_2_0 Vertex_Shader_Transform();
PixelShader = compile ps_2_0 PixelShaderFunction( gTextureSampler );
}
}