2
\$\begingroup\$

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: https://i.stack.imgur.com/Ytc2a.png

I currently got this: https://i.stack.imgur.com/nWwaS.png (The red likes are a simple texture a black background with a red cross in it, for testing purposes: https://i.stack.imgur.com/gA0b4.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.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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();
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Yes this worked. I didn't use the shader but your code example helped with the sphere. Thanks! \$\endgroup\$
    – Sparky41
    Dec 2, 2012 at 16:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .