立方体贴图


立方体贴图

立方体贴图其实就是一个包含了六个纹理的纹理,我们所有生成和绑定纹理的操作都是一样的,唯一不同的就是当我们把纹理加载到GPU时需要用一个循环去加载六个纹理。对于skybox的顶点着色器和片段着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#version 330 core
layout (location = 0) in vec3 aPos;

//直接把天空盒立方体的位置坐标送给片段着色器作为纹理采样的坐标
out vec3 TexCoords;

//不在需要model矩阵
uniform mat4 projection;
uniform mat4 view;

void main()
{
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww;
}

#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{
FragColor = texture(skybox, TexCoords);
}

在顶点着色器中我们采用了透视除法的方法,将gl_Position的xyz坐标除以w分量。相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为w / w = 1.0。最终的标准化设备坐标将永远会有一个等于1.0的z值:最大的深度值。结果就是天空盒只会在没有可见物体的地方渲染。

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//准备着色器程序
Shader* myShader = new Shader("vertexSource-Depth.vert", "fragmentSource-Depth.frag");
Shader* skyboxShader = new Shader("Skybox.vert", "Skybox.frag");

//常规套路,创建相应的VAO,解析和储存数据
unsigned int cubeVAO, cubeVBO;
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));

unsigned int skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

myShader->use();
glm::mat4 model = glm::mat4(1.0f);
viewMat = camera.GetViewMatrix();
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

glUniformMatrix4fv(glad_glGetUniformLocation(myShader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glad_glGetUniformLocation(myShader->ID, "view"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glad_glGetUniformLocation(myShader->ID, "projection"), 1, GL_FALSE, glm::value_ptr(projMat));

glBindVertexArray(cubeVAO);
glUniform1i(glGetUniformLocation(myShader->ID, "texture1"), 1);
glDrawArrays(GL_TRIANGLES, 0, 36);

glDepthFunc(GL_LEQUAL);
skyboxShader->use();
viewMat = glm::mat4(glm::mat3(camera.GetViewMatrix()));
glUniformMatrix4fv(glad_glGetUniformLocation(skyboxShader->ID, "view"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glad_glGetUniformLocation(skyboxShader->ID, "projection"), 1, GL_FALSE, glm::value_ptr(projMat));

glUniform1i(glGetUniformLocation(skyboxShader->ID, "skybox"), 1);
glBindVertexArray(skyboxVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthFunc(GL_LESS);

这样就可以画出我们想要的天空盒:

环境映射

下一步我们来做环境映射,让物体像一面镜子一样可以反射周围的物体。它的实现方法就是根据观察者的视角去对环境纹理进行采样。我们改一下画箱子的着色器就可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#version 330 core 								
layout (location = 0) in vec3 aPos;
//不再需要纹理,需要法线的输入
layout (location = 1) in vec3 aNormal;

out vec3 position;//像素点位置
out vec3 normal;//法线

uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;

void main(){
position = vec3(model * vec4(aPos, 1.0));
normal = mat3(transpose(inverse(model))) * aNormal;//避免非垂直法线被拉伸
gl_Position = projection * view * model * vec4(aPos, 1.0);
}

#version 330 core
in vec3 position;
in vec3 normal;

out vec4 fragColor;

uniform vec3 cameraPos;
uniform samplerCube skybox;//用于采样的环境纹理

void main(){
vec3 viewDir = normalize(position - cameraPos);//得到视角方向
vec3 reflectDir = reflect(viewDir, normalize(normal));//得到反射向量,对应着这个视角看得到的环境纹理,这里直接把它当成镜面反射
fragColor = vec4(texture(skybox, reflectDir).rgb, 1.0);
}

之后给uniform出来的法线赋值,改变一下挖顶点的部分就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned int cubeVAO, cubeVBO;
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
//挖法线
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));

myShader->SetUniform3f("cameraPos", camera.Position);

glBindVertexArray(cubeVAO);
//赋予箱子着色器的采样纹理
glUniform1i(glGetUniformLocation(myShader->ID, "skybox"), 1);
glDrawArrays(GL_TRIANGLES, 0, 36);

这样就可以得到一个正方形箱子形状的镜子:

折射

现在我们来实现一下折射的效果,它和反射效果一样需要计算视角方向,不过折射需要考虑一个折射率的问题,不同介质的折射率是不一样的。我们使用折射之后弯曲的那个向量对环境纹理进行采样。而折射的实现直接用refract函数就可以。改动后的片段着色器如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 330 core                                      
in vec3 position;
in vec3 normal;

out vec4 fragColor;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main(){
float glassRatio = 0.658;
vec3 viewDir = normalize(position - cameraPos);
//唯一一个改变,得到折射向量
vec3 refractDir = refract(viewDir, normalize(normal), glassRatio);
fragColor = vec4(texture(skybox, refractDir).rgb, 1.0);
}

这样我们可以得到一个类似放大镜的折射效果:


好想回学校啊。。。


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC