2
votes

I have a shader I'm attempting to use and I've come across an issue that i can't solve since my knowledge of glsl is limited. I'm using a texture as a mask and to debug this issue I simply use this textures pixel color as the gl_FragColor, I'll post some images to show what it looks like and what it should look like.

Image link; https://imgur.com/EBt2vbL

It seems related to the coordinates from gl_TexCoord[0].xy not getting the proper coordinates of the dissolve texture

main.cpp

#include "Engine.h"

#include <stdio.h>
#include <iostream>
#include <windows.h>

int main(int argc, char *argv[])
{
    try
    {
        Engine game;
        game.Run();
    }
    catch (std::exception& err)
    {
        std::cout << "\nException: " << err.what() << std::endl;
    }

    return 0;
}

Engine.h

#pragma once

#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>

#include <vector>
#include <iostream>

class Engine
{
public:
    Engine();

    void Run();
    void HandleEvents(sf::Time deltaTime);
    void Update(sf::Time deltaTime);
    void BuildVertices();
    void Draw();

private:
    bool running;
    bool hasFocus;
    bool fullScreen;


    sf::RenderWindow mainWindow;
    sf::Time deltaTime;
    sf::Event event;

    sf::Vector2i screenResolution;
    sf::Vector2i mousePosition;

    sf::VertexArray vertices;
    sf::Vertex vertex;
    sf::Shader dissolveShader;
    sf::Texture dissolveTexture;
    sf::RenderStates renderState;
    float dissolveValue;

    sf::Texture objectSpriteSheetTexture;
};

Engine.cpp

#include "Engine.h"

static const sf::Time TimePerFrame = sf::seconds(1.f / 60.f);

Engine::Engine()
    : hasFocus(true)
    , fullScreen(fullScreen)
    , running(false)
    , dissolveValue(1.0f)
    , vertices(sf::Quads)
{

    mainWindow.create(sf::VideoMode(640, 480), "Test", sf::Style::Titlebar);
    mainWindow.setPosition(sf::Vector2i(0, 0));
    screenResolution.x = 640;
    screenResolution.y = 480;

    // 512x512 sheet, each sprite is 128x128
    if (!objectSpriteSheetTexture.loadFromFile("ObjectSheet.png"))
        std::cout << "failed to load ObjectSheet.png" << std::endl;

    if (!dissolveTexture.loadFromFile("DissolveTexture.png"))
        std::cout << "failed to load DissolveTexture.png" << std::endl;

    if (!dissolveShader.loadFromFile("DissolveShader.frag", sf::Shader::Fragment))
    {
        std::cout << "failed to load DissolveShader.frag" << std::endl;
    }

    dissolveShader.setUniform("sourceTexture", sf::Shader::CurrentTexture);
    dissolveShader.setUniform("dissolveTexture", dissolveTexture);

    renderState.shader = &dissolveShader;
    renderState.texture = &objectSpriteSheetTexture;
}



void Engine::Run()
{
    // main loop
    sf::Clock clock;
    sf::Time timeSinceLastUpdate = sf::Time::Zero;

    sf::Time elapsedTime;

    running = true;

    while(running)
    {
        elapsedTime = clock.restart();
        timeSinceLastUpdate += elapsedTime;

        HandleEvents(TimePerFrame);
        while(timeSinceLastUpdate > TimePerFrame)
        {
            timeSinceLastUpdate -= TimePerFrame;
            Update(TimePerFrame);
        }

        BuildVertices();
        Draw();
    }
}


void Engine::HandleEvents(sf::Time deltaTime)
{
    mousePosition = sf::Mouse::getPosition(mainWindow);

    while(mainWindow.pollEvent(event))
    {
        if(event.type == sf::Event::Closed)
            mainWindow.close();

        if (event.type == sf::Event::KeyPressed)
        {
            if (event.key.code == sf::Keyboard::Escape)
            {
                running = false;
            }
        }
    }
}

void Engine::Update(sf::Time deltaTime)
{
}

void Engine::BuildVertices()
{
    vertices.clear();

    int frameSize = 128;

    sf::Vector2i objectPosition(100, 100);
    sf::Vector2i spriteSheetTextureCoordinates(0, 128);

    vertex.position.x = objectPosition.x;
    vertex.position.y = objectPosition.y;
    vertex.texCoords.x = spriteSheetTextureCoordinates.x;
    vertex.texCoords.y = spriteSheetTextureCoordinates.y;
    vertices.append(vertex);

    vertex.position.x = objectPosition.x + frameSize;
    vertex.position.y = objectPosition.y;
    vertex.texCoords.x = spriteSheetTextureCoordinates.x + frameSize;
    vertex.texCoords.y = spriteSheetTextureCoordinates.y;
    vertices.append(vertex);

    vertex.position.x = objectPosition.x + frameSize;
    vertex.position.y = objectPosition.y + frameSize;
    vertex.texCoords.x = spriteSheetTextureCoordinates.x + frameSize;
    vertex.texCoords.y = spriteSheetTextureCoordinates.y + frameSize;
    vertices.append(vertex);

    vertex.position.x = objectPosition.x;
    vertex.position.y = objectPosition.y + frameSize;
    vertex.texCoords.x = spriteSheetTextureCoordinates.x;
    vertex.texCoords.y = spriteSheetTextureCoordinates.y + frameSize;
    vertices.append(vertex);
}

