[PloobsEngine] Tutorial 7 – Ploobs Materials


Hi, This tutorial is the first from the series: PloobsEngine Advanced Tutorials. It will talk about how to use normal, specular, glow and paralax mapping with PloobsEngine.  (tutorials series here)

One of the greatest beneficts of Deferred Shading is the possibility of separating the light and geometry calculation. For us, in simple way, this mean that we dont need to rewrite all the phong calculation in every shader we build. The materials are just different ways of processing the geometry, the lighting evaluations remains exactely the same.

This article will show how to use and more important: how to extend the ploobs engine materials.

We did not implement lots of materials for Forward Rendering (only the very basic XNABasicEffect), almost all are in Deferred Rendering.

As we showed in previous tutorials, all IObjects have an IMaterial instance and all IMaterial instances have an IShader instance. The Ploobs Materials are just implementations of the IShader abstract class.

We have basicaly two types of IShader Implementations (both extends IShader), the ones used for deferred rendering and those used for forward rendering. You know the type of them using a MaterialType property of the IShader or by looking at the class name (the deferred shaders starts with Deferred + “ShaderName” + Shader Ex: DeferredNormalShader and the forward starts with Forward +  “ShaderName” + Shader — this is just our convention to make things easier =P).

The Deferred Shaders can only be used with the DeferredRenderTechnich (explained in previous tutorials) and the Forward Shaders can be used both in DeferredRenderTechnich (they wont be affected by the lights) and in ForwardRenderTechnich.

In this article we will talk more about Deferred Materials, the Forward ones are very simple and follow the same pattern. You can learn more about them in the Advanced Packages Demos.

Normal Mapping

Normal mapping is a technique in computer graphics for simulating bumps and wrinkles on the surface of an object. This is achieved by perturbing the surface normals of the object and using the perturbed normal during illumination calculations. The result is an apparently bumpy surface rather than a perfectly smooth surface although the surface of the underlying object is not actually changed. Theorically there is a difference between Normal mapping and Bump mapping, however we will use both meaning the same thing (pertubing surface normals -> normal mapping). Rigorouly, a bump map is a texture that defines the height of bumps on a surface, on a per-pixel basis. Bump maps encode the height as a gray-scale image and a normal map is a texture that defines which way light bounces off a surface, on a per-pixel basis. Normal maps encode the “direction of light reflections” (not exactely this, but this is enough for most uses) using RGB colors. (the “directions” can be in object space or tangent space). PloobsEngine uses Tangent space (more common nowadays)  Normal Mapping.

The model in which the Normal Mapping will be applyied MUST have Normals and Tangents in their vertices atributes (if they do not exist, an exception will be throw). The XNA importer can create those informations for you, just check the option as shown in the following image:

Content Processor Properties

Also, we need to attach the Normal Map texture to the corresponding IModelo (where the IShader will search for the texture). The following code shows how to create a simple object that uses normal mapping.

                IModelo model = new SimpleModel(factory, "ModelName", "DiffuseTextureName", "NormalMapName");
                IPhysicObject po = new TriangleMeshObject(model, Vector3.Zero, Matrix.Identity, Vector3.One, MaterialDescription.DefaultBepuMaterial());
                DeferredCustomShader shader = new DeferredCustomShader(false, true, false, false); ///activate only the normal mapping
                DeferredMaterial dm = new DeferredMaterial(shader);
                IObject obj = new IObject(dm, model, po);

The DeferredCustomShader is used for all the “mappings” (normal, glow,specular and paralax). In this sample we just activate the normal mappings.

Specular Mapping

Specular mapping is just a texture that specifies in pixel-level the “shinness” and the intensity of the surfaces (used in the Phong lightining evaluations). The usage is very similar to the Normal Mapping, you just need to attach the specular texture to the model and create the right shader. A code sample is provided below:

                IModelo model = new SimpleModel(factory, "ModelName", "DiffuseTextureName");
                model.SetTexture("SpecularMapName", TextureType.SPECULAR);
                IPhysicObject po = new TriangleMeshObject(model, Vector3.Zero, Matrix.Identity, Vector3.One, MaterialDescription.DefaultBepuMaterial());
                DeferredCustomShader shader = new DeferredCustomShader(false, false, true, false);
                DeferredMaterial dm = new DeferredMaterial(shader);
                IObject obj = new IObject(dm, model, po);

