投影纹理贴图

本文是OpenGL 4.0 Shading Language Cookbook的学习笔记。

我们可以对场景中的物体进行投影贴图,就像投影仪投射图像一样。这个技术通常被叫做投影纹理贴图,它可以产生非常漂亮的渲染效果。

下图使用投影纹理贴图技术渲染。左边的纹理(Stan Shebs via Wikimedia Commons)被投射到场景中的茶壶和平面上。

为了将纹理投影到物体的表面,我们需要确定投影仪和表面的相对位置,从而确定我们使用的纹理坐标。一个简单的方法就是将投影仪看作位于场景中的一个相机。我们以投影仪的位置为原点定义一个新的坐标系,定义一个视图矩阵(V)来转换坐标到投影仪的坐标系,定义一个投影矩阵(P)来转换视锥体到大小为2中心在原点的立方体。我们将这两个矩阵相乘后再乘以一个辅助矩阵来缩放和平移立方体,从而使立方体的体积为1,中心位于坐标(0.5,0.5,0.5),我们使用的矩阵操作如下所示:

实质上,我们要进行的就是将视锥体中物体的坐标转换到0到1之间。上面的矩阵可以将视锥体中的物体坐标从世界坐标系转换到0到1之间(齐次坐标),从而可以被我们用来作为纹理坐标来访问纹理。需要注意的是,转换后的坐标是齐次坐标,需要我们除以w成分才能得到真实的坐标位置。

关于这一技术的更多细节,可以阅读 Cass Everitt的论文。(http://developer.nvidia.com/content/projectivetexture-mapping)

在本例,我们使用投射纹理贴图技术向场景中的物体投射一张纹理。

实现

在OpenGL程序中设置顶点位置location为0,法线location为1。我们还需要为Phong反射模型提供材料和光照设置(参阅下面的片段着色器代码)。最后,我们还需要提供模型矩阵ModelMatrix(用来转换坐标到世界坐标系)。

Uniform变量ProjectorTex被我们设置为0,对应我们要投影的纹理。

我们采取下面的步骤来向场景投射纹理:

1. 在OpenGL程序中,载入纹理到0号纹理单元。绑定纹理到GL_TEXTURE_2D目标后,使用下面的代码对纹理进行设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

2. 在OpenGL程序中,我们通过设置Uniform变量ProjectorMatrix来为投影仪提供变换矩阵。我们使用下面的代码生成ProjectorMatrix矩阵,代码使用了我们在第一章中介绍的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));
mat4 m = projScaleTrans * projProj * projView;
// Set the uniform variable
int loc = glGetUniformLocation(progHandle, "ProjectorMatrix");
glUniformMatrix4fv(loc, 1, GL_FALSE, &m[0][0]);

3. 使用下面的代码作为顶点着色器:

#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;
}

4. 使用下面的代码作为片段着色器:

#version 400
in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;
uniform sampler2D ProjectorTex;
struct MaterialInfo {
	vec3 Kd;
	vec3 Ks;
	vec3 Ka;
	float Shininess;
};
uniform MaterialInfo Material;
struct LightInfo {
	vec3 Intensity;
	// Light position in eye coordinates
	vec4 Position;
};
uniform LightInfo Light;
layout( location = 0 ) 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);
	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);
	if( ProjTexCoord.z > 0.0 )
		projTexColor= textureProj(ProjectorTex,ProjTexCoord);
	FragColor = vec4(color,1.0) + projTexColor * 0.5;
}

原理

载入纹理到OpenGL程序时,我们将纹理的s方向和t方向环绕模式设置为GL_CLAMP_TO_BORDER。这个设置下,如果纹理坐标不在0到1的范围内,纹理就不会贡献颜色,它会返回(0,0,0,0)作为纹理值。

我们在OpenGL程序中设置投影仪的变换矩阵。在这里,我们使用GLM库的glm::lookAt库生成投影仪的视图矩阵。我们将投影仪放置坐标(5,5,5)出,让它朝向坐标(-2,-4,0),并设置的向上方向向量为(0,1,0)。这个函数和gluLookAt的作用一样。它根据我们给的参数返回一个坐标变换矩阵。

接着,我们调用glm::perspective函数生成投影矩阵,编写上面提到的缩放/平移矩阵,然后将这两个矩阵分别存储在变量projProj和projScaleTrans中。我们将矩阵projScaleTrans,projProj和projView相乘的结果存储在m中,并将这个值赋给Uniform变量ProjectorTex。

在顶点着色器,我们使用了三个输出变量EyeNormal,EyePosition和ProjTexCoord。其中前两个是观察坐标系下的顶点法线和顶点位置。我们在main函数中对输入变量进行变换,将变换结果赋值给输出变量。

我们通过通过使用ModelMatrix矩阵变换物体坐标到世界坐标系,然后使用投影变换得到投影纹理坐标ProjTexCoord。

在片段着色器的main函数中,我们使用Phong反射模型计算着色,并将计算出的结果存储在变量color。接着,我们需要查询纹理数据。首先,我们检查ProjTexCoord的z坐标,如果它为负,则说明该坐标位于投影仪之后,我们就不需要进行纹理数据查询,否则,我们使用textureProj作为纹理坐标查询纹理数据,并将结果存储在变量projTexColor中。

我们使用textureProj函数访问被用来投射的纹理。它在使用参数给定的坐标前,会进行透视除法,使用真正的位置信息来访问纹理。

最后,我们将投射得到的纹理颜色加上Phong模型计算出的颜色得到最终的片段颜色。为了避免投影颜色占比过大,我们对投影的纹理颜色值进行了一定的缩放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值