BUMPMAPPING WITH GLSL
当我开始学习凹凸贴图和视差映射时,我发现很多教程涉及一个简单的矩形,但没有什么接近现实生活的用途:
这是我填补空白的难题。
概念
BumpMapping允许设计师通过100,000多个多边形生物表达自己的创造力。一旦艺术完成,就会自动生成低聚合模型(5000多边形)以及法线贴图。
在运行时,通过将低模型与法线贴图组合来加回细节。
照明模型。
细节被添加到低聚合物表面反应的光。照明方程是Blinn-Phong,其中:(
pixelColor= Ambient + (Diffuse + Specular) * Shadow但让我们忘记阴影)。
Ambient = ambientMaterial * ambientLight
Diffuse = diffuseMaterial * diffuseLight * lamberFactor
lamberFactor = max (dot (lightVec, normal), 0.0)
Specular = specularMaterial * specularLight * speculatCoef
speculatCoef = pow (max (dot (halfVec, normal), 0.0), shininess)
细节:
- 环境几乎是一个常数。
- 漫射取决于光矢量与表面法向量之间的角度。
- 镜面取决于眼矢量与表面法向量之间的角度。
注意:当我们处理法向量时,可以用简单的点积来获得余弦值。
通常,每个计算都是在眼睛空间中进行的,但是在凹凸映射中,法线图中的法向量表示为切线空间。因此,我们需要转换所有需要的向量。为了做到这一点,我们使用矩阵:Eye space - > Tangent space。
切线空间数学。
每个顶点的矩阵如下:
[Normal.x Normal.y Normal.z] [BiNormal.x BiNormal.y BiNormal.z] [Tangent.x tangent.y Tangent.z]
正常很容易计算。一个简单的交叉产品每张脸。顶点的法线等于法线(与该顶点相关的所有面)的和,最后归一化。
对于模型中的每个面
{
通过交叉产品产生面部正常
在每个顶点前面加上法线
}
对于模型中的每个顶点
规范正态矢量
对于正切和二次正交,您可以在任何好的数学书中找到解决方案(我强烈推荐3D游戏编程数学)。这是一个代码示例:
generateNormalAndTangent(float3 v1,float3 v2,text2 st1,text2 st2)
{
float3 normal = v1.crossProduct(v2);
float coef = 1 /(st1.u * st2.v - st2.u * st1.v);
float3切线;
tangent.x = coef *((v1.x * st2.v)+(v2.x * -st1.v));
tangent.y = coef *((v1.y * st2.v)+(v2.y * -st1.v));
tangent.z = coef *((v1.z * st2.v)+(v2.z * -st1.v));
float3 binormal = normal.crossProduct(tangent);
}
就像法线一样:对于连接到该顶点的每个面积累积切线和次数,然后通过归一化进行平均。
在您的实现中,尝试可视化您生成的矢量,它们需要一致,因为它们将被GPU插值。

