openGL法线贴图和纹理贴图结合使用,以增强三维物体表面细节

2023-11-15

openGL系列文章目录

前言

凹凸贴图的一种替代方法是使用查找表来替换法向量。这样我们就可以在不依赖数学函
数的情况下,对凸起进行构造,例如月球上的陨石坑所对应的凸起。一种使用查找表的常
见方法叫作法线贴图。
为了理解法线贴图的工作原理,我们首先注意,向量通过3 字节存储,X、Y 和Z 分量
各占1 字节,就可以达到合理的精度。这样,我们就可以将法向量存储在彩色图像文件中,
其中R、G 和B 分量分别对应于X、Y 和Z。图像中的RGB 值以字节存储,通常被解释为[0…1]
范围内的值,但是向量可以有正负值分量。如果我们将法向量分量限制在[−1…+1]范围内,
那么在图像文件中将法向量N 存储为像素的简单转换是:

在这里插入图片描述
法线贴图使用一个图像文件(称为法线贴图),该图像文件包含在光照下所期望表面外
观的法向量。在法线贴图中,向量相对于任意平面XY 表示,其X 和Y 分量表示与“垂直”
的偏差,其Z 分量设置为1,严格垂直于XY 平面的向量(即没有偏差)将表示为(0, 0, 1),
而不垂直的向量将具有非零的X 和/或Y 分量。我们需要使用上面的公式将值转换至RGB 空
间;例如,(0, 0, 1)将存储为(0.5, 0.5, 1),因为实际偏移的范围为[−1…+1],而RGB 值的
范围为[0…1]。我们可以通过纹理单元的另一种妙用来生成这样一幅法线贴图:我们在纹理单元中存储
所需的法向量而非颜色。然后,在给定片段中,我们就可以使用采样器从法线贴图中查找值,
接下来,我们将所得的值作为法向量,而非输出像素颜色(在纹理贴图中我们是这么做的)。
图1 展示了一个法线贴图图像文件的例子,通过将GIMP 法线贴图插件[GI16]应用于
Luna [LU16]纹理而生成。法线贴图图像文件并不适合作为图像查看,我们展示这幅图就是为了
指明这一点,法线贴图最终看起来基本都是蓝色的。这是因为图像文件中每个像素的B 值(蓝色值)都是1(最大蓝色值),这会让它在作为图像时看起来是“蓝色的”。
图3 展示了两个不同的法线贴图图像文件(它们都由Luna [LU16]的纹理构建)以及在
Blinn-Phong 光照模型下将它们应用于球体的结果。从法线贴图查找到的法向量不能直接使用,因为它们是相对于上述的任意XY 平面定义
的,并没有考虑它们在物体上的位置以及在相机空间中的方向。这个问题的解决策略是建
立一个转换矩阵,用于将法向量转换为相机空间,如下所示。
在对象的每个顶点处,我们考虑与对象相切的平面。顶点处的物体的法向量垂直于该切
面。我们在该切面中定义两个相互垂直的向量,同时也垂直于法向量,称为切向量和副切
向量(有时称为副法向量)。构造我们期望的变换矩阵要求我们的模型包括每个顶点的切向
量(可以通过计算切向量和法向量的叉积来构建副切向量)。如果模型中没有定义切向量,
则需要通过计算得到它们。在球体的情况下,可以通过计算得到精确的切向量。
在这里插入图片描述
图1

一、法线贴图?

对于那些表面不可导以至于无法精确求解切向量的模型,其切向量可以通过近似得到,
例如在构造(或加载)模型时,将每个顶点指向下一个顶点的向量作为切向量。请注意,这种近似可能会导致切向量与顶点法向量不严格垂直。因此,如果要实现适用于各种模型
的法线贴图,需要考虑这种可能性(我们的解决方案中对此进行了处理)。
切向量与顶点、纹理坐标以及法向量一样,是从缓冲区(VBO)传递到顶点着色器中的
顶点属性。然后,顶点着色器通过应用MV 矩阵的逆转置并将结果沿着流水线转发以由光
栅器进行插值并最终进入片段着色器,从而对正常向量进行处理。逆转置的应用将法向量
和切向量转换为相机空间,之后我们使用叉积构造副切向量。一旦我们在相机空间中得到法向量、切向量和副切向量,就可以使用它们来构造矩阵(依其分量命名为“TBN”矩阵),该矩阵用于将从法线贴图中检索到的法向量转换为在相机空间中相对于物体表面的法向量。在片段着色器中,新法向量的计算在calcNewNormal()函数中完成。函数的第三行[包含dot(tangent,normal)]的计算确保切向量垂直于法向量。新的切向量和法向量的叉积就是副切向量。然后,我们创建一个类型为mat3 的3×3 矩阵,作为TBN。mat3 构造函数接收3 个向量作为参数,生成一个矩阵,其中顶行是第一个向量,中间行是第二个向量,底行是第三个向量(类似于从摄像机位置构建视图矩阵,见图2)。着色器使用片段的纹理坐标来提取与当前片段对应的法线贴图单元。着色器在提取时使用采样器变量“normMap”,并被绑定到纹理单元0(注意:因此在C++ / OpenGL 应用程序中必须将法线贴图图像附加到纹理单元0)。因为需要将颜色分量从纹理中存储范围[0…1]转换为其原始范围[−1 … + 1],我们将其乘以2.0 再减去1.0。
然后将TBN 矩阵应用于所得法向量以产生当前像素的最终法向量。着色器的其余部分与用于Phong 光的片段着色器相同。片段着色器代码基于Etay Meiri [ME11]的版本,制作法线贴图图像可以使用各种各样的工具。有的图像编辑工具就有制作法线贴图的功能,例如GIMP [GI16]和Photoshop [PH16]。它们通过分析图像中的边缘,推断凸起和凹陷,并产生相应的法线贴图。图4 显示了由Hastings-Trew [HT16]基于NASA 卫星数据创建的月面纹理图。其相应的法线贴图由GIMP 法线贴图插件[GP16],通过处理由Hastings-Trew 创建的黑白版本月面纹理。
图5 展示了使用两种不同方式渲染的,用以表现月球表面的球体。图5 左图中,
球体使用了原始的纹理贴图;图10.6 右图中,球体使用法线贴图的图像作为纹理(供参考)。它们都没有应用法线贴图。虽然左侧使用了纹理的“月球”非常逼真,但仔细观察
可以发现,纹理图案很明显拍摄于阳光从左侧照亮月球的时候,因为其山脊的阴影投射到
了右侧(在底部中心附近的火山口中最明显)。如果我们使用Phong 着色为此场景添加光
照,然后移动月球、相机或灯光来给场景添加动画,就会发现月球上的阴影不会如我们期
望地改变。此外,随着光源的移动(或相机移动),期望中会在山脊上出现许多镜面高光。但
是图5 左图使用了标准纹理的球体将只产生一个镜面高光,对应于光滑球体上所出
现的高光,这看起来非常不现实。配合法线贴图可以显著提高这类对象在光照下的真实感。
在这里插入图片描述
图2
在这里插入图片描述
图3
在这里插入图片描述
图4
在这里插入图片描述
图5
当我们在球体上使用法线贴图(而不是纹理)时,我们会得到图6 所示的结果。尽管
它不像标准纹理那么真实(现在),但是现在它确实响应了光照变化。图6的第一张图像
中从左侧进行光照,第二张图像中则从右侧进行光照。请注意蓝色和黄色箭头所示部分展
示了山脊周围漫反射光的变化以及镜面反射高光的移动。
在这里插入图片描述

图7 展示了在使用Phong 光照模型的情况下,将法线贴图与标准纹理相结合的效果。
月球的图像通过漫射区域进行了增强,镜面高光区域也会响应光源的移动(或相机或物体
移动)。两个图像中的光照分别来自左侧和右侧。
在这里插入图片描述
我们的程序现在需要两个纹理—— 一个用于月球表面图像,一个用于法线贴图——因此
需要有两个采样器。

二、代码

1.主程序

#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 "Utils.h"
#include "Sphere.h"
#include "SOIL2/SOIL2.h"
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

static const int screenWidth = 1920;
static const int screenHeight = 1080;

static const float pai = 3.1415926f;
float toRadians(float degrees) { return degrees * 2.f * pai / (float)360.f; }

static const int numVAOs = 1;
static const int numVBOs = 4;

float cameraX = 0, cameraY = 0, cameraZ = 0;
float sphereX = 0, sphereY = 0, sphereZ = 0;
float lightLocX = 0.f, lightLocY = 0.f, lightLocZ = 0.f;	//全局光照光源位置

GLuint renderingProgram = 1;
GLuint vao[numVAOs] = { 0 };
GLuint vbo[numVBOs] = { 0 };

// variable allocation for display
GLuint mvLoc = 0, projLoc = 0, nLoc = 0;
//phong 光照
GLuint globalAmbLoc = 0, ambLoc = 0, diffLoc = 0, specLoc = 0, posLoc = 0, mAmbLoc = 0, mDiffLoc = 0, mSpecLoc = 0, mShinLoc = 0;
int width = 0, height = 0;
float aspect = 0.f;
//MVP 矩阵
glm::mat4 mMat(1.f), vMat(1.f), pMat(1.f), mvMat(1.f), invTrMat(1.f);
glm::vec3 currentLightPos(0.f, 0.f, 0.f);

float lightPos[3] = { 0.f };
float rotAmt = -2.5f;

Sphere mySphere(48);
int numSphereVertices = 0;
int numSphereIndices = 0;

GLuint moonNormalMap = 0;
GLuint moonTexture = 0;

// white light
float globalAmbient[4] = { 0.1f, 0.1f, 0.1f, 1.f };
float lightAmbient[4] = { 0.f, 0.f, 0.f, 1.f };
float lightDiffuse[4] = { 1.f, 1.f, 1.f, 1.f };
float lightSpecular[4] = { 1.f, 1.f, 1.f, 1.f };

// silver material
float* matAmb = Utils::silverAmbient();
float* matDiff = Utils::silverDiffuse();
float* matSpec = Utils::silverSpecular();
float matShin = Utils::silverShininess();

Camera camera(glm::vec3(0.f, 0.f, 3.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)
{
	numSphereIndices = mySphere.getNumIndices();

	vector<int> ind = mySphere.getIndices();

	vector<glm::vec3> vert = mySphere.getVertices();
	vector<glm::vec2> text = mySphere.getTexCoords();
	vector<glm::vec3> norm = mySphere.getNormals();
	vector<glm::vec3> tang = mySphere.getTangents();

	vector<float> pValues;
	vector<float> tValues;
	vector<float> nValues;
	vector<float> tanValues;

	numSphereVertices = mySphere.getNumVertices();

	for (int i = 0; i < mySphere.getNumIndices(); i++)
	{
		pValues.push_back(vert[ind[i]].x);  //gl_element_array_buffer,顶点对应成索引
		pValues.push_back(vert[ind[i]].y);
		pValues.push_back(vert[ind[i]].z);

		tValues.push_back(text[ind[i]].s);
		tValues.push_back(text[ind[i]].t);

		nValues.push_back(norm[ind[i]].x);
		nValues.push_back(norm[ind[i]].y);
		nValues.push_back(norm[ind[i]].z);

		tanValues.push_back(tang[ind[i]].x);
		tanValues.push_back(tang[ind[i]].y);
		tanValues.push_back(tang[ind[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);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
	glBufferData(GL_ARRAY_BUFFER, tanValues.size() * sizeof(float), &tanValues[0], GL_STATIC_DRAW);
}

void initallLights(glm::mat4 vMatrix)
{
	glm::vec3 transformed = glm::vec3(vMatrix * glm::vec4(currentLightPos, 1.f));
	lightPos[0] = transformed.x;
	lightPos[1] = transformed.y;
	lightPos[2] = transformed.z;

	// get the locations of the light and material fields in the shader
	globalAmbLoc = glGetUniformLocation(renderingProgram, "globalAmbient");
	ambLoc = glGetUniformLocation(renderingProgram, "light.ambient");
	diffLoc = glGetUniformLocation(renderingProgram, "light.diffuse");
	specLoc = glGetUniformLocation(renderingProgram, "light.specular");
	posLoc = glGetUniformLocation(renderingProgram, "light.position");
	mAmbLoc = glGetUniformLocation(renderingProgram, "material.ambient");
	mDiffLoc = glGetUniformLocation(renderingProgram, "material.diffuse");
	mSpecLoc = glGetUniformLocation(renderingProgram, "material.specular");
	mShinLoc = glGetUniformLocation(renderingProgram, "material.shininess");

	//  set the uniform light and material values in the shader
	glProgramUniform4fv(renderingProgram, globalAmbLoc, 1, globalAmbient);
	glProgramUniform4fv(renderingProgram, ambLoc, 1, lightAmbient);
	glProgramUniform4fv(renderingProgram, diffLoc, 1, lightDiffuse);
	glProgramUniform4fv(renderingProgram, specLoc, 1, lightSpecular);
	glProgramUniform3fv(renderingProgram, posLoc, 1, lightPos);
	glProgramUniform4fv(renderingProgram, mAmbLoc, 1, matAmb);
	glProgramUniform4fv(renderingProgram, mDiffLoc, 1, matDiff);
	glProgramUniform4fv(renderingProgram, mSpecLoc, 1, matSpec);
	glProgramUniform1f(renderingProgram, mShinLoc, matShin);
}

void init(GLFWwindow* window)
{
	renderingProgram = Utils::createShaderProgram("vertShader.vert", "fragShader.frag");

	cameraX = 0.f, cameraY = 0.f, cameraZ = 2.f;
	sphereX = 0.f, sphereY = 0.f, sphereZ = -1.f;
	lightLocX = 3.f, lightLocY = 2.f, lightLocZ = 3.f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(toRadians(45.f), aspect, 0.1f, 1000.f);

	setupVertices();

	moonTexture = Utils::loadTexture("moon.jpg");
	moonNormalMap = Utils::loadTexture("moonNORMAL.jpg");
}

void display(GLFWwindow* window, double currentTime)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(0.f, 0.2f, 0.8f, 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");
	nLoc = glGetUniformLocation(renderingProgram, "norm_matrix");

	//vMat = glm::translate(glm::mat4(1.f), glm::vec3(-cameraX, -cameraY, -cameraZ));  //注意相机的Z方向,否则看不到物体
	mMat = glm::translate(glm::mat4(1.f), glm::vec3(sphereX, sphereY, sphereZ));
	mMat = glm::rotate(mMat, toRadians(20.f), glm::vec3(1.f, 0.f, 0.f));
	mMat = glm::rotate(mMat, rotAmt, glm::vec3(0.f, 1.f, 0.f));
	rotAmt += 0.002f;
	mvMat = vMat * mMat;

	invTrMat = glm::transpose(glm::inverse(mvMat));

	currentLightPos = glm::vec3(lightLocX, lightLocY, lightLocZ);
	initallLights(vMat);

	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
	glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));

	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);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(3);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, moonNormalMap);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, moonTexture);

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);

	glDrawArrays(GL_TRIANGLES, 0, numSphereIndices);
}

void window_size_callback(GLFWwindow* window, int newWidth, int newHeight)
{
	//glfwGetFramebufferSize(window, &width, &height);
	glViewport(0, 0, newWidth, newHeight);
	aspect = (float)newWidth / (float)newHeight;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.f);  //1.0472f
}

