Monday, May 26, 2014

15: Water Shader

     

      Water is one of the most complex shaders to render in games. Thanks to Petri Wilhelsmen's ocean shader demo, I was able to implement an ocean shader with wave ripples and light reflecting on the surface. The ocean plane is built up by vertexes and a deform shader was used to create the ocean waves using a normal map. I found a link to his sample here. The sample version of this tutorial was written in XNA 3.1 and I was able to convert it to XNA 4.0.



As shown in this image above, 
I am still tweaking the resolution the texture model when scaled for big environments in my game. 


       My next water shader test was based on Canton Javier Ferrero's Water Shader example program. It was written in XNA 3.1 and I have successfully converted it to XNA 4.0. The XNA 3.0 version of the program was updated by David Mariscal Fernandez. You can find the original sample from the XNA Community website. In his example, he created a technique to calculate the reflection map and reflection scene first and then used a component called fresnel that calculates the angle between the surface normal and the view direction. Then the water color component was adapted. The water has real-time reflection as well. You can see a video of it in action below. I have also made the XNA 4.0 version I converted available to download below. Microsoft made a lot of changes to XNA between versions 3.1 and 4.0. Some of them are a real pain. For example, they got rid of point sprites, which many game projects with particle systems in XNA 3.1 utilize. They also got rid of clipping planes, which many projects with water planes in XNA 3.1 use to render reflections and refraction. 





Download Source Code:





Friday, May 2, 2014

14: Attaching Non Skinned Models to an Animated Model








       Either animated or non-skinned models can now be attached to animated characters.  By non-skinned I mean just regular 3D models with no bones or animations in them. So models don't necessarily have to be rigged. I am using the XNAnimation Library and I recently got it to work in XNA 4.0. I was also able to get it to support multiple animation takes from a single .fbx file by creating a merge content processor. You can find the solution to this in my blog post here. In the future, attaching models will work great for character customization, vehicle customization and weapon customization. Gamers love being able to add armor to their characters and weapons to their vehicles. 

       For now, I am keept it brief with just a simple cowboy hat and one of my gun models to make sure I was able to get the program working right. I will soon make the entire menu system into a 3D scene. The camera will transition to different areas of the 3D scene after the player make certain selections. One of the major problems I encountered after figuring out how to attach models was making sure the model "looks" like its attached and is in the correct position I wanted it to be in. For example, after I attached the gun model, it appeared to look detached from the arm of the player. To fix this, I multiplied the parent bone transform of the gun model I wanted to attach to the world matrix of the character model and then multiplied that to the character model's right hand bone.




When attaching non-skinned models to your character, make sure you position them in the correct location in your modeling program.



Also, make sure that you are positioning them on the non-skinned model of your characters and creatures and input the correct bone number and information in the program.




Example source code below:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using XNAnimation;
using XNAnimation.Controllers;
using System;

namespace ModelAttachment
{

    public class ModelAttachment : Microsoft.Xna.Framework.Game
    {
        int RIGHT_HAND_BONE_ID;
        ///
        /// The AnimationController class handles the animation playback
        ///
        AnimationController animationController;

       // The SkinnedModel class handles the skeletal animated models
        SkinnedModel marine;

        Matrix view;
        Matrix projection;

        Model gun;

        public override void LoadContent()
        {
            RIGHT_HAND_BONE_ID = 15;
            // Calculate camera view and projection matrices.
            view = Matrix.CreateLookAt(new Vector3(-5, 5, -30),
            new Vector3(1, 3, 0), Vector3.Up);
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 10, 10000);

            LoadMarineModel();
        }

        ///
        /// Unloads graphics content for this screen.
        ///
        public override void UnloadContent()
        {
            content.Unload();
        }

        public string idle = "Idle";

        private void LoadMarineModel()
        {
            gun = content.Load<Model>("Models/weapon");
            marine = content.Load<SkinnedModel>("Models/PlayerMarine");
            animationController = new AnimationController(marine.SkeletonBones);
            animationController.StartClip(marine.AnimationClips["Idle"]);
        }
        public override void Update(GameTime gameTime)
        {
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            // Update the animation according to the elapsed time      
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }


        ///
        /// Draws the background screen.
        ///
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
            Drawmarine(marine, view, projection);
            DrawGun(gun,view, projection, animationController.SkinnedBoneTransforms[RIGHT_HAND_BONE_ID];

        }

        ///
        /// Draws the marine model
        ///
         private void Drawmarine(SkinnedModel marine, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in marine.Model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    foreach (SkinnedEffect effect in mesh.Effects)
                    {
                        effect.SetBoneTransforms(animationController.SkinnedBoneTransforms);
                        effect.World = Matrix.Identity;

                        // OPTIONAL - Configure lights
                        effect.EnableDefaultLighting();
                        effect.AmbientLightColor = new Vector3(0.1f);
                        effect.DiffuseColor = new Vector3(0.8f);
                        effect.SpecularColor = new Vector3(0.3f);
                        effect.SpecularPower = 8;

                        // Camera Properties
                        effect.View = view;
                        effect.Projection = projection;
                    }
                }
                mesh.Draw();
            }
        }


        public void DrawGun(Model model, Matrix view, Matrix projection, Matrix world)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            foreach (ModelMesh mm in model.Meshes)
            {
                foreach (BasicEffect be in mm.Effects)
                {
                    be.View = view;
                    be.Projection = projection;
                    be.World = transforms[mm.ParentBone.Index] * world;
                    be.EnableDefaultLighting();
                    be.AmbientLightColor = new Vector3(0.1f);
                    be.SpecularPower = 5;
                }
                mm.Draw();
            }
        }
    }
}