[Shader&Texture] Texture Coordinate precision?


#1

Hi,

I’ve been trying to implement this paper in GLSL in order to do 2D n-body particle collision.

Like this paper says, by using 2 different FBOs, one (SPATIAL TEX) has particle’s location data as a form of texture in the order of texture coordinates, and the other (INDEXED TEX) has texture coordinates date as a form of frame buffer where I draw vertices which has texCoord as a color of itself so that I can get nearest neighbors by looking up SPATIAL TEX and fetch the neighbors’ coordinates from there and use them in INDEXED TEX for calculating forces or such.

I’m not 100% sure whether this would work well but seems promising and easier than other ways using complex space partitioning and sorting algorithm.

So, for the first step, I was thinking, if I draw a 2D grid of vertices which has normalized position values into a frame-buffer and then if I could access vertices’ value in the frame-buffer by using vertices’ position values as a texture coordinates then this method of the paper would technically work. Right?

However it seems not working, and it seems the values are slightly off every frame as I ping-ponging them. It’s really hard to explain in words but you will know when you see the code. Following is my entire code,

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"

using namespace ci;
using namespace ci::app;
using namespace std;

class pixelPerfectTestApp : public App {
  public:
    static void prepareSettings( Settings *settings );
	void setup() override;
	void update() override;
	void draw() override;
    
    CameraOrtho m_cam;
    
    gl::VaoRef vao_p_layout;
    gl::VboRef vbo_p_loc;
    gl::VboRef vbo_p_coord;
    
    gl::FboRef fbo_p_indexed_loc[2];
    gl::GlslProgRef shdr_p_indexed_loc;
    
    const uint32_t num_p = 256*256;
    const uint32_t p_buff_size = sqrt(num_p);
    
    int w, h;
    
    bool is_init = false;
    
    string p_indexed_vert =
    CI_GLSL( 150,
            in vec4	p_position;
            in vec2	p_texCoord0;
            uniform mat4 ciModelViewProjection;
            out vec2 texCoord;
            void main(){
                texCoord = p_texCoord0;
                gl_Position = ciModelViewProjection * p_position;
            });
    
    string p_indexed_loc_frag =
    CI_GLSL( 150,
            in vec2 texCoord;
            uniform sampler2D src_tex;
            uniform bool is_init;
            uniform vec2 res;
            out vec4 fragColor;
            
            void main(){
                vec2 pp_loc = texture(src_tex, texCoord).rg;
                vec2 loc = is_init ? pp_loc : texCoord;
                
                fragColor = vec4(loc,1,1);
            });
};

void pixelPerfectTestApp::setup()
{
//    gl::enable(GL_TEXTURE_RECTANGLE_ARB);
    
    m_cam.setOrtho(0, 1, 0, 1, -1, 1);
    
    w = getWindowWidth();
    h = getWindowHeight();
    
    std::vector<vec4> _p(num_p);
    std::vector<vec2> _coord(num_p);
    for(uint32_t y = 0; y < p_buff_size; y++)
        for(uint32_t x = 0; x < p_buff_size; x++){
            int _i = x + y * p_buff_size;
            // custom loc&coord to match those identically
            // +.5f pixel perfect
            // - https://stackoverflow.com/a/8885105
            // for texel fetching in tex2d
            // - https://gamedev.stackexchange.com/a/90566
            
            _p[_i] = vec4(
                          ((x +.5f)/(float)p_buff_size),
                          ((y +.5f)/(float)p_buff_size),
                          0.f, 1.f);
            _coord[_i] = vec2(((x +.5f)/(float)p_buff_size),
                              ((y +.5f)/(float)p_buff_size));
            
//            cout << _i << "/" << num_p << " : " << _p[_i] << endl;
        }
    
    vbo_p_loc = gl::Vbo::create( GL_ARRAY_BUFFER, _p.size() * sizeof(vec4), _p.data(), GL_STATIC_DRAW );
    vbo_p_coord = gl::Vbo::create( GL_ARRAY_BUFFER, _coord.size() * sizeof(vec2), _coord.data(), GL_STATIC_DRAW );
    
    vao_p_layout = ci::gl::Vao::create();
    {
        gl::ScopedVao vao(vao_p_layout);
        {
            gl::ScopedBuffer vbo(vbo_p_loc);
            ci::gl::vertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
            ci::gl::enableVertexAttribArray(0);
        }
        {
            gl::ScopedBuffer vbo(vbo_p_coord);
            ci::gl::vertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
            ci::gl::enableVertexAttribArray(1);
        }
    }
    
    shdr_p_indexed_loc =
    gl::GlslProg::create(gl::GlslProg::Format()
                         .vertex(p_indexed_vert)
                         .fragment(p_indexed_loc_frag)
                         .attribLocation("p_position", 0)
                         .attribLocation("p_texCoord0", 1)
                         );
    
    gl::Fbo::Format fbo_format;
    fbo_format
    .setColorTextureFormat(gl::Texture::Format()
//                           .target(GL_TEXTURE_RECTANGLE_ARB)
                           .target(GL_TEXTURE_2D)
                           .minFilter(GL_NEAREST)
                           .magFilter(GL_NEAREST)
//                           .internalFormat(GL_RGB32F_ARB)
                           .internalFormat(GL_RGBA32F)
                           .wrap(GL_CLAMP_TO_EDGE)
                           );
    
    for(int i = 0; i < 2; i++){
        fbo_p_indexed_loc[i] = gl::Fbo::create( w, h, fbo_format.colorTexture() );
    }
}

