LearnOpenGL学习笔记—高级光照 05:视差贴图

【项目地址:点击这里这里这里

本节对应官网学习内容:视差贴图
结合英文原站,中文站,以及个人实践进行描述(这一节英文原站描述有一些不合理之处,进行了更正)。

1 引入

视差贴图(Parallax Mapping)是一种类似于法线贴图的技术,但是基于不同的原理。
就像法线贴图一样,它是一种可以显着提高纹理表面的细节并赋予其深度感的技术。
虽然也是一种错觉,但视差贴图在传达深度感方面要好得多,并且与法线贴图一起可以提供令人难以置信的逼真的效果。

尽管视差贴图不一定是与高级光照直接相关的技术,但仍将在此进行讨论,因为该技术是法线贴图的逻辑跟进。
请注意,强烈建议在学习视差贴图之前先了解法线贴图,尤其是切线空间。

视差贴图属于位移贴图(Displacement Mapping)技术的一种,它对根据储存在纹理中的几何信息对顶点进行位移或偏移。

一种实现方法是,比如要渲染一个平面,该平面具有大约1000个顶点,我们根据纹理中的某个值来替换这些顶点中的每个顶点,该值可以告诉我们该特定区域的平面高度。

这种包含每个纹理像素高度值的纹理称为高度图。从简单砖块表面的几何特性派生的示例高度图看起来像这样:
在这里插入图片描述
整个平面上的每个顶点都根据从高度贴图采样出来的高度值进行位移,根据材质的几何属性,平坦的平面变换成凹凸不平的表面。
例如一个平坦的平面利用上面的高度贴图进行置换能得到以下结果:
在这里插入图片描述
置换顶点有一个问题——平面必须由很多顶点组成才能获得具有真实感的效果,否则看起来效果并不会很好。
一个平坦的表面上有1000个顶点的计算量太大了。

我们能否不用这么多的顶点就能取得相似的效果呢?

事实上,上面的表面就是用6个顶点渲染出来的(两个三角形)。

上面的那个表面使用视差贴图技术渲染,位移贴图技术不需要额外的顶点数据来表达深度,它像法线贴图一样采用一种聪明的手段欺骗用户的眼睛。

视差贴图背后的思想是,修改纹理坐标使一个fragment的表面看起来比实际的更高或者更低,所有这些都是根据观察方向和高度贴图进行的。
为了理解它如何工作,看看下面砖块表面的图片:
在这里插入图片描述
这里粗糙的红线代表高度贴图中的数值的立体表达,向量 V ⃗ \vec{V} V 代表观察方向。
如果平面进行实际位移,观察者会在点B看到表面
然而我们的平面没有实际上进行位移,观察方向将在点A与平面接触。

视差贴图的目的是,在A位置上的fragment不再使用点A的纹理坐标而是使用点B的
随后我们用点B的纹理坐标采样,观察者就像看到了点B一样。

这个技巧就是描述如何从点A得到点B的纹理坐标。
视差贴图尝试通过对从fragment到观察者的方向向量 V ⃗ \vec{V} V 的进行缩放的方式解决这个问题。

1.1 偏移方法一

缩放的的程度是,缩放后它到表面的距离等于H(a)。
注意,这里英文官网的方法是 长度 缩放为H(a)(也就是第二种偏移方法)
但是它给出的代码,其实是利用Z的高度
在刚看到代码部分,没看下去的时候我很困惑为何前后描述不一致,在此提醒
这个用Z的缩放方法可以看这个paper的4.1节
Parallax Mapping with Offset Limiting: A Per­-Pixel Approximation of Uneven Surfaces

所以我们将 V ⃗ \vec{V} V 的高度,缩放为高度贴图在点A处H(A)采样得来的值。下图展示了经缩放得到的向量 P ⃗ \vec{P} P
在这里插入图片描述

我们随后选出 P ⃗ \vec{P} P 以及这个向量在平面投影的坐标作为纹理坐标的偏移量(texture coordinate offset)

因为向量 P ⃗ \vec{P} P 是使用从高度贴图得到的高度值计算出来的,所以一个fragment的高度越高位移的量越大。

但是使用Z进行偏移的方法很少是正确的,任何出现视差的表面都会有不同的高度。

这种方法在陡峭看下来的视角下,纹理坐标偏移往往很小。小偏移量意味着Tn处的高度可能非常接近to处的高度。因此,偏移量将几乎是正确的。

但是随着视角变浅,偏移值接近有限。

当偏移值显著增大时,Tn索引与之相似的高度的概率逐渐衰减为随机概率。

这个问题可以将具有复杂高度图案的曲面简化为一团闪闪发光的像素,这些像素看起来与原始纹理贴图完全不同。

1.2 偏移方法二

解决这个问题的一个简单方法是限制偏移量,使其不会超过H(a)。
可以看这个paper的4.3节(这也是英文网站它给的方法,但是代码上却没这样做)
Parallax Mapping with Offset Limiting: A Per­-Pixel Approximation of Uneven Surfaces

因为视差映射是一种近似,任何极限值都可以产生,但这个方法很好,它通过两条指令在程序中导出代码。
在这里插入图片描述
在这里插入图片描述

T n = T o + ( H ( a ) ∙ V x , y ) T_n = T_o + (H(a) ∙ V{x, y} ) Tn=To+(H(a)Vx,y)

这个技巧在大多数时候都没问题,但“点B”是粗略估算得到的。当表面的高度变化很快的时候,看起来就不会真实,因为向量 P ⃗ \vec{P} P 最终不会和B接近。
在这里插入图片描述

视差贴图的另一个问题是,当表面被任意旋转以后很难指出从 P ⃗ \vec{P} P 获取哪一个坐标。
我们在视差贴图中使用了另一个坐标空间,这个空间 P ⃗ \vec{P} P 向量的x和y元素总是与纹理表面对齐。如果看了法线贴图教程,就知道我们实现它的方法,我们还是在切线空间中实现视差贴图。

将fragment到观察者的向量 P ⃗ \vec{P} P ,转换到切线空间中,经变换的 P ⃗ \vec{P} P 向量的x和y元素将于表面的切线和副切线向量对齐。
由于切线和副切线向量与表面纹理坐标的方向相同,我们可以用 P ⃗ \vec{P} P 的x和y元素作为纹理坐标的偏移量,这样就不用考虑表面的方向了。

理论都有了,下面我们来动手实现视差贴图。

2 视差贴图

我们将使用一个简单的2D平面,在把它发送给GPU之前我们先计算它的切线和副切线向量;和法线贴图教程做的差不多。(我们直接继续在法线贴图的例子上做吧)
我们将向平面贴diffuse纹理、法线贴图以及一个位移贴图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个例子中我们将视差贴图和法线贴图连用。
因为视差贴图生成表面位移了的幻觉,当光照不匹配时这种幻觉就被破坏了。
法线贴图通常根据高度贴图生成,法线贴图和高度贴图一起用能保证光照能和位移匹配。

可能已经注意到,上面链接上的那个位移贴图和教程一开始的那个高度贴图相比是颜色是相反的。
这是因为使用反色高度贴图(也叫深度贴图)去模拟深度比模拟高度更容易。

下图我们讲讲这个轻微的改变:
在这里插入图片描述
我们再次获得A和B,但是这次我们用向量 V ⃗ \vec{V} V 减去点A的纹理坐标得到 P ⃗ \vec{P} P

我们通过在着色器中用1.0减去采样得到的高度贴图中的值来取得深度值,而不再是高度值,或者简单地在图片编辑软件中把这个纹理进行反色操作,就像我们对连接中的那个深度贴图所做的一样。

位移贴图是在片段着色器中实现的,因为三角形表面的所有位移效果都不同。