void Engine::Draw()
{
    mainWindow.clear(sf::Color::Black);

    dissolveShader.setUniform("dissolveValue", dissolveValue);

    mainWindow.draw(vertices, renderState);

    mainWindow.display();
}

the vertex shader is a standard pass through handled by sfml.


the fragment shader;

#version 130

// used as the mask to determine if a pixel of the source texture should be drawn, 128x128
uniform sampler2D dissolveTexture;

// the texture of the object i'm drawing, a 128x128 part of a 512x512 sprite sheet
uniform sampler2D sourceTexture;

// set to 1.0 for debug 
uniform float dissolveValue;

void main( void ) 
{
    vec4 sourceColor = texture2D(sourceTexture, gl_TexCoord[0].xy);
    vec4 maskColor = texture2D(dissolveTexture, gl_TexCoord[0].xy);

    if(maskColor.r <= dissolveValue)
    {
        // it would return the source pixel color here one the issue is solved
        // gl_FragColor = sourceColor;

        // debuging, so returning the mask textures pixel color
        gl_FragColor = maskColor;
    }
    else
    {
        gl_FragColor = sourceColor;
    }
}

I'm probably overlooking something simple, so if someone can point me in the right direction i'd appreciate it, thanks!

1
Right, I can do that, will have it up soon, gotta run out for an hour - Brandon Jansma
Minimal example added, thanks - Brandon Jansma
Texture coordinates should be in the range [0-1] though it can exceed that range but that's for something else. You should debug and check the value of assigned tex coords - Asesh
I am aware of this for the shader, however sfml doesn't use the same coordinate system. also I'm not sure how to debug the shader code like you would for c++ - Brandon Jansma

1 Answers

1
votes

The texture coordinates, for the GLSL function texture (formerly texture2D) range from 0.0 to 1.0 where (0.0, 0.0) is in general the bottom-left corner and (1.0, 1.0) is the top-right corner of the texture image.

But, the SFML library scales the texture cooridnates by the size of the curren t texture (sf::Shader::CurrentTexture). This means the texture coordinates have to be set up in the range of the current texture size:

This means you have to set up the texture coordinates like this:

void Engine::BuildVertices()
{
    vertices.clear();

    int frameSize = 128;
    sf::Vector2i objectPosition(100, 100);

    sf::Vector2i texSize(512, 512);

    vertex.position  = sf::Vector2f(objectPosition.x, objectPosition.y);
    vertex.texCoords = sf::Vector2f(0.0f, 0.0f);
    vertices.append(vertex);

    vertex.position  = sf::Vector2f(objectPosition.x + frameSize, objectPosition.y);
    vertex.texCoords = sf::Vector2f(texSize.x, 0.0f);
    vertices.append(vertex);

    vertex.position  = sf::Vector2f(objectPosition.x + frameSize, objectPosition.y + frameSize);
    vertex.texCoords = sf::Vector2f(texSize.x, texSize.y);
    vertices.append(vertex);

    vertex.position  = sf::Vector2f(objectPosition.x, objectPosition.y + frameSize);
    vertex.texCoords = sf::Vector2f(0.0f, texSize.y);
    vertices.append(vertex);
}


You have a mask texture with the size of 128*128, and you have tiled sprite (4*4 tiles) with the size of 512*512. I recommend to add a texture coordinate offset uniform (texOffset) and a texture scale uniform (texScale) to the fragment shader, with allows to select a tile of the texture:

#version 130

uniform sampler2D dissolveTexture;
uniform sampler2D sourceTexture;
uniform float     dissolveValue;
uniform vec2      texScale;
uniform vec2      texOffset;

void main( void ) 
{
    vec4 sourceColor = texture2D(sourceTexture, texOffset+texScale*gl_TexCoord[0].xy);
    vec4 maskColor   = texture2D(dissolveTexture, gl_TexCoord[0].xy);

    gl_FragColor = mix( sourceColor, maskColor, step(maskColor.r, dissolveValue) );
}

You have to set the uniforms in the function Draw. The scale is given by the reciprocal of the number of tile rows and colums. The offset is the index of the tile multiplied by the scale factor:

void Engine::Draw()
{
    mainWindow.clear(sf::Color::Black);

    dissolveValue = 0.5f;
    dissolveShader.setUniform("dissolveValue", dissolveValue);

    float scale_x = 1.0f/4.0f;
    float scale_y = 1.0f/4.0f;
    int i_x = 1; // column of tile (form 0 to 3)
    int i_y = 2; // row of tile (form 0 to 3)

    dissolveShader.setUniform("texScale", sf::Glsl::Vec2(scale_x, scale_y));
    dissolveShader.setUniform("texOffset", sf::Glsl::Vec2(i_x*scale_x, i_y*scale_y));

    mainWindow.draw(vertices, renderState);

    mainWindow.display();
}