void pixelPerfectTestApp::update()
{
    {
        gl::ScopedFramebuffer fbo_scope(fbo_p_indexed_loc[0]);
        {
            gl::clear(Color(.0f,.0f,.0f));
            gl::ScopedViewport viewportScope( ivec2( 0 ), fbo_p_indexed_loc[0]->getSize() );
            gl::pushMatrices();
            gl::setMatrices( m_cam );
            
            gl::ScopedGlslProg shdr_scope(shdr_p_indexed_loc);
            {
                gl::ScopedTextureBind tex0(fbo_p_indexed_loc[1]->getColorTexture(), 1);
                shdr_p_indexed_loc->uniform("src_tex", 1);
                shdr_p_indexed_loc->uniform("is_init", is_init);
                
                gl::ScopedVao vao_scope(vao_p_layout);
                gl::context()->setDefaultShaderVars();
                
                gl::drawArrays(GL_POINTS, 0, num_p);
            }
            gl::popMatrices();
        }
    }
    std::swap(fbo_p_indexed_loc[0], fbo_p_indexed_loc[1]);
    
    is_init = true;
}

void pixelPerfectTestApp::draw()
{
    gl::setMatricesWindow( getWindowSize() );
    gl::clear(Color(0, 0, 0));
    
    gl::draw(fbo_p_indexed_loc[1]->getColorTexture(), toPoints( fbo_p_indexed_loc[1]->getColorTexture()->getBounds() ) );
}

void pixelPerfectTestApp::prepareSettings( Settings* settings ){
    settings->setWindowSize( 850, 850 );
}

CINDER_APP( pixelPerfectTestApp, RendererGl, pixelPerfectTestApp::prepareSettings )

and what’s happening here at the beginning


The colors are supposed to stay same (representing coordinates) but it is changed as frame goes by

Based on this, I assume the problem would be in a precision in texture coordinates. I have tried to do the same thing with Rectangular ARB Texture but I couldn’t make it.
I’m not even sure whether I’m suspecting a right point or not. Any insight or direction would be very appreciated!

Thanks!

P.S. I’m self-taught so if there’s something looked absurd in my code then please don’t hesitate to point it out, I would really appreciated.


#2

Hi AV,

Hope you’ve already figured out yourself. If not, I tried fixing your code (but didn’t read the paper) and here are some insights:

First of all, you’re rasterizing points with normalized coordinates into 850 x 850 pixel viewport, but you have 256 points in each direction. Since your FBOs don’t have MSAA enabled, the points will snap to nearest pixels, and instead of being uniformly packed, appear like this:

When you use texture coordinates to access the points, it becomes very unpredictable as you’ll have to count on the sampler to have the exact same rounding behavior.

So you’ll want to make the ping-pong FBOs 256 x 256 size. I tested with your code by changing the window size to 256 x 256 and it worked.

And in case you’re curious what’s wrong with your texture sampling code in the fragment shader, let’s say we change your window size to 512 x 512, you’ll have to either use

vec2 pp_loc = texture(src_tex, texCoord + vec2(0.5) / vec2(512)).rg;

or

vec2 pp_loc = texelFetch(src_tex, ivec2(texCoord * 512), 0).rg;

to make your code work. The technique you used in the following code

_coord[_i] = vec2(((x +.5f)/(float)p_buff_size),
                              ((y +.5f)/(float)p_buff_size));

didn’t work because p_buff_size is not the same as your texture size.


#3

Hi @phil

Thanks for your insight in depth! It helps a lot :slight_smile: By the way, are you the ITP Phil that I know? Haha glad to see you in here.

Just curiosity, is there a way that I can still use screen size buffer and keep the 256 size points and keep them perfectly aligned to texture coordinate?


#4

Hi AV, you got me!

I just browsed the paper and found that it suggests

The underlying texture size is set to the expected number of objects.

So I believe that it is optimal to use 256 x 256 textures in your case, and naturally there’s no alignment problem. I think what you need is a mechanism for mapping texture pixel positions (2D, 0-1) to world space positions, and once you have that, you can draw the “objects” with the mapped positions to your screen FBO after advancing the simulation by offscreen updating the 256 x 256 texture.


#5

Will give it a shot! Thanks a lot Phill :))) I’ll personally contact you to ask something more details.