在片段着色器中我们将需要计算fragment到观察者到方向向量 V ⃗ \vec{V} V 所以我们需要观察者位置和在切线空间中的fragment位置。

法线贴图教程中我们已经有了一个顶点着色器,我们用那个就好了。(可以看上一篇笔记)
在片段着色器中,我们实现视差贴图的逻辑。片段着色器看起来会是这样的:

......
uniform sampler2D depthMap;

uniform float height_scale;

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);

void main()
{
    
               
    // offset texture coordinates with Parallax Mapping
    vec3 viewDir   = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec2 texCoords = ParallaxMapping(fs_in.TexCoords,  viewDir);

    // then sample textures with new texture coords
    vec3 diffuse = texture(diffuseMap, texCoords);
    vec3 normal  = texture(normalMap, texCoords);
    normal = normalize(normal * 2.0 - 1.0);
    // proceed with lighting code
    [...]    
}
  

我们定义了一个叫做ParallaxMapping的函数,它把 fragment的纹理坐标 和 切线空间中的fragment到观察者的方向向量 为输入。

这个函数返回经位移的纹理坐标,然后我们使用这些经位移的纹理坐标进行diffuse和法线贴图的采样。

最后fragment的diffuse颜色和法线向量就正确的对应于表面的经位移的位置上了。
我们来看看ParallaxMapping函数的内部:
(注意这里和官网代码不一样了,我们用了第二种偏移方法,也就是把观察向量的长度拉伸到height,因为是单位向量,所以xy也会拉伸到height倍)

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    
     
    float height =  texture(depthMap, texCoords).r;    
    vec2 p = viewDir.xy  * height * height_scale;
    return texCoords - p;    
}

这个相对简单的函数是我们所讨论过的内容的直接表述。
我们用本来的纹理坐标texCoords从高度贴图中来采样,得到当前fragment的高度H(A)。

然后计算出 P ⃗ \vec{P} P ,注意此时的x和y元素在切线空间中,拉伸height倍。

我们同时引入额一个height_scale的uniform,来进行一些额外的控制,因为视差效果如果没有一个缩放参数通常会过于强烈。然后我们用纹理坐标减去 P ⃗ \vec{P} P 来获得最终的经过位移纹理坐标。

我们没和官网一样用Z来偏移,也就是官网提到的那个技术,叫做有偏移量限制的视差贴图(Parallax Mapping with Offset Limiting)。

选择哪一个技术是个人偏好问题。

官网的偏移方法一的代码如下(本笔记中未使用)

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    
     
    float height =  texture(depthMap, texCoords).r;    
    vec2 p = viewDir.xy / viewDir.z * (height * height_scale);
    return texCoords - p;    
}

最后的纹理坐标随后被用来进行采样(diffuse和法线)贴图,下图展示的没有视差贴图的效果
在这里插入图片描述

下图所展示的位移效果中height_scale等于0.1:
在这里插入图片描述

这里我们会看到只用法线贴图和与视差贴图相结合的法线贴图的不同之处。

因为视差贴图尝试模拟深度,它实际上能够根据你观察它们的方向使砖块叠加到其他砖块上。

在视差贴图的那个平面里你仍然能看到在边上有古怪的失真。

原因是在平面的边缘上,纹理坐标超出了0到1的范围进行采样,根据纹理的环绕方式导致了不真实的结果。

解决的方法是当它超出默认纹理坐标范围进行采样的时候就丢弃这个

if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
	discard;

丢弃了超出默认范围的纹理坐标的所有fragment,视差贴图的表面边缘给出了正确的结果。
注意,这个技巧不能在所有类型的表面上都能工作,但是应用于平面上它还是能够是平面看起来真的进行位移了:
在这里插入图片描述
看起来不错,运行起来也很快,因为我们只要给视差贴图提供一个额外的纹理样本就能工作。

当从一个角度看过去的时候,会有一些问题产生(和法线贴图相似),陡峭的地方会产生不正确的结果,从下图你可以看到:
在这里插入图片描述
问题的原因是这只是一个大致近似的视差映射。
还有一些技巧让我们在陡峭的高度上能够获得几乎完美的结果。
例如,我们不再使用单一样本,取而代之使用多样本来找到最近点B会得到怎样的结果?

3 陡峭视差映射

陡峭视差映射(Steep Parallax Mapping)是视差映射的扩展,原则是一样的,但不是使用一个样本而是多个样本来确定向量 P ⃗ \vec{P} P B B B

即使在陡峭的高度变化的情况下,它也能得到更好的结果,原因在于该技术通过增加采样的数量提高了精确性。

陡峭视差映射的基本思想是将总深度范围划分为同一个深度/高度的多个层。

从每个层中我们沿着 P ⃗ \vec{P} P 方向移动采样纹理坐标,直到我们找到一个采样低于当前层的深度值。看看下面的图片:
在这里插入图片描述
我们从上到下遍历深度层,我们把每个深度层和储存在深度贴图中的它的深度值进行对比。

如果这个层的深度值小于深度贴图的值,就意味着这一层的 P ⃗ \vec{P} P 向量部分还要在这层之下。

我们继续这个处理过程直到有一层的深度高于储存在深度贴图中的值:这个点就在(经过位移的)表面下方。

这个例子中我们可以看到第二层(H(T2) = 0.73)的深度贴图的值仍低于第二层的深度值0.4,所以我们继续。

下一次迭代,这一层的深度值0.6大于深度贴图中采样的深度值(H(T3) = 0.37)。

所以我们便可以假设第三层向量 P ⃗ \vec{P} P 是可用的位移几何位置。

我们可以用从向量 P ⃗ \vec{P} P 在第三层的纹理坐标偏移 T 3 T_3 T3,来对fragment的纹理坐标进行位移。

可以看到随着深度层的增加精确度也在提高。

为实现这个技术,我们只需要改变ParallaxMapping函数,因为所有需要的变量都有了:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    
     
    // number of depth layers
    const float numLayers = 10;
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy * height_scale; 
    vec2 deltaTexCoords = P / numLayers;
  
    [...]     
}   

然后我们遍历所有层,从上开始,知道找到小于这一层的深度值的深度贴图值:

// get initial values
vec2  currentTexCoords     = texCoords;
float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
  
while(currentLayerDepth < currentDepthMapValue)
{
    
    
    // shift texture coordinates along direction of P
    currentTexCoords -= deltaTexCoords;
    // get depthmap value at current texture coordinates
    currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
    // get depth of next layer
    currentLayerDepth += layerDepth;  
}

return currentTexCoords;

这里我们循环每一层深度,直到沿着 P ⃗ \vec{P} P 向量找到第一个返回低于(位移)表面的深度的纹理坐标偏移量。从fragment的纹理坐标减去最后的偏移量,来得到最终的经过位移的纹理坐标向量,这次就比传统的视差映射更精确了。
在这里插入图片描述
我们可以通过对视差贴图的一个属性的利用,对算法进行一点提升。
当垂直看一个表面的时候纹理时位移比以一定角度看时的小。
我们可以在垂直看时使用更少的样本,以一定角度看时增加样本数量:

const float minLayers = 8.0;
const float maxLayers = 32.0;
float numLayers = mix(maxLayers, minLayers, max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0));  

这里我们得到viewDir和正z方向的点乘,使用它的结果根据我们看向表面的角度调整样本数量(注意正z方向等于切线空间中的表面的法线)。
如果我们所看的方向平行于表面,我们就是用32层。
下图可以看到层数变多了
在这里插入图片描述

陡峭视差贴图同样有自己的问题。因为这个技术是基于有限的样本数量的,我们会遇到锯齿效果以及图层之间有明显的断层。

我们可以通过增加样本的方式减少这个问题,但是很快就会花费很多性能。
有些旨在修复这个问题的方法:不是使用低于表面的第一个位置(也就是上文不是从0.2开始),而是在两个接近的深度层进行插值找出更匹配的B。

