一、原理
1. 为什么要使用视差贴图?
上一篇法线贴图使用了法线纹理,通过法线的变化来控制漫反射和镜面反射的强度,加强了纹理渲染的层次感,明暗渐变更符合实际情况。视差贴图在法线贴图之上,增加物体表面的凹凸感。
法线贴图 | 法线贴图+视差贴图 |
---|
可以明显看到,视差贴图在每一块的边缘部分立体感更强了,凹凸有致啊。
2. 视差贴图的实现原理
视差贴图原理图上图是深度贴图原理的切面表示(墙上一块砖),可以想象下:“把一张2维的砖墙纹理,膨胀成一个三维的立体砖墙”,表面积肯定会变大,相当于把2维的图冲压出一个3维的凹凸不平的壳,一定有多个点对应平面纹理上同一个采样点。视差贴图算法的核心就是设计一个算法,把2维的纹理映射成一个三维的采样。
砖的表面是毛糙不平的,整个深度贴图是这样的: 砖墙深度贴图算法理解:
- 从人眼看纹理上的A点,要模拟拉升后立体效果,要取“膨胀”起来的B点对应的纹理,相当于把B点从平面纹理上拉起来挡住A。
- 真实的B点是无法精确计算的,因为深度贴图是离散的,并非是可以计算的函数(解方程就可以了),只能估算,本文给出一个估算方法:
- 取 A点到人眼(相机位)坐标的向量 -->标准化得到向量P1;
- 从深度贴图上取出A点的深度值H(A),P2 = P * H(A),P2的x y是A的偏差,A.xy + P.xy得到B‘,B‘ 即有误差的B点,可以理解为H(A)绕着A点旋转到AB方向
- 在片段着色器中取纹理时,取A点--->变成取B‘点
二、代码说明
1. 顶点着色器 parallax_mapping.vs,和上一篇法线贴图没有变化,通过切线空间变换,计算切线向量空间的视线向量、光照向量和片段坐标
#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 projection;
uniform mat4 view;
uniform mat4 model;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
vs_out.FragPos = vec3(model * vec4(position, 1.0));
vs_out.TexCoords = texCoords;
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 T = normalize(normalMatrix * tangent);
vec3 N = normalize(normalMatrix * normal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 TBN = transpose(mat3(T, B, N));
vs_out.TangentLightPos = TBN * lightPos;
vs_out.TangentViewPos = TBN * viewPos;
vs_out.TangentFragPos = TBN * vs_out.FragPos;
}
2. 片段着色器 parallax_mapping.vs,有些变化,增加了深度贴图,计算坐标视差
取出纹理后,增加视差偏移,视差偏移的算法如下,其他的逻辑和上一篇法线贴图的fs没有区别。主程序中代码几乎不变,主程序完整代码附录在文章末尾 代码1
...
vec2 texCoords = fs_in.TexCoords;
texCoords = ParallaxMapping(fs_in.TexCoords, viewDir);
...
...
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
float height = texture(depthMap, texCoords).r;
return texCoords - viewDir.xy / viewDir.z * (height * height_scale);
}
...
fs完整代码,增加了bool值parallax控制视差是否生效,便于对比效果;增加height_scale变量控制偏移的强度,默认在主程序中是0.1f
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec2 TexCoords;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in;
uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;
uniform bool parallax;
uniform float height_scale;
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
float height = texture(depthMap, texCoords).r;
return texCoords - viewDir.xy / viewDir.z * (height * height_scale);
}
void main()
{
vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
vec2 texCoords = fs_in.TexCoords;
if(parallax)
{
texCoords = ParallaxMapping(fs_in.TexCoords, viewDir);
}
// obtain normal from normal map
vec3 normal = texture(normalMap, texCoords).rgb;
normal = normalize(normal * 2.0 - 1.0);
// Get diffuse color
vec3 color = texture(diffuseMap, texCoords).rgb;
// Ambient
vec3 ambient = 0.1 * color;
// Diffuse
vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * color;
// Specular
vec3 reflectDir = reflect(-lightDir, normal);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = vec3(0.2) * spec;
FragColor = vec4(ambient + diffuse + specular, 1.0f);
}
三、优化
1. 边缘失真
超出[0, 1]范围进行纹理采样,根据纹理环绕方式导致了不真实的结果,笔者这里是采用repeat方式处理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
坐标是不在[0,1]丢弃即可,新增两行代码
if(parallax)
{
texCoords = ParallaxMapping(fs_in.TexCoords, viewDir);
if (texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
{
discard;
}
}
法线贴图+视差贴图 | 边缘失真优化 |
---|
2. 陡峭视差映射
普通视差贴图在大倾斜的角度下有扭曲、失真,可以通过陡峭视差贴图优化(Steep Parallax Map)
倾斜角度大有失真 | 陡峭视差映射 |
---|
普通视差贴图的算法误差还是比较大,特别是倾斜视角的渲染,陡峭视差映射的优化原理看上图:
- 把深度分多层级,视线向量与每一层相交一个点
- 依次按照这些点取深度贴图中的值,比较该深度值与层深,大于层深究继续循环迭代,一直找到一个深度值 < 层深为止的点,终止循环
核心思想就是渐进式的“试探”,找到一个尽量逼近的值,这里很明显能想到可以再回退一点,算法更精确,或者取T3 和 T2的平均值也行(上图中),其实下面的视差遮蔽贴图就是这么实现的
下面看代码的具体实现,和普通视差贴图的代码区别很小:
- 这个demo用了深度起伏较大的深度纹理,主程序里替换掉三张纹理(漫反射纹理、法线纹理、深度纹理)
unsigned int diffuseMap = loadTexture("resource/wood.png");
unsigned int normalMap = loadTexture("resource/toy_box_normal.png");
unsigned int heightMap = loadTexture("resource/toy_box_disp.png");
- 修改片段着色器中计算纹理偏移的逻辑
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)
// viewDir.xy * height_scale 是简单的增加一个缩小因子,限制偏移的范围,不然viewDir.xy值可能很大,就是一个简单实现的算法,不用太纠结
vec2 p = viewDir.xy * height_scale;
// 上图画的不是太好,有点误导,每一层的高度应该是一样的,所以向量在x y 平面上每一层的偏移值也是线性递增的,p / numLayers就是每一层的delta值
vec2 deltaTexCoords = p / numLayers;
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;
currentLayerDepth += layerDepth;
}
return texCoords - currentTexCoords;
}
3. 视差遮蔽贴图(Parallax Occlusion Mapping)
陡峭视差贴图算法有明显的优化空间,就是视差遮蔽贴图,原理类似。不过笔者的电脑上实现的效果感觉也不差
视差遮蔽贴图
片段着色器的代码修改
vec2 Parallaxmapping(vec2 texCoords, vec3 viewDir)
{
... //陡峭视差贴图部分代码,计算出层深 > 贴图深度的点
// 前一个纹理坐标点
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
// 当前深度差
float afterDepth = currentLayerDepth - currentDepthMapValue;
// 前(图中往左)一个坐标点深度差,
float beforeDepth = texture(parallaxMap, prevTexCoords).r - (currentLayerDepth - layerDepth);
// 当前坐标点的深度值权重
float weight = afterDepth / (afterDepth + beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
4. 浮雕视差贴图(Relief Parallax Mapping)
浮雕视差贴图查了下字典,relief还真有浮雕的意思,难道是说浮起来更立体更真实?
浮雕视差贴图比遮蔽视差贴图更进一步优化,通过反复2分法,无限逼近真实的值
代码如下:
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
... //陡峭视差贴图的源码
vec2 dtex = deltaTexCoords / 2; //纹理步长取半
float deltaLayerDepth = layerDepth / 2; //深度步长取半
//计算当前的纹理和层深度
currentTexCoords += dtex;
currentLayerDepth -= deltaLayerDepth;
const int numSearches = 10; //进行10次2分渐进
for (int i = 0; i < numSearches; ++i) {
//每次纹理步长和深度步长都会减半
dtex /= 2;
deltaLayerDepth /= 2;
//采样当前纹理
currentDepthMapValue = texture(parallaxMap, currentTexCoords).r;
if (currentDepthMapValue > currentLayerDepth) {
//如果当前深度大于层深度,往左逼近
currentTexCoords -= dtex;
currentLayerDepth += deltaLayerDepth;
}
else {
//如果当前深度小于层深度,往右逼近
currentTexCoords += dtex;
currentLayerDepth -= deltaLayerDepth;
}
}
return currentTexCoords;
}
五、完整代码
1. demo-1主程序代码
#include <glad/glad.h>
#include <GLFW/glfw3.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>
#include "Shader.h"
#include "camera.h"
#include "model.h"
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);
unsigned int loadCubemap(vector<std::string> faces);
void renderScene (const Shader &shader);
void renderCube();
void RenderQuad();
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
bool blinn = false;
bool blinnKeyPressed = false;
bool gammaEnabled = true;
bool gammaKeyPressed = false;
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;
// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;
unsigned int planeVAO;
unsigned int planeVBO;
unsigned int woodTexture;
bool parallax_mapping = true;
float height_scale = 0.1;
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "天哥学opengl", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// tell GLFW to capture our mouse
// glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// glPolygonMode(GL_FRONT_AND_BACK ,GL_LINE );
// configure global opengl state
// -----------------------------
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// build and compile shaders
// -------------------------
Shader shader("parallax_mapping.vs", "parallax_mapping.fs");
unsigned int diffuseMap = loadTexture("resource/bricks.jpg");
unsigned int normalMap = loadTexture("resource/bricks_normal.jpg");
unsigned int heightMap = loadTexture("resource/bricks_disp.jpg");
shader.use();
shader.setInt("diffuseMap", 0);
shader.setInt("normalMap", 1);
shader.setInt("depthMap", 2);
glm::vec3 lightPos(0.5f, 1.0f, 0.3f);
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processInput(window);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
// reset viewport
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
shader.setMat4("projection", projection);
shader.setMat4("view", view);
glm::mat4 model = glm::mat4(1.0f);
// model = glm::rotate(model, (float)glfwGetTime() * -10, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // Rotates the quad to show normal mapping works in all directions
shader.setMat4("model", model);
shader.setVec3("lightPos", lightPos);
shader.setVec3("viewPos", camera.Position);
shader.setFloat("height_scale", height_scale);
shader.setInt("parallax", parallax_mapping);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normalMap);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, heightMap);
RenderQuad();
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.1f));
shader.setMat4("model", model);
RenderQuad();
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
bool startRecord = false;
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS && !gammaKeyPressed)
{
gammaEnabled = !gammaEnabled;
gammaKeyPressed = true;
}
if (glfwGetKey(window, GLFW_KEY_B) == GLFW_RELEASE)
{
gammaKeyPressed = false;
}
if (glfwGetKey(window, GLFW_KEY_Y))
{
std::cout << "Y" << std::endl;
startRecord = true;
firstMouse = true;
}
if (glfwGetKey(window, GLFW_KEY_N))
{
std::cout << "N" << std::endl;
startRecord = false;
}
if (startRecord) {
return;
}
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
height_scale == 0.001;
if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
height_scale += 0.001;
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && !gammaKeyPressed)
{
parallax_mapping = !parallax_mapping;
gammaKeyPressed = true;
}
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_RELEASE)
{
gammaKeyPressed = false;
}
}
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}
// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
// std::cout << "xpos : " << xpos << std::endl;
// std::cout << "ypos : " << ypos << std::endl;
if (startRecord) {
return;
}
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
// std::cout << "xoffset : " << xoffset << std::endl;
// std::cout << "yoffset : " << yoffset << std::endl;
camera.ProcessMouseMovement(xoffset, yoffset);
}
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
// utility function for loading a 2D texture from file
// ---------------------------------------------------
unsigned int loadTexture(char const * path)
{
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
if (data)
{
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
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);
stbi_image_free(data);
}
else
{
std::cout << "Texture failed to load at path: " << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
unsigned int loadCubemap(vector<std::string> faces)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++) {
unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
}
else
{
std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
stbi_image_free(data);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
return textureID;
}
void renderScene(const Shader &shader)
{
// room cube
glm::mat4 model = glm::mat4(1.0f);
model = glm::scale(model, glm::vec3(5.0f));
shader.setMat4("model", model);
glDisable(GL_CULL_FACE); // note that we disable culling here since we render 'inside' the cube instead of the usual 'outside' which throws off the normal culling methods.
shader.setInt("reverse_normals", 1); // A small little hack to invert normals when drawing cube from the inside so lighting still works.
renderCube();
shader.setInt("reverse_normals", 0); // and of course disable it
glEnable(GL_CULL_FACE);
// cubes
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(4.0f, -3.5f, 0.0));
model = glm::scale(model, glm::vec3(0.5f));
shader.setMat4("model", model);
renderCube();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 3.0f, 1.0));
model = glm::scale(model, glm::vec3(0.75f));
shader.setMat4("model", model);
renderCube();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-3.0f, -1.0f, 0.0));
model = glm::scale(model, glm::vec3(0.5f));
shader.setMat4("model", model);
renderCube();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-1.5f, 1.0f, 1.5));
model = glm::scale(model, glm::vec3(0.5f));
shader.setMat4("model", model);
renderCube();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-1.5f, 2.0f, -3.0));
model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
model = glm::scale(model, glm::vec3(0.75f));
shader.setMat4("model", model);
renderCube();
}
// renderCube() renders a 1x1 3D cube in NDC.
// -------------------------------------------------
unsigned int cubeVAO = 0;
unsigned int cubeVBO = 0;
void renderCube()
{
// initialize (if necessary)
if (cubeVAO == 0)
{
float vertices[] = {
// back face
-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left
// front face
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
// left face
-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
-1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
// right face
1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
// bottom face
-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
// top face
-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left
1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left
-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
};
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
// fill buffer
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// link vertex attributes
glBindVertexArray(cubeVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// render Cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
}
// RenderQuad() Renders a 1x1 quad in NDC
unsigned int quadVAO = 0;
unsigned int quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
// 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;
float 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);
float quadVertices[] = {
// 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
};
std::cout << "tangent1: " << tangent1.r << " " << tangent1.g << " " << tangent1.b << std::endl;
std::cout << "bitangent1: " << bitangent1.r << " " << bitangent1.g << " " << bitangent1.b << std::endl;
std::cout << "tangent2: " << tangent2.r << " " << tangent2.g << " " << tangent2.b << std::endl;
std::cout << "bitangent2: " << bitangent2.r << " " << bitangent2.g << " " << bitangent2.b << std::endl;
// std::cout << "tangent1: " << tangent1 << std::endl;
// std::cout << "tangent2: " << tangent2 << std::endl;
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, 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)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
网友评论