Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm having issue with a shield effect i'm trying to develop. I want to do a shield effect that surrounds part of a model like this: http://i.imgur.com/jPvrf.png

I currently got this: http://i.imgur.com/Jdin7.png (The red likes are a simple texture a black background with a red cross in it, for testing purposes: http://i.imgur.com/ODtzk.png where the smaller cross in the middle shows the contact point)

This sphere is drawn via a primitive (DrawIndexedPrimitives)

This is how i calculate the pieces of the sphere using a class i've called Sphere

(this class is based off the code here: http://xbox.create.msdn.com/en-US/education/catalog/sample/primitives_3d)

public class Sphere { // During the process of constructing a primitive model, vertex // and index data is stored on the CPU in these managed lists. List vertices = new List(); List indices = new List();

    // Once all the geometry has been specified, the InitializePrimitive
    // method copies the vertex and index data into these buffers, which
    // store it on the GPU ready for efficient rendering.
    VertexBuffer vertexBuffer;
    IndexBuffer indexBuffer;
    BasicEffect basicEffect;

    public Vector3 position = Vector3.Zero;
    public Matrix RotationMatrix = Matrix.Identity;

    public Texture2D texture;

    /// <summary>
    /// Constructs a new sphere primitive,
    /// with the specified size and tessellation level.
    /// </summary>
    public Sphere(float diameter, int tessellation, Texture2D text, float up, float down, float portstar, float frontback)
    {
        texture = text;
        if (tessellation < 3)
            throw new ArgumentOutOfRangeException("tessellation");

        int verticalSegments = tessellation;
        int horizontalSegments = tessellation * 2;

        float radius = diameter / 2;

        // Start with a single vertex at the bottom of the sphere.
        AddVertex(Vector3.Down * ((radius / up) + 1), Vector3.Down, Vector2.Zero);//bottom position5

        // Create rings of vertices at progressively higher latitudes.
        for (int i = 0; i < verticalSegments - 1; i++)
        {
            float latitude = ((i + 1) * MathHelper.Pi /
                                        verticalSegments) - MathHelper.PiOver2;

            float dy = (float)Math.Sin(latitude / up);//(up)5
            float dxz = (float)Math.Cos(latitude);

            // Create a single ring of vertices at this latitude.
            for (int j = 0; j < horizontalSegments; j++)
            {
                float longitude = j * MathHelper.TwoPi / horizontalSegments;

                float dx = (float)(Math.Cos(longitude) * dxz) / portstar;//port and starboard (right)2
                float dz = (float)(Math.Sin(longitude) * dxz) * frontback;//front and back1.4

                Vector3 normal = new Vector3(dx, dy, dz);

                AddVertex(normal * radius, normal, new Vector2(j, i));
            }
        }

        // Finish with a single vertex at the top of the sphere.
        AddVertex(Vector3.Up * ((radius / down) + 1), Vector3.Up, Vector2.One);//top position5

        // Create a fan connecting the bottom vertex to the bottom latitude ring.
        for (int i = 0; i < horizontalSegments; i++)
        {
            AddIndex(0);
            AddIndex(1 + (i + 1) % horizontalSegments);
            AddIndex(1 + i);
        }

        // Fill the sphere body with triangles joining each pair of latitude rings.
        for (int i = 0; i < verticalSegments - 2; i++)
        {
            for (int j = 0; j < horizontalSegments; j++)
            {
                int nextI = i + 1;
                int nextJ = (j + 1) % horizontalSegments;

                AddIndex(1 + i * horizontalSegments + j);
                AddIndex(1 + i * horizontalSegments + nextJ);
                AddIndex(1 + nextI * horizontalSegments + j);

                AddIndex(1 + i * horizontalSegments + nextJ);
                AddIndex(1 + nextI * horizontalSegments + nextJ);
                AddIndex(1 + nextI * horizontalSegments + j);
            }
        }

        // Create a fan connecting the top vertex to the top latitude ring.
        for (int i = 0; i < horizontalSegments; i++)
        {
            AddIndex(CurrentVertex - 1);
            AddIndex(CurrentVertex - 2 - (i + 1) % horizontalSegments);
            AddIndex(CurrentVertex - 2 - i);
        }

        //InitializePrimitive(graphicsDevice);
    }

    /// <summary>
    /// Adds a new vertex to the primitive model. This should only be called
    /// during the initialization process, before InitializePrimitive.
    /// </summary>
    protected void AddVertex(Vector3 position, Vector3 normal, Vector2 texturecoordinate)
    {
        vertices.Add(new VertexPositionNormal(position, normal, texturecoordinate));
    }


    /// <summary>
    /// Adds a new index to the primitive model. This should only be called
    /// during the initialization process, before InitializePrimitive.
    /// </summary>
    protected void AddIndex(int index)
    {
        if (index > ushort.MaxValue)
            throw new ArgumentOutOfRangeException("index");

        indices.Add((ushort)index);
    }


    /// <summary>
    /// Queries the index of the current vertex. This starts at
    /// zero, and increments every time AddVertex is called.
    /// </summary>
    protected int CurrentVertex
    {
        get { return vertices.Count; }
    }

    public void InitializePrimitive(GraphicsDevice graphicsDevice)
    {
        // Create a vertex declaration, describing the format of our vertex data.

        // Create a vertex buffer, and copy our vertex data into it.
        vertexBuffer = new VertexBuffer(graphicsDevice,
                                        typeof(VertexPositionNormal),
                                        vertices.Count, BufferUsage.None);

        vertexBuffer.SetData(vertices.ToArray());

        // Create an index buffer, and copy our index data into it.
        indexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort),
                                      indices.Count, BufferUsage.None);

        indexBuffer.SetData(indices.ToArray());

        // Create a BasicEffect, which will be used to render the primitive.
        basicEffect = new BasicEffect(graphicsDevice);
        //basicEffect.EnableDefaultLighting();
    }

    /// <summary>
    /// Draws the primitive model, using the specified effect. Unlike the other
    /// Draw overload where you just specify the world/view/projection matrices
    /// and color, this method does not set any renderstates, so you must make
    /// sure all states are set to sensible values before you call it.
    /// </summary>
    public void Draw(Effect effect)
    {
        GraphicsDevice graphicsDevice = effect.GraphicsDevice;

        // Set our vertex declaration, vertex buffer, and index buffer.
        graphicsDevice.SetVertexBuffer(vertexBuffer);

        graphicsDevice.Indices = indexBuffer;

        graphicsDevice.BlendState = BlendState.Additive;

        foreach (EffectPass effectPass in effect.CurrentTechnique.Passes)
        {
            effectPass.Apply();

            int primitiveCount = indices.Count / 3;

            graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                 vertices.Count, 0, primitiveCount);

        }
        graphicsDevice.BlendState = BlendState.Opaque;
    }


    /// <summary>
    /// Draws the primitive model, using a BasicEffect shader with default
    /// lighting. Unlike the other Draw overload where you specify a custom
    /// effect, this method sets important renderstates to sensible values
    /// for 3D model rendering, so you do not need to set these states before
    /// you call it.
    /// </summary>
    public void Draw(Camera camera, Color color)
    {
        // Set BasicEffect parameters.
        basicEffect.World = GetWorld();
        basicEffect.View = camera.view;
        basicEffect.Projection = camera.projection;
        basicEffect.DiffuseColor = color.ToVector3();
        basicEffect.TextureEnabled = true;
        basicEffect.Texture = texture;


        GraphicsDevice device = basicEffect.GraphicsDevice;
        device.DepthStencilState = DepthStencilState.Default;

        if (color.A < 255)
        {
            // Set renderstates for alpha blended rendering.
            device.BlendState = BlendState.AlphaBlend;
        }
        else
        {
            // Set renderstates for opaque rendering.
            device.BlendState = BlendState.Opaque;
        }

        // Draw the model, using BasicEffect.
        Draw(basicEffect);
    }

    public virtual Matrix GetWorld()
    {
        return /*world */ Matrix.CreateScale(1f) * RotationMatrix * Matrix.CreateTranslation(position);
    }
}



