歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 投影紋理映射(Projective Texture Mapping)

投影紋理映射(Projective Texture Mapping)

日期:2017/3/1 9:32:31   编辑:Linux編程

一、紋理投影映射簡介

投影紋理映射用於映射一個紋理到物體上,好比將幻燈片投影到牆上一樣。

投影紋理映射經常在一些陰影算法以及體繪制(Volume Rendering)算法中用到。嚴格的說,只要涉及到“紋理實時和空間頂點對應”,通常都要用到投影紋理映射技術。

下面是一個紋理投影映射的實例效果圖:

圖一 紋理投影映射效果圖

二、紋理投影映射優點

1、將紋理與空間頂點實時對應,不需要預先在建模軟件中生成紋理坐標。

2、使用投影映射時,可以有效避免紋理扭曲現象。

下面圖顯示了投影紋理映射和普通紋理貼圖的效果對比:

圖二 投影空間插值和真實空間插值對比

三、原理以及實現步驟

為了把紋理投影到一個表面上,我們所要做的是根據表面點的位置和投影源來確定紋理坐標。我們可以把投影源攝像成一個攝像機,位於場景的某處。就像OpenGL中定義一個攝像機一樣,我們這樣的一個坐標系統(不放稱之為投影坐標系統):中心點位於投影源所在的位置,視圖矩陣V將坐標轉化到投影坐標系統,透視投影矩陣P將視景體轉換為一個大小為2的立方體,其中心點位於投影坐標系統的原點。將這兩個矩陣疊加在一起,再加上縮放和平移的變換矩陣(用於把剛才得到的大小為2的立方體變為大小為1,而且中心點位於(0.5,0.5,0.5))。我們就得到了下面的變換矩陣:

圖三 投影紋理映射坐標變換矩陣

需要注意的是:這樣得到的坐標是沒有被歸一化的,在訪問紋理之前需要將各分量除以其w分量。

上述的矩陣P、V均可以通過攝像機的位置、攝影角度求得。

投影紋理映射通常包含了兩個內容:紋理坐標被分配到頂點上的方式以及圖元光柵化階段它們的計算方式。我們經常把紋理映射看成將紋理圖像應用到圖元上--事實確實是這樣,但是其中包含著很多數學計算。

光柵化的一些細節

當進行投影紋理映射時,我們使用齊次(homogeneous)紋理坐標,或者叫位於投影空間的坐標。當進行非投影紋理映射時,我們使用real紋理坐標,或者叫位於real space的坐標。對於2D紋理投影映射,3分量的齊次坐標(s,t,q)被在圖元以及每個片斷上插值,插值後的齊次坐標被投影到real 2D紋理坐標(s/q,t/q),然後訪問紋理。而對於非投影的2D紋理映射,2分量的real坐標(s,t)被在圖元上插值,然後直接用於訪問紋理圖像。

這樣也就導致了前面圖二中的插值效果上的差別。

分配齊次紋理坐標

上述關於光柵化的討論基於的一個假設是每個頂點已經被分配了齊次紋理坐標。作為應用程序編寫者,這是我們所要做的工作。下面討論如何在OpenGL中實現它。

設想紋理是從一個投影儀投影到一個場景上的。這個投影儀和攝像機有很多共同屬性:它有視圖變換(將world space 坐標變換到projector space或者叫eye space),有投影變換(將projector space視體映射到剪切坐標)。最後,我們實施縮放和偏移操作來進行范圍映射。對於攝像機,x和y是依據當前的視口設置進行映射,z是依據當前的深度范圍進行映射。對於投影紋理映射,而范圍映射一般是把每個坐標映射到區間[0,1]內。

下圖對比了兩種變換:應用到頂點坐標用於計算window space位置坐標的變換和計算投影紋理坐標的變換。

圖四 應用到world space頂點坐標上的用於計算window space坐標的攝像機變換和應用到world space頂點坐標上用於計算投影紋理坐標的投影變換很相似

分配紋理坐標很重要的一方面是利用OpenGL的紋理坐標生成的功能。它是根據頂點的其他屬性來產生紋理坐標。比如GL_OBJECT_LINEAR和GL_EYE_LINEAR,在object space和eyes pace內的頂點坐標分別被用來產生紋理坐標。另外一些方式使用了其他補貼的屬性。GL_SPEREMAP和GL_REFLECTION_MAP使用了eye space頂點坐標和法線。GL_NORMAL_MAP則是根據法線向量產生紋理坐標。

OpenGL對於每個坐標方向可以使用不同的產生紋理坐標的方法。比如你可以在S方向采用GL_SPHERE_MAP,在T上采用GL_REFLECTION_MAP,在R上采用GL_OBJECT_LINEAR,Q上采用GL_EYE_LINEAR。不過這種特性並不是很有用,我們通常采用相同的方式。

下面以一個實際的例子來說明如何實現投影紋理映射(OpenGL+GLSL),其效果及開頭所給出的圖一。圖中繪制了一個茶壺,然後從一個方向將一幅紋理圖像投影到茶壺上面。

頂點著色器projtex.vs:

#version 400

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;

out vec3 EyeNormal; // Normal in eye coordinates
out vec4 EyePosition; // Position in eye coordinates
out vec4 ProjTexCoord;

uniform mat4 ProjectorMatrix;//用於計算投影紋理映射的矩陣

uniform vec3 WorldCameraPosition;
uniform mat4 ModelViewMatrix;//模型視圖矩陣
uniform mat4 ModelMatrix;//模型變換矩陣
uniform mat3 NormalMatrix;//法線變換矩陣
uniform mat4 ProjectionMatrix;//投影變換矩陣
uniform mat4 MVP;//模型視圖變換矩陣

void main()
{
vec4 pos4 = vec4(VertexPosition,1.0);

EyeNormal = normalize(NormalMatrix * VertexNormal);
EyePosition = ModelViewMatrix * pos4;
ProjTexCoord = ProjectorMatrix * (ModelMatrix * pos4);
gl_Position = MVP * pos4;
}

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;

out vec3 EyeNormal; // Normal in eye coordinates
out vec4 EyePosition; // Position in eye coordinates
out vec4 ProjTexCoord;

uniform mat4 ProjectorMatrix;//用於計算投影紋理映射的矩陣

uniform vec3 WorldCameraPosition;
uniform mat4 ModelViewMatrix;//模型視圖矩陣
uniform mat4 ModelMatrix;//模型變換矩陣
uniform mat3 NormalMatrix;//法線變換矩陣
uniform mat4 ProjectionMatrix;//投影變換矩陣
uniform mat4 MVP;//模型視圖變換矩陣

void main()
{
vec4 pos4 = vec4(VertexPosition,1.0);

EyeNormal = normalize(NormalMatrix * VertexNormal);
EyePosition = ModelViewMatrix * pos4;
ProjTexCoord = ProjectorMatrix * (ModelMatrix * pos4);
gl_Position = MVP * pos4;
}

上面的代碼前兩行是將法線和頂點坐標變換到eye space。

然後是計算投影紋理坐標:先將位於object space的坐標變換到world space,然後通過左乘紋理投影矩陣變換計算出紋理投影坐標。

最後計算內置的gl_Position即剪切平面坐標。

片斷著色器projtex.fs(下面只列出一部分):

#version 400

in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;

uniform sampler2D ProjectorTex;

layout( location = 0 ) out vec4 FragColor;

void main() {
vec3 color = phongModel(vec3(EyePosition), EyeNormal);

vec4 projTexColor = vec4(0.0);
if( ProjTexCoord.z > 0.0 )
//textureProj:紋理坐標各分量會除以最後一個分量
projTexColor = textureProj( ProjectorTex, ProjTexCoord );

FragColor = vec4(color,1.0) + projTexColor * 0.5;
}

#version 400

in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;

uniform sampler2D ProjectorTex;

layout( location = 0 ) out vec4 FragColor;

void main() {
vec3 color = phongModel(vec3(EyePosition), EyeNormal);

vec4 projTexColor = vec4(0.0);
if( ProjTexCoord.z > 0.0 )
//textureProj:紋理坐標各分量會除以最後一個分量
projTexColor = textureProj( ProjectorTex, ProjTexCoord );

FragColor = vec4(color,1.0) + projTexColor * 0.5;
}

函數phongModel是計算phong光照模型的。

注意其中的textureProj函數,它專門用於投影紋理訪問的。它的紋理坐標的各分量會除以最後一個分量,然後才訪問紋理。

在這個例子中,我們假設投影儀位於(2.0,5.0,5.0),朝向是(-2.0,-4.0,0.0),方向(0.0,1.0,0.0)是向上的。我們采用了一個外部庫GLM庫來根據投影儀的這些信息計算出各個變換矩陣,代碼如下:

vec3 projPos = vec3(2.0f,5.0f,5.0f);
vec3 projAt = vec3(-2.0f,-4.0f,0.0f);
vec3 projUp = vec3(0.0f,1.0f,0.0f);
mat4 projView = glm::lookAt(projPos, projAt, projUp);
mat4 projProj = glm::perspective(30.0f, 1.0f, 0.2f, 1000.0f);
mat4 projScaleTrans = glm::translate(vec3(0.5f)) * glm::scale(vec3(0.5f));
prog.setUniform("ProjectorMatrix", projScaleTrans * projProj * projView);

vec3 projPos = vec3(2.0f,5.0f,5.0f);
vec3 projAt = vec3(-2.0f,-4.0f,0.0f);
vec3 projUp = vec3(0.0f,1.0f,0.0f);
mat4 projView = glm::lookAt(projPos, projAt, projUp);
mat4 projProj = glm::perspective(30.0f, 1.0f, 0.2f, 1000.0f);
mat4 projScaleTrans = glm::translate(vec3(0.5f)) * glm::scale(vec3(0.5f));
prog.setUniform("ProjectorMatrix", projScaleTrans * projProj * projView);

OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm

OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm

Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm

OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm

Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm

如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm

《OpenGL超級寶典》學習筆記 http://www.linuxidc.com/Linux/2013-10/91414.htm

Copyright © Linux教程網 All Rights Reserved