int main(int argc, char** argv)
{
	int glfwState = glfwInit();
	if (glfwState == GLFW_FALSE)
	{
		cout << "Initialize GLFW 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, GL_TRUE);

	GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "Texture plus normal", nullptr, nullptr);
	if (!window)
	{
		cout << "GLFW create window failed,invoke glfwCreateWindow()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwMakeContextCurrent(window);

	//glfwSetWindowSizeCallback(window, window_size_callback);

	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 texCoords;
layout(location = 2) in vec3 vertNormal;
layout(location = 3) in vec3 vertTangent;

out vec3 varyingLightDir;
//out vec3 varyingLightPos;
out vec3 varyingVertPos;
out vec3 varyingNormal;
out vec3 varyingTangent;
out vec3 originalVertex;
out vec2 tc;

layout(binding = 0) uniform sampler2D s;
layout(binding = 1) uniform sampler2D t;

struct PositionalLight
{
	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	vec3 position;
};

struct Material
{
	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	float shininess;
};

uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;

void main(void)
{
	varyingVertPos = (mv_matrix * vec4(vertPos, 1.f)).xyz;
	varyingLightDir = light.position - varyingVertPos;
	tc = texCoords;

	originalVertex = vertPos;

	varyingNormal = (norm_matrix * vec4(vertNormal, 1.f)).xyz;
	varyingTangent = (norm_matrix * vec4(vertTangent, 1.f)).xyz;

	gl_Position = proj_matrix * mv_matrix * vec4(vertPos, 1.f);
}

2.片元着色器

#version 460 core

in vec3 varyingLightDir;
in vec3 varyingVertPos;
in vec3 varyingNormal;
in vec3 varyingTangent;
in vec3 originalVertex;
in vec2 tc;

out vec4 fragColor;

layout(binding = 0) uniform sampler2D s;
layout(binding = 1) uniform sampler2D t;

struct PositionalLight
{
	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	vec3 position;
};

struct Material
{
	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	float shininess;
};

uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;

vec3 calcNewNormal()
{
	vec3 normal = normalize(varyingNormal);
	vec3 tangent = normalize(varyingTangent);
	tangent = normalize(tangent - dot(tangent, normal) * normal);
	vec3 bitangent = cross(tangent, normal);
	mat3 tbn = mat3(tangent, bitangent, normal);
	vec3 retrivedNormal = texture(s, tc).xyz;
	retrivedNormal = retrivedNormal * 2.f - 1.f;
	vec3 newNormal = tbn * retrivedNormal;
	newNormal = normalize(newNormal);
	
	return newNormal;
}

void main(void)
{
	// normalize the light, normal, and view vectors:
	vec3 L = normalize(varyingLightDir);
	vec3 V = normalize(-varyingVertPos);
	vec3 N = calcNewNormal();

	// get the angle between the light and surface normal:
	float cosTheta = dot(L, N);

	// compute light reflection vector, with respect N:
	vec3 R = normalize(reflect(-L, N));   //以表面顶点为基准,入射光和反射光方向一致

	// angle between the view vector and reflected light:
	float cosPhi = dot(V, R);

	vec4 texC = texture(t, tc);

	// compute ADS contributions with surface texture image:
	fragColor = globalAmbient + light.ambient * texC																
				+ light.diffuse * texC * max(cosTheta, 0.f)
				+ light.specular *texC * pow(max(cosPhi, 0.f), material.shininess);
	
}

运行效果

在这里插入图片描述

源码下载

源码下载地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

openGL法线贴图和纹理贴图结合使用,以增强三维物体表面细节 的相关文章

  • glDrawElements 在 PyOpenGL 中绘制立方体

    我最近开始通过 Python 学习 OpenGL 这要归功于几个教程 尤其是 Nicolas P Rougier 的教程 http www labri fr perso nrougier teaching opengl http www l
  • Qt 5 和 OS X Mavericks 问题

    我正在使用 Cmake 在 OS X 10 9 上构建 QT 项目 自 Mavericks 以来 OpenGL 标头的位置似乎发生了变化 文件夹 System Library Frameworks OpenGL framework Head
  • 着色器/矩阵问题 - 看不到对象

    我试图在屏幕上放置一个立方体并点亮它 我想要在立方体上添加 phong 阴影 当我运行代码时 我可以看到背景图像 但看不到立方体 我相当确定立方体本身是正确的 因为我已经设法用纯色着色器显示它 我已经设法编译着色器程序 但我根本看不到立方体
  • libgdx 中帧缓冲区的结果不明确

    我得到以下奇怪的结果帧缓冲区 http libgdx badlogicgames com nightlies docs api com badlogic gdx graphics glutils FrameBuffer htmllibgdx
  • OpenGL 将着色器附加到程序

    有没有办法访问附加到程序的着色器 也就是说 给定一个程序 我可以做类似的事情 vertexShader getVertexShaderFromProgram program 我想在验证我的程序的函数中记录着色器编译状态 但我只保留对程序的引
  • OpenGL NURBS 曲面

    我正在学习 OpenGL 我想要一个中间有轻微驼峰的表面 我目前正在使用这段代码 但我不确定如何调整 ctrl 点以使其达到我想要的方式 它目前就像 我想要这样的 我不完全确定我应该使用哪些控制点 并且我对其工作原理感到困惑 include
  • 退出 glutFullScreen()

    我不明白为什么当我按 f 时它进入全屏但不退出全屏 在这个方法的开头我已经设置了bool fullscreen false 这是我的切换代码 case f toggle screenmode if fullscreen glutFullSc
  • 三角形未在 OSX 上的 OpenGL 2.1 中绘制

    我正在学习有关使用 OpenGL 在 Java 中创建游戏引擎的教程 我正在尝试在屏幕上渲染一个三角形 一切运行良好 我可以更改背景颜色 但三角形不会显示 我还尝试运行作为教程系列的一部分提供的代码 但它仍然不起作用 教程链接 http b
  • OpenGL z轴指向哪里?

    我正在尝试了解 OpenGL 坐标系 我到处都看到它被描述为右撇子 但这与我的经验不符 我尝试绘制一些形状和 3 d 对象 我发现 z 轴显然指向 屏幕 而 x 指向右侧 y 指向上方 这是左手坐标系的描述 我缺少什么 编辑 例如 http
  • 使用 glDrawElements 时在 OpenGL 核心配置文件中选取三角形

    我正在使用 glDrawElements 绘制三角形网格 并且希望能够使用鼠标单击来拾取 选择三角形 三角形的网格可以很大 在固定功能 OpenGL 中 可以使用 GL SELECT http content gpwiki org inde
  • glEnableVertexAttribArray 中“index”参数的含义以及(可能)OS X OpenGL 实现中的错误

    1 我是否正确理解 要使用顶点数组或VBO进行绘制 我需要所有属性在着色器程序链接之前调用glBindAttribLocation 或者在着色器程序成功链接后调用glGetAttribLocation 然后使用glVertexAttribP
  • OpenGL 中连续暂停

    void keyPress unsigned char key int x int y int i switch key case f i 3 while i x pos 3 sleep 100 glutPostRedisplay 上面是在
  • lnk1104:无法打开“LIBC.lib”链接

    使用 GLee 将着色器写入我的 OpenGL 项目并编译后 我收到了错误LNK1104 cannot open file LIBC lib 我尝试按照其他人的建议添加它并忽略它 但没有解决问题 有没有其他方法可以解决我错过的这个问题 以下
  • glut 库中缺少 glutInitContextVersion()

    我正在练习一些 opengl 代码 但是当我想通过以下方式强制 opengl 上下文使用特定版本的 opengl 时glutInitContextVersion 它编译过程失败并给出以下消息 使用未声明的标识符 glutInitContex
  • 为什么OpenGL使用float而不是double? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 将四元数旋转转换为旋转矩阵?

    基本上 给定一个四元数 qx qy qz qw 我如何将其转换为OpenGL旋转矩阵 我也对哪个矩阵行是 向上 向右 向前 等感兴趣 我有一个四元数的相机旋转 我需要在向量中 以下代码基于四元数 qw qx qy qz 其中顺序基于 Boo
  • GLSL NVidia 方形神器

    当 GLSL 着色器在以下 GPU 上生成不正确的图像时 我遇到了问题 GT 430 GT 770 GTX 570显卡760 但在这些上正常工作 英特尔高清显卡 2500英特尔高清4000英特尔4400显卡740MRadeon HD 631
  • 三角形纹理映射OpenGL

    我正在开发一个使用 Marching Cubes 算法并将数据更改为 3D 模型的项目 现在我想在 OpenGL 中为我的 3D 模型使用纹理映射 我首先尝试了一个简单的示例 它将图片映射到三角形上 这是我的代码 int DrawGLSce
  • 使用 GLSL 直接在着色器中从位置计算平移矩阵

    我正在开发 C OpengL 程序以及 GLSL 顶点和片段着色器 我正在创建同一对象的多个实例 我只需要改变实例之间的对象位置 这是我所做的 我正在使用一个统一变量 它是一个变换矩阵数组 每个矩阵代表一个对象实例 MVP 也是一个变换矩阵
  • 即使手动设置显示环境变量后,WSL Ubuntu 也会显示“错误:无法打开显示”

    我在 WSL Ubuntu 上使用 g 我使用 git 克隆了 GLFW 存储库 使用了ccmake命令配置并生成二进制文件 然后使用make在 build 目录中最终创建 a文件 我安装了所有OpenGL相关的库 usr ld 我不记得我

随机推荐

  • Hive表的几种存储格式及在性能调优应用

    一 理论知识学习 底层决定上层建筑 此部分内容引用了 Hive表的几种存储格式 海贼王一样的男人 博客园 Hive的文件存储格式 textFile textFile为默认格式 存储方式 行存储 缺点 磁盘开销大 数据解析开销大 压缩的tex
  • QT background-color: transparent

    改行代码的作用是可以把背景颜色设为透明 transparent 是默认的 background color transparent 别小看这个 这个 css 代码 在 qt 样式表里应用 可以实现挺好看的效果 如果你遇到有些字体被背景颜色遮
  • 在指定内存上创建对象——placement new机制

    一 介绍 一般来说 使用new申请空间时 是从系统的 堆 heap 中分配空间 申请所得空间的位置是随机的 但是 在某些特殊情况下 可能需要在已分配的特定内存创建对象 比如内存池 这就是所谓的 定位放置new placement new 操
  • Contrastive Loss (对比损失)

    Contrastive Loss 对比损失 在caffe的孪生神经网络 siamese network 中 其采用的损失函数是contrastive loss 这种损失函数可以有效的处理孪生神经网络中的paired data的关系 cont
  • 【CSS】CSS基础知识

    选择器 element 直接选择全部的元素 如 div 选择所有的div元素 id 选择某一id的元素 如 title 选择id为title的元素 class 选择包含某个class的部分元素 如 item 选择class为item的元素
  • 【MySQL基础】MySQL基本数据类型

    序号 系列文章 1 MySQL基础 MySQL介绍及安装 2 MySQL基础 MySQL基本操作详解 3 MySQL基础 MySQL基本数据类型 文章目录 前言 1 数字类型 1 1 整型类型 1 2 浮点数类型 1 3 定点数类型 1 4
  • 如何用YOLOv5玩转半监督(附源码实现)

    目录 引言 背景 目标检测 域自适应 DA Faster SWDA SCL NLDA MEAA UMT MSDA USDAF SIGMA DTPL MTOR 方法 Mean Teacher Model Pseudo Training Ima
  • Redis系列三

    1 6 Redis事务 事务可以一次执行多个命令 并且带有以下两个重要的保证 事务是一个单独的隔离操作 事务中的所有命令都会序列化 按顺序地执行 事务在执行的过程中 不会被其他客户端发送来的命令请求所打断 事务是一个原子操作 事务中的命令要
  • python-正则表达式入门初级篇

    Python 正则表达式入门 初级篇 本文主要为没有使用正则表达式经验的新手入门所写 转载请写明出处 引子 首先说 正则表达式是什么 正则表达式 又称正规表示式 正规表示法 正规表达式 规则表达式 常规表示法 英语 Regular Expr
  • java深入体会

    javase01阶段 idea几个常用设置 1 调整字体大小 2 自动导包 3 不区分大小写 4 设置背景颜色 小数参与计算 1 小数参与计算才会出现小数 int a 10 int b 20 1 a b 30 1 2 小数参与计算有几率出现
  • Vue echart toolbox 工具栏点击 自定义全屏按钮 显示到弹出框中

    最终效果 说明 由于页面上的echart图表过多 写一个vue子组件 直接在父页面调取复用 安装插件 npm i echarts 在 main js 中引用 import echarts from echarts Vue prototype
  • 读标准03-IEEE1451.5标准协议尝鲜实现

    读标准03 IEEE1451 5标准协议尝鲜实现 前面两个文章里面已经详细描述了 TEDS 和 Message 的组成 这里 C 的实现分两个部分 分别对 TEDS 和 Message 的 数据结构实现 与 帧打包与解析的算法实现 第一版
  • java 程序猿必备技能——Debug详解

    Debug的引入和概述 IDEA中Debug的使用 Debug演示 一 前言 在我们以往的程序执行中 只能看到控制台上展示的最终结果 无法直观清晰地看到程序内部每一个变量的加载 更迭 以及代码执行的内部逻辑 而Debug 断点调试 可以让我
  • python3最新版本-mac下安装Python3.*(最新版本)

    前言 mac系统自带python 不过以当前mac系统的最新版本为例 自带的python版本都是2 版本 虽然不影响老版本项目的运行 但是python最新的3 版本的一些语法与2 版本并不相同 网上的教程大神们也肯定都更新出了最新版的教程
  • VC实现对话框文件拖拽

    使用过QQ的人都知道 只要把文件拖拽到消息框中就可以传送文件了 那么这种功能是如何实现的呢 其实很简单 只需要响应一个WM DROPFILES消息就可以了 在基于对话框的程序中 默认是没有这个消息的 按下Ctrl W 弹出类向导对话框 选择
  • kettle通过java步骤获取汉字首拼

    kettle通过java步骤获取汉字首拼 用途描述 一组数据 需要获取汉字首拼后 输出 实现效果 添加jar包 pinyin4j 2 5 0 jar 自定义常量数据 Java代码 完整代码 import net sourceforge pi
  • 用指针交换两个数

    题目描述 利用指针交换用户输入的两个数 输入 测试次数t 共t行 每行两个整数 输出 共t行 每行输出交换后的两个整数 输入样例1 2 1 2 35 21 输出样例1 2 1 21 35 思路分析 用a和b两个变量把数存储下来 再用两个指针
  • swift4.0自定义UITabBarController +UINavigationController

    首先开始项目之前我们需要搭建框架 首选UITabBarController UINavigationController 下面的代码是整理好的 包括我们会遇到的问题解决方法都在里面 自定义UINavigationController cla
  • TypeScript算法-19. 删除链表的倒数第 N 个结点

    TypeScript算法 19 删除链表的倒数第 N 个结点 思路 代码 思路 要删除倒数第N个节点 就要找到倒数第N 1个节点 然后直接将next指针指向next next即可 为了找到倒数第N 1个节点 可以使用快慢指针 让快指针先走n
  • openGL法线贴图和纹理贴图结合使用,以增强三维物体表面细节

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一 法线贴图 二 代码 1 主程序 2 着色器程序 运行效果 源码下载 前言 凹凸贴图的一种替代方法是使用查找表来替换法向量 这样我们就可以在不依赖数学函 数的情况下 对凸