public struct VertexPositionNormal : IVertexType
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 TextureCoordinate;


    /// <summary>
    /// Constructor.
    /// </summary>
    public VertexPositionNormal(Vector3 position, Vector3 normal, Vector2 textCoor)
    {
        Position = position;
        Normal = normal;
        TextureCoordinate = textCoor;
    }

    /// <summary>
    /// A VertexDeclaration object, which contains information about the vertex
    /// elements contained within this struct.
    /// </summary>
    public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
    (
        new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
        new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
        new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
    );

    VertexDeclaration IVertexType.VertexDeclaration
    {
        get { return VertexPositionNormal.VertexDeclaration; }
    }

}

A simple call to the class to initialise it. The Draw method is called in the master draw method in the Gamecomponent.

My current thoughts on this are:

  1. The direction of the weapon hitting the ship is used to get the middle position for the texture
  2. Wrap a texture around the drawn sphere based on this point of contact

Problem is i'm not sure how to do this. Can anyone help or if you have a better idea please tell me i'm open for opinion? :-) Thanks.

share|improve this question
add comment

1 Answer

up vote 1 down vote accepted

Looks like you're on the right track. I did this before in a game, and instead of changing where on the texture the impact is centered, I merely rotated the sphere. I also used a procedural shader instead of a texture. Here's some of the code I used (public domain, do whatever you want with it).

