Projective texture mapping GLSL

#1

Hey folks!

I’m struggling with some of the matrix math for a projective texture shader (https://en.wikipedia.org/wiki/Projective_texture_mapping).

The image is showing up on the plane, but not in the position or with the skew that I was expecting. I’m working from an example in OpenGL 4.0 Shading Language Cookbook that doesn’t appear to be available online. It crossed my mind that the issue could be author’s use of glm::lookAt vs ofMatrix4x4::makeLookAtViewMatrix, or the fact that I’m using an ARB texture, but switching to a square texture didn’t fix it.

Here’s my OF code. Would be super grateful for any direction/corrections.

//--------------------------------------------------------------
void ofApp::setup(){
    
	ofEnableLighting();
	ofEnableDepthTest();
	
    texture.load("Rhythmus.jpg");
    texture.getTexture().setTextureMinMagFilter(GL_LINEAR, GL_LINEAR);
    texture.getTexture().setTextureWrap(GL_CLAMP_TO_BORDER_ARB, GL_CLAMP_TO_BORDER_ARB);
    
    textureProjectionShader.load("TextureProjection");
	
	plane.set(20000,20000,2,2);
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    ofBackground(0);
    
    camera.setFarClip(1000);
    camera.setPosition(0,0,800);
    camera.lookAt(ofVec3f());
	camera.begin();
    
    // projector coordinates
    ofVec3f projectorPos = ofVec3f( 0, 0, 400 );
    ofVec3f projectorLookAt = ofVec3f( sin(ofGetElapsedTimef()) * 100, cos(ofGetElapsedTimef())*100, 0);
    ofVec3f projectorUp = ofVec3f( 0, 1, 0);
    
    // projector's view matrix
    ofMatrix4x4 projectionView;// = ofMatrix4x4::newLookAtMatrix(projectorPos, projectorLookAt, projectorUp);
    projectionView.makeLookAtViewMatrix(projectorPos, projectorLookAt, projectorUp);
    
    // projector's perspective materix
    float aspect = float(texture.getWidth()) / texture.getHeight();
    ofMatrix4x4 projectionProj = ofMatrix4x4::newPerspectiveMatrix(60, 1.0/aspect, 1.0, 2000.0);
    
    // translate into texture space (0.0 - 1.0)
    ofMatrix4x4 projectionTrans;
    projectionTrans.makeTranslationMatrix(ofVec3f(0.5, 0.5, 0.5));
    projectionTrans.scale(ofVec3f(0.5, 0.5, 0.5));

    // create our projector matrices
    ofMatrix4x4 projectorMat = projectionTrans * projectionProj * projectionView;
    
    // set our plane's material properties
    ofFloatColor ambientMat = ofFloatColor(0.1,0.1,0.1,1.0);
    ofFloatColor diffuseMat = ofFloatColor(0.5,0.5,0.5,1.0);
    ofFloatColor specularMat = ofFloatColor(0.0,0.0,0.0,1.0);
    float shininessMat = 0;
    
    // finally, our model matrix
    ofMatrix4x4 modelMatrix = ofMatrix4x4::newIdentityMatrix();
    
    textureProjectionShader.begin();
    
    textureProjectionShader.setUniformMatrix4f("modelMatrix", modelMatrix);
    textureProjectionShader.setUniformMatrix4f("projectorMatrix", projectorMat);
    textureProjectionShader.setUniformTexture("projectorTex", texture, 0);
    textureProjectionShader.setUniform3fv("material.ka", &ambientMat.r);
    textureProjectionShader.setUniform3fv("material.kd", &diffuseMat.r);
    textureProjectionShader.setUniform3fv("material.ks", &specularMat.r);
    textureProjectionShader.setUniform1f("material.shininess", shininessMat);
    textureProjectionShader.setUniform3f("light.intensity", 1.0f, 1.0f, 1.0f);
    textureProjectionShader.setUniform3f("light.position", 0.0, 0.0, 30.0);
    
    plane.draw();
    
    textureProjectionShader.end();
    
    // debugin'
    
    ofSetColor(255);
    ofDrawSphere(projectorPos, 5);
    ofDrawSphere(projectorLookAt, 2);
    ofDrawLine(projectorPos, projectorLookAt);
    
	camera.end();
    
    // preview the texture
    
    float w = 60;
    float h = 60 * 1.0/aspect;
    
    texture.draw(10, 10, w, h);
}

My vertex shader

#version 150

uniform mat4 projectorMatrix;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 textureMatrix;
uniform mat4 normalMatrix;
uniform mat4 modelViewProjectionMatrix;

in vec4  position;
in vec2  texcoord;
in vec4  color;
in vec3  normal;

out vec3 eyeNormal;
out vec4 eyePosition;
out vec4 projTextCoord;

void main(){

	eyeNormal = normalize( normalMatrix * vec4(normal, 1.0) ).xyz;
	eyePosition = modelViewMatrix * position;
	projTextCoord = projectorMatrix * modelMatrix * position;

	gl_Position = modelViewProjectionMatrix * position;
}

And finally the fragment shader:

#version 150

uniform sampler2DRect projectorTex;

struct MaterialInfo {

	vec3 ka;
	vec3 kd;
	vec3 ks;
	
	float shininess;
};

uniform MaterialInfo material;

struct LightInfo {

	vec3 intensity;
	vec4 position;
};

uniform LightInfo light;

in vec3 eyeNormal;
in vec4 eyePosition;
in vec4 projTextCoord;

out vec4 fragColor;

vec3 phongModel( vec3 pos, vec3 norm ){

	vec3 s = normalize(vec3(light.position)-pos);
	vec3 v = normalize(-pos.xyz);
	vec3 r = reflect( -s, norm);
	
	vec3 ambient = light.intensity * material.ka;
	float sDotN = max( dot(s, norm), 0.0 );
	vec3 diffuse = light.intensity * material.kd * sDotN;
	vec3 spec = vec3(0.0, 0.0, 0.0);
	
	if( sDotN > 0.0 ){
	
		spec = light.intensity * material.ks * pow(max(dot(r,v),0.0), material.shininess);
	}
	
	return ambient + diffuse + spec;
}

void main(){

	vec3 color = phongModel( vec3( eyePosition), eyeNormal);
	vec4 projTexColor = vec4( 0.0, 0.0, 0.0, 0.0);
	
	if( projTextCoord.z > 0.0 ){

		projTexColor = textureProj( projectorTex, projTextCoord );
	}
	
	fragColor = vec4(color, 1.0) + projTexColor;
}

Also attaching the source code (and the book example).

TextureProjection.zip (93.7 KB)

1 Like
Dynamic 3D projection mapping with head tracking
#2

Turns out it was both the creation of the matrices and the rectangular texture size …

Here’s the updated draw() call

    // projector coordinates
    ofVec3f projectorPos = ofVec3f( 0, 0, 400 );
    ofVec3f projectorLookAt = ofVec3f( sin(ofGetElapsedTimef()) * 200, cos(ofGetElapsedTimef())*200, 0);
    ofVec3f projectorUp = ofVec3f( 0, 1, 0);
    
    // projector's view matrix
    ofMatrix4x4 projectionView;
    projectionView.makeLookAtViewMatrix(projectorPos, projectorLookAt, projectorUp);
    
    // projector's perspective materix
    float aspect = float(texture.getWidth()) / texture.getHeight();
    ofMatrix4x4 projectionProj = ofMatrix4x4::newPerspectiveMatrix(30, aspect, 0.2, 2000.0);
    
    // translate into texture space (0.0 - 1.0)
    ofMatrix4x4 projectionTrans = ofMatrix4x4::newIdentityMatrix();
    projectionTrans.scale(ofVec3f(0.5, 0.5, 0.5));
    projectionTrans.translate(ofVec3f(0.5, 0.5, 0.5));
    projectionTrans.scale(ofVec3f(480, 360, 1)); // @note: image texture size

    // create our projector matrices
    //ofMatrix4x4 projectorMat = projectionTrans * projectionProj * projectionView; // wrong way! 
    ofMatrix4x4 projectorMat =  projectionView * projectionProj * projectionTrans ;
5 Likes
#3

Really appreciate sample codes.

One revision though.
When openframeworks loads an image, it renders up-side down, since opengl’s origin is bottom-left and yet in 2D screen openframeworks’ origin is top-left like opencv.

Therefore, in youre code, the image is rendered up-side down.
To compensate for this,

projectionTrans.scale(ofVec3f(0.5, 0.5, 0.5));
should be
projectionTrans.scale(ofVec3f(0.5, -0.5, 0.5));

#4

Nice catch @ofIan!

I suppose that’s one of the perils of testing your code with abstract imagery :slight_smile: