在opengl-4-shading-language-cookbook-3rd这本书上有一章节 "Applying a projected texture"
这个例子本身是有点问题的,整个投影纹理原理和过程这里不讲述(和相机投影一样)
关键点是如何求出我们自定义相机视锥体内的顶点对应的纹理坐标,注意视锥体内,这点很重要,那本书就没注意这点。
物体顶点经过我们自定义观察坐标系最终投影到裁减空间的坐标(也就是matProj * matView * vertex)都是在一个平面上的,接下来我们需要执行透视除法(在opengl里这步我们无需关心,opengl会帮我们处理这一步,但是我们自定义的投影过程需要我们执行这一步),之后所有在视锥体内的点坐标都会归到 [-1,1] 之间,这个在 [-1,1] 之间的坐标就是我们需要的纹理坐标。但是在
在书里面相关观察和投影矩阵是这样定义的:
vec3 projPos = vec3(5.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(glm::radians(30.0f), 1.0f, 0.2f, 1000.0f);
mat4 bias = glm::translate(mat4(1.0f), vec3(0.5f));
bias = glm::scale(bias, vec3(0.5f));
prog.setUniform("ProjectorMatrix", bias * projProj * projView);
这个bias是干什么的呢?,注意上面已经说过经过透视投影坐标会归到 [-1,1]之间,但是我们的纹理坐标却是 [0, 1]之间的,所以先乘以0.5再加上0.5就到 [0,1] 之间了。
它的顶点着色器是这样写的:
#version 430
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 mat4 ModelViewMatrix;
uniform mat4 ModelMatrix;
uniform mat3 NormalMatrix;
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;
}
片段着色器是这样写的:
#version 430
in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;
layout(binding=0) uniform sampler2D ProjectorTex;
layout( location = 0 ) out vec4 FragColor;
void main() {
vec3 color = blinnPhong(EyePosition.xyz, normalize(EyeNormal));
vec3 projTexColor = vec3(0.0);
if( ProjTexCoord.z > 0)
projTexColor = textureProj( ProjectorTex, ProjTexCoord ).rgb;
FragColor = vec4(color + projTexColor * 0.5, 1);
}
这里只贴出纹理投影相关代码,Phong光照部分没贴,不是我们的重点,运行程序效果着这样的:
这不看起来挺好的吗?有什么不对呢?
注意在代码里关于纹理设置
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, flowerTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
注意纹理缠绕设置的是GL_CLAMP_TO_BORDER,现在我们将他设置为GL_REPEAT:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, flowerTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
运行看效果:
发现所有的地方都被贴上了纹理,为什么会这样呢?
注意观察片段着色器代码:
#version 430
in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;
layout(binding=0) uniform sampler2D ProjectorTex;
layout( location = 0 ) out vec4 FragColor;
void main() {
vec3 color = blinnPhong(EyePosition.xyz, normalize(EyeNormal));
vec3 projTexColor = vec3(0.0);
if( ProjTexCoord.z > 0)
projTexColor = textureProj( ProjectorTex, ProjTexCoord ).rgb;
FragColor = vec4(color + projTexColor * 0.5, 1);
}
这句话:
if( ProjTexCoord.z > 0)
projTexColor = textureProj( ProjectorTex, ProjTexCoord ).rgb;
也就是说只要投影点在相机的前方都会对纹理进行取色,其实这个判断是不对的,注意到目前为止我们还没有进行透视除法,也就是说现在ProjTexCoord的坐标并不是 在[0, 1]之间,所以书里面使用textureProj函数而不是常规的texture来取色,textureProj函数会帮我们进行透视除法也就是除以ProjTexCoord.w,在textureProj之前坐标是不确定的,所有这种做法是没办法精确判断当前的的点应不应该贴纹理,所以在我把纹理缠绕改成重复贴就会发现所有纹理坐标超出[0,1]的顶点也都被贴上纹理,因此我们不能使用textureProj函数,要自己执行透视除法和把坐标归到 [0,1] 之间,所有我们不需要在外面定义bias这个矩阵,矩阵相关代码修改如下:
vec3 projPos = vec3(5.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(glm::radians(30.0f), 1.0f, 0.2f, 1000.0f);
prog.setUniform("ProjectorMatrix", projProj * projView);
顶点着色器不需要修改,片段着色器修改为:
#version 430
in vec3 EyeNormal; // Normal in eye coordinates
in vec4 EyePosition; // Position in eye coordinates
in vec4 ProjTexCoord;
layout(binding=0) uniform sampler2D ProjectorTex;
layout( location = 0 ) out vec4 FragColor;
void main() {
vec3 color = blinnPhong(EyePosition.xyz, normalize(EyeNormal));
vec3 projTexColor = vec3(0.0);
vec2 coord = (ProjTexCoord.xy/ProjTexCoord.w + 1.0 ) /2.0;
if( coord.x >= 0.0 && coord.x <= 1.0f && coord.y >= 0.0 && coord.y <= 1.0f)
projTexColor = texture( ProjectorTex, coord ).rgb;
FragColor = vec4(color + projTexColor * 0.5, 1);
}
这里我们自己执行透视除法并把坐标归到 [0,1 ],然后判断坐标是否在[0,1]之间,只有在这个范围内,也就是相机视锥体范围内,纹理才会被投影,效果如下:
这样即使纹理缠绕改成重复贴也不会有问题 ,
最后如果你想深究投影矩阵到底干了啥,可以看看这个文章:http://www.songho.ca/opengl/gl_projectionmatrix.html