CPU侧
在openGL方面,必须做一些事情:
- 绑定顶点数组
- 绑定正常数组
- 绑定纹理坐标数组
- 绑定元素索引数组
- 将切线数组绑定到着色器
- 绑定颜色纹理
- 绑定法线贴图(凹凸贴图)
- 绑定高度贴图纹理(视差映射)
//为了动画目的,每个帧更新顶点VBO glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboVertexId); glVertexPointer(3,GL_FLOAT,0,0); //与顶点VBO相同:每帧更新 glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboNormalId); glNormalPointer(GL_FLOAT,0,0); // VBO,创建和填充一次,纹理坐标永远不会改变 glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboTexturId); glTexCoordPointer(2,GL_FLOAT,0,0); //切线生成以前,不需要通过二进制,一个十字制品就会生成它 glVertexAttribPointerARB(tangentLoc,3,GL_FLOAT,GL_FALSE,0,tangentArraySkinPointer); // VBO,创建和填充一次,要绘制的元素永远不会改变 glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vboElementsId); glDrawElements(GL_TRIANGLES,meshes [i] .facesCount * 3,GL_UNSIGNED_INT,0); glActiveTextureARB(GL_TEXTURE0); glBindTexture(diffuseTextureId); glUniform1iARB(diffuseTextureUniform,0); glActiveTextureARB(GL_TEXTURE1); glBindTexture(normalTextureId); glUniform1iARB(normalTextureUniform,0); glActiveTextureARB(GL_TEXTURE2); glBindTexture(heightTextureId); glUniform1iARB(heightTextureUniform,0);
GPU边
顶点着色器的作用是构建Blinn-Phong模型中使用的矩阵和旋转矢量,因此:
- 用正交和正切的交叉积产生双切线。
- 组合三个向量以形成旋转矩阵,从相机空间到切线空间。
- 旋转光源和相机矢量。
在片段着色器中:
- 从法线贴图中检索正常坐标。
- 将值从[-1,1]转换为[0,1]。
- 计算角度,产生漫反射,漫反射和镜面反射。
- 添加漫反射,漫反射和镜面成分。
顶点着色器
属性vec3切线
变化的vec3 lightVec;
变化vec3 halfVec;
改变vec3 eyeVec;
void main()
{
gl_TexCoord [0] = gl_MultiTexCoord0;
//构建矩阵眼空间 - >切线空间
vec3 n = normalize(gl_NormalMatrix * gl_Normal);
vec3 t =归一化(gl_NormalMatrix * tangent);
vec3 b =交叉(n,t);
vec3顶点位置= vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 lightDir = normalize(gl_LightSource [0] .position.xyz - vertexPosition);
//通过切线转换光和半角矢量
vec3 v
vx = dot(lightDir,t);
vy = dot(lightDir,b);
vz = dot(lightDir,n);
lightVec = normalize(v);
vx = dot(vertexPosition,t);
vy = dot(vertexPosition,b);
vz = dot(vertexPosition,n);
eyeVec = normalize(v);
vertexPosition = normalize(vertexPosition);
/ *归一化halfVector以将其传递给片段着色器* /
//不需要除以2,结果就是归一化。
// vec3 halfVector = normalize((vertexPosition + lightDir)/ 2.0);
vec3 halfVector = normalize(vertexPosition + lightDir);
vx = dot(halfVector,t);
vy = dot(halfVector,b);
vz = dot(halfVector,n);
//不需要归一化,t,b,n和halfVector是法向量。
//规范化(v);
halfVec = v;
gl_Position = ftransform();
}
片段着色器
uniform sampler2D diffuseTexture;
均匀采样器
//新的bumpmapping
变化的vec3 lightVec;
变化vec3 halfVec;
改变vec3 eyeVec;
void main()
{
//从法线图查找正常,从[0,1]移动到[-1,1]范围,进行归一化
vec3 normal = 2.0 * texture2D(normalTexture,gl_TexCoord [0] .st).rgb - 1.0;
normal = normalize(normal);
//计算漫射照明
float lamberFactor = max(dot(lightVec,normal),0.0);
vec4 diffuseMaterial = 0.0;
vec4 diffuseLight = 0.0;
//计算镜面照明
vec4镜面材料;
vec4镜面光
浮萍
//计算环境
vec4 ambientLight = gl_LightSource [0] .ambient;
if(lamberFactor> 0.0)
{
diffuseMaterial = texture2D(diffuseTexture,gl_TexCoord [0] .st);
diffuseLight = gl_LightSource [0] .diffuse;
//在doom3中,镜面值来自纹理
镜面材料= vec4(1.0);
specularLight = gl_LightSource [0] .specular;
shininess = pow(max(dot(halfVec,normal),0.0),2.0);
gl_FragColor = diffuseMaterial * diffuseLight * lamberFactor;
gl_FragColor + = specularMaterial * specularLight * shininess;
}
gl_FragColor + = ambientLight;
}
结果


注意:影子组件不在着色器片段中,但可以在下载的代码中找到它。
视频
该视频显示一个2000多边形Hellknight:
- 原始模型。
- 型号与512x512凹凸贴图。
- 模型具有512x512凹凸贴图和漫反射/镜面映射。
- 模型与512x512凹凸贴图和漫反射/镜面映射和阴影。
该代码具有C ++ md5模型查看器,您可以通过config.cfg配置很多,并在scene.cfg中定义场景。我包括地狱骑士md5,所以任何人都可以运行演示,我希望这将被容忍作为教育的目的。

下载
2010年4月5日:似乎二进制分发在Windows 7中不起作用。一旦我有一段时间,我必须看看这个。
推荐阅读
几本书,了解更多关于凹凸贴图和视差映射的书籍。Doom3是一个很好的资源来学习,每个模型都可以轻松访问和纯文本。
本文详细介绍了如何使用GLSL实现凹凸贴图技术,包括理论基础、切线空间数学计算、顶点及片段着色器编写等内容,并提供了代码示例。
1250

被折叠的 条评论
为什么被折叠?