EDIT: Uploaded a video of it: http://www.youtube.com/watch?v=DqKkaJHf1gg

(NOTE: This code is for XNA 3.1, and has a couple calls to internal functions, so it won't work out of the box. It also does a lot of allocation, so will not perform well on the 360)

[SingletonRenderer]
public sealed class ShieldRenderer : Renderer
{
    private static readonly Texture3D _noiseTexture = SpacerGame.load<Texture3D>("textures/noise");

    /// <summary>
    /// Since the default render state culls back-facing edges (i.e. those with normals opposite the
    /// viewer), the shader is not see-through. This could be fixed by order-independent trasnparency
    /// or disabling backface culling, but since we're only going to be seeing the shader from one
    /// direction, it's easier (and faster, and provides more control) to fake the effect by rotating
    /// the sphere a bit towards the camera.
    /// </summary>
    private const float ROTATION_Y = -MathHelper.Pi * 3 / 8;
    private const float NOISE_SPEED = 0.15f;
    private const float IMPACT_TIME = 1.0f;

    private readonly Sphere _sphere;
    private readonly ShieldShader _shader;
    private readonly ReaderWriterCollection<SimpleList<ShieldImpact>, ShieldImpact> _impacts;
    private readonly ShieldParameters _params;

    public ShieldRenderer() : base(RenderPass.SHIELDS)
    {
        _params = Effects.initShieldRenderer(this);
        _sphere = new Sphere(1, 20);
        _shader = new ShieldShader
        {
            noise = _noiseTexture,
            speed = NOISE_SPEED,
        };
        _impacts = new ReaderWriterCollection<SimpleList<ShieldImpact>, ShieldImpact>();
    }

    public override void draw(DeltaT dt)
    {
        _impacts.synchronize();
        foreach(ShieldImpact impact in _impacts)
        {
            // Update time
            if(impact.startTime == 0) impact.startTime = dt.totalActual;
            else                      impact.time += dt.dtAt(impact.target.pos);

            // Kill off dead impacts
            if (impact.time > IMPACT_TIME)
            {
                _impacts.Remove(impact);
                continue;
            }

            // Skip offscreen targets
            if(!impact.target.bounds.isPartiallyOnScreen())
                continue;

            _shader.worldViewProj = impact.baseTransform *
                impact.target.pos.toScreenWvpMatrix();
            _shader.startTime = impact.startTime;
            _shader.color = impact.color;
            _shader.time = impact.time / IMPACT_TIME;

            _shader.begin();
            _sphere.draw(_shader.shield);
            _shader.end();
        }
    }

    protected override void dispose()
    {
        base.dispose();
        _sphere.Dispose();
        _shader.Dispose();
    }

    protected override void finalize()
    {
        base.finalize();
        _impacts.synchronize();
        _impacts.Clear();
        _impacts.Dispose();
    }

    public void addImpact(Entity target, float direction, float shieldStrength)
    {
        // TODO -- figurre out right shield radius
        _impacts.Add(new ShieldImpact
        {
            target = target,
            color = _params.colors.sample(shieldStrength).ToVector3(),
            baseTransform = Matrix.CreateScale(target.size.X) *
                Matrix.CreateRotationY(ROTATION_Y) *
                Matrix.CreateRotationZ(-direction),
        });
    }

    private sealed class ShieldImpact : ISimpleListNode<ShieldImpact>
    {
        public Entity target;
        public Matrix baseTransform;
        public Vector3 color;
        public float startTime;
        public float time;

        ShieldImpact ISimpleListNode<ShieldImpact>.next { get; set; }
        ShieldImpact ISimpleListNode<ShieldImpact>.prev { get; set; }
    }
}

public sealed class ShieldParameters
{
    public ColorGradient colors;
}

Shader:

#include "common.fxh"

// @params
float4x4 _worldViewProj;
texture _noise;
float _time;
float _startTime;
float _speed;
float3 _color;
// @end

sampler sNoise = sampler_state { texture = <_noise>; magfilter = ANISOTROPIC; minfilter = ANISOTROPIC; mipfilter = ANISOTROPIC; AddressU = WRAP; AddressV = WRAP; };

PixelInfo shieldVS(float4 inPos : POSITION, float2 inUv : TEXCOORD)
{
    PixelInfo p;
    p.uv = inUv;
    p.pos = mul(inPos, _worldViewProj);
    return p;
}

static const float NOISINESS = 1;
static const float NOISE_SCALE_PRE_EXP = 1.25;
static const float NOISE_SCALE_POST_EXP = 3;
static const float NOISE_EXP = 4;

static const float DISTANCE_EXP_MIN = 0.015;
static const float DISTANCE_EXP_MAX = 0.06;
static const float DISTANCE_VALUE_CLAMP = 0.1;
static const float DISTANCE_SCALE = 144;

static const float TIME_SCALE_PRE_EXP = 3;
static const float TIME_SCALE_POST_EXP = 1;
static const float TIME_EXP = 3;

float4 shieldPS(PixelInfo p) : COLOR0
{
    // Get some noise-sampled noise (a la the background)
    float3 vpos;
    float3 spos = float3(p.uv * NOISINESS + float2(_startTime, _startTime), _time * _speed);
    vpos.x = tex3D(sNoise, spos + 0.00).r - 0.5;
    vpos.y = tex3D(sNoise, spos + 0.33).r - 0.5;
    vpos.z = tex3D(sNoise, spos + 0.67).r - 0.5;
    float sample = tex3D(sNoise, vpos).r;
    sample = pow(sample * NOISE_SCALE_PRE_EXP, NOISE_EXP) * NOISE_SCALE_POST_EXP;

    // Fade out with time
    float timeFactor = (pow((1 - _time) * TIME_SCALE_PRE_EXP, TIME_EXP)) * TIME_SCALE_POST_EXP;

    // Glow more closer to the center; spread out over time
    float d = distance(float2(smoothstep(0.125, 0.875, p.uv.x), p.uv.y), float2(0.5, 0.5));
    float distanceFactor = 1 - pow(d, lerp(DISTANCE_EXP_MIN, DISTANCE_EXP_MAX, _time));
    distanceFactor = smoothstep(DISTANCE_VALUE_CLAMP, 1, distanceFactor * distanceFactor * DISTANCE_SCALE);

    // If alpha > 1, multiply the color by it to get an HDRish effect
    float alpha = sample * timeFactor * distanceFactor;
    return float4(alpha > 1 ? alpha * _color : _color, saturate(alpha));
}

technique shield
{
    // @passes
    pass shield { VertexShader = compile vs_1_1 shieldVS(); PixelShader = compile ps_2_0 shieldPS(); }
    // @end
}

Code for sphere:

/// <summary>
/// Simple sphere mesh generator.
/// </summary>
public sealed class Sphere : IDisposable
{
    private readonly VertexBuffer _vertexBuf;
    private readonly IndexBuffer _indexBuf;
    private readonly VertexDeclaration _vertexDecl;
    private readonly int _nVerticies;
    private readonly int _nFaces;

    public Sphere(float radius, int slices)
    {
        _nVerticies = (slices + 1) * (slices + 1);
        int nIndicies = 6 * slices * (slices + 1);

        var indices = new int[nIndicies];
        var vertices = new VertexPositionNormalTexture[_nVerticies];
        float thetaStep = MathHelper.Pi / slices;
        float phiStep = MathHelper.TwoPi / slices;

        int iIndex = 0;
        int iVertex = 0;
        int iVertex2 = 0;

        for (int sliceTheta = 0; sliceTheta < slices + 1; sliceTheta++)
        {
            float r = (float) Math.Sin(sliceTheta * thetaStep);
            float y = (float) Math.Cos(sliceTheta * thetaStep);

            for (int slicePhi = 0; slicePhi < (slices + 1); slicePhi++)
            {
                float x = r * (float) Math.Sin(slicePhi * phiStep);
                float z = r * (float) Math.Cos(slicePhi * phiStep);

                vertices[iVertex].Position = new Vector3(x, y, z) * radius;
                vertices[iVertex].Normal = Vector3.Normalize(new Vector3(x, y, z));
                vertices[iVertex].TextureCoordinate = new Vector2((float)slicePhi / slices,
                    (float)sliceTheta / slices);
                iVertex++;

                if (sliceTheta != (slices - 1))
                {
                    indices[iIndex++] = iVertex2 + (slices + 1);
                    indices[iIndex++] = iVertex2 + 1;
                    indices[iIndex++] = iVertex2;
                    indices[iIndex++] = iVertex2 + (slices);
                    indices[iIndex++] = iVertex2 + (slices + 1);
                    indices[iIndex++] = iVertex2;
                    iVertex2++;
                }
            }
        }

        GraphicsDevice device = RenderManager.current.device;
        _vertexBuf = new VertexBuffer(device, typeof(VertexPositionNormalTexture), _nVerticies, BufferUsage.None);
        _indexBuf = new IndexBuffer(device, typeof(int), nIndicies, BufferUsage.None);
        _vertexDecl = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements);
        _vertexBuf.SetData(vertices, 0, vertices.Length);
        _indexBuf.SetData(indices, 0, indices.Length);
        _nFaces = nIndicies / 3;
    }

    public void draw(EffectPass pass)
    {
        GraphicsDevice device = RenderManager.current.device;
        device.Indices = _indexBuf;
        device.VertexDeclaration = _vertexDecl;
        device.Vertices[0].SetSource(_vertexBuf, 0, VertexPositionNormalTexture.SizeInBytes);

        pass.Begin();
        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _nVerticies, 0, _nFaces);
        pass.End();
    }

    public void Dispose()
    {
        _vertexBuf.Dispose();
        _indexBuf.Dispose();
        _vertexDecl.Dispose();
    }
}
share|improve this answer
    
Yes this worked. I didn't use the shader but your code example helped with the sphere. Thanks! –  Sparky41 Dec 2 '12 at 16:56
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.