BumpMap(凹凸纹理)+Specular(镜面反射)
http://hi.baidu.com/shallow_sleep/blog/item/9ce5ed0f619f892c6159f373.html
《游戏开发图形学》(《Shaders for Game Programmers and Artists》)的Specular(镜面反射)的算法是不正确的,《计算机图形学》相关部分才是正解。先写下Diffuse(漫反射),Specular(镜面反射)的特点和算法。
Diffuse(漫反射):自一个方向的光,经漫反射使光均匀向各方向传播。漫反射是由表面的粗糙不平引起的,与视点无关,漫反射光的空间分布是均匀的。
I=dot(L,N)
L 从p点指向光源的向量
N 法线向量
Specular(镜面反射):对于理想镜面,反射光集中在一个方向,并遵守反射定律。对一般的光滑表面,反射光集中在一个范围内,且由反射定律决定的反射方向光最大。因此,对于同一点来说,从不同位置所观察到的镜面反射光强是不同的。将V和R都格式化为单位向量,可表示为:
I=dot(R,V)的n次方
R 反射光向量 R=2N*dot(N,L)-L
V 视线方向
n 为反射指数,反映了物体表面的光泽程度,一般为 1~2000,n越大物体表面越光滑
在反射方向附近形成很亮的光斑,称为高光现象
BumpMap(凹凸纹理):是一张纹理图,用(r,g,b)记录了各个点的法线方向(x,y,z)。从这个纹理获取法线从而计算光照而获得模型本不具有的细节。
需要用tangent(切线),normal(法线)和binormal构建一个该点的坐标系,把光线向量和向量从视口坐标系换到该坐标系中。然后进行上面计算。具体见书。
需注意的是需要加一个软件定义的float4x4 matView矩阵,不加摄像机的话需要用它把点的位置,切线向量,法线向量和binormal向量换算到该坐标系中,因为视口是不变的,但是在旋转物体中,实际上摄像机也就是眼睛的位置是改变的,不然镜面发射是无法随之而改变。RenderMonkey可以新建一个默认的bumpMap,可参考之。
Bump Mapping(http://hi.baidu.com/shallow_sleep/blog/item/4f43a9ce955fab31b700c85b.html)
同样是一个谣言众多的东西。思想可以见:RenderMonkey 笔记~6:Chapter10~BumpMap(凹凸纹理)+Specular(镜面反射)
《游戏开发图形学》(《Shaders for Game Programmers and Artists》)和《Interactive Computer Graphics》里的代码都是不对的,nehe所谓的BumpMap也只是使用了多重纹理,根本没有计算光线、法线和视线的关系(所以是没有明暗面的)。
最后改出来的效果也不是很好,光的亮度没有办法衰减,那种像火把照到石头墙上的效果还蛮难做的。
Bump Mapping中,是需要把物体上的点投射到视角的矩阵中,然后把光线向量和视线向量投射到以物体上某点为基准的坐标系中。
varying vec3 lightv;
varying vec3 viewv;
uniform vec3 camera;
attribute vec3 tangent;
attribute vec3 binormal;
void main()
{
gl_Position=ftransform();
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
vec4 vPos=gl_ModelViewMatrix*gl_Vertex;
vec3 vTangent=(gl_ModelViewMatrix * hvec4(tangent.x, tangent.y, tangent.z, 0.0)).xyz;
vec3 vBinormal=(gl_ModelViewMatrix * hvec4(binormal.x, binormal.y, binormal.z, 0.0)).xyz;
vec3 vNormal=(gl_ModelViewMatrix * hvec4(gl_Normal.x, gl_Normal.y, gl_Normal.z, 0.0)).xyz;
mat3 TBNMatrix = mat3(vTangent, vBinormal, vNormal);
lightv = camera.xyz-vPos.xyz;
lightv *= TBNMatrix;
viewv = camera.xyz-vPos.xyz;
viewv *= TBNMatrix;
lightv=normalize(lightv);
viewv=normalize(viewv);
}
varying vec3 lightv;
varying vec3 viewv;
uniform sampler2D texMap;
uniform sampler2D normalMap;
void main()
{
vec4 texColor= texture2D(texMap,gl_TexCoord[0].st);
vec3 normal = normalize(texture2D(normalMap, gl_TexCoord[0].st).rgb * 2.0 - 1.0);
vec4 diffuse = texColor*(gl_LightSource[0].diffuse*0.3*max(0.0, dot(normal, lightv)));
vec3 reflect=normalize(2.0*normal*max(0.0,dot(normal, lightv))-lightv);
float nxHalf = max(0.0,dot(reflect, viewv));
vec4 specular = gl_LightSource[0].specular*pow(nxHalf, 1);
vec4 ambient=texColor*gl_LightSource[0].ambient;
gl_FragColor=specular+diffuse+ambient;
}
下面写出一个渲染到CubeMap的代码,因为要渲染六个面,所以场景要渲染六次, 所以动态CubeMap是十分需要性能的.
//Function to generate the cibemats for this frame
//PosX, PosY, PosZ 为需要采样的中心坐标,一般把他定在反射物体的中心.
const int CUBE_MAP_SIZE=64; //动态CubeMap的大小
void CreateCubeMap(GLfloat PosX, GLfloat PosY, GLfloat PosZ)
{
int i;
glViewport(0,0,CUBE_MAP_SIZE,CUBE_MAP_SIZE);
glScissor(0,0,CUBE_MAP_SIZE,CUBE_MAP_SIZE);
glEnable(GL_SCISSOR_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90,1,1,2000); //设定90度,每一面的图才能完整的拼在一起
glMatrixMode(GL_MODELVIEW);
for(i=0;i<=5;i++)
{
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//渲染每个面到CubeMap上面去
switch(i)
{
case 0:
glRotatef( 90,0,1,0);
glRotatef(180,1,0,0);
break;
case 1:
glRotatef(-90,0,1,0);
glRotatef(180,1,0,0);
break;
case 2:
glRotatef(-90,1,0,0);
break;
case 3:
glRotatef( 90,1,0,0);
break;
case 4:
glRotatef(180,1,0,0);
break;
case 5:
glRotatef(180,0,0,1);
break;
}
glTranslatef(PosX,PosY,PosZ);
glScalef(-1,-1,-1);
RenderScene(); //渲染场景, 可以换成你自己的场景渲染函数
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB,CubeMap_TEX);
glCopyTexSubImage2D(CubeMap_define[i],0,0,0,0,0,CUBE_MAP_SIZE,CUBE_MAP_SIZE);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
}
//恢复以前的设置
//SCREEN_WIDTH,SCREEN_HEIGHT是当前OGL程序的水平,垂直分辨率.
glScissor(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
glDisable(GL_SCISSOR_TEST);
glResizeWnd(SCREEN_WIDTH,SCREEN_HEIGHT);
}
glResizeWnd是一段Delphi代码,很容易翻译成C++的
procedure glResizeWnd(Width, Height : Integer);
begin
if (Height = 0) then // prevent divide by zero exception
Height := 1;
SCREEN_WIDTH := Width;
SCREEN_HEIGHT := Height;
glViewport(0, 0, Width, Height); // Set the viewport for the OpenGL window
glMatrixMode(GL_PROJECTION); // Change Matrix Mode to Projection
glLoadIdentity(); // Reset View
gluPerspective(45.0, Width/Height, 1.0, 200.0); // Do the perspective calculations. Last value = max clipping depth
glMatrixMode(GL_MODELVIEW); // Return to the modelview matrix
glLoadIdentity(); // Reset View
end;