In this sample we used a Model function to add the texture (other way to do the same stuff we did in the normal map sample=P) and changed the shader constructor parameters.

Paralax Mapping

Parallax mapping (also called offset mapping or virtual displacement mapping) is an enhancement of the bump mapping or normal mapping techniques applied to textures in 3D rendering applications such as video games. To the end user, this means that textures such as stone walls will have more apparent depth and thus greater realism with less of an influence on the performance of the simulation. Parallax mapping was introduced by Tomomichi Kaneko in 2001.

Parallax mapping is implemented by displacing the texture coordinates at a point on the rendered polygon by a function of the view angle in tangent space (the angle relative to the surface normal) and the value of the height map at that point. At steeper view-angles, the texture coordinates are displaced more, giving the illusion of depth due to parallax effects as the view changes.

Parallax mapping described by Kaneko is a single step process that does not account for occlusion. Subsequent enhancements have been made to the algorithm incorporating iterative approaches to allow for occlusion and accurate silhouette rendering. PloobsEngine uses only the basic paralax algorithm.

The usage is pretty much the same as Normal and Specular. Our implementation uses Normal mapping and Paralax together, so you need to provide BOTH, the normal texture and the paralax texture. The Advanced Demos has a sample showing how to use and tune the paralax parameters.

A basic Paralax setup is shown below:

                ///Need to load the height, the normal texture and the difuse texture
                SimpleModel sm = new SimpleModel(factory,"..\\Content\\Model\\cubo", "..\\Content\\Textures\\color_map");
                sm.SetTexture("Textures\\normal_map", TextureType.BUMP);
                sm.SetTexture("Textures\\height_map", TextureType.PARALAX);                

                BoxObject pi = new BoxObject(new Vector3(200, 110, 0),1,1 ,1, 5, new Vector3(100, 100, 100),Matrix.Identity,MaterialDescription.DefaultBepuMaterial());
                ///Enable paralax and Normal Mapping;
                paralax = new DeferredCustomShader(false, true, false, true);
                paralax.SpecularIntensity = 0.2f;
                paralax.SpecularPower = 30;
                IMaterial mat = new DeferredMaterial(paralax);
                IObject obj3 = new IObject(mat, sm, pi);
                this.World.AddObject(obj3);

Glow Mapping

Glow map is a texture that specifies self luminance areas ( that resists to illumination changes) of an object in per pixel level. Our Glow technich also apply some tricks to improve the appearance of the glow area.

The usage is a bit different, you need to do the common stuffs (add the texture to the model and set the right shader) and add a PostEffect. For now you only need to know that a post effect is something that “process” the final 2D framebuffer image (will be explained in more details in next tutorials).

The whole process is shown below:

                SimpleModel sm = new SimpleModel(factory,"..\\Content\\Model\\cubo", "..\\Content\\Textures\\color_map");
                sm.SetTexture("Textures\\glow", TextureType.GLOW);                

                BoxObject pi = new BoxObject(new Vector3(200, 110, 0),1,1 ,1, 5, new Vector3(100, 100, 100),Matrix.Identity,MaterialDescription.DefaultBepuMaterial());
                Shader = new DeferredCustomShader(true, false, false, false);
                IMaterial mat = new DeferredMaterial(paralax);
                IObject obj3 = new IObject(mat, Shader, pi);
                this.World.AddObject(obj3);            

            this.RenderTechnic.AddPostEffect(new GlowPostEffect());

Attention to the Added post effect and to the CustomShader parameters. Remember that you can always combine different maps in the same object. A single object can have Specular and Normal mappings. To see the Materials in action, check the PloobsAdvanced Demos.

Extending the PloobsEngine Materials

Although ploobsengine has some pre-builded materials, it does not fill all of our need as a game developer. So it is very important to know how to create your own shaders/materials with the engine.

Creating a Forward Shader

Before talking about Forward shader extendions, lets explain the basic class that all shaders must extend:

    //     Base Class For all Shaders
    public abstract class IShader
    {
        public IShader();
        public abstract MaterialType MaterialType { get; }
        public float ShaderId { get; set; }
        public virtual void BasicDraw(GameTime gt, IObject obj, Matrix view, Matrix projection, IList lights, RenderHelper render, Plane? clippingPlane, bool useAlphaBlending = false);
        public virtual void DepthExtractor(GameTime gt, IObject obj, Matrix View, Matrix projection, RenderHelper render);
        public virtual void Draw(GameTime gt, IObject obj, RenderHelper render, ICamera cam, IList lights);
        public virtual void Initialize(GraphicInfo ginfo, GraphicFactory factory, IObject obj);
        public virtual void PosDrawPhase(GameTime gt, IObject obj, RenderHelper render, ICamera cam, IList lights);
        public virtual void PreDrawPhase(GameTime gt, IWorld world, IObject obj, RenderHelper render, ICamera cam);
        public virtual void PreUpdate(IObject ent, IList lights);
        public virtual void Update(GameTime gt, IObject ent, IList lights);
    }

The property MaterialType must be extendend and defines if the shader is deferred or forward.

The property ShaderId is not used in the ForwardShading, only in deferred. (will be explained)

The Initialize Method is used to load resources (including the effect, auxiliar textures …) and create resoruces (like render targets …)

The PreUpdate Method is called ONCE (before the first update) in the IShader lifetime and is used to make specialized initializations

The Update Method is called every frame (update part of the main loop)

The PreDraw Method is called all frames BEFORE all Draw methods of every object (the engine call all PreDraw in a loop and after it calls the Draw). Here you can do thinks like render the scene from diferent point of view (for dynamic reflection technics for example). The current render target setted is not the framebuffer necessarily, dont put informations on it, cause it will be erased.

The Draw Method is where you draw the model. Here you put the main shader with all light calculation … (in forward rendering)

The PosDraw Method is pretty similar to the PreDraw, the difference is that it is called after ALL the Draws and also WRITES in the Framebuffer. It is used for some special technichs like Glow (we dont use this aproach for glow in  ploobsengine =P, but is possible)

The DepthExtractor method is called when necessary by the shadow renderer. The purpouse of this method is to render the depth in projected space (Z/W). We provide an implementation (the method is virtual) that fits almost all circustances (only cases like animation and billboards needs another implementation). See the IShader code to understand how it is done =P

The BasicDraw method is called when necessary by the reflection/refractor renderer (same idea of th DepthExtractor). The purpose of this method it to render a simplification of the scene with clipping planes enabled. Again, the engine provides an implementation that fits 99% of the cases.. See the IShader code to understand how it is done.

You actually only need to override the MaterialType property, everything else is virtual, but normally we override the methods Initialize and Draw and the property MaterialType.

The following code shows a simple example of a new material (we also show the effect shader code, so you can see all the stuffs combined =P )

Effect shader (simple phong evaluation for a single directional light)

///Constantes
float4x4	World;
float4x4	View;
float4x4	Projection;
float3      LightDirection;
float4		LightColor;

float3      camPosition;

float4		SpecularColor;
float		SpecularPower;
float		SpecularIntensity;

float4      AmbientColor;
float		AmbientIntensity;

float		DiffuseIntensity;

///Textura e Samples para a textura Diffuse
texture DiffuseTexture;
sampler2D DiffuseSampler = sampler_state
{
	Texture = ;
    ADDRESSU = CLAMP;
	ADDRESSV = CLAMP;
	MAGFILTER = ANISOTROPIC;
	MINFILTER = ANISOTROPIC;
	MIPFILTER = LINEAR;
};

///Entrada do VertexShader
struct VertexShaderInput
{
    float3 Position : POSITION0;
	float3 Normal : Normal0;
	float2 TexCoord : TexCoord0;
};

///Saida do Vertex Shader
struct VertexShaderOutput
{
    float4 Position			: POSITION0;
    float3 N				: TEXCOORD0;
    float2 TextureCoord  		: TEXCOORD1;
    float3 V				: TEXCOORD2;
};

///VertexShader
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    ///Transforma os vertices
    float4 worldPosition = mul(float4(input.Position,1), World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    output.N = mul(float4(input.Normal,0), World);
    output.TextureCoord = input.TexCoord;
    output.V = camPosition -  worldPosition;
    return output;
}

