How can I 'pinch' a texture to create a perspective-like effect?


#1

Hello all,
I would like to render a 2D texture composed of horizontal and/or vertical lines in a polar coordinate space such that horizontal lines become concentric circles and vertical lines become radii.

I had everything working great using arcs and 1D textures and having the texture repeat (e.g. only horizontal lines) and this worked very well for concentric circles. Since going to a 2D texture with both H and V stripes a lot has changed and I can’t get the radii. What I would like is something like this, where I would just rotate a thinner triangle around a pivot:

I tried overlapping two vertices and tinkering with texcoords and I just end up with this:

I can’t get any kind of ‘pinch’ where stripes get closer together on one side of the triangle and further apart in the other side. Note, I’m doing all of this in 2D and I can imagine there could be some work-around using 3D and perspective, but I’d rather avoid that…

Here is a snippet of my attempt:

            ofPath circleSection;
            circleSection.moveTo(0, 0);
            circleSection.lineTo(900, 0);
            circleSection.lineTo(900, 450);
            circleSection.lineTo(0, 0);
            circleSection.close();
            ofMesh circleSectionMesh = circleSection.getTessellation(); // Convert path to mesh.

            circleSectionMesh.addTexCoord( ofVec2f(0, layerWidth) ); // 0
            circleSectionMesh.addTexCoord( ofVec2f(layerWidth, 0) ); // 1
            circleSectionMesh.addTexCoord( ofVec2f(layerWidth, layerWidth) ); // 2
            circleSectionMesh.addTexCoord( ofVec2f(0, 0) ); // 3

            layers.getTexture().bind();
            circleSectionMesh.draw();
            layers.getTexture().unbind();

Thank you!


#2

Hi,
you can do that with son nasty tricks with the texture coordinates, but I think that the best way to achieve such is using a shader.

create a new project:
in replace the contents of ofApp.h with

#pragma once
#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		
		void draw();

		
	ofFbo fbo;
	
	ofShader shader;
	ofPlanePrimitive plane;
	
};

replace the contents of ofApp.cpp with:


#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
	float w = ofGetWidth(), h = ofGetHeight();
	

	fbo.allocate(h,h,GL_RGBA);
	fbo.begin();
	ofClear(0,255);
	ofSetColor(ofColor::yellow);
	int numLines = 20;
	float dist = h / numLines;
	ofSetLineWidth(dist/2);
	for(int i = 0; i < numLines; i++){
		ofDrawLine(0, i * dist , h, i * dist);
	}
	
	for(int i = 0; i < numLines; i++){
		ofSetColor(i/float(numLines)*255);
		ofDrawLine(i * dist , 0, i * dist, h);
	}
	fbo.end();
	
	///you can replace the fbo for an ofImage and load the texture you want to use.
	
	shader.load("shader");


	
	plane.set(h, h, 10, 10);
	plane.mapTexCoords(0, 0, h, h);
	plane.move(h/2, h/2, 0);
	
}

//--------------------------------------------------------------
void ofApp::draw(){
	
	fbo.getTexture().bind();
	shader.begin();
	shader.setUniform2f("resolution", fbo.getWidth(), fbo.getHeight());
	plane.draw();
	shader.end();
	
	fbo.getTexture().unbind();
}

in your projects folder, in bin/data create two files shader.frag and shader.vert
in shader.vert put the following

#version 120

varying vec2 texCoordVarying;

void main()
{
    texCoordVarying = gl_MultiTexCoord0.xy; 

    // send the vertices to the fragment shader
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

and in shader.frag put:

#version 120

// this is how we receive the texture
uniform sampler2DRect tex0;

uniform vec2 resolution;
varying vec2 texCoordVarying;
#define TWO_PI 6.28318530718
void main()
{
	vec2 st = texCoordVarying/resolution;
	
	// Use polar coordinates instead of cartesian
	vec2 toCenter = vec2(0.5)-st;
	
	float angle = atan(toCenter.y,toCenter.x);
	float radius = length(toCenter)*2;
	
	if(radius > 1.0f){
		gl_FragColor = vec4(0.,0.,0.,0.);
	}else{
	vec2 polarCoord = vec2(radius, ((angle/TWO_PI)+0.5));
    gl_FragColor = texture2DRect(tex0, polarCoord * resolution);
	}
}

hope this helps. cheers


#3

Thanks @roymacdonald ! This looks really promising and seems to be simply rect to polar coordinate conversion in a shader. My stripes are actually thresholded sinewaves, which I was planning to reimpliment as a shader eventually anyway.


#4

yes, is super straight forwards.
Be careful with the sinewaves, as calculating the sine or cosine is expensive; thus thresholding sinewaves might not be the most efficient way to achieve it.


#5

I integrated your shader and it’s working quite well! Thank you. Do you have any thoughts on avoiding some of the aliasing / moire effects due to jaggy edges, especially near centre?

Regarding computational load, the possibility of large soft edges (spanning say, 1920px) is why I chose sinewaves. I can only presume this would be more efficient than a very large Gaussian kernel blur? A linear ramp would be fast, but I prefer the nonlinearity of the sinewave (and Gaussian).

Thanks again! Following are a couple teasers for your effort.


#6

Hi, well I guess that it is just moire and dealing with it can be quite challenging. Maybe using a larger texture might help. You’ve got to test.
The other option is to avoid the texture warping and generate all directly in the shader, which I guess can give you better results.
cheers