两种最流行的解决方法叫做Relief Parallax Mapping和Parallax Occlusion Mapping,Relief Parallax Mapping更精确一些,但是比Parallax Occlusion Mapping性能开销更多。
因为Parallax Occlusion Mapping的效果和前者差不多但是效率更高,因此这种方式更经常使用,所以我们将在下面讨论一下。

4 视差遮蔽映射

视差遮蔽映射(Parallax Occlusion Mapping)和陡峭视差映射的原则相同,但不是用触碰的第一个深度层的纹理坐标,而是在触碰之前和之后,在深度层之间进行线性插值。

我们根据表面的高度距离哪个深度层的深度层值的距离来确定线性插值的大小。

看看下面的图片就能了解它是如何工作的:
在这里插入图片描述
你可以看到大部分和陡峭视差映射一样,不一样的地方是有个额外的步骤,两个深度层的纹理坐标围绕着交叉点的线性插值。这也是近似的,但是比陡峭视差映射更精确。

视差遮蔽映射的代码基于陡峭视差映射,所以并不难:

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    
     

	// number of depth layers
	const float minLayers = 8.0;
	const float maxLayers = 32.0;
	float numLayers = mix(maxLayers, minLayers, max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0));  
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy * height_scale; 
    vec2 deltaTexCoords = P / numLayers;


	// get initial values
	vec2  currentTexCoords  = texCoords;
	float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
  
	while(currentLayerDepth < currentDepthMapValue)
	{
    
    
		// shift texture coordinates along direction of P
		currentTexCoords -= deltaTexCoords;
		// get depthmap value at current texture coordinates
		currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
		// get depth of next layer
		currentLayerDepth += layerDepth;  
	}

	// get texture coordinates before collision (reverse operations)
	vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

	// get depth after and before collision for linear interpolation
	float afterDepth  = currentDepthMapValue - currentLayerDepth;
	float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
 
	// interpolation of texture coordinates
	float weight = afterDepth / (afterDepth - beforeDepth);
	vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

	return finalTexCoords; 
    
}

在对(位移的)表面几何进行视差寻找,找到深度层之后,我们获取视差偏移前的纹理坐标。
然后我们计算当前深度与前后深度层深度之间的距离,并在两个值之间进行插值。
线性插值的方式是在两个层的纹理坐标之间进行的基础插值。
函数最后返回最终的经过插值的纹理坐标。

视差遮蔽映射的效果非常好,尽管有一些可以看到的轻微的不真实和锯齿的问题,这仍是一个好方法,因为除非是放得非常大或者观察角度特别陡,否则也看不到。
在这里插入图片描述
视差贴图是提升场景细节非常好的技术,但是使用的时候还是要考虑到它会带来一点不自然。
大多数时候视差贴图用在地面和墙壁表面,这种情况下查明表面的轮廓并不容易,同时观察角度往往趋向于垂直于表面。
这样视差贴图的不自然也就很难能被注意到了,对于提升物体的细节可以祈祷难以置信的效果。

5 代码

顶点着色器

#version 330 core									  

layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 tangent;
layout (location = 4) in vec3 bitangent;