//I=Ai*Ac+Di*Dc*N.L+Si*Sc*(R.V)^n
//R=2*(N.L)*N-L
///Pixel Shader
float4 PixelShaderAmbientFunction(VertexShaderOutput input) : COLOR0
{
    float3 Normal = normalize(input.N);
    float3 LightDir = -normalize(LightDirection);
    float3 ViewDir = normalize(input.V);
    float4 diffuseColor = tex2D(DiffuseSampler,input.TextureCoord);

    float Diff = saturate(dot(Normal, LightDir));
    // R = 2 * (N.L) * N – L
    float3 Reflect = normalize(2 * Diff * Normal - LightDir);
    float Specular = pow(saturate(dot(Reflect, ViewDir)), SpecularPower);
    // I = Dc*A + Dcolor * Dintensity * N.L + Sintensity * Scolor * (R.V)n
    return AmbientColor*AmbientIntensity + LightColor * DiffuseIntensity * diffuseColor * Diff + SpecularIntensity * SpecularColor * Specular;
}

///Pixel shader bonus que utiliza a propria cor diffuse como AmbientColor
float4 PixelShaderDiffuseAsAmbientFunction(VertexShaderOutput input) : COLOR0
{
    float3 Normal = normalize(input.N);
    float3 LightDir = -normalize(LightDirection);
    float3 ViewDir = normalize(input.V);
    float4 diffuseColor = tex2D(DiffuseSampler,input.TextureCoord);

    float Diff = saturate(dot(Normal, LightDir));
    // R = 2 * (N.L) * N – L
    float3 Reflect = normalize(2 * Diff * Normal - LightDir);
    float Specular = pow(saturate(dot(Reflect, ViewDir)), SpecularPower);
    // I = Dc*A + Dcolor * Dintensity * N.L + Sintensity * Scolor * (R.V)n
    return diffuseColor*AmbientIntensity + LightColor* DiffuseIntensity * diffuseColor * Diff + SpecularIntensity * SpecularColor * Specular;
}

technique TechniqueNormal
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 PixelShaderAmbientFunction();
    }
}

IShader implementation

namespace Tutorial2.Shaders
{
    public class BasicPhong : IShader
    {
        public BasicPhong(Vector3 LightDirection,Color LightColor,Color SpecularColor,float SpecularPower, float SpecularIntensity, Color AmbientColor,float AmbientIntensity, float DiffuseIntensity)
        {
            this.lightDirection = Vector3.Normalize(LightDirection);
            this.LightColor = LightColor;
            this.specularColor = SpecularColor;
            this.specularPower = SpecularPower;
            this.specularIntensity = SpecularIntensity;
            this.ambientColor = AmbientColor;
            this.ambientIntensity = AmbientIntensity;
            this.diffuseIntensity = DiffuseIntensity;
        }

        private Effect phong;

        Vector3 lightDirection;

        public Vector3 LightDirection
        {
            get { return lightDirection; }
            set { lightDirection = Vector3.Normalize(value); }
        }
        Color specularColor;

        Color lightColor;

        public Color LightColor
        {
            get { return lightColor; }
            set { lightColor = value; }
        }

        public Color SpecularColor
        {
            get { return specularColor; }
            set { specularColor = value; }
        }
        float specularPower;

        public float SpecularPower
        {
            get { return specularPower; }
            set { specularPower = value; }
        }
        float specularIntensity;

        public float SpecularIntensity
        {
            get { return specularIntensity; }
            set { specularIntensity = value; }
        }
        Color ambientColor;

        public Color AmbientColor
        {
            get { return ambientColor; }
            set { ambientColor = value; }
        }
        float ambientIntensity;

        public float AmbientIntensity
        {
            get { return ambientIntensity; }
            set { ambientIntensity = value; }
        }
        float diffuseIntensity;

        public float DiffuseIntensity
        {
            get { return diffuseIntensity; }
            set { diffuseIntensity = value; }
        }

