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;
///
/// Constructs a new sphere primitive,
/// with the specified size and tessellation level.
///
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);
}
///
/// Adds a new vertex to the primitive model. This should only be called
/// during the initialization process, before InitializePrimitive.
///
protected void AddVertex(Vector3 position, Vector3 normal, Vector2 texturecoordinate)
{
vertices.Add(new VertexPositionNormal(position, normal, texturecoordinate));
}
///
/// Adds a new index to the primitive model. This should only be called
/// during the initialization process, before InitializePrimitive.
///
protected void AddIndex(int index)
{
if (index > ushort.MaxValue)
throw new ArgumentOutOfRangeException("index");
indices.Add((ushort)index);
}
///
/// Queries the index of the current vertex. This starts at
/// zero, and increments every time AddVertex is called.
///
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();
}
///
/// 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.
///
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;
}
///
/// 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.
///
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;
///
/// Constructor.
///
public VertexPositionNormal(Vector3 position, Vector3 normal, Vector2 textCoor)
{
Position = position;
Normal = normal;
TextureCoordinate = textCoor;
}
///
/// A VertexDeclaration object, which contains information about the vertex
/// elements contained within this struct.
///
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:
- The direction of the weapon hitting the ship is used to get the middle position for the texture
- 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.
Answer
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("textures/noise");
///
/// 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.
///
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, 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, 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
{
public Entity target;
public Matrix baseTransform;
public Vector3 color;
public float startTime;
public float time;
ShieldImpact ISimpleListNode.next { get; set; }
ShieldImpact ISimpleListNode.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:
///
/// Simple sphere mesh generator.
///
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();
}
}
No comments:
Post a Comment