openGL系列文章目录

前言

定义了环面、光照和材质特性。接着将环面顶点以及相关法向量读入缓冲区。display()函数与之前程序中的类似,在这里不同的是它同时也将光照和材质信息传入顶点着色器。为了传入这些信息,它调用installLights(),将光源在视觉空间中的位置,以及材质的ADS 特性,读入相应的统一变量以供着色器使用。注意,我们提前定义了这些统一位置变量,以求更好的性能。

一、环境光、漫反射、镜面光

其中一个重要的细节是变换矩阵MV,用来将顶点位置移动到视觉空间,但它并不总能正确地将法向量也调整进视觉空间。直接对法向量应用MV 矩阵不能保证法向量依然与物体表面垂直。正确的变换是MV 的逆转置矩阵,=这个新增的矩阵叫作“invTrMat”,通过统一变量传入着色器。变量lightPosV 包含光源在相机空间中的位置。我们每帧只需要计算一次,因此我们在installLights()中[在display()中调用]而非着色器中计算。其中顶点着色器使用了一些我们目前没有见过的符号。注意,在顶点着色器最后进行了向量加
法,并且在GLSL 中可用。我们将会在展示着色器之后讨论其他符号。

二、例子

主程序

#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 "Utils.h"
#include "Torus.h"
#include "camera.h"
#include <iostream>
#include <string>
#include <fstream>

void key_move();

using namespace std;

static const float pai = 3.1415926f;

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

static const int screen_width = 1920;
static const int screen_height = 1080;

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

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

float aspect = 0.f;
float torusLocX = 0.f, torusLocY = 0.f, torusLocZ = 0.f;

GLboolean keys[1024] = { GL_FALSE };
float deltaTime = 0.f;
float lastFrame = 0.f;
GLboolean firstMouse = GL_TRUE;
float lastLocX = 0.f;
float lastLocY = 0.f;

//Camera camera(glm::vec3(0.f, 0.f, 5.f));

GLuint renderingProgram = 0;
GLuint textureId = 0;
int width = 0;
int height = 0;

Camera camera(glm::vec3(0.f, 0.5f, 6.f));

Torus myTorus(0.5f, 0.2f, 48);
int numTorusVertices = myTorus.getNumVertices();
int numTorusIndices = myTorus.getNumIndices();

glm::vec3 lightLoc = glm::vec3(5.f, 2.f, 2.f);
float amt = 0.f; //Y轴旋转分量

// variable allocation for display
GLuint mvLoc = 0, projLoc = 0, nLoc = 0;
GLuint globalAmbLoc = 0.f, ambLoc = 0.f, diffLoc = 0.f, specLoc = 0.f, posLoc = 0.f, mambLoc = 0.f, mdiffLoc = 0.f, mspecLoc = 0.f, mshiLoc = 0.f;

glm::mat4 mMat(1.f), vMat(1.f), mvMat(1.f), pMat(1.f), invTrMat(1.f), rMat(1.f);
glm::vec3 currentLightPos(0), transformed(0);
float lightPos[3] = { 0.f };

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

//gold material
float* matAmb = Utils::goldAmbient();
float* matDif = Utils::goldDiffuse();
float* matSpe = Utils::goldSpecular();
float matShi = Utils::goldShininess();

void installLights(glm::mat4 vMatrix)
{
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");
mshiLoc = 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);
//glProgramUniform3fv(renderingProgram, posLoc, 1, lightPos);
glProgramUniform4fv(renderingProgram, mambLoc, 1, matAmb);
glProgramUniform4fv(renderingProgram, mdiffLoc, 1, matDif);
glProgramUniform4fv(renderingProgram, mspecLoc, 1, matSpe);
//glProgramUniform3fv(renderingProgram, mshiLoc, 1, &matShi);
glProgramUniform1f(renderingProgram, mshiLoc, matShi);
}

void setupVertices(void)
{
vector<int> ind = myTorus.getIndices();
vector<glm::vec3> vert = myTorus.getVertices();
vector<glm::vec2> tex = myTorus.getTexCoords();
vector<glm::vec3> norm = myTorus.getNormals();

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

for (int i=0; i<myTorus.getNumVertices(); i++)
{
pValues.push_back(vert[i].x);
pValues.push_back(vert[i].y);
pValues.push_back(vert[i].z);

tValues.push_back(tex[i].s);
tValues.push_back(tex[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);
//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_ELEMENT_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER , ind.size() * sizeof(int), &ind[0], GL_STATIC_DRAW);
}

void init(GLFWwindow* window)
{
renderingProgram = Utils::createShaderProgram("BlinnPhongShaders/vertShader.glsl", "BlinnPhongShaders/fragShader.glsl");
torusLocX = 0.f, torusLocY = 0.f, torusLocZ = 0.f;
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(glm::radians(45.f), aspect, 0.01f, 1000.f);
//vMat = camera.GetViewMatrix();

setupVertices();
}

void display(GLFWwindow* window, double currentTime)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.1f, 0.6f, 0.2f, 1.f);

glUseProgram(renderingProgram);

deltaTime = currentTime - lastFrame;
lastFrame = currentTime;

key_move();

mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
nLoc = glGetUniformLocation(renderingProgram, "norm_matrix");

vMat = camera.GetViewMatrix();
mMat = glm::translate(glm::mat4(1.f), glm::vec3(torusLocX, torusLocY, torusLocZ));
//mMat = glm::rotate(mMat, glm::radians(30.f), glm::vec3(1.f, 0.f, 0.f));
mMat = glm::translate(mMat, glm::vec3(torusLocX, torusLocY,torusLocZ));
mMat *= glm::rotate(mMat, toRadians(30.f), glm::vec3(1.f, 0.f, 0.f));

currentLightPos = glm::vec3(lightLoc.x, lightLoc.y, lightLoc.z);
amt += 0.5f;
rMat = glm::rotate(glm::mat4(1.f), toRadians(amt), glm::vec3(0.f, 0.f, 1.f));
currentLightPos = glm::vec3(rMat * glm::vec4(currentLightPos, 1.f));

installLights(vMat);
//installLights(vMat);
mvMat = vMat * mMat;
invTrMat = glm::transpose(glm::inverse(mvMat)); //这里为什么要用转置矩阵

//更改一个uniform矩阵变量或数组的值。要更改的uniform变量的位置由location指定,location的值应该由glGetUniformLocation函数返回
// 将透视矩阵和MV 矩阵复制给相应的统一变量
/*通过一致变量(uniform修饰的变量)引用将一致变量值传入渲染管线。
location : uniform的位置。
count : 需要加载数据的数组元素的数量或者需要修改的矩阵的数量。
transpose : 指明矩阵是列优先(column major)矩阵(GL_FALSE)还是行优先(row major)矩阵(GL_TRUE)。
value : 指向由count个元素的数组的指针。
*/
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]);
//指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置
/*Parameters
index
指定要修改的顶点属性的索引值

size
指定每个顶点属性的组件数量。必须为1、2、3或者4。初始值为4。(梦维:如position是由3个(x, y, z)组成,而颜色是4个(r, g, b, a))

type
指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。

normalized
指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。

stride
指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。

pointer
指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0。*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
//启用或禁用通用顶点属性数组,参数0索引和着色器中的layout(location = 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, textureId);

//背面剔除,默认情况下,背面剔除是关闭的
//glEnable(/*GL_CULL_FACE | */GL_DEPTH_TEST); //开启背面剔除,注意:这里不能开启深度测试!!!!!! 否则贴图纹理有重叠,不知道为什么?:
//glEnable(GL_CULL_FACE | GL_DEPTH_TEST); //GL_CULL_FACE 和 GL_DEPTH_TEST 不能写在一起
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, numTorusIndices, GL_UNSIGNED_INT, 0);
}

void window_size_callback(GLFWwindow* window, int newWidth, int newHeight)
{
glfwGetWindowSize(window, &newWidth, &newHeight);
glViewport(0, 0, newWidth, newHeight);
aspect = (float)newWidth / (float)height;
pMat = glm::perspective(glm::radians(45.f), aspect, 0.001f, 1000.f);
}

void press_key_callback(GLFWwindow* window, int key, int scancode, int active, int model)
{
if ((key == GLFW_KEY_ESCAPE) && (active == GLFW_PRESS))
{
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
if (active == GLFW_PRESS)
{
keys[key] = GLFW_TRUE;
}
else if (active == GLFW_RELEASE)
{
keys[key] = GLFW_FALSE;
}
}

void key_move()
{
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);
}
}

void mouse_move_callback(GLFWwindow*, double xPos, double yPos)
{
if (firstMouse)
{
lastLocX = xPos;
lastLocY = yPos;
firstMouse = GL_FALSE;
}

double offsetX = xPos - lastLocX;
double offsetY = lastLocY - yPos;

lastLocX = xPos;
lastLocY = yPos;

camera.ProcessMouseMovement(offsetX, offsetY);
}

void mouse_scroll_callback(GLFWwindow* window, double xPos, double yPos)
{
camera.ProcessMouseScroll(xPos);
}

