2014-03-29 5 views
8

Ich versuche, eine einfache projektive Textur-Mapping-Ansatz mit Shadern in OpenGL 3+ zu implementieren. Obwohl es einige Beispiele im Web gibt, habe ich Probleme, ein funktionierendes Beispiel mit Shadern zu erstellen.OpenGL projektive Textur-Mapping über Shader

Ich plane eigentlich die Verwendung von zwei Shadern, eine, die eine normale Szene zeichnet, und eine andere für projektive Textur-Mapping. Ich habe eine Funktion zum Zeichnen einer Szene void ProjTextureMappingScene::renderScene(GLFWwindow *window) und ich benutze glUseProgram(), um zwischen Shadern zu wechseln. Die normale Zeichnung funktioniert gut. Es ist mir jedoch unklar, wie ich die projektive Textur auf einen bereits texturierten Würfel auftragen soll. Muss ich irgendwie einen Stencil-Puffer oder ein Framebuffer-Objekt verwenden (der Rest der Szene sollte davon nicht betroffen sein)?

Ich glaube auch nicht, dass meine projektiven Textur Mapping Shader richtig sind, seit dem zweiten Mal, wenn ich einen Würfel render es schwarz zeigt. Außerdem habe ich versucht, mit Farben zu debuggen, und nur die t Komponente des Shaders scheint nicht Null zu sein (der Würfel erscheint also grün). Ich überschreibe die texColor im Fragment-Shader unten nur für Debugging-Zwecke.

Vertexshader

#version 330 

uniform mat4 TexGenMat; 
uniform mat4 InvViewMat; 

uniform mat4 P; 
uniform mat4 MV; 
uniform mat4 N; 

layout (location = 0) in vec3 inPosition; 
//layout (location = 1) in vec2 inCoord; 
layout (location = 2) in vec3 inNormal; 

out vec3 vNormal, eyeVec; 
out vec2 texCoord; 
out vec4 projCoords; 

void main() 
{ 
    vNormal = (N * vec4(inNormal, 0.0)).xyz; 

    vec4 posEye = MV * vec4(inPosition, 1.0); 
    vec4 posWorld = InvViewMat * posEye; 
    projCoords  = TexGenMat * posWorld; 

    // only needed for specular component 
    // currently not used 
    eyeVec = -posEye.xyz; 

    gl_Position = P * MV * vec4(inPosition, 1.0); 
} 

FragmentShader

#version 330 

uniform sampler2D projMap; 
uniform sampler2D gSampler; 
uniform vec4 vColor; 

in vec3 vNormal, lightDir, eyeVec; 
//in vec2 texCoord; 
in vec4 projCoords; 

out vec4 outputColor; 

struct DirectionalLight 
{ 
    vec3 vColor; 
    vec3 vDirection; 
    float fAmbientIntensity; 
}; 

uniform DirectionalLight sunLight; 

void main (void) 
{ 
    // supress the reverse projection 
    if (projCoords.q > 0.0) 
    { 
     vec2 finalCoords = projCoords.st/projCoords.q; 
     vec4 vTexColor = texture(gSampler, finalCoords); 
     // only t has non-zero values..why? 
     vTexColor = vec4(finalCoords.s, finalCoords.t, finalCoords.r, 1.0); 
     //vTexColor = vec4(projCoords.s, projCoords.t, projCoords.r, 1.0); 
     float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection)); 
     outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0); 
    } 
} 

Erstellung von TexGen Matrix

biasMatrix = glm::mat4(0.5f, 0, 0, 0.5f, 
        0, 0.5f, 0, 0.5f, 
        0, 0, 0.5f, 0.5f, 
        0, 0, 0, 1); 

    // 4:3 perspective with 45 fov 
    projectorP = glm::perspective(45.0f * zoomFactor, 4.0f/3.0f, 0.1f, 1000.0f); 
    projectorOrigin = glm::vec3(-3.0f, 3.0f, 0.0f); 
    projectorTarget = glm::vec3(0.0f, 0.0f, 0.0f); 
    projectorV = glm::lookAt(projectorOrigin, // projector origin 
            projectorTarget,  // project on object at origin 
            glm::vec3(0.0f, 1.0f, 0.0f) // Y axis is up 
            ); 
    mModel = glm::mat4(1.0f); 
... 
texGenMatrix = biasMatrix * projectorP * projectorV * mModel; 
invViewMatrix = glm::inverse(mModel*mModelView); 

Render Cube wieder

Es ist mir auch unklar, was die Modellansicht des Würfels sein sollte? Sollte es die Ansichtsmatrix vom Diaprojektor (wie es jetzt ist) oder vom normalen Ansichtsprojektor verwenden? Gegenwärtig wird der Würfel in der Mitte der Szenenansicht schwarz (oder grün, wenn debuggen) gerendert, wie es vom Diaprojektor erscheinen würde (Ich habe einen Umschalt-Hotkey gemacht, damit ich sehen kann, was der Diaprojektor "sieht"). Der Würfel bewegt sich auch mit der Ansicht. Wie bekomme ich die Projektion auf den Würfel selbst?

mModel = glm::translate(projectorV, projectorOrigin); 
// bind projective texture 
tTextures[2].bindTexture(); 
// set all uniforms 
... 
// bind VBO data and draw 
glBindVertexArray(uiVAOSceneObjects); 
glDrawArrays(GL_TRIANGLES, 6, 36); 

Umschalten zwischen Hauptszene Kamera und Diaprojektor Kamera

if (useMainCam) 
{ 
    mCurrent = glm::mat4(1.0f); 
    mModelView = mModelView*mCurrent; 
    mProjection = *pipeline->getProjectionMatrix(); 
} 
else 
{ 
    mModelView = projectorV; 
    mProjection = projectorP; 
} 

Antwort

5

ich das Problem gelöst haben. Ein Problem, das ich hatte, war, dass ich die Matrizen in den zwei Kamerasystemen verwechselte (Welt- und projektive Texturkamera). Wenn ich jetzt die Uniformen für den projektiven Textur-Mapping-Teil anlege, verwende ich die korrekten Matrizen für die MVP-Werte - die gleichen, die ich für die Welt-Szene verwende.

glUniformMatrix4fv(iPTMProjectionLoc, 1, GL_FALSE, glm::value_ptr(*pipeline->getProjectionMatrix())); 
glUniformMatrix4fv(iPTMNormalLoc, 1, GL_FALSE, glm::value_ptr(glm::transpose(glm::inverse(mCurrent)))); 
glUniformMatrix4fv(iPTMModelViewLoc, 1, GL_FALSE, glm::value_ptr(mCurrent)); 
glUniformMatrix4fv(iTexGenMatLoc, 1, GL_FALSE, glm::value_ptr(texGenMatrix)); 
glUniformMatrix4fv(iInvViewMatrix, 1, GL_FALSE, glm::value_ptr(invViewMatrix)); 

Ferner ist die invViewMatrix ist nur die Umkehrung der Ansicht Matrix nicht die Modellansicht (das nicht das Verhalten in meinem Fall ändern, da das Modell Identität war, aber es ist falsch). Für mein Projekt wollte ich nur ein paar Objekte mit projektiven Texturen selektiv rendern. Um dies zu tun, muss ich für jedes Objekt sicherstellen, dass das aktuelle Shader-Programm für projektive Texturen unter Verwendung von glUseProgram(projectiveTextureMappingProgramID) ist.Als nächstes I die erforderlichen Matrizen für dieses Objekt berechnen:

texGenMatrix = biasMatrix * projectorP * projectorV * mModel; 
invViewMatrix = glm::inverse(mView); 

den Shadern zurückzukommen, ist der Vertex-Shader korrekte außer dass ich die UV-Texturkoordinaten (inCoord) für das aktuelle Objekt und gespeichert sie erneut hinzugefügt in texCoord.

Für den Fragment-Shader habe ich die Hauptfunktion geändert, um die projektive Textur zu klammern, so dass es nicht wiederholt (ich konnte nicht mit der Client-Seite GL_CLAMP_TO_EDGE arbeiten) und ich verwende auch die Standard-Objekt Textur und UV-Koordinaten, falls der Projektor gilt nicht für das ganze Objekt (ich auch Beleuchtung von der projektiven Textur entfernt, da es nicht in meinem Fall ist erforderlich):

void main (void) 
{ 
    vec2 finalCoords = projCoords.st/projCoords.q; 
    vec4 vTexColor  = texture(gSampler, texCoord); 
    vec4 vProjTexColor = texture(projMap, finalCoords); 
    //vec4 vProjTexColor = textureProj(projMap, projCoords); 
    float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection)); 

    // supress the reverse projection 
    if (projCoords.q > 0.0) 
    { 
     // CLAMP PROJECTIVE TEXTURE (for some reason gl_clamp did not work...) 
     if(projCoords.s > 0 && projCoords.t > 0 && finalCoords.s < 1 && finalCoords.t < 1) 
      //outputColor = vProjTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0); 
      outputColor = vProjTexColor*vColor; 
     else 
      outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0); 
    } 
    else 
    { 
     outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0); 
    } 
} 

Wenn Sie stecken bleiben und aus irgendeinem Grunde nicht das bekommen Shadern zu arbeiten, können Sie ein Beispiel in "OpenGL 4.0 Shading Language Cookbook" (Texturen Kapitel) ausprobieren - Ich habe dies tatsächlich vermisst, bis ich es selbst funktioniert.

Zusätzlich zu all dem oben, eine große Hilfe für die Fehlersuche, wenn der Algorithmus richtig funktioniert, war das Frustum (als Drahtmodell) für die projektive Kamera zu zeichnen. Ich habe einen Shader zum Frustum-Zeichnen verwendet. Das Fragment-Shader weist nur eine Farbe, während der Vertex-Shader mit Erläuterungen unten aufgeführt ist:

#version 330 

// input vertex data 
layout(location = 0) in vec3 vp; 

uniform mat4 P; 
uniform mat4 MV; 
uniform mat4 invP; 
uniform mat4 invMV; 
void main() 
{ 
    /*The transformed clip space position c of a 
    world space vertex v is obtained by transforming 
    v with the product of the projection matrix P 
    and the modelview matrix MV 

    c = P MV v 

    So, if we could solve for v, then we could 
    genrerate vertex positions by plugging in clip 
    space positions. For your frustum, one line 
    would be between the clip space positions 

    (-1,-1,near) and (-1,-1,far), 

    the lower left edge of the frustum, for example. 

    NB: If you would like to mix normalized device 
    coords (x,y) and eye space coords (near,far), 
    you need an additional step here. Modify your 
    clip position as follows 

    c' = (c.x * c.z, c.y * c.z, c.z, c.z) 

    otherwise you would need to supply both the z 
    and w for c, which might be inconvenient. Simply 
    use c' instead of c below. 


    To solve for v, multiply both sides of the equation above with 

      -1  
    (P MV) 

    This gives 

      -1  
    (P MV) c = v 

    This is equivalent to 

     -1 -1  
    MV P c = v 

    -1 
    P is given by 

    |(r-l)/(2n)  0   0  (r+l)/(2n) | 
    |  0 (t-b)/(2n)  0  (t+b)/(2n) | 
    |  0   0   0   -1  | 
    |  0   0 -(f-n)/(2fn) (f+n)/(2fn)| 

    where l, r, t, b, n, and f are the parameters in the glFrustum() call. 

    If you don't want to fool with inverting the 
    model matrix, the info you already have can be 
    used instead: the forward, right, and up 
    vectors, in addition to the eye position. 

    First, go from clip space to eye space 

     -1 
    e = P c 

    Next go from eye space to world space 

    v = eyePos - forward*e.z + right*e.x + up*e.y 

    assuming x = right, y = up, and -z = forward. 
    */ 
    vec4 fVp = invMV * invP * vec4(vp, 1.0); 
    gl_Position = P * MV * fVp; 
} 

Die Uniformen sind wie folgt verwendet (stellen Sie sicher, dass die richtigen Matrizen verwenden):

// projector matrices 
glUniformMatrix4fv(iFrustumInvProjectionLoc, 1, GL_FALSE, glm::value_ptr(glm::inverse(projectorP))); 
glUniformMatrix4fv(iFrustumInvMVLoc, 1, GL_FALSE, glm::value_ptr(glm::inverse(projectorV))); 
// world camera 
glUniformMatrix4fv(iFrustumProjectionLoc, 1, GL_FALSE, glm::value_ptr(*pipeline->getProjectionMatrix())); 
glUniformMatrix4fv(iFrustumModelViewLoc, 1, GL_FALSE, glm::value_ptr(mModelView)); 

zu erhalten die Eingangs Eckpunkte für die Stumpfes des Vertex-Shader benötigt man die folgenden tun können, um die Koordinaten (dann einfach in Ihre Vertex-Array) zu erhalten:

glm::vec3 ftl = glm::vec3(-1, +1, pFar); //far top left 
glm::vec3 fbr = glm::vec3(+1, -1, pFar); //far bottom right 
glm::vec3 fbl = glm::vec3(-1, -1, pFar); //far bottom left 
glm::vec3 ftr = glm::vec3(+1, +1, pFar); //far top right 
glm::vec3 ntl = glm::vec3(-1, +1, pNear); //near top left 
glm::vec3 nbr = glm::vec3(+1, -1, pNear); //near bottom right 
glm::vec3 nbl = glm::vec3(-1, -1, pNear); //near bottom left 
glm::vec3 ntr = glm::vec3(+1, +1, pNear); //near top right 

glm::vec3 frustum_coords[36] = { 
    // near 
    ntl, nbl, ntr, // 1 triangle 
    ntr, nbl, nbr, 
    // right 
    nbr, ftr, ntr, 
    ftr, nbr, fbr, 
    // left 
    nbl, ftl, ntl, 
    ftl, nbl, fbl, 
    // far 
    ftl, fbl, fbr, 
    fbr, ftr, ftl, 
    //bottom 
    nbl, fbr, fbl, 
    fbr, nbl, nbr, 
    //top 
    ntl, ftr, ftl, 
    ftr, ntl, ntr 
}; 

Schließlich sa id und getan ist, ist es schön zu sehen, wie es aussieht:

texture projection example image

Wie Sie bewerben ich mich zwei projektive Texturen sehen kann, ein von Biohazard Bild auf Suzanne Affenkopf Blender und eine Smiley-Textur auf dem Boden und ein kleiner Würfel. Sie können auch sehen, dass der Würfel teilweise von der projektiven Textur bedeckt ist, während der Rest mit seiner Standardtextur angezeigt wird. Schließlich können Sie den grünen Kegelstumpf für die Projektorkamera sehen - und alles sieht korrekt aus.