I've been working around to make a little light shader.
It works perfectly, I mean, the light fades as it's supposed to, it's a circle around my character moving with it.
It could be perfect only if that resizing event wasn't existing.
When SFML resizes the window, it enlarges everything, but in a strange way. It enlarged everything but shaders.
I tried to resize my window (I love resizing pixel graph games, I find it most beautiful. So I don't want to prevent the resizing event).
Here's my shader :
uniform vec3 light;
void main(void) {
float distance = sqrt(pow(gl_FragCoord.x - light.x, 2) + pow(gl_FragCoord.y - light.y, 2));
float alpha = 1.;
if (distance <= light.z) {
alpha = (1.0 / light.z) * distance;
}
gl_FragColor = vec4(0., 0., 0., alpha);
}
So, the problem is, my window is showed at 1280 x 736 (to fit with 32x32 textures), and I have a 1920 x 1080 monitor. When I enlarge the window to fit in 1920 x 1080 (title bar included), the whole thing resizes correctly, everything's fine, but the shader is now 1920x1080 (minus the title bar). So the shader needs different coordinates (what's supposed to be in x = 32, y = 0 is, for the shader, in x = 48 y = 0).
So I was wondering, is it possible to enlarge the shader with the whole window ? Should I use events or something like that ?
Thanks for your answers ^^
EDIT : Here's some pics :
So this is the light shader before it resizes (it's dark everywhere but on the player, like it's supposed to be).
Then I resize the window, the player doesn't move, the textures fit the entire window, but the light moved.
So, to explain correctly, when I resize the window, I want everything to fit the window, so it's full of textures, but when I do that, the coordinates given to my shader are the ones before resizing, and if I move it moves as if I didn't resize the window, so the light is never on my player again.
I'm not sure it's clearer, but I tried my best.
EDIT2 : Here's my code which calls the shader :
void Graphics::UpdateLight() {
short radius = 65; // 265 on the pictures
int x = m_game->GetPlayer()->GetSprite()->getPosition().x + CASE_LEN / 2; // Setting on the middle of the player sprite (CASE_LEN is a const which contains the size of a case (here 32))
int y = HEIGHT - (m_game->GetPlayer()->GetSprite()->getPosition().y + CASE_LEN / 2); // (the "HEIGHT -" part was set because it seems that y = 0 is on the bottom of the texture for GLSL)
sf::Vector3f shaderLight;
shaderLight.x = x;
shaderLight.y = y;
shaderLight.z = radius;
m_lightShader.setParameter("light", shaderLight);
}
The code snippet you're showing really only updates the shader coordinates (and from a quick glimpse it looks fine). The bug most likely happens somewhere where you're actually drawing things.
I'd use a completely different approach, because your shader approach might get rather tedious once you're rendering multiple things, other light sources, etc.
As such I'd suggest you render a light map to a render texture (which would essentially be like "black = no light, color = light of that color").
Rather than trying to explain everything in text, I've written a quick commented example program which will draw a window on screen and move some light sources over a background image (I've used the one that comes with SFML's shader example):
There are no requirements other than having a file called "background.jpg" in your startup path.
Feel free to copy this code or use it for inspiration. Just keep in mind this isn't optimized and really just a quick edit to show the general idea.
#include <SFML/Graphics.hpp>
#include <vector>
#include <cmath>
const float PI = 3.1415f;
struct Light
{
sf::Vector2f position;
sf::Color color;
float radius;
};
int main()
{
// Let's setup a window
sf::RenderWindow window(sf::VideoMode(640, 480), "SFML Lights");
window.setVerticalSyncEnabled(false);
window.setFramerateLimit(60);
// Create something simple to draw
sf::Texture texture;
texture.loadFromFile("background.jpg");
sf::Sprite background(texture);
// Setup everything for the lightmap
sf::RenderTexture lightmapTex;
// We're using a 512x512 render texture for max. compatibility
// On modern hardware it could match the window resolution of course
lightmapTex.create(512, 512);
sf::Sprite lightmap(lightmapTex.getTexture());
// Scale the sprite to fill the window
lightmap.setScale(640 / 512.f, 480 / 512.f);
// Set the lightmap's view to the same as the window
lightmapTex.setView(window.getDefaultView());
// Drawable helper to draw lights
// We'll just have to adjust the first vertex's color to tint it
sf::VertexArray light(sf::PrimitiveType::TriangleFan);
light.append({sf::Vector2f(0, 0), sf::Color::White});
// This is inaccurate, but for demo purposes…
// This could be more elaborate to allow better graduation etc.
for (float i = 0; i <= 2 * PI; i += PI * .125f)
light.append({sf::Vector2f(std::sin(i), std::cos(i)), sf::Color::Transparent});
// Setup some lights
std::vector<Light> lights;
lights.push_back({sf::Vector2f(50.f, 50.f), sf::Color::White, 100.f });
lights.push_back({sf::Vector2f(350.f, 150.f), sf::Color::Red, 150.f });
lights.push_back({sf::Vector2f(150.f, 250.f), sf::Color::Yellow, 200.f });
lights.push_back({sf::Vector2f(250.f, 450.f), sf::Color::Cyan, 100.f });
// RenderStates helper to transform and draw lights
sf::RenderStates rs(sf::BlendAdd);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
}
}
bool flip = false; // simple toggle to animate differently
// Draw the light map
lightmapTex.clear(sf::Color::Black);
for(Light &l : lights)
{
// Apply all light attributes and render it
// Reset the transformation
rs.transform = sf::Transform::Identity;
// Move the light
rs.transform.translate(l.position);
// And scale it (this could be animated to create flicker)
rs.transform.scale(l.radius, l.radius);
// Adjust the light color (first vertex)
light[0].color = l.color;
// Draw the light
lightmapTex.draw(light, rs);
// To make things a bit more interesting
// We're moving the lights
l.position.x += flip ? 2 : -2;
flip = !flip;
if (l.position.x > 640)
l.position.x -= 640;
else if (l.position.x < 0)
l.position.x += 640;
}
lightmapTex.display();
window.clear(sf::Color::White);
// Draw the background / game
window.draw(background);
// Draw the lightmap
window.draw(lightmap, sf::BlendMultiply);
window.display();
}
}
Related
I create a little game on cocos2d-x and have some problem in mobile version. Game have layer with terrain and character and layer with ui/info objects. Layer with terrain does not move. And layer with ui/info move with character (so it static on screen).
In mobile version all sprites from ui layer are trembling, but only sprites, labels are static. In PC version sprites and labels are also static.
Create label and sprite. Label static on PC (Win and Mac) and mobile (Android), sprite static on PC and tremble on mobile:
auto infoLayer = m_params->getGameInfoDelegate(); // class GameInfo
auto size = Director::getInstance()->getVisibleSize();
TTFConfig ttfconfig("fonts/Marker Felt.ttf", 100);
auto label = Label::createWithTTF(ttfconfig, "0");
label->setPosition(Vec2(size.width / 2, size.height / 2 + 40));
label->setString("Hello");
infoLayer->getLayer()->addChild(label, 10);
auto spr = Sprite::create();
spr->setColor(Color3B(200, 100, 100));
spr->setTextureRect(Rect(0, 0, 150, 150));
spr->setPosition(Vec2(size.width / 2, size.height / 2 - 40));
infoLayer->getLayer()->addChild(spr, 9);
Update position layer and camera:
update(float t)
{
...
m_cameraFollow->update();
...
}
void CameraFollow::update()
{
float moveX;
float moveY;
...
m_camera->move(Vec2(moveX, moveY)); // class GameCamera
}
void GameCamera::move(const cocos2d::Vec2& m)
{
float x;
float y;
...
m_position.x = x;
m_position.y = y;
m_camera->setPosition(m_position); // class cocos2d::Camera
auto infoPanel = m_params->getGameInfoDelegate(); // class GameInfo
if(infoPanel)
{
infoPanel->setMoving(m_position - m_startPosition);
}
}
class GameInfo : public cocos2d::Layer, public GameInfoDelegate
void GameInfo::setMoving(const cocos2d::Vec2 &position)
{
this->setPosition(position);
}
So, how i can fix it?
The answer to your question is complicated. The main reason is that your phone does not have the same processing power as your computer, and Cocos2d-x uses some clever optimizations to try and hide that. With moving sprites, it has to redraw them every frame (usually 30-60 fps), and slight inconsistencies can lead to this effect.
To remedy this, I would double check that your fps is 60, because 30 fps will lead to trembling. Also, if you're updating the sprite's position in update(float dt), I would try and use the physics engine instead, with velocities. If that isn't an option, maybe try to have less layers, because the more sprites you draw ontop of one another, the more it will look like it is jittering. Let me know if any of these solutions work.
The issue may be related to how you are moving the camera. Setting new X,Y coordinates through your update method without factoring in the delta time on each update call will result in jerky movement on screen.
You need to smooth out the movement from one location to another.
Try this:
update(float dt)
{
...
m_cameraFollow->update(dt);
...
}
void CameraFollow::update(float dt)
{
float moveX;
float moveY;
float speed = 1.0f;
...
Vec2 cameraPosition = m_camera->getPosition();
Vec2 targetPosition = Vec2(moveX, moveY);
Vec2 newPosition = cameraPosition.lerp(targetPosition, dt * speed);
m_camera->move(newPosition);
}
I am working on a simple racing game where you drive a car on a track.
The track is grey and the background green, and any time the color i get on a given point (the front of the car) is not grey, the car should stop because it went out of the track.
However the track is NOT drawn with sfml, rather a downloaded image i made.
So, is there any method for getting the color of a pixel on an IMAGE, as long as the rgb values match?
Here is the pseudo code to do this:
while game is running
get color (car x value, car y value)
if color is not grey
car stops
Thank you!
You can get the sf::Color of a pixel in an sf::Image by using the appropriate method: sf::Image::getPixel. It takes X and Y coordinates.
Example:
sf::Image track;
if (!track.loadFromFile("track.jpg"))
{
// oops, loading failed, handle it
}
sf::Color color = track.getPixel(0, 0); // gets the color of the upper left corner pixel
You'll want to use sf::Image's getPixel(int x, int y) funciton. Your pseudo code would look something like this:
sf::Image track;
sf::Color grey_color; // You'll need to define what the grey color is.
// Take sf::Color(100, 100, 100) as an example.
if (!track.loadFromFile("track.jpg")) {
std::cout << "Uhoh" << std::endl;
}
while (gameIsRunning) {
sf::Color color_at_car = track.getPixel(car.getx(), car.gety());
if (color_at_car != grey_color) {
car.stop();
}
}
Hope this helps!
I found this (http://lodev.org/cgtutor/raycasting.html) tutorial on the Internet and was interested and wanted to make my own. I wanted to do it in SFML though, and I wanted to extend it, and make a 3D version, so there could be different levels the player can walk on. Thus, you would need 1 ray for every pixel, and thus each pixel would have to be drawn independently. I found this (http://www.sfml-dev.org/tutorials/2.1/graphics-vertex-array.php) tutorial, and it seemed easy enough to have the array be of individual vertices. To start, I figured the best thing to do would be to create a class that could read the pixels returned by the rays, and draw them to the screen. I used the VertexArray, but things were not working for some reason. I tried to isolate the problem, but I've had little success. I wrote a simple vertex array of just green pixels that should fill up part of the screen, and still there are problems. The pixels only show my code and the pic. of what I mean.
#include "SFML/Graphics.hpp"
int main() {
sf::RenderWindow window(sf::VideoMode(400, 240), "Test Window");
window.setFramerateLimit(30);
sf::VertexArray pointmap(sf::Points, 400 * 10);
for(register int a = 0;a < 400 * 10;a++) {
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
pointmap[a].color = sf::Color::Green;
}
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(pointmap);
//</debug>
window.display();
}
return 0;
}
I meant for this to just fill in the top 10 rows with Green, but apparently that is not what I did... I think if I can figure out what is causing this not to work, I can probably fix the main problem. Also if you think there is a better way to do this instead, you could let me know :)
Thanks!
I think you misused the vertex array. Take a look at the sf::Quads primitive in the tutorial's table : you need to define 4 points (coordinates) to draw a quad, and a pixel is just a quad of side length 1.
So what you need is to create a vertex array of size 400*10*4, and set the same position to every following four vertices.
You can also use another method provided by SFML : draw directly a texture pixel by pixel and display it. It may not be the most efficient thing to do (you'll have to compare with vertices) but it has the advantage of being rather simple.
const unsigned int W = 400;
const unsigned int H = 10; // you can change this to full window size later
sf::UInt8* pixels = new sf::UInt8[W*H*4];
sf::Texture texture;
texture.create(W, H);
sf::Sprite sprite(texture); // needed to draw the texture on screen
// ...
for(register int i = 0; i < W*H*4; i += 4) {
pixels[i] = r; // obviously, assign the values you need here to form your color
pixels[i+1] = g;
pixels[i+2] = b;
pixels[i+3] = a;
}
texture.update(pixels);
// ...
window.draw(sprite);
The sf::Texture::update function accepts an array of sf::UInt8. They represent the color of each pixel of the texture. But as the pixels need to be 32bit RGBA, 4 following sf::UInt8 are the RGBA composants of the pixel.
Replace the line:
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
With:
pointmap[a].position = sf::Vector2f(a % 400,(a/400) % 400);
I'm trying to set up a two-stage render of objects in a 3D engine I'm working on written in C++ with DirectX9 to facilitate transparency (and other things). I thought it was all working nicely until I noticed some dodgyness on the edge of objects rendered before objects using this two stage method.
The two stage method is simple:
Draw model to off-screen ("side") texture of same size using same zbuffer (no MSAA is used anywhere)
Draw off-screen ("side") texture over the top of the main render target with a suitable blend and no alpha test or write
In the image below the left view is with the two stage render of the gray object (a lamppost) with the body in-front of it rendered directly to the target texture. The right view is with the two-stage render disabled, so both are rendered directly onto the target surface.
On close inspection it is as if the side texture is offset by exactly 1 pixel "down" and 1 pixel "right" when rendered over the target surface (but is rendered correctly in-place). This can be seen in an overlay of the off screen texture (which I get my program to write out to a bitmap file via D3DXSaveTextureToFile) over a screen shot below.
One last image so you can see where the edge in the side texture is coming from (it's because rendering to the side texture does use z test). Left is screen short, right is side texture (as overlaid above).
All this leads me to believe that my "overlaying" isn't very effective. The code that renders the side texture over the main render target is shown below (note that the same viewport is used for all scene rendering (on and off screen)). The "effect" object is an instance of a thin wrapper over LPD3DXEFFECT, with the "effect" field (sorry about shoddy naming) being a LPD3DXEFFECT itself.
void drawSideOver(LPDIRECT3DDEVICE9 dxDevice, drawData* ddat)
{ // "ddat" drawdata contains lots of render state information, but all we need here is the handles for the targetSurface and sideSurface
D3DXMATRIX idMat;
D3DXMatrixIdentity(&idMat); // create identity matrix
dxDevice->SetRenderTarget(0, ddat->targetSurface); // switch to targetSurface
dxDevice->SetRenderState(D3DRS_ZENABLE, false); // disable z test and z write
dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);
vertexOver overVerts[4]; // create square
overVerts[0] = vertexOver(-1, -1, 0, 0, 1);
overVerts[1] = vertexOver(-1, 1, 0, 0, 0);
overVerts[2] = vertexOver(1, -1, 0, 1, 1);
overVerts[3] = vertexOver(1, 1, 0, 1, 0);
effect.setTexture(ddat->sideTex); // use side texture as shader texture ("tex")
effect.effect->SetTechnique("over"); // change to "over" technique
effect.setViewProj(&idMat); // set viewProj to identity matrix so 1/-1 map directly
effect.effect->CommitChanges();
setAlpha(dxDevice); // this sets up the alpha blending which works fine
UINT numPasses, pass;
effect.effect->Begin(&numPasses, 0);
effect.effect->BeginPass(0);
dxDevice->SetVertexDeclaration(vertexDecOver);
dxDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, overVerts, sizeof(vertexOver));
effect.effect->EndPass();
effect.effect->End();
dxDevice->SetRenderState(D3DRS_ZENABLE, true); // revert these so we don't mess everything up drawn after this
dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
}
The C++ side definition for the VertexOver struct and constructor (HLSL side shown below somewhere):
struct vertexOver
{
public:
float x;
float y;
float z;
float w;
float tu;
float tv;
vertexOver() { }
vertexOver(float xN, float yN, float zN, float tuN, float tvN)
{
x = xN;
y = yN;
z = zN;
w = 1.0;
tu = tuN;
tv = tvN;
}
};
Inefficiency in re-creating and passing the vertices down to the GPU each draw aside, what I really want to know is why this method doesn't quite work, and if there are any better methods for overlaying textures like this with an alpha blend that won't exhibit this issue
I figured that the texture sampling may matter somewhat in this matter, but messing about with options didn't seem to help much (for example, using a LINEAR filter just makes it fuzzy as you might expect implying that the offset isn't as clear-cut as a 1 pixel discrepancy). Shader code:
struct VS_Input_Over
{
float4 pos : POSITION0;
float2 txc : TEXCOORD0;
};
struct VS_Output_Over
{
float4 pos : POSITION0;
float2 txc : TEXCOORD0;
float4 altPos : TEXCOORD1;
};
struct PS_Output
{
float4 col : COLOR0;
};
Texture tex;
sampler texSampler = sampler_state { texture = <tex>;magfilter = NONE; minfilter = NONE; mipfilter = NONE; AddressU = mirror; AddressV = mirror;};
// side/over shaders (these make up the "over" technique (pixel shader version 2.0)
VS_Output_Over VShade_Over(VS_Input_Over inp)
{
VS_Output_Over outp = (VS_Output_Over)0;
outp.pos = mul(inp.pos, viewProj);
outp.altPos = outp.pos;
outp.txc = inp.txc;
return outp;
}
PS_Output PShade_Over(VS_Output_Over inp)
{
PS_Output outp = (PS_Output)0;
outp.col = tex2D(texSampler, inp.txc);
return outp;
}
I've looked about for a "Blended Blit" or something but I can't find anything, and other related searches have only brought up forums implying that rendering a quad with an orthographic projection is the way to go about doing this.
Sorry if I've given far too much detail for this issue but it's both interesting and infuriating and any feedback would be greatly appreciated.
It looks for me that you problem is the mapping of texels to pixels. You must offset a screen-aligned quad with a half pixel to match the texels direct to the screenpixels. This issue is explaines here: Directly Mapping Texels to Pixels (MSDN)
For anyone else hitting a similar wall, my specific problem solved by adjusting the U and V values of the verticies sent to the GPU for the overlaid texture triangles thus:
for (int i = 0; i < 4; i++)
{
overVerts[i].tu += 0.5 / (float)ddat->targetVp->Width; // ddat->targetVp is the viewport in use, and the viewport is the same size as the texture
overVerts[i].tv += 0.5 / (float)ddat->targetVp->Height;
}
See Directly Mapping Texels to Pixels as provided by Gnietschow's answer for an explanation as to why this makes sense.
I'm a openFrameworks newbie. I am learning basic 2d drawing which is all great so far. I have drawn a circle using:
ofSetColor(0x333333);
ofFill;
ofCircle(100,650,50);
My question is how do I give the circle a variable name so that I can manipulate in the mousepressed method? I tried adding a name before the ofCircle
theball.ofSetColor(0x333333);
theball.ofFill;
theball.ofCircle(100,650,50);
but get I 'theball' was not declared in this scope error.
As razong pointed out that's not how OF works. OF (to the best of my knowledge) provides a handy wrapper to a lot of OpenGL stuff. So you should use OF calls to effect the current drawing context (as opposed to thinking of a canvas with sprite objects or whatever). I usually integrate that kind of thing into my objects. So lets say you have a class like this...
class TheBall {
protected:
ofColor col;
ofPoint pos;
public:
// Pass a color and position when we create ball
TheBall(ofColor ballColor, ofPoint ballPosition) {
col = ballColor;
pos = ballPosition;
}
// Destructor
~TheBall();
// Make our ball move across the screen a little when we call update
void update() {
pos.x++;
pos.y++;
}
// Draw stuff
void draw(float alpha) {
ofEnableAlphaBlending(); // We activate the OpenGL blending with the OF call
ofFill(); //
ofSetColor(col, alpha); // Set color to the balls color field
ofCircle(pos.x, pos.y, 5); // Draw command
ofDisableAlphaBlending(); // Disable the blending again
}
};
Ok cool, I hope that makes sense. Now with this structure you can do something like the following
testApp::setup() {
ofColor color;
ofPoint pos;
color.set(255, 0, 255); // A bright gross purple
pos.x, pos.y = 50;
aBall = new TheBall(color, pos);
}
testApp::update() {
aBall->update()
}
testApp::draw() {
float alpha = sin(ofGetElapsedTime())*255; // This will be a fun flashing effect
aBall->draw(alpha)
}
Happy programming.
Happy designing.
You can't do it that way. ofCircle is a global drawing method and draws just a circle.
You can declare a variable (or better three int for rgb - since you can't use ofColor as an argument for ofSetColor) that store the color for the circle and modify it in the mousepressed method.
Inside the draw method use your variables for ofSetColor before rendering the circle.