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

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

运行效果

在这里插入图片描述

源码下载

源码下载地址

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: OpenGL是一种高效、跨平台的图形库,可以用来创建和渲染三维场景。使用OpenGL,我们可以通过一系列的图形操作来搭建和呈现一个虚拟的三维环境。 首先,我们需要设置OpenGL的环境,包括窗口大小、光照设置和投影矩阵。然后,我们可以使用OpenGL提供的函数来创建各种基本的几何图形,如立方体、球体和圆柱体等。我们可以通过设定位置、大小和纹理等参数来定制这些几何图形的外观。 接下来,我们可以添加光照效果以增强场景的真实感。OpenGL支持多种光照模型,包括环境光、漫反射光和镜面光等。我们可以通过设置光照的位置、颜色和强度来调整每个物体的光照效果。 另外,我们还可以添加纹理来给物体赋予具体的外观。纹理可以是图片、文字或其他自定义的图案。我们可以在创建几何图形时为其指定纹理坐标,并将纹理与之关联起来,使得物体能够显示出纹理细节和色彩。 最后,为了实现交互式的三维场景,我们可以使用OpenGL提供的事件处理函数来响应用户的输入。通过检测用户的鼠标和键盘操作,我们可以实现物体的旋转、平移和缩放等交互效果。 总而言之,使用OpenGL可以轻松创建并渲染三维场景,通过设置几何图形、光照和纹理以及处理用户输入,我们可以实现一个生动、互动的虚拟环境。 ### 回答2: OpenGL是一种常用的图形库,可以用来创建和渲染三维场景。通过OpenGL,我们可以生成各种几何形状,应用纹理、光照和阴影效果,以及进行交互和动画。 要创建一个三维场景,我们首先需要设置好OpenGL的环境和绘制窗口。之后,我们可以定义场景中的各种几何体,例如立方体、球体或者复杂的模型。这些几何体可以通过定义顶点坐标和法线来表示,也可以通过加载外部模型文件来获取。一旦几何体被定义好,我们就可以将其送入OpenGL的渲染流水线中进行处理。 在渲染流水线中,我们可以通过设置变换矩阵来控制几何体的位置、旋转和缩放。然后,我们可以为每个几何体分配材质,并设置光照参数。这包括光源的位置、强度和颜色,以及材质的反射率和漫反射和镜面反射的比例。这些光照效果可以通过Gouraud或Phong着色模型来计算。 完成设置后,我们可以使用OpenGL的绘制函数来将几何体显示在屏幕上。这些函数可以根据我们的需求进行调用,例如绘制线条、点或者填充多边形。我们还可以设置剪裁窗口和透视投影来创建逼真的图像。 除了静态的几何体绘制外,OpenGL还支持交互和动画。我们可以通过捕捉鼠标和键盘事件来控制相机或物体的移动,实现用户与场景的交互。我们还可以使用定时器、插值和变换来创建动画效果,使场景中的物体移动、旋转或者变形。 总的来说,使用OpenGL可以实现丰富多样的三维场景。通过设置渲染管线、几何体、光照和材质,以及加入交互和动画,我们可以创建逼真的图像并与用户进行交互。 ### 回答3: OpenGL是一种用于编写三维图形程序的开放图形库。通过使用OpenGL,开发者可以创建具有真实感和交互性的三维场景。 创建OpenGL三维场景的步骤通常包括以下几个主要阶段: 1. 初始化:首先,我们需要初始化OpenGL环境。这包括设置视口(viewport)和投影矩阵等。视口定义了绘图区域的尺寸和位置,而投影矩阵则决定了视景体的形状和大小。 2. 创建物体:下一步是创建需要显示的物体。我们可以定义物体的顶点坐标、法线向量、颜色和纹理等属性。这些属性通常保存在顶点缓冲对象(vertex buffer object, VBO)中。 3. 创建着色器程序:着色器程序是OpenGL在渲染过程中执行的一组函数。我们可以编写顶点着色器和片段着色器来控制每个顶点和每个像素的处理。着色器程序在图形渲染管线的不同阶段执行,从而实现图形的渲染和着色。 4. 渲染场景:现在,我们可以将创建的物体放入场景中进行渲染。通过设置模型矩阵、视图矩阵和投影矩阵,我们可以将物体放置到适当的位置、朝向和距离,并在屏幕上进行正确的投影。 5. 控制交互:为了使场景更具交互性,可以通过处理用户输入来控制场景中的物体和相机。例如,可以使用键盘或鼠标控制相机的位置和方向,或者使用鼠标选择并移动场景中的物体。 通过以上步骤,我们可以使用OpenGL创建一个简单的三维场景。这个场景可以包括多个物体纹理贴图、光照效果等。通过不断优化和扩展,我们可以开发出更复杂的三维图形程序,并实现更逼真的场景渲染。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值