openGL系列文章目录
前言
现在我们扩展法线贴图的概念——从纹理图像用于扰动法向量到扰乱顶点位置本身。实
际上,以这种方式修改对象的几何体具有一定的优势,例如使表面特征沿着对象的边缘可
见,并使特征能够响应阴影贴图。我们将会看到,它还可以帮助构建地形。
一种实用的方法是使用纹理图像来存储高度值,然后使用该高度值来提升(或降低)顶
点位置。含有高度信息的图像称为高度图,使用高度图更改对象的顶点的方法称为高度贴
图①。高度图通常将高度信息编码为灰度颜色:(0,0,0)(黑色)=低高度,(1,1,1)(白色)=
高高度。这样一来通过算法或使用“画图”程序就可以轻松创建高度图。图像的对比度越
高,其表示的高度变化越大。这些概念将在图1(显示随机生成的地图)和图1(显
示有组织的模式的地图)中说明。
图1
改变顶点位置是否有用取决于改变的模型。顶点操作可以在顶点着色器中轻松完成,当
模型顶点细节级别够高(例如在足够高精度的球体中)时,改变顶点高度的方法效果很好。
但是,当模型的顶点数量很少(例如立方体的角)时,渲染对象的表面需要依赖于光栅器
中的顶点插值来填充细节。当顶点着色器中可用于改变高度的顶点很少时,许多像素的高
度将无法从高度图中检索,而需要由插值生成,从而导致表面细节较差。当然,在片段着
色器中是不可能进行顶点操作的,因为这时顶点已被光栅化为像素位置。
程序10.4 展示了一个将顶点“向外”(即在表面法向量的方向上)移动的顶点着色器代
码。它通过将顶点法向量乘以从高度图检索所得的值,然后将该乘积与顶点位置相加,以
“向外”移动顶点。
图2
一、高度贴图原理
图10.14(见彩插)展示了通过在画图程序中涂鸦创建的简单高度图(左上角)。高度图
图像中还绘制了一个白色矩形。绿色版本的高度图(左下角)用作纹理。使用程序10.4 中
展示的着色器将高度图应用于100×100 的矩形网格模型时,会产生类似“地形”的感觉(如
图10.14 右图所示)。注意白色矩形是如何生成右边的悬崖的。
图10.14 展示的渲染结果还算可以,因为模型(网格和球体)有足够数量的顶点来对高
度贴图值进行采样。也就是说,模型具有大量的顶点,而高度图相对粗糙并且以低分辨率
充分地采样。然而,仔细观察仍然会发现存在分辨率伪影,例如沿图10.14 中地形右侧凸起
的矩形盒子的左下边缘。凸起的矩形盒子两侧看起来不是完美矩形,而且颜色有渐变效果,
其原因是底层网格100 像素×100 像素的分辨率无法与高度图中的白色矩形完全对齐,从而
导致纹理的光栅化坐标沿侧面产生伪影。
当尝试将其应用于要求更严苛的高度贴图时,在顶点着色器中进行高度贴图的限制会进
一步暴露。考虑图10.5 中展示的月球图像。法线贴图在捕获图像细节方面表现非常出色(如
图10.9 和图10.11 所示),而且由于它是灰度图,因此尝试将其作为高度图应用似乎很自然。
但是,基于顶点着色器的高度贴图会无法胜任这个任务,因为顶点着色器中采样的顶点数
(即使对于精度=500 的球体)比起图像中的细节级别,仍然太少。相较之下,法线贴图能够
很好地捕获细节,因为在片段着色器中对法线贴图的采样是像素级的。
图3
二、代码实现
1.c++主程序
#include "glew/glew.h"
#include "glfw/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "camera.h"
#include "ImportedModel.h"
#include "Utils.h"
static const float pai = 3.1415926f;
float toRadians(float degrees) { return degrees * 2.f * pai / (float)360.f; }
static const int screenWidth = 1920;
static const int screenHeight = 1080;
int width = 0, height = 0;
static const int numVAOs = 1;
static const int numVBOs = 3;
GLuint renderingProgram = 0;
GLuint vao[numVAOs] = { 0 };
GLuint vbo[numVBOs] = { 0 };
float cameraX = 0.f, cameraY = 0.f, cameraZ = 0.f;
float gndLocX = 0.f, gndLocY = 0.f, gndLocZ = 0.f;
GLuint mvLoc = 0, projLoc = 0;
float aspect = 0.f;
glm::mat4 mMat(1.f), vMat(1.f), pMat(1.f), mvMat(1.f);
ImportedModel ground("grid.obj");
int numGroundVertices = 0;
GLuint heightMap = 0;
GLuint heightTexture = 0;
Camera camera(glm::vec3(0.f, 0.f, 1.f));
//float cameraX = 0.f, cameraY = 0.f, cameraZ = 5.f;
GLboolean keys[1024] = { GL_FALSE };
GLboolean b_firstMouse = GL_TRUE;
float deltaTime = 0.f;
float lastFrame = 0.f;
float lastLocX = 0.f;
float lastLocY = 0.f;
void do_movement()
{
if (keys[GLFW_KEY_W])
{
camera.ProcessKeyboard(FORWARD, deltaTime);
}
if (keys[GLFW_KEY_S])
{
camera.ProcessKeyboard(BACKWARD, deltaTime);
}
if (keys[GLFW_KEY_A])
{
camera.ProcessKeyboard(LEFT, deltaTime);
}
if (keys[GLFW_KEY_D])
{
camera.ProcessKeyboard(RIGHT, deltaTime);
}
/*if (keys[GLFW_KEY_ESCAPE])
{
glfwSetWindowShouldClose(window, GL_TRUE);
}*/
}
void key_press_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if ((key == GLFW_KEY_ESCAPE) && (action == GLFW_PRESS))
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
if (action == GLFW_PRESS)
{
keys[key] = GLFW_TRUE; //这里一定一定不能写成“==“,否则 按键WSAD按键失效!!!!!!!
}
else if (action == GLFW_RELEASE)
{
keys[key] = GLFW_FALSE; //这里一定一定不能写成“==“,否则 按键WSAD按键失效!!!!!!!
}
}
void mouse_move_callback(GLFWwindow* window, double xPos, double yPos)
{
if (b_firstMouse)
{
lastLocX = xPos;
lastLocY = yPos;
b_firstMouse = GL_FALSE;
}
float xOffset = xPos - lastLocX;
float yOffset = lastLocY - yPos;
lastLocX = xPos;
lastLocY = yPos;
camera.ProcessMouseMovement(xOffset, yOffset);
}
void mouse_scroll_callback(GLFWwindow* window, double xPos, double yPos)
{
camera.ProcessMouseScroll(yPos);
}
void setupVertices(void)
{
numGroundVertices = ground.getNumVertices();
vector<glm::vec3> vert = ground.getVertices();
vector<glm::vec2> text = ground.getTextureCoords();
vector<glm::vec3> norm = ground.getNormals();
vector<float> pValues;
vector<float> tValues;
vector<float> nValues;
for (int i=0; i<ground.getNumVertices(); i++)
{
pValues.push_back(vert[i].x);
pValues.push_back(vert[i].y);
pValues.push_back(vert[i].z);
tValues.push_back(text[i].s);
tValues.push_back(text[i].t);
nValues.push_back(norm[i].x);
nValues.push_back(norm[i].y);
nValues.push_back(norm[i].z);
}
glGenVertexArrays(numVAOs, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, pValues.size() * sizeof(float), &pValues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, tValues.size() * sizeof(float), &tValues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, nValues.size() * sizeof(float), &nValues[0], GL_STATIC_DRAW);
}
void init(GLFWwindow* window)
{
renderingProgram = Utils::createShaderProgram("vertShader.vert", "fragShader.frag");
cameraX = 0.03f, cameraY = 0.03, cameraZ = 0.8f;
gndLocX = 0.f, gndLocY = 0.f, gndLocZ = 0.f;
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(toRadians(45.f), aspect, 0.01f, 1000.f);
setupVertices();
heightTexture = Utils::loadTexture("heightTexture.jpg");
heightMap = Utils::loadTexture("height.jpg");
}
void display(GLFWwindow* window, double currentTime)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f, 0.2f, 0.5f, 1.f);
glUseProgram(renderingProgram);
deltaTime = currentTime - lastFrame;
lastFrame = currentTime;
do_movement();
//这句必须要有,否则鼠标中键失效
pMat = glm::perspective(camera.Zoom, aspect, 0.1f, 1000.f);
//没有这句,背景就没在相机视点上了,把圆环移到相机的位置
//mMat = glm::translate(glm::mat4(1.f), glm::vec3(cameraX, cameraY, 4.5f));
vMat = camera.GetViewMatrix();
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
//vMat = glm::translate(glm::mat4(1.f), glm::vec3(-cameraX, -cameraY, -cameraZ));
mMat = glm::translate(glm::mat4(1.f), glm::vec3(gndLocX, gndLocY, gndLocZ));
mMat = glm::rotate(mMat, toRadians(25.f), glm::vec3(1.f, 0.f, 0.f));
mvMat = vMat * mMat;
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(2);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, heightTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, heightMap);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, ground.getNumVertices());
}
void window_size_callback(GLFWwindow* window, int newWidth, int newHeight)
{
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
pMat = glm::perspective(toRadians(45.f), aspect, 0.01f, 1000.f);
}
int main(int argc, char** argv)
{
int glfwState = glfwInit();
if (glfwState == GLFW_FALSE)
{
cout << "GLFW initialize failed, invoke glfwInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "Height Mapped Terrain", nullptr, nullptr);
if (!window)
{
cout << "GLFW create window failed, invoke glfwCreateWindow()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
int glewState = glewInit();
if (GLEW_OK != glewState)
{
cout << "GLEW initialize failed, invoke glewInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, window_size_callback);
glfwSetCursorPosCallback(window, mouse_move_callback);
glfwSetScrollCallback(window, mouse_scroll_callback);
glfwSetKeyCallback(window, key_press_callback);
init(window);
while (!glfwWindowShouldClose(window))
{
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
return 0;
}
2.着色器程序
1.顶点着色器
#version 460 core
layout(location = 0) in vec3 vertPos;
layout(location = 1) in vec2 texCoord;
layout(location = 2) in vec3 vertNormal;
out vec2 tc;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout(binding = 0) uniform sampler2D t; // for texture
layout(binding = 1) uniform sampler2D h; // for height map
void main(void)
{
vec4 p = vec4(vertPos, 1.f) + vec4(vertNormal * ((texture(h, texCoord).r / 5.f)), 1.f);
tc = texCoord;
// "p"是高度图所改变的顶点位置
// 由于高度图是灰度图,因此使用其任何颜色分量
// 都可以(我们使用"r")。除以5.0 用来调整高度
gl_Position = proj_matrix * mv_matrix * p;
}
2.片元着色器
#version 460 core
in vec2 tc;
out vec4 fragColor;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout(binding = 0) uniform sampler2D t; // for texture
layout(binding = 1) uniform sampler2D h; // for height map
void main(void)
{
fragColor = texture(t, tc);
}