out VS_OUT {
    
    
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;



uniform mat4 modelMat;

uniform mat4 viewMat;
uniform mat4 projMat;


uniform vec3 lightPos;
uniform vec3 viewPos;

void main(){
    
    										   
   gl_Position =  projMat * viewMat * modelMat * vec4(position.xyz,1.0);   
   vs_out.FragPos=vec3(modelMat * vec4(position.xyz,1.0));
   vs_out.TexCoords=texCoords;

   mat3 normalMatrix = transpose(inverse(mat3(modelMat)));
   vec3 T = normalize(normalMatrix * tangent);
   vec3 B = normalize(normalMatrix * bitangent);
   vec3 N = normalize(normalMatrix * normal);    
      
   mat3 TBN = transpose(mat3(T, B, N));  
   vs_out.TangentLightPos = TBN * lightPos;
   vs_out.TangentViewPos  = TBN * viewPos;
   vs_out.TangentFragPos  = TBN * vs_out.FragPos;
}

片段着色器

#version 330 core	

 
in VS_OUT {
    
    
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} fs_in;


uniform sampler2D normalMap; 
uniform sampler2D DiffuseTexture; 
uniform sampler2D depthMap;

out vec4 FragColor;			

uniform vec3 lightColor;
uniform vec3 ambientColor;
uniform float height_scale;

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    
     

	// number of depth layers
	const float minLayers = 8.0;
	const float maxLayers = 32.0;
	float numLayers = mix(maxLayers, minLayers, max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0));  
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy * height_scale; 
    vec2 deltaTexCoords = P / numLayers;


	// get initial values
	vec2  currentTexCoords  = texCoords;
	float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
  
	while(currentLayerDepth < currentDepthMapValue)
	{
    
    
		// shift texture coordinates along direction of P
		currentTexCoords -= deltaTexCoords;
		// get depthmap value at current texture coordinates
		currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
		// get depth of next layer
		currentLayerDepth += layerDepth;  
	}

	// get texture coordinates before collision (reverse operations)
	vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

	// get depth after and before collision for linear interpolation
	float afterDepth  = currentDepthMapValue - currentLayerDepth;
	float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
 
	// interpolation of texture coordinates
	float weight = afterDepth / (afterDepth - beforeDepth);
	vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

	return finalTexCoords; 
    
}


void main(){
    
    
	vec3 finalResult = vec3(0,0,0);
	// 从法线贴图范围[0,1]获取法线
    // 将法线向量转换为范围[-1,1]

	// offset texture coordinates with Parallax Mapping
    vec3 dirToCamera = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec2 texCoords = ParallaxMapping(fs_in.TexCoords,  dirToCamera);
	if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
		discard;
	//texCoords=fs_in.TexCoords;

    // then sample textures with new texture coords

	vec3 uNormal =texture(normalMap, texCoords).rgb;

    uNormal =normalize( uNormal * 2.0f - 1.0f);   


	
	//ambient
	vec3 ambient=  ambientColor * texture(DiffuseTexture,texCoords).rgb;

	
	vec3 dirToLight=normalize(fs_in.TangentLightPos-fs_in.TangentFragPos);	
	//diffuse
	float diffIntensity = max(dot(dirToLight,uNormal), 0.0);
	vec3 diffuseColor =   diffIntensity * texture(DiffuseTexture,texCoords).rgb * lightColor;
	//Blinn-Phong specular
	vec3 halfwarDir = normalize(dirToLight + dirToCamera);
	float specularAmount = pow(max(dot(uNormal, halfwarDir), 0.0),32.0f);
	vec3 specularColor = vec3(0.2) * specularAmount * lightColor;
	vec3 result = (diffuseColor+specularColor);

	finalResult += result;
	finalResult += ambient;
	
	FragColor=vec4(finalResult,1.0);


}

main.cpp

#include <iostream>

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "Material.h"
#include "Shader.h"
#include "Camera.h"



#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

//鼠标移动镜头
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
//滚轮缩放
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
//检测输入
void processInput(GLFWwindow *window);
//导入图
unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format);





#pragma region Camera Declare
//建立camera
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 5.0f);
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
Camera camera(cameraPos, cameraTarget, cameraUp);
#pragma endregion

#pragma region Input Declare
//移动用
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

void processInput(GLFWwindow* window) {
    
    
	//看是不是按下esc键 然后退出
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
    
    
		glfwSetWindowShouldClose(window, true);
	}
	//更流畅点的摄像机系统
	if (deltaTime != 0) {
    
    
		camera.cameraPosSpeed = 5 * deltaTime;
	}
	//camera前后左右根据镜头方向移动
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.PosUpdateForward();
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.PosUpdateBackward();
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.PosUpdateLeft();
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.PosUpdateRight();
	if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
		camera.PosUpdateUp();
	if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
		camera.PosUpdateDown();
}
float lastX;
float lastY;
bool firstMouse = true;



