我们可以用纹理代表物体周围的环境,然后把纹理贴到物体上,实现映射周围的场景目的,这项技术也被乘坐环境贴图(EnvironmentMapping)。环境贴图通常来模拟反射或者折射效果。
最常见的环境贴图(Environment Mapping)是立方体cube map。
立方体贴图(cube map texture)通常由六个分开的图像组成,在设置环绕方式时采用GL_CLAMP_TO_EDGE,这样可以避免在cubeedge中避免边框颜色。
一个反射效果如下图:
vertex shader
#version 430 layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexNormal; layout (location = 2) in vec2 VertexTexCoord; out vec3 ReflectDir; uniform bool DrawSkyBox; uniform vec3 WorldCameraPosition; uniform mat4 ModelViewMatrix; uniform mat4 ModelMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP; void main() { if( DrawSkyBox ) { ReflectDir = VertexPosition; } else { vec3 worldPos = vec3( ModelMatrix * vec4(VertexPosition,1.0) ); vec3 worldNorm = vec3(ModelMatrix * vec4(VertexNormal, 0.0)); vec3 worldView = normalize( WorldCameraPosition - worldPos ); ReflectDir = reflect(-worldView, worldNorm ); } gl_Position = MVP * vec4(VertexPosition,1.0); }
#version 430 in vec3 ReflectDir; layout(binding=0) uniform samplerCube CubeMapTex; uniform bool DrawSkyBox; uniform float ReflectFactor; uniform vec4 MaterialColor; layout( location = 0 ) out vec4 FragColor; void main() { // Access the cube map texture vec4 cubeMapColor = texture(CubeMapTex, ReflectDir); if( DrawSkyBox ) FragColor = cubeMapColor; else FragColor = mix( MaterialColor, cubeMapColor, ReflectFactor); }
在顶点shader中,主要计算反射方向(ReflectDir),然后在片元shader中通过ReflectDir来获取cubemap。
在片元shader中如果不绘制天空盒(DrawSkyBox是false),则我们可以在世界坐标系中计算viewer的反射方向。即通过 reflect(-worldView, worldNorm ) 来计算ReflectDir。
之所以在世界坐标系中计算是因为:如果在相机坐标系的话,当相机移动了反射方向也不会改变。
在vertexshader中,我们转化position到世界坐标系中存储到worldPos,我们用同样方法转换法线normal。注意:用 ModelMatrix转化normal时其第四个分量一定是0(避免ModelMatrix转换分量的影响),还有,模型矩阵一定不能包含任何非均匀缩放元素(anynon-uniform scaling component)。否则,法线的转换不正确。见:http://blog.csdn.net/zhuyingqingfen/article/details/19335427
在fragmentshader将会用ReflectDir访问立方体贴图。可以想象成这样:一束光线从viewer开始,射到物体表面然后反射,最终射到cubemap上(最终的片元颜色就是射到cubemap上那点的颜色 值);
如果我们渲染skybox(DrawSkyBoxis true),那么我们用顶点位置作为反射方向。为什么?,因为当天空盒被渲染的时候,我们想要天空盒上的位置(对应于cubemap上的位置,天空盒确实是cubemap 渲染出来的)。在片元shader中,ReflectDir将会用作访问cubemap 的纹理坐标。因此,如果我们想访问cubemap上的位置(对应于中心在原点的一个cube的位置),我们需要这个指向这个位置的向量,我们需要的这个向量就是这个点减去原点位置(0,0,0),因而我们只需要这个点的位置。
天空盒经常被渲染成viewer在天空盒的中心位置,天空盒会随着viewer移动(所以,viewer总会在天空盒的中心位置)
在这篇教程中有两点需要注意:
1. 渲染的物体(如茶壶)只会反射环境贴图的场景,而不会反射任何场景中其他的物体。如果想反射其他物体,我们需要产生一个环境贴图(通过把相机放到物体的位置上,然后让相机朝向各个坐标抽方向(正负),渲染六次),然后用这个环境贴图反射到物体上。当然如果场景中的任何一个物体发生移动,我们需要重新生成环境贴图。(这在实时交互程序中一般不可取)。
2. 在上面的shader中,我们计算的反射方向是取的cube map上的点作为反射方向(DrawSkyBox为true即认为物体(上面的茶壶)在原点,cube map 上的一点减去原点即为反射方向,但实际上是相机在原点而不是茶壶),这就意味着我们忽略了物体的位置,认为环境贴图在无穷远处。