BUMPMAPPING WITH GLSL

本文详细介绍了如何使用GLSL实现凹凸贴图技术,包括理论基础、切线空间数学计算、顶点及片段着色器编写等内容,并提供了代码示例。

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,所以任何人都可以运行演示,我希望这将被容忍作为教育的目的。

ZenFrag引擎的配置示例。


下载

2010年4月5日:似乎二进制分发在Windows 7中不起作用。一旦我有一段时间,我必须看看这个。

推荐阅读

几本书,了解更多关于凹凸贴图和视差映射的书籍。Doom3是一个很好的资源来学习,每个模型都可以轻松访问和纯文本。

### Bump Mapping 基本概念 Bump Mapping 是一种用于模拟表面细节的技术,通过改变光照计算来创建视觉上的高度差异效果。这种方法并不会真正移动物体表面的几何位置,而是利用纹理影响法线方向,在不增加额外几何复杂度的情况下提供更丰富的材质表现[^3]。 ### 实现过程 为了在 GAMES101 的框架内实现 bump mapping 功能,主要涉及以下几个方面: #### 法线贴图生成 通常情况下,bump map 或者称为 normal map 是预先制作好的图像文件。这些图片存储了相对于切空间(tangent space)下的法向量信息。每个像素的颜色代表了一个特定的方向矢量,其中红色通道对应X轴分量,绿色通道对应Y轴分量,蓝色通道则表示Z轴分量[^4]。 #### 修改着色器代码 对于 fragment shader 来说,需要读取来自 texture sampler 的法线数据,并将其转换成世界坐标系中的实际法线值。这涉及到将 tangent, bitangent 和 normal 组合成矩阵,再乘以从切空间到视图空间或世界空间的变化矩阵[^2]。 ```glsl // GLSL Fragment Shader Code Snippet vec3 tangentSpaceNormal = texture(normalMap, TexCoords).rgb; tangentSpaceNormal = normalize(tangentSpaceNormal * 2.0 - 1.0); mat3 TBN = transpose(mat3(TBNMatrix)); normal = normalize(TBN * tangentSpaceNormal); ``` 上述片段展示了如何获取并处理法线贴图中的 RGB 数据,以及怎样构建TBN变换矩阵来进行正确的法线转换。 #### 更新光照计算 一旦得到了新的法线向量之后,就可以按照标准的 Phong/Blinn-Phong 模型或者其他任何所选的光照算法重新评估该fragment处的光照强度。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值