//鼠标控制镜头方向
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    
    
	if (firstMouse == true)
	{
    
    
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}
	float deltaX, deltaY;
	float sensitivity = 0.05f;

	deltaX = (xpos - lastX)*sensitivity;
	deltaY = (ypos - lastY)*sensitivity;

	lastX = xpos;
	lastY = ypos;

	camera.ProcessMouseMovement(deltaX, deltaY);

};
//缩放
float fov = 45.0f;

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    
    
	if (fov >= 1.0f && fov <= 45.0f)
		fov -= yoffset;
	if (fov <= 1.0f)
		fov = 1.0f;
	if (fov >= 45.0f)
		fov = 45.0f;
}

#pragma endregion



#pragma region Light Declare
glm::vec3 LightPos(1.0f, 0.0f, 1.0f);
glm::vec3 LightColor(1.0f, 1.0f, 1.0f);
#pragma endregion

//加载一般的图片
unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format) {
    
    
	unsigned int TexBuffer;
	glGenTextures(1, &TexBuffer);
	glBindTexture(GL_TEXTURE_2D, TexBuffer);

	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	// 加载并生成纹理
	int width, height, nrChannel;
	unsigned char *data = stbi_load(filename, &width, &height, &nrChannel, 0);
	if (data) {
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		printf("Failed to load texture");
	}
	glBindTexture(GL_TEXTURE_2D, 0);
	stbi_image_free(data);
	return TexBuffer;
}