int main(int argc, char** argv)
{
int glfwState = glfwInit();
if (GLFW_FALSE == glfwState)
{
cout << "GLFW initialize failed......invoke glfwInit().....Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
glfwTerminate();
exit(EXIT_FAILURE);
}

/*因为我们要使用OpenGL 4.6,所以我们把GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR对应的hint都设置为4和6。
因为我们要使用OpenGL核心模式(这个后面会提到更多),所以我们把GLFW_OPENGL_PROFILE对应的hint设置为GLFW_OPENGL_CORE_PROFILE,
表示使用OpenGL核心模式。最后,把GLFW_RESIZABLE对应的hint设置为GLFW_FALSE,表示窗口不允许用户调整大小。
之所以这样做是因为如果允许用户调整大小,大小发生变化后,窗口的绘制区域默认不变(依然是原来窗口的区域),
也就是说窗口上绘制的图像的大小、位置不会发生改变。为了避免这种现象发生,我们就简单地不让用户调整窗口大小
(当然也有更好的方法,就是用GLFW设置一个窗口大小的回调函数,但这样比较简单)。*/
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(screen_width, screen_height, "Light ADS", nullptr, nullptr);
if (!window)
{
cout << "GLFW create window failed,invoke glfwCreateWindow()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
glfwTerminate();
exit(EXIT_FAILURE);
}

/*此函数使调用线程上的指定窗口的 OpenGL 或 OpenGL ES 上下文成为当前上下文。
一次只能在单个线程上使上下文成为当前上下文,并且每个线程一次只能有一个当前上下文。
在线程之间移动上下文时,必须先使其在旧线程上变为非当前状态,然后再在新线程上变为当前状态。
*/
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, press_key_callback);
glfwSetCursorPosCallback(window, mouse_move_callback);
glfwSetScrollCallback(window, mouse_scroll_callback);
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);
}

/*此函数设置当前 OpenGL 或 OpenGL ES 上下文的交换间隔,即从调用glfwSwapBuffers开始等待的屏幕更新次数,
然后再交换缓冲区并返回。这有时称为垂直同步、垂直回扫同步或仅vsync。
支持WGL_EXT_swap_control_tear和GLX_EXT_swap_control_tear扩展的上下文也接受负交换间隔,这允许驱动程序立即交换,
即使帧到达有点晚。您可以使用glfwExtensionSupported检查这些扩展。
上下文必须在调用线程上是最新的。在没有当前上下文的情况下调用此函数将导致GLFW_NO_CURRENT_CONTEXT错误。
此功能不适用于 Vulkan。如果您使用 Vulkan 进行渲染,请改为查看交换链的当前模式。
*/
glfwSwapInterval(1);

printf("%s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));//开始初始化过程
const GLubyte* renderer = glGetString(GL_RENDERER);
const GLubyte* vendor = glGetString(GL_VENDOR);
const GLubyte* version = glGetString(GL_VERSION);
const GLubyte* glslVersion = glGetString(GL_SHADING_LANGUAGE_VERSION);
GLint major, minor;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
printf("GL Vendor : %s\n", vendor);
printf("GL Renderer : %s\n", renderer);
printf("GL Version (string) : %s\n", version);
printf("GL Version (integer) : %d.%d\n", major, minor);
printf("GLSL Version : %s\n", glslVersion);

glGetError(); // Debug GLEW bug fix

/*默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,
意味着数据在着色器端是不可见的,哪怕数据已经上传到GPU,由glEnableVertexAttribArray启用指定属性,
才可在顶点着色器中访问逐顶点的属性数据。glVertexAttribPointer或VBO只是建立CPU和GPU之间的逻辑连接,
从而实现了CPU数据上传至GPU。但是,数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,
这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
*/
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

init(window);

while (!glfwWindowShouldClose(window))
{
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}

glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);

return 0;
}

顶点着色器

#version 460 core

layout (location = 0) in vec3 vertPos;
layout (location = 1) in vec3 vertNorm;

out vec3 varyingNormal;
out vec3 varyingLightDir;
out vec3 varyingVertPos;
out vec3 varyingHalfVector;

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()
{
varyingVertPos = (proj_matrix * mv_matrix * vec4(vertPos, 1.f)).xyz;
varyingLightDir = light.position - varyingVertPos;
varyingNormal = (norm_matrix * vec4(vertNorm, 1.f)).xyz;

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

片元着色器

#version 460 core

in vec3 varyingNormal;
in vec3 varyingLightDir;
in vec3 varyingVertPos;
in vec3 varyingHalfVector;

out vec4 fragColor;

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()
{
// normalize the light, normal, and view vectors:
vec3 L = normalize(varyingLightDir);
vec3 N = normalize(varyingNormal);
vec3 V = normalize(varyingVertPos);

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

// halfway vector varyingHalfVector was computed in the vertex shader,
// and interpolated prior to reaching the fragment shader.
// It is copied into variable H here for convenience later.
//在顶点着色器中计算了中间向量varyingHalfVector,
//并在到达片段着色器之前进行插值。
//为了方便以后使用,这里将其复制到变量H中。
vec3 H = normalize(varyingHalfVector);

// get angle between the normal and the halfway vector
float cosPhi = dot(H, N);

// compute ADS contributions (per pixel):
vec3 ambient = ((globalAmbient * material.ambient) + (light.ambient * material.ambient)).xyz;
vec3 diffuse = (light.diffuse.xyz * material.diffuse.xyz) * max(cosTheta, 0.f);
vec3 specular = light.specular.xyz * material.specular.xyz * pow(max(cosPhi, 0.f), material.shininess);
fragColor = vec4((ambient + diffuse + specular), 1.f);
}

运行效果

openGL光照_顶点着色器

源码下载

​源码下载地址​