Aby wyjaśnić na czym polega efekt zacznę od dwóch zrzutów ekranu z jego implementacją na wygenerowanym torusie.
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 ); } }