int main() {
    
    

#pragma region Open a Window
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// Open GLFW Window
	GLFWwindow* window = glfwCreateWindow(800, 600, "My OpenGL Game", NULL, NULL);
	if (window == NULL)
	{
    
    
		printf("Open window failed.");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	// 关闭鼠标显示
	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
	// 回调函数监听鼠标
	glfwSetCursorPosCallback(window, mouse_callback);

	// 回调函数监听滚轮
	glfwSetScrollCallback(window, scroll_callback);


	// Init GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK)
	{
    
    
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}

	glEnable(GL_DEPTH_TEST);
	glViewport(0, 0, 800, 600);
#pragma endregion

#pragma region Plane Declare

	// positions
	glm::vec3 pos1(-1.0, 1.0, 0.0);
	glm::vec3 pos2(-1.0, -1.0, 0.0);
	glm::vec3 pos3(1.0, -1.0, 0.0);
	glm::vec3 pos4(1.0, 1.0, 0.0);
	// texture coordinates
	glm::vec2 uv1(0.0, 1.0);
	glm::vec2 uv2(0.0, 0.0);
	glm::vec2 uv3(1.0, 0.0);
	glm::vec2 uv4(1.0, 1.0);
	// normal vector
	glm::vec3 nm(0.0, 0.0, 1.0);

	// calculate tangent/bitangent vectors of both triangles
	glm::vec3 tangent1, bitangent1;
	glm::vec3 tangent2, bitangent2;
	// - triangle 1
	glm::vec3 edge1 = pos2 - pos1;
	glm::vec3 edge2 = pos3 - pos1;
	glm::vec2 deltaUV1 = uv2 - uv1;
	glm::vec2 deltaUV2 = uv3 - uv1;

	GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

	tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
	tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
	tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
	tangent1 = glm::normalize(tangent1);

	bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
	bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
	bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
	bitangent1 = glm::normalize(bitangent1);

	// - triangle 2
	edge1 = pos3 - pos1;
	edge2 = pos4 - pos1;
	deltaUV1 = uv3 - uv1;
	deltaUV2 = uv4 - uv1;

	f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

	tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
	tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
	tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
	tangent2 = glm::normalize(tangent2);


	bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
	bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
	bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
	bitangent2 = glm::normalize(bitangent2);



	GLfloat planeVertices[] = {
    
    
		// Positions            // normal         // TexCoords  // Tangent                          // Bitangent
		pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
		pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
		pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,

		pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
		pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
		pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
	};


#pragma endregion


#pragma region Init Shader Program
	Shader* Shader_NormalTest = new Shader("vertex_NormalTest.vert", "fragment_NormalTest.frag");
#pragma endregion

	//GLuint DiffuseTexture = LoadImageToGPU("brickwall.jpg", GL_RGB, GL_RGB);
	//GLuint NormalTexture = LoadImageToGPU("brickwall_normal.jpg", GL_RGB, GL_RGB);

	GLuint DiffuseTexture = LoadImageToGPU("bricks2.jpg", GL_RGB, GL_RGB);
	GLuint NormalTexture = LoadImageToGPU("bricks2_normal.jpg", GL_RGB, GL_RGB);
	GLuint DepthTexture = LoadImageToGPU("bricks2_disp.jpg", GL_RGB, GL_RGB);

	Shader_NormalTest->use();
	glUniform1i(glGetUniformLocation(Shader_NormalTest->ID, "DiffuseTexture"), 0);
	glUniform1i(glGetUniformLocation(Shader_NormalTest->ID, "normalMap"), 1);
	glUniform1i(glGetUniformLocation(Shader_NormalTest->ID, "depthMap"), 2);
	
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);


	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat)));
	glEnableVertexAttribArray(4);
	glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat)));
	


	//model
	glm::mat4 modelMat;
	//view
	glm::mat4 viewMat;
	//projection
	glm::mat4 projMat;
	//modelMat = glm::rotate(modelMat, glm::radians(-90.0f), glm::vec3(1.0, 0.0, 0.0));
	while (!glfwWindowShouldClose(window))
	{
    
    
		

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
		
		// 显示相关的数据
		float currentFrame = glfwGetTime();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;

		// 检测输入

		processInput(window);
		
		//设置好uniform缓冲中的观察矩阵
		viewMat = camera.GetViewMatrix();


		//设置好透视矩阵
		projMat = glm::perspective(glm::radians(fov), 800.0f/600.0f, 0.1f, 100.0f);
		

		Shader_NormalTest->use();
		//modelMat = glm::rotate(modelMat, glm::radians(0.5f), glm::vec3(1.0, 0.0, 0.0));
		glUniform3f(glGetUniformLocation(Shader_NormalTest->ID, "viewPos"), camera.Position.x, camera.Position.y, camera.Position.z);
		glUniform3f(glGetUniformLocation(Shader_NormalTest->ID, "lightPos"), LightPos.x, LightPos.y, LightPos.z);

		glUniform3f(glGetUniformLocation(Shader_NormalTest->ID, "ambientColor"), 0.3f, 0.3f, 0.3f);
		glUniform3f(glGetUniformLocation(Shader_NormalTest->ID, "lightColor"),LightColor.x, LightColor.y, LightColor.z);
		glUniform1f(glGetUniformLocation(Shader_NormalTest->ID, "height_scale"), 0.1f);

		//modelMat = glm::rotate(modelMat, glm::radians(1.0f), glm::vec3(1.0, 0.0, 0.0));
		glUniformMatrix4fv(glGetUniformLocation(Shader_NormalTest->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
		glUniformMatrix4fv(glGetUniformLocation(Shader_NormalTest->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
		glUniformMatrix4fv(glGetUniformLocation(Shader_NormalTest->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, NormalTexture);
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, DepthTexture);
		// 绘制网格
		glBindVertexArray(VAO);

		glDrawArrays(GL_TRIANGLES,0, 6);


		// Swap the buffers
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwTerminate();
	return 0;

}

猜你喜欢

转载自blog.csdn.net/weixin_43803133/article/details/109403758