Sgt. Conker We are "absolutely fine"

3Nov/097

Article : Realistic Soft Edged Water In XNA

This looks good, but there isnt much detail in the reflection. Let’s quickly add a skybox to the scene.

Download this cubemap: Link

Add it to your Content project.

Right click on your project and choose New>New Item>Code File, and call it Skybox.cs.

Paste the following code:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace LensFlare
{
    class Skybox
    {
        private VertexDeclaration VertexDeclaration;
        private VertexBuffer VertexBuffer;
        private IndexBuffer IndexBuffer;

        private TextureCube Environment;
        private Effect Shader;

        private GraphicsDevice Device;

        public Skybox(GraphicsDevice device, TextureCube environment, Effect shader)
        {
            this.Environment = environment;
            this.Shader = shader;

            this.Device = device;

            this.VertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);
            this.VertexBuffer = new VertexBuffer(device, typeof(VertexPositionColor), 8, BufferUsage.WriteOnly);
            this.IndexBuffer = new IndexBuffer(device, 36 * sizeof(ushort), BufferUsage.WriteOnly, IndexElementSize.SixteenBits);

            VertexPositionColor[] vertices = new VertexPositionColor[8];
            vertices[0].Position = new Vector3(-1, 1, 1);
            vertices[1].Position = new Vector3(1, 1, 1);
            vertices[2].Position = new Vector3(1, 1, -1);
            vertices[3].Position = new Vector3(-1, 1, -1);

            vertices[4].Position = new Vector3(-1, -1, 1);
            vertices[5].Position = new Vector3(1, -1, 1);
            vertices[6].Position = new Vector3(1, -1, -1);
            vertices[7].Position = new Vector3(-1, -1, -1);

            this.VertexBuffer.SetData(vertices, 0, 8);

            vertices = null;

            ushort[] indices = new ushort[36];

            // top
            indices[0] = 0; indices[1] = 1; indices[2] = 3;
            indices[3] = 2; indices[4] = 3; indices[5] = 1;

            //bottom
            indices[6] = 5; indices[7] = 4; indices[8] = 6;
            indices[9] = 7; indices[10] = 6; indices[11] = 4;

            // front
            indices[12] = 3; indices[13] = 2; indices[14] = 7;
            indices[15] = 6; indices[16] = 7; indices[17] = 2;

            // back
            indices[18] = 1; indices[19] = 0; indices[20] = 4;
            indices[21] = 4; indices[22] = 5; indices[23] = 1;

            // left
            indices[24] = 0; indices[25] = 3; indices[26] = 4;
            indices[27] = 4; indices[28] = 3; indices[29] = 7;

            // right
            indices[30] = 2; indices[31] = 1; indices[32] = 6;
            indices[33] = 5; indices[34] = 6; indices[35] = 1;

            this.IndexBuffer.SetData(indices, 0, 36);

            indices = null;
        }

        ~Skybox()
        {
            this.VertexDeclaration.Dispose();
            this.VertexDeclaration = null;

            this.VertexBuffer.Dispose();
            this.VertexBuffer = null;

            this.IndexBuffer.Dispose();
            this.IndexBuffer = null;

            this.Device = null;

            this.Environment.Dispose();
            this.Environment = null;

            this.Shader.Dispose();
            this.Shader = null;
        }

        public void Draw(Matrix view, Matrix projection)
        {
            view.Translation = Vector3.Zero;

            Matrix viewProjection = view*projection;

            this.Device.VertexDeclaration = this.VertexDeclaration;
            this.Device.Vertices[0].SetSource(this.VertexBuffer, 0, VertexPositionColor.SizeInBytes);
            this.Device.Indices = this.IndexBuffer;

            RenderState state = Device.RenderState;

            state.DepthBufferEnable = false;
            state.DepthBufferWriteEnable = false;

            state.CullMode = CullMode.None;

            state.AlphaBlendEnable = false;
            state.AlphaTestEnable = false;

            this.Device.Textures[0] = this.Environment;

            this.Shader.Begin();
            this.Shader.Parameters["ViewProjection"].SetValue(viewProjection);

            this.Shader.Techniques[0].Passes[0].Begin();
            this.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);

            this.Shader.Techniques[0].Passes[0].End();

            this.Shader.End();

            state.DepthBufferEnable = true;
            state.DepthBufferWriteEnable = true;

            state.CullMode = CullMode.CullCounterClockwiseFace;
        }
    }
}

We also need a shader for it. Right click the content project and add a new Effect called Skybox.fx. Fill it with the following shader:

samplerCUBE SkySampler : register(s0);

float4x4 ViewProjection;

void VS(in float4 position : POSITION0, out float4 pos : POSITION0, out float3 texCoord : TEXCOORD0)
{
    pos = mul(float4(position.xyz, 1), ViewProjection).xyww;

    texCoord = position.xyz;
}

float4 PS(in float3 pos : TEXCOORD0) : COLOR0
{
    float3 sample = pow(texCUBE(SkySampler, normalize(pos)), 1.2);

    float3 result = pow(sample, 10) * 10;
    result += sample * 4;
    result *= 0.25f;

    return float4(result, 1);
}

technique Sky
{
    pass pass1
    {
        VertexShader = compile vs_2_0 VS();
        PixelShader = compile ps_2_0 PS();
    }
}

We are getting close to the final result, we just need to create and draw this skybox. Create a new field in your game class:

private Skybox Sky;

Initialize it at the end of the LoadContent method:

this.Sky = new Skybox(GraphicsDevice, Content.Load("Miramar"), Content.Load("Skybox"));

And render it into the reflection and backbuffers. In DrawReflection() right before the DrawTerrain call, add:

this.Sky.Draw(reflectionView, reflectionProjection);

Then in the Draw() method, before the final call to DrawTerrain(), add:

this.Sky.Draw(view, projection);

About CorporalX

Professional XNA/.NET developer.
Comments (7) Trackbacks (0)
  1. Finally, a subject on just water and not necessarily ‘adding’ water for Xna.
    I am going to try this out as my goal is to just do water itself in Xna, for now.

  2. This is very similar to the technique I use to render water as a post processing pass. In my implementation I render geometry into the stencil buffer and output linear view space depth. Then run a full screen quad over the results of that to produce the water in the back-buffer with stencil rejection enabled.

  3. Hey,

    is there a way you could re-upload the sample and images for this tutorial? Would be really nice.
    BTW this is just the tutorial i’ve been looking for for months … thank you soooo much :D

  4. Existence – Fixed the images and sample download.

  5. Hey Sarge,

    The pixel shader broke when I ran this under Xna 3.0. Band-aid solution: saved the pixel shader to a static variable before executing begin command with the Effect, then set it back once it went to null. Is there a quick fix to get around the ravages of Microsoft progress?

    Best,
    Dave

  6. Nota bene: my previous fix didn’t really work. If you receive the debugging message about not having a valid vertex or pixel shader, then you will probably have to set the pixel shader device version to 2 in the .fx file, instead of 3.

  7. This sample doesnt seem to work for me it causes problems with the for loops:

    for (int x = 0; x < Tesselation-1; x++)

    it underlines the ‘;’ after -1 and the ‘)’


Leave a comment


*

No trackbacks yet.