I am working on a 2D menu for a game using OpenTK in C#.
At the moment, the menu is separated in 3 different texture quads, or 'layers', which looks like this.
Layer 1: The base appearance of the buttons.
Layer 2: The appearance of the 'continue' button when the mouse hovers over it.
Layer 3: A 'gear' which holds the buttons, as well as their text/name.
Each of these layers consists of a semi-transparent (32-bit .png) texture bound to a Quad.
When drawing only layers 1 & 3, the textures seem to work properly, but when I want to show Layer 2 as well, layer 3 disappears from my screen, as seen here. In this image, only my base buttons (layer 1) and highlighted 'continue' button (layer 2) are drawn.
I believe this is an issue with my blend function, and the fact that I am drawing more than 2 sprites. I am relatively new to OpenTK/OpenGL, and I was hoping someone here could help me fix this problem. I will add some of my code below:
When the game starts, I set up some GL properties:
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.CullFace);
GL.Enable(EnableCap.Texture2D);
GL.Enable(EnableCap.Blend); //I migh t be missing something here, like textEnv?
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
This part of the code draws my 2D elements. I took it from a tutorial on OpenTK.
public void DrawHUD()
{
// Clear only the depth buffer, so that everything
// we draw for the HUD will appear in front of the
// world objects.
GL.Clear(ClearBufferMask.DepthBufferBit);
// Reset the ModelView matrix so the following
// objects are not affected by the camera position
GL.LoadIdentity();
////draw 3d hud elements (weapon in the main game)
// Disable lighting for the HUD graphics
// GL.Disable(EnableCap.Lighting);
// Save the current perspective projection
GL.MatrixMode(MatrixMode.Projection);
GL.PushMatrix();
// Switch to orthogonal view
GL.LoadIdentity();
GL.Ortho(0, Width, 0, Height, -1, 1); // Bottom-left corner pixel has coordinate (0, 0)
// Go back to working with the ModelView
GL.MatrixMode(MatrixMode.Modelview);
//// Draw the HUD elements
GameEngine.Draw2D();
// Switch back to perspective view
GL.MatrixMode(MatrixMode.Projection);
GL.PopMatrix();
GL.MatrixMode(MatrixMode.Modelview);
// Turn lighting back on
// GL.Enable(EnableCap.Lighting);
}
I add the menu 'layers' to an arraylist, which I then use to draw using Graphic, a class which holds the position/texture info of my 'layer'
public virtual void Draw2D()
{
Vector2 pos;
int Z = 0; //to fight z-fighting? this fixed an issue where drawing a 2nd sprite would also make the 1st one dissapear partially
foreach (Graphic G in _2DList)
{
if (G.visible())
{
pos = G.position();
Renderer.DrawHUDSprite(pos.X, pos.Y, Z, G.W(), G.H(), G.Texture());
Z++;
}
}
}
Finally, my draw function for hudsprites looks like this:
public static void DrawHUDSprite(float x, float y, float z, float width, float height, int textID)
{
// Save the ModelView matrix
GL.PushMatrix();
// Move to the correct location on screen
GL.Translate(x, y, z);
GL.BindTexture(TextureTarget.Texture2D, textID);
// Setup for drawing the texture
GL.Color3(Color.White);
// Draw a flat rectangle
GL.Begin(PrimitiveType.Quads);
GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0, 0, 0);
GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(width, 0, 0);
GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(width, height, 0);
GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0, height, 0);
GL.End();
// Restore the ModelView matrix
GL.BindTexture(TextureTarget.Texture2D, 0);
GL.PopMatrix();
}
If this has anything to do with the way I load my textures, I will add the code for this as well.
I managed to solve my issue on my own: The problem lies with my draw2D() function, where I use a Z coordinate to prevent clipping issues. The Z-coordinate when drawing in 2D could only be in the range of [0..1].
My earlier solution which increments Z by 1 (Z++) would cause issues with more than 2 textures/Graphics (Z>1, meaning the quad is not displayed). The fixed version looks like this:
public virtual void Draw2D()
{
if (_2DList.Count > 0) { //pervents dividing by 0, and skips memory allocation if we have no Graphics
Vector2 pos;
float Z = 0; //to fight z-fighting, the sprites ar drawn in the order they were added.
float step = 1.0f / _2DList.Count; //limiting the Z between [0..1]
foreach (Graphic G in _2DList)
{
if (G.visible())
{
pos = G.position();
Renderer.DrawHUDSprite(pos.X, pos.Y, Z, G.W(), G.H(), G.Texture());
Z += step; //with z starting at 0, it will never be 1.
}
}
}
}
Related
I'm using OpenTK and C#, I have defined a plane in 3D space as follows:
GL.Begin(BeginMode.Quads);
GL.Color3(Color.Magenta);
GL.Vertex3(-100.0f, -25.0f, -150.0f);
GL.Vertex3(-100.0f, -25.0f, 150.0f);
GL.Vertex3( 200.0f, -25.0f, 100.0f);
GL.Vertex3( 200.0f, -25.0f, -100.0f);
GL.End();
Can anyone please help me to make the plane transparent?
So you want something like this?
There are a lot of things to take care of to get there.
It all starts with a Color object that contains an alpha value<255. For example Color.FromArgb(85, Color.Turquoise) for the sphere below.
The main render class, sets up the camera view and renders all the lights, and then renders all the objects in the scene:
public void RenderOnView(GLControl control)
{
control.MakeCurrent();
var camera=views[control];
GL.Clear(ClearBufferMask.ColorBufferBit|ClearBufferMask.DepthBufferBit);
GL.Disable(EnableCap.CullFace);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
camera.LookThrough();
if (EnableLights)
{
GL.LightModel(LightModelParameter.LightModelAmbient, new[] { 0.2f, 0.2f, 0.2f, 1f });
GL.LightModel(LightModelParameter.LightModelLocalViewer, 1);
GL.Enable(EnableCap.Lighting);
foreach (var light in lights)
{
light.Render();
}
}
else
{
GL.Disable(EnableCap.Lighting);
GL.ShadeModel(ShadingModel.Flat);
}
GL.Enable(EnableCap.LineSmooth); // This is Optional
GL.Enable(EnableCap.Normalize); // These is critical to have
GL.Enable(EnableCap.RescaleNormal);
for (int i=0; i<objects.Count; i++)
{
GL.PushMatrix();
objects[i].Render();
GL.PopMatrix();
}
control.SwapBuffers();
}
Then each object has a base rendering code Render(), which calls more specialized code Draw()
public void Render()
{
if (Shading==ShadingModel.Smooth)
{
GL.Enable(EnableCap.ColorMaterial);
GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse);
GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, SpecularColor);
GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, EmissionColor);
GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, Shinyness);
GL.Enable(EnableCap.Lighting);
}
else
{
GL.Disable(EnableCap.ColorMaterial);
GL.Disable(EnableCap.Lighting);
}
GL.ShadeModel(Shading);
GL.Translate(Position);
GL.Scale(Scale, Scale, Scale);
Draw(); // Draws triangles and quads to make up a shape
}
and for example to draw a quad surface you have
protected void DrawQuad(Color color, params Vector3[] nodes)
{
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.Enable(EnableCap.PolygonOffsetFill);
// special code when translucent
if (color.A<255)
{
GL.Enable(EnableCap.Blend);
GL.DepthMask(false);
}
GL.Begin(PrimitiveType.Quads);
GL.Color4(color); //this is where the color with alpha is used
for (int i=0; i<nodes.Length; i++)
{
GL.Vertex3(nodes[i]);
}
GL.End();
// special code when translucent
if (color.A<255)
{
GL.Disable(EnableCap.Blend);
GL.DepthMask(true);
}
}
also the code to draw the outline of a quad to be called after DrawQaud()
protected void DrawLineLoop(Color color, params Vector3[] nodes)
{
GL.Disable(EnableCap.PolygonOffsetFill);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
if (color.A<255)
{
GL.Enable(EnableCap.Blend);
GL.DepthMask(false);
}
GL.Begin(PrimitiveType.LineLoop);
GL.Color4(color);
for (int i=0; i<nodes.Length; i++)
{
GL.Vertex3(nodes[i]);
}
GL.End();
if (color.A<255)
{
GL.Disable(EnableCap.Blend);
GL.DepthMask(true);
}
}
Finally I found solution of my question:
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.One);
GL.Enable(EnableCap.Blend);
//Definition of Plane
GL.Begin(BeginMode.Quads);
GL.Color4(0, 0.2, 1, 0.5);
GL.Vertex3(-100.0f, -25.0f, -150.0f);
GL.Vertex3(-100.0f, -25.0f, 150.0f);
GL.Vertex3( 200.0f, -25.0f, 100.0f);
GL.Vertex3( 200.0f, -25.0f, -100.0f);
GL.End();
GL.Disable(EnableCap.Blend);
In computing, Transparency effects are fained using color blending.For The special case of transprency, we talk about Àlpha Blending`
For transparency the blending factor is usualy stored in the 4th component of the colour (the A in RGBA) which stands for alpha. So you have to set all your colors with it.
example for half transparent blue (like glass):
GL.Color4(0,0,1,0.5f);
In OpenGL, blending have to be activated with the following command, which enable a supplementary stage on the rendering pipeline.
GL.Enable( EnableCap.Blend );
Then, because blending could be used to mix colors for other purpose than transparency, you have to specify the blending function to use. Here is the common function for transparency:
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
http://www.opentk.com/doc/chapter/2/opengl/fragment-ops/blending
Simply use GL.Color4 instead of GL.Color3. The 4th value will be the alpha
I'm having a little trouble with a texture2d I'm trying to draw in XNA. Basically, I have a power-up "cooldown fill-effect" going on. I have a texture that I'm trying to draw partially as the cooldown decreases. So, for example, at 10% cooldown done I'm drawing only 10% cooldown of of the texture (the bottom), 20% done only 20% of the bottom of the texture, and so on.
The problem I'm having is, when drawing the texture, it keeps wobbling as it fills up.
Note that below, ActiveSkillTexture is my preloaded fill texture. It's size is the size of the fully filled graphic.
InterfaceDrawer.Draw is a method that calls SpriteBatch.Draw, but does some extra stuff beforehand. For all intents and purposes, it's the same as SpriteBatch.Draw.
Scale is my scale factor, it's just a float between 0 and 1.
MyDest is a pre-calculated position for where this texture should draw (from the top-left, as usual).
Here's a snippet of code:
Rectangle NewBounds = ActiveSkillTexture.Bounds;
float cooldown = GetCooldown(ActiveSkillId);
if (cooldown > 0) //cooldown timer
{
//Code that calculated cooldown percent which I'm leaving out
if (percentdone != 1) //the percentage the cooldown is done
{
//code for fill-from bottom --
float SubHeight = ActiveSkillTexture.Height * percentremaining;
float NewHeight = ActiveSkillTexture.Height * percentdone;
NewBounds.Y += (int) SubHeight;
NewBounds.Height = (int) NewHeight;
MyDest.Y += SubHeight * Scale;
}
}
if (ActiveSkillTexture != null)
InterfaceDrawer.Draw(SpriteBatch, ActiveSkillTexture, MyDest, NewBounds, Color, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.0f);
I know you can't see it, but it's basically wobbling up and done as it fills. I tried printing out the values for the destination, the newbounds rectangle, etc. and they all seemed to consistently increase and not "sway", so I'm not sure what's going on. Interestingly enough, if I fill it from the top, it doesn't happen. But that's probably because I don't have to do math to alter the destination position each time I draw it (because it should draw from the top-left corner each time).
Any help would be greatly appreciated. Thanks!
I think this would be easier if you set the Vector2.Origin parameter of your spriteBatch.Draw as the bottom-left of your texture.
In this way you simply increase your sourceRectangle.Height, with something like this:
sourceRectangle.Height = ActiveSkillTexture.Height * percentdone;
without doing that useless math to find the destination position.
Very odd problem, when I try and draw my billboard sprite it always appears as a white block, changing the .draw color property still draws it as white, it also doesn't matter it I use a jpeg, or transparent png.
[EDIT]
So I'm now trying to use a Viewport instead of a basic effect to just get an x and y screen coordinate, I'll fix any scaling issue later, however now the image stays in the exact same spot (on the screen, it doesn't change position based on the camera) and doesn't get any bigger or smaller based on how far away it is
My new billboard rendering function:
public void Draw(Camera camera, GraphicsDevice device, SpriteBatch spriteBatch, Texture2D Texture)
{
Viewport viewport = new Viewport(new Rectangle(0, 0, 800, 480));
Vector3 viewSpaceTextPosition = viewport.Project(this.position, camera.Projection, camera.View, camera.World);
spriteBatch.Begin();
spriteBatch.Draw(Texture, new Vector2(viewSpaceTextPosition.X, viewSpaceTextPosition.Y), null, Color.White, 0, new Vector2(Texture.Bounds.Center.X, Texture.Bounds.Center.Y), this.Scale, SpriteEffects.None, viewSpaceTextPosition.Z);
spriteBatch.End();
device.RasterizerState = RasterizerState.CullCounterClockwise;
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
}
So is my use of Viewport wrong or do I just need to use it's information differently in spriteBatch.Draw()?
I think this should do the trick using viewport project... taking two projection points and calculating its distance you get a value affected by depth... so if it's deeper that value will be smaller.
public void Draw(Camera camera, GraphicsDevice device, SpriteBatch spriteBatch, Texture2D Texture)
{
Vector3 pos1= device.Viewport.Project(
this.position,
camera.Projection, camera.View, camera.World);
Vector3 pos2= device.Viewport.Project(
this.position+ Vactor3.UnitY*10,
camera.Projection, camera.View, camera.World);
Vector2 pos = new Vector2(pos1.X, pos1.Y);
Vector2 origin = new Vector2(Texture.Bounds.Center.X, Texture.Bounds.Center.Y);
float Scale = Vector3.Distance(pos1, pos2) * CustomRatio;
spriteBatch.Begin();
spriteBatch.Draw(Texture, pos, null, Color.White, 0,
origin, Scale, SpriteEffects.None, 0);
spriteBatch.End();
device.RasterizerState = RasterizerState.CullCounterClockwise;
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
}
In other hand... your previous code seems to be extracted from a source that drinks from this article made by the guy behind Xna that explain how to use basiceffect to draw billboards in 3D with spritebatch...
http://blogs.msdn.com/b/shawnhar/archive/2011/01/12/spritebatch-billboards-in-a-3d-world.aspx
I hope it helps you
Figured it out, you have to enable textures on the effect and then set effect.Texture to the Texture2D you want to use just before calling spriteBatch.Begin() in the draw function
Vector2 drawPos = (-screenPosition);
drawPos.X *= (float)device.DisplayMode.Width;
drawPos.Y *= (float)device.DisplayMode.Height;
spriteBatch.Draw(
texture,
drawPos,
getRectangle(),
Color.White,
rotation,
getOrigin(),
1.0f / zoom,
SpriteEffects.None,
0);
I have a drawPos essentialy being 0..1 and multiply that with the display width and height. screenposition is obtained by dragging the screen. With other elements, primitives, the position is correct and is exactly being dragged along with the input. However when drawing a sprite the sprite is moving to quickly, faster than the input, giving a sort of parallax effect, not what I want.
I somehow get the feeling I am using the parameters wrong, and spriteBatch.Draw(..) does not need pixelcoordinates..
Width and height is obtained by texture loader.
public Vector2 getOrigin()
{
return new Vector2(width / 2, height / 2);
}
public Rectangle getRectangle()
{
return new Rectangle(
0,
0,
width,
height);
}
Also, I am developing for Windows Phone.
The getRectangle() method is basically useless, you are specifying a source rectangle which is the same size as the texture - use null instead (unless ofcourse you've just simplified your code for us).
How is screenPosition defined? I don't understand why you'd times it by -1. You are also using the screen that the window is on to get width/height with device.DisplayMode which is something you should avoid because this will only work when your window is the same size as the screen.
Try drawing the sprite to Vector2.Zero without an origin, and with the sourceRectangle set to null. Slowly add back in your other parameters and see where the error is occuring. Can't really say much else without more information!
spriteBatch.Draw(
texture,
Vector2.Zero,
null,
Color.White,
0,
Vector2.Zero,
1,
SpriteEffects.None,
1);
This is what I would use to make sure the sprite is actually displaying properly.
i have this problem when my Sprite rotation origin is fixed at top left corner of window (same with sprite.Draw and sprite.Draw2D)
Either way if i change rotation center it's still at top left. I need sprite to rotate around its Z axis.
Edit:
I have tried this:
hereMatrix pm = Matrix.Translation(_playerPos.X + 8, _playerPos.Y + 8, 0);
sprite.Transform = Matrix.RotationZ(_angle) * pm;
sprite.Draw(playerTexture, textureSize, new Vector3(8, 8, 0), new Vector3(_playerPos.X, _playerPos.Y, 0), Color.White);
But it does not seem to works well...
When you draw it, is it in the correct place?
I believe that the multiplication order is reversed, and that you shouldn't be transforming by the players position in the transform.
// shift centre to (0,0)
sprite.Transform = Matrix.Translation(-textureSize.Width / 2, -textureSize.Height / 2, 0);
// rotate about (0,0)
sprite.Transform *= Matrix.RotationZ(_angle);
sprite.Draw(playerTexture, textureSize, Vector3.Zero,
new Vector3(_playerPos.X, _playerPos.Y, 0), Color.White);
Edit
You could also use the Matrix.Transformation method to get the matrix in one step.
I've got the solution for you, it's a simple method that you can use everytime you want to draw a sprite.
With this method you will be able to rotate the sprite with your desired rotation center.
public void drawSprite(Sprite sprite, Texture texture, Point dimension, Point rotationCenter, float rotationAngle, Point position)
{
sprite.Begin(SpriteFlags.AlphaBlend);
//First draw the sprite in position 0,0 and set your desired rotationCenter (dimension.X and dimension.Y represent the pixel dimension of the texture)
sprite.Draw(texture, new Rectangle(0, 0, dimension.X, dimension.Y), new Vector3(rotationCenter.X, rotationCenter.Y, 0), new Vector3(0, 0, 0), Color.White);
//Then rotate the sprite and then translate it in your desired position
sprite.Transform = Matrix.RotationZ(rotationAngle) * Matrix.Translation(position.X, position.Y, 0);
sprite.End();
}