        public override void Draw(Microsoft.Xna.Framework.GameTime gt, PloobsEngine.SceneControl.IObject obj, PloobsEngine.SceneControl.RenderHelper render, PloobsEngine.Cameras.ICamera cam, IList lights)
        {
            ///
            IModelo modelo = obj.Modelo;
            ///Recupera a transformacao do Objecto
            Matrix objTransform = obj.WorldMatrix;

            ///Sera os parametros do shader
            phong.Parameters["LightDirection"].SetValue(LightDirection);
            phong.Parameters["LightColor"].SetValue(LightColor.ToVector4());
            phong.Parameters["SpecularColor"].SetValue(SpecularColor.ToVector4());
            phong.Parameters["SpecularPower"].SetValue(SpecularPower);
            phong.Parameters["SpecularIntensity"].SetValue(SpecularIntensity);
            phong.Parameters["AmbientColor"].SetValue(AmbientColor.ToVector4());
            phong.Parameters["AmbientIntensity"].SetValue(AmbientIntensity);
            phong.Parameters["DiffuseIntensity"].SetValue(DiffuseIntensity);

            phong.Parameters["camPosition"].SetValue(cam.Position);
            phong.Parameters["DiffuseTexture"].SetValue(modelo.getTexture(TextureType.DIFFUSE));
            phong.Parameters["View"].SetValue(cam.View);
            phong.Parameters["Projection"].SetValue(cam.Projection);

            ///Desenha o IModelo,
            ///O IModelo contem diversos meshes, e cada mesh contem um array de BatchInformation
            for (int i = 0; i < modelo.MeshNumber; i++)
            {
                BatchInformation[] bi = modelo.GetBatchInformation(i);
                for (int j = 0; j < bi.Count(); j++)
                {
                    ///Compoe a World Matrix do Objecto como uma Combinacao da Matriz LocalTransformation
                    ///do modelo (que vem dos softwares de modelagem) e a matrix objTransform do objeto (transformacao do objeto no espaco fisico)
                    phong.Parameters["World"].SetValue(bi[j].ModelLocalTransformation * objTransform);
                    ///renderiza o modelo utilizando o efeito Phong
                    render.RenderBatch(bi[j],phong);
                }
            }

        }

        public override void Initialize(PloobsEngine.Engine.GraphicInfo ginfo, PloobsEngine.Engine.GraphicFactory factory, PloobsEngine.SceneControl.IObject obj)
        {
            base.Initialize(ginfo, factory, obj);
            phong = factory.GetEffect("Effects/phong",true);
            phong.CurrentTechnique = phong.Techniques["TechniqueNormal"];
        }

        public override MaterialType MaterialType
        {
            get { return PloobsEngine.Material.MaterialType.FORWARD; }
        }
    }

}

For some samples about extending ForwardRendering check our Phong samples.

Deferred Material

Extending Deferred Material is a bit diferent. The main problem is the Draw method.

In deferred Rendering, the Draw method does not render the model to the screen. Instead, it only extract some informations from the geometry and render them to the GBuffer. The easiest way to understand this is by example.

The following code is our classic deferred shader code : (Effect)

float4x4 World;
float4x4 ViewProjection;
float specularIntensity = 0;
float specularPower = 0;
float id;

texture Texture;
sampler diffuseSampler = sampler_state
{
    Texture = (Texture);
};

struct VertexShaderInput
{
    float3 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4  Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    float3 Normal : TEXCOORD1;
    float2 Depth : TEXCOORD2;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    float4 worldPosition = mul( float4(input.Position,1), World);
    output.Position  = mul(worldPosition, ViewProjection);
    output.TexCoord = input.TexCoord;                             //pass the texture coordinates further
    output.Normal =mul(input.Normal,World);                       //get normal into world space
    output.Depth.x = output.Position.z;
    output.Depth.y = output.Position.w;
    return output;    

}
struct PixelShaderOutput
{
    float4 Color : COLOR0;
    float4 Normal : COLOR1;
    float4 Depth : COLOR2;
    float4 Extra1 : COLOR3;
};

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
    PixelShaderOutput output;
    output.Color = tex2D(diffuseSampler, input.TexCoord);					   //output Color
    output.Color.a = specularIntensity;                                        //output SpecularIntensity
    output.Normal.rgb = 0.5f * (normalize(input.Normal) + 1.0f);               //transform normal domain
    output.Normal.a = specularPower;                                           //output SpecularPower
    output.Depth = input.Depth.x / input.Depth.y;                              //output Depth
    output.Extra1.rgb =  0;
    output.Extra1.a =  id;  

    return output;
}

technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

As you sow, the shader write in four targets at the same time (Pixel shader outputs for ColorX semantics), and in each of one we put diferent informations.

In the Color0 Semantic RGB channels we put the Diffuse color and in A we put the specular intensity
In the Color1 Semanctis RGB channels we put the Normal in World Space and in A we put the specular power

In Color2 Semantic we put the Depth (Pos projected Z/W). It is a 32 bits render target, so there is only one channel R.

In Color3 Semantics RGB channels we put the Glow Map (and 0 if not used) and in A we put the ShaderID

The shader ID is just a Tag that we can put in all the objects, the ploobsengine has one special tag, every object with ShaderID > 0.9 wont be affected by light. You can use the ShaderID to apply post process only in specific objects. (will be explained in future tutorials, for now, let it be the default 0)

Here is the IShader implementation that interacts with the effect

#if !WINDOWS_PHONE
namespace PloobsEngine.Material
{
    public class DeferredNormalShader : IShader
    {
        private Effect _shader;
        EffectParameter ViewProjectionParameter;
        EffectParameter TextureParameter;
        EffectParameter SpecularPowerParameter;
        EffectParameter SpecularIntensityParameter;
        EffectParameter IdParameter;
        EffectParameter WorldParameter;

        public DeferredNormalShader(float specularIntensity = 0, float specularPower = 0)
        {
            if (specularPower < 0)
            {
                ActiveLogger.LogMessage("specularPower cannot be negative, setting to 0", LogLevel.RecoverableError);
                specularPower = 0;
            }
            if (specularIntensity < 0)
            {
                ActiveLogger.LogMessage("specularIntensity cannot be negative, setting to 0", LogLevel.RecoverableError);
                specularIntensity = 0;
            }
            this.specularIntensity = specularIntensity;
            this.specularPower = specularPower;
        }

        private float specularIntensity = 0f;

        public float SpecularIntensity
        {
            get { return specularIntensity; }
            set { specularIntensity = value; }
        }
        private float specularPower = 0f;

        public float SpecularPower
        {
            get { return specularPower; }
            set { specularPower = value; }
        }

        public override void Draw(GameTime gt, IObject obj, RenderHelper render, ICamera camera, IList lights)
        {
                IModelo modelo = obj.Modelo;
                IdParameter.SetValue(shaderId);
                SpecularIntensityParameter.SetValue(specularIntensity);
                SpecularPowerParameter.SetValue(specularPower);
                ViewProjectionParameter.SetValue(camera.ViewProjection);

                for (int i = 0; i < modelo.MeshNumber; i++)
                {
                    BatchInformation[] bi = modelo.GetBatchInformation(i);
                    for (int j = 0; j < bi.Count(); j++)
                    {
                        TextureParameter.SetValue(modelo.getTexture(TextureType.DIFFUSE,i,j));
                        WorldParameter.SetValue(bi[j].ModelLocalTransformation * obj.WorldMatrix);
                        render.RenderBatch(bi[j], _shader);
                    }
                }
        }

        public override void Initialize(Engine.GraphicInfo ginfo, Engine.GraphicFactory factory, IObject obj)
        {
            this._shader = factory.GetEffect("RenderGBuffer",false,true);
            ViewProjectionParameter = this._shader.Parameters["ViewProjection"];
            TextureParameter = this._shader.Parameters["Texture"];
            IdParameter = this._shader.Parameters["id"];
            SpecularIntensityParameter = this._shader.Parameters["specularIntensity"];
            SpecularPowerParameter = this._shader.Parameters["specularPower"];
            WorldParameter = this._shader.Parameters["World"];
            base.Initialize(ginfo, factory, obj);
        }
        public override MaterialType MaterialType
        {
            get { return MaterialType.DEFERRED; }
        }
    }
}
#endif

This is all for today =P

The next tutorial probably will be about our PloobsImporter (loading scenes from 3DS MAX)

Links

, , , , , , , , , , ,

  1. #1 by Porn Movie on 21 de junho de 2017 - 10:03 pm

    Hello, Neat post. There’s a problem along with your site in web explorer, could check this? IE still is the market chief and a large component to folks will omit your great writing due to this problem.|

  2. #2 by human hair weave on 22 de junho de 2017 - 3:35 am

    i like this human hair weave https://www.youtube.com/watch?v=koiFnDsfNPU omg i is going to be buying an additional a single

  3. #3 by free download for windows pc on 22 de junho de 2017 - 6:00 am

    although websites we backlink to below are considerably not related to ours, we really feel they may be essentially worth a go by, so possess a look

  4. #4 by roadside emergency on 22 de junho de 2017 - 6:03 am

    very few sites that happen to be detailed beneath, from our point of view are undoubtedly effectively worth checking out

  5. #5 by golden goose femme pas cher on 22 de junho de 2017 - 7:08 am

    installations instruments orbeautiful de laboratoire Sir Humphrys Londres, peut-être un signe supplémentaire ofNorth-Sud rivalité et l’amertume de classe. Stephenson lui-même signé defiantlyInventor du Tube Capillaire Lamp.100George Stephenson 1781-1848 était un ingénieur doué, autodidacte, et plus tard thedesigner de la machine à vapeur de chemin de fer au début, la célèbre Rocket Stephenson whichbrought lui renommée internationale. Il était un inventeur de génie, un homme honnête et nofraud. Il devait être le héros d’un de Samuel Smiless industrialbiographies en circulation en 1859. Il est clair qu’il a été véritablement induit en erreur par les annonces prématurées de novembre lampe prototype Davys. Il a admis plus tard qu’il neverunderstood l’analyse scientifique du méthane, ou les principes derrière la irongauze finale Davy Safety Lamp. Il

  6. #6 by katana blade on 22 de junho de 2017 - 7:54 am

    Great quality

  7. #7 by самолетни билети за холандия on 22 de junho de 2017 - 12:11 pm

    Spot on with this write-up, I truly think this website needs much more consideration. I’ll probably be again to read much more, thanks for that info.

  8. #8 by samurai in japanese on 22 de junho de 2017 - 1:56 pm

    beautiful and extremely well made sword. Bought two of them one for myselff and a collector

  9. #9 by Naginata Sword on 22 de junho de 2017 - 2:05 pm

    This is an awesome weapon. Yes, weapon. This is not a toy or a show piece. This Katana came very sharp and battle ready. A few passes in my ceramic sharpener and it is razor sharp. The details, from the braided silk over sting ray skin hilt to the carbon steel blade are impressive. And I paid about half the price listed on the “Cold Steel” site. Bottom line… If you want a heavy sword that is a SWORD you can’t beat this blade.

  10. #10 by https://pt.wikipedia.org/wiki/Antônio_Carlos_de_Arruda_Botelho on 22 de junho de 2017 - 2:32 pm

    Thanks a bunch for sharing this with all of us you really
    understand what you’re speaking about! Bookmarked.
    Please additionally seek advice from my website =). We may have a link change arrangement among us

  11. #11 by hobbyhuren kleve on 22 de junho de 2017 - 3:51 pm

    Wieder spürte ich eine Hand meiner Muschi, diesmal allerdings einem anderen Mann.Später zogen wir uns etwas über die Schultern, weil es ein wenig kühl wurde.

  12. #12 by hobbynutten sachsen on 22 de junho de 2017 - 4:12 pm

    Viele vollgewichste nackte Teens machen einen Fingerfick mit ihren saftigen feuchten Mösen das auch dir gleich ganz heiß werden wird.

  13. #13 by heavy equipment repairs miami on 22 de junho de 2017 - 4:40 pm

    please go to the websites we follow, like this 1, because it represents our picks in the web

  14. #14 by best plumbing services on 22 de junho de 2017 - 4:52 pm

    very handful of internet websites that come about to be in depth below, from our point of view are undoubtedly properly worth checking out

  15. #15 by certified plumbers on 22 de junho de 2017 - 5:33 pm

    here are some links to websites that we link to since we believe they’re worth visiting

  16. #16 by katies on 22 de junho de 2017 - 5:34 pm

    indian food 400 calorie diet

  17. #17 by Clay Tempered Sanmai Blade sword on 22 de junho de 2017 - 5:51 pm

    This is a great sword. You can see the quality on the blade, and the balance is perfect. A great value.

  18. #18 by katie on 22 de junho de 2017 - 6:38 pm

    diet pills 4009 v

  19. #19 by Samurai Sword on 22 de junho de 2017 - 7:03 pm

    The katana arrived in perfect conditions, just as described, inside a box and with a fabric cover.

1 440 441 442
(não será publicado)