openGL阴影实现(硬阴影)

您所在的位置:网站首页 如何画立体爱心和阴影 openGL阴影实现(硬阴影)

openGL阴影实现(硬阴影)

2024-07-17 21:28| 来源: 网络整理| 查看: 265

openGL系列文章目录

文章目录 openGL系列文章目录前言一、阴影体二、阴影贴图阴影贴图(第1 轮)——从光源位置“绘制”物体阴影贴图(中间步骤)——将Z 缓冲区复制到纹理阴影贴图(第2 轮)——渲染带阴影的场景 代码运行效果完成工程下载参考

前言

为了给3D 场景添加阴影,人们设计了许多有趣的方法。其中一种很适合在地平面上(如 图8.1 所示)绘制阴影,又相对不需要太大计算代价的方法,叫作投影阴影(projective shadows)。给定一个位于(XL,YL,ZL)的点光源、一个需要渲染的物体以及一个投射阴影的 平面,可以通过生成一个变换矩阵,将物体上的点(XW,YW,ZW)变换为相应阴影在平面上的 点(XS,0,ZS)。之后将其生成的“阴影多边形”绘制出来,通常使用暗色物体与地平面纹理 混合作为其纹理,如图1所示。

在这里插入图片描述 图1

使用投影阴影进行投射的优点是它的高效和易于实现。但是,它仅适用于平坦表面—— 这种方法无法投射阴影于曲面或其他物体。即使如此,它仍然适用于有室外场景并对性能 要求较高的应用,很多游戏中的场景都属于这类。

一、阴影体

Franklin C. Crow 在1977 年提出了另一个重要的方法,这个方法先找到被物体阴影覆盖 的阴影体,之后减少视体与阴影体相交部分中的多边形的颜色强度。图2 展示了阴影体中 的立方体,因此,立方体绘制时会更暗。 阴影体的优点在于其高度准确,比起其他方法来更不容易产生伪影。但是,计算出阴影 体以及每个多边形是否在其中这件事,即使对于现代GPU 来说,计算代价也很大。几何着 色器可以用于计算阴影体,模板缓冲区①可以用于判断像素是否在阴影体内。有些显卡对于 特定的阴影体操作优化提供了硬件支持。 在这里插入图片描述 图2

二、阴影贴图

阴影贴图是用于投射阴影最实用也最流行的方法之一。虽然它并不总是像阴影体一样准 确(且通常伴随着讨厌的伪影),但阴影贴图实现起来更简单,可以在各种情况下使用,并 享有强大的硬件支持。 如果我们不在这里澄清前一段中的“更简单”这个词,那将是我们的疏忽。虽然阴影贴 图比阴影体(在概念和实践中)更简单,但它绝不“简单”!对学生来说,通常在3D 图形 课程中最难实现的技术之一就是阴影贴图。着色器程序本质上很难调试,阴影贴图需要几 个组件和着色器模块的完美协调 阴影贴图基于一个非常简明的想法:光线无法看到的任何东西都在阴影中。也就是说, 如果对象#1 阻挡光到达对象#2,等同于光不能“看到”对象#2。 这个想法的强大之处在于我们已经有了方法来确定物体是否可以被“看到”——使用Z 缓冲区的隐藏面消除算法(HSR),如2.1.7 节所述。因此,计算阴影的策略是,暂时将摄像 机移动到光的位置,应用Z 缓冲区HSR 算法,然后使用生成的深度信息来计算阴影。 因此,渲染场景需要两轮:第1 轮从灯光的角度渲染场景(但实际上没有将其绘制到屏 幕上),第2 轮从摄像机的角度渲染场景。第1 轮的目的是从光的角度生成Z 缓冲区。完成 第1 轮之后,我们需要保留Z 缓冲区并使用它来帮助我们在第2 轮生成阴影。第2 轮实际 绘制场景。 我们的策略可以更加精炼。 􀀠 (第1 轮)从灯光的位置渲染场景。然后,对于每个像素,深度缓冲区包含光与最 近的对象之间的距离。 􀀠 将深度缓冲区复制到单独的“阴影缓冲区”。 􀀠 (第2 轮)正常渲染场景。对于每个像素,在阴影缓冲区中查找相应的位置。如果 相机到渲染点的距离大于从阴影缓冲区检索到的值,则在该像素处绘制的对象离光 线的距离,比离光线最近的对象更远,因此该像素处于阴影中。 当发现像素处于阴影中时,我们需要使其更暗。一种简单而有效的方法是仅渲染其环境 光,忽略其漫反射和镜面反射分量。 上述方法通常被称为“阴影缓冲区”。而当我们在第二步中,将深度缓冲区复制到纹理 中,则称为“阴影贴图”。当纹理对象用于储存阴影深度信息时,我们称其为阴影纹理, OpenGL 通过sampler2DShadow 类型支持阴影纹理(稍后讨论)。这样,我们就可以利用片 段着色器中纹理单元和采样器变量(即“纹理贴图”)的硬件支持功能,在第2 轮快速执行 深度查找。我们现在修改的策略是: 􀀠 (第1 轮)与之前相同; 􀀠 将深度缓冲区的内容复制进纹理对象; 􀀠 (第2 轮)与之前相同,不过阴影缓冲区变为阴影纹理。 现在我们来实现这些步骤。

阴影贴图(第1 轮)——从光源位置“绘制”物体

在第一步中,我们首先将相机移动到灯光的位置然后渲染场景。我们的目标不是在显示 器上实际绘制场景,而是完成足够的渲染过程以正确填充深度缓冲区。因此,没有必要为 像素生成颜色,我们的第一遍将仅使用顶点着色器,但片段着色器不执行任何操作。 当然,移动相机需要构建适当的观察矩阵。根据场景的内容,我们需要在光源处依合适 的方向来看场景。通常,我们希望此方向朝向最终在第2 轮中呈现的区域。 这个方向通常依场景而定——在我们的场景中,我们通常会将相机从光源指向原点。 第1 轮中有几个需要处理的重要细节。 􀀠 配置缓冲区和阴影纹理。 􀀠 禁用颜色输出。 􀀠 从光源到视野中的物体构建一个LookAt 矩阵。 􀀠 启用GLSL 第1 轮着色器程序,该程序仅包含图8.5 中的简单顶点着色器,准备接 收MVP 矩阵。在这种情况下,MVP 矩阵将包括对象的模型矩阵M、前一步中计算 的LookAt 矩阵(作为观察矩阵V),以及透视矩阵P。我们将该MVP 矩阵称为 “shadowMVP”,因为它是基于光而不是相机的观察点。由于实际上没有显示来自光 源的视图,因此第1 轮着色器程序的片段着色器不会执行任何操作。 在这里插入图片描述 图3 􀀠 为每个对象创建shadowMVP 矩阵,并调用glDrawArrays()。第1 轮中不需要包含 纹理或光照,因为对象不会渲染到屏幕上。

阴影贴图(中间步骤)——将Z 缓冲区复制到纹理

OpenGL 提供了两种将Z 缓冲区深度数据放入纹理单元的方法。第一种方法是生成空阴 影纹理,然后使用命令glCopyTexImage2D()将活动深度缓冲区复制到阴影纹理中。 第二种方法是在第1 轮中构建一个“自定义帧缓冲区”(而不是使用默认的Z 缓冲区), 并使用命令glFrameBufferTexture()将阴影纹理附加到它上面。OpenGL 在3.0 版中引入该命 令,以进一步支持阴影纹理。使用这种方法时,无须将Z 缓冲区“复制”到纹理中,因为 缓冲区已经附加了纹理,深度信息由OpenGL 自动放入纹理中。我们将在实现中使用这种 方法。

阴影贴图(第2 轮)——渲染带阴影的场景

我们在这里渲染完整的场景及 其中的所有物体,以及光照、材质和装饰场景中物体的纹理。同时,我们还需要添加必要 的代码,以确定每个像素是否在阴影中。 第2 轮的一个重要特征是它使用了两个MVP 矩阵。一个是将对象坐标转换为屏幕坐标 的标准MVP 矩阵(如我们之前的大多数示例所示)。另一个是在第1 轮中生成的shadowMVP 矩阵,用于从光源的角度进行渲染——现在将在第2 轮中用于从阴影纹理中查找深度信息。 在第2 轮中,从纹理贴图尝试查找像素时,情况比较复杂。OpenGL 相机使用[−1…+ 1] 坐标空间,而纹理贴图使用[0…1]空间。常见的解决方案是构建一个额外的矩阵变换,通常 称为B,它将用于从摄像机空间到纹理空间的转换(或“偏离”,biases,因此名称)。得到 B 的过程很简单——先缩放为1/2,再平移1/2。 在这里插入图片描述 之后将B 合并入shadowMVP 矩阵以备在第2 轮中使用,如下: ( 1) shadowMVP2 [ ][shadowMVP ] pass = B 假设我们使用阴影纹理附加到我们的自定义帧缓冲区的方法,OpenGL 提供了一些相对 简单的工具,用于确定绘制对象时,像素是否处于阴影中。以下是第二阶段处理的详细信 息摘要。 􀀠 构建变换矩阵B,用于从光照空间转换到纹理空间[更合适在init()中进行]。 􀀠 启用阴影纹理以进行查找。 􀀠 启用颜色输出。 􀀠 启用GLSL 第2 轮渲染程序,包含顶点着色器和片段着色器。 􀀠 根据摄像机位置(正常)为正在绘制的对象构建MVP 矩阵。 􀀠 构建shadowMVP2 矩阵(包含B 矩阵,如前所述)——着色器将需要用它查找阴 影纹理中的像素坐标。 􀀠 将生成的矩阵变换发送到着色器统一变量。 􀀠 像往常一样启用包含顶点、法向量和纹理坐标(如果使用)的缓冲区。 􀀠 调用glDrawArrays()。 除了渲染任务外,顶点和片段着色器还需要额外承担一些任务。 􀀠 顶点着色器将顶点位置从相机空间转换为光照空间,并将结果坐标发送到顶点属性 中的片段着色器,以便对它们进行插值。这样片段着色器可以从阴影纹理中检索正 确的值。 􀀠 片段着色器调用textureProj()函数,该函数返回0 或1,指示像素是否处于阴影中(所 涉及的机制将在后面解释)。如果它在阴影中,则着色器通过剔除其漫反射和镜面 反射分量来输出更暗的像素。 阴影贴图是一种常见任务,因此GLSL 为其提供了一种特殊类型的采样器变量,称为 sampler2DShadow(如前所述),可以附加到C++ / OpenGL 应用程序中的阴影纹理。 textureProj()函数用于从阴影纹理中查找值,它类似于我们之前在第5 章中看到的texture(), 其区别是除了textureProj()函数使用vec3 来索引纹理而不是通常的vec2。由于像素坐标是 vec4,因此需要将其投影到2D 纹理空间上,以便在阴影纹理贴图中查找深度值。正如我们 将在下面看到的,textureProj()为完成了这些功能。 顶点着色器和片段着色器代码的其余部分实现了Blinn-Phong 着色。

代码 #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 "Torus.h" #include "ImportedModel.h" #include #include #include using namespace std; void passOne(void); void passTwo(void); static const int screen_width = 1920; static const int screen_height = 1080; static const float pai = 3.14159265f; float toRadins(float degree) { return (degree * 2.f * pai) / (float)360.f; } static const int numVAOs = 1; static const int numVBOs = 5; GLuint vao[numVAOs] = { 0 }; GLuint vbo[numVBOs] = { 0 }; GLuint renderingProgram1 = 0, renderingProgram2 = 0; ImportedModel pyramid("pyr.obj"); Camera myCamera(glm::vec3(0.f, 2.f, 3.f)); Torus myTorus(0.6f, 0.4f, 48); int numPyramidVertices = 0, numTorusVertices = 0, numTorusIndices = 0; glm::vec3 torusLoc(1.6f, 0.f, -0.3f); glm::vec3 pyrLoc(-1.f, 0.1f, 0.3f); glm::vec3 cameraLoc(0.f, 0.2f, 6.0f); glm::vec3 lightLoc(-3.8f, 2.2f, 1.1f); float amt = 0.f; // white light float globalAmbient[4] = { 0.7f, 0.7f, 0.7f, 1.f }; float lightAmbient[4] = { 0.f, 0.f, 0.f, 1.f }; float lightDiffuse[4] = { 1.0f }; float lightSpecular[4] = { 1.0f }; // gold material float* gMatAmb = Utils::goldAmbient(); float* gMatDif = Utils::goldDiffuse(); float* gMatSpe = Utils::goldSpecular(); float gMatShi = Utils::goldShininess(); // bronze material float* bMatAmb = Utils::bronzeAmbient(); float* bMatDif = Utils::bronzeDiffuse(); float* bMatSpe = Utils::bronzeSpecular(); float bMatShi = Utils::bronzeShininess(); float thisAmb[4] = { 0.f }, thisDif[4] = { 0.f }, thisSpe[4] = { 0.f }, matAmb[4] = { 0.f }, matDif[4] = { 0.f }, matSpe[4] = { 0.f }; float thisShi = 0.f, matShi = 0.f; // shadow stuff int scSizeX = 0, scSizeY = 0; GLuint shadowTex = 0, shadowBuffer = 0; glm::mat4 lightVMatrix(1.f); glm::mat4 lightPMatrix(1.f); glm::mat4 shadowMVP1(1.f); glm::mat4 shadowMVP2(1.f); glm::mat4 b(1.f); // variable allocation for display GLuint mvLoc = 0, projLoc = 0, nLoc = 0, sLoc = 0; int width = 0, height = 0; float aspect = 0.f; glm::mat4 pMat(1.f), vMat(1.f), mMat(1.f), mvMat(1.f), invTrMat(1.f), rMat(1.f); glm::vec3 currentLightPos(0.f), transformed(0.f); float lightPos[3] = { 0.f }; GLuint globalAmbLoc(0), ambLoc(0), difLoc(0), speLoc(0), posLoc(0), mAmbLoc(0), mDifLoc(0), mSpecLoc(0), mShiLoc(0); glm::vec3 origin(0.f); glm::vec3 up(0.f, 1.f, 0.f); Camera camera(glm::vec3(0.f, 1.f, 4.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 window_size_callback(GLFWwindow* window, int newWidth, int newHeight) { glfwGetFramebufferSize(window, &newWidth, &newHeight); aspect = (float)newWidth / (float)newHeight; glViewport(0, 0, newWidth, newHeight); pMat = glm::perspective(glm::radians(45.f), aspect, 0.01f, 1000.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 installLights(int renderingProgram, glm::mat4 vMatrix) { transformed = glm::vec3(vMatrix * glm::vec4(currentLightPos, 1.f)); lightPos[0] = transformed.x; lightPos[1] = transformed.y; lightPos[2] = transformed.z; matAmb[0] = thisAmb[0]; matAmb[1] = thisAmb[1]; matAmb[2] = thisAmb[2]; matAmb[3] = thisAmb[3]; matDif[0] = thisDif[0]; matDif[1] = thisDif[1]; matDif[2] = thisDif[2]; matDif[3] = thisDif[3]; matSpe[0] = thisSpe[0]; matSpe[1] = thisSpe[1]; matSpe[2] = thisSpe[2]; matSpe[3] = thisSpe[3]; matShi = thisShi; // get the locations of the light and material fields in the shader globalAmbLoc = glGetUniformLocation(renderingProgram, "globalAmbient"); ambLoc = glGetUniformLocation(renderingProgram, "light.ambient"); difLoc = glGetUniformLocation(renderingProgram, "light.diffuse"); speLoc = glGetUniformLocation(renderingProgram, "light.specular"); posLoc = glGetUniformLocation(renderingProgram, "light.position"); mAmbLoc = glGetUniformLocation(renderingProgram, "material.ambient"); mDifLoc = 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, difLoc, 1, lightDiffuse); glProgramUniform4fv(renderingProgram, speLoc, 1, lightSpecular); glProgramUniform3fv(renderingProgram, posLoc, 1, lightPos); glProgramUniform4fv(renderingProgram, mAmbLoc, 1, matAmb); glProgramUniform4fv(renderingProgram, mDifLoc, 1, matDif); glProgramUniform4fv(renderingProgram, mSpecLoc, 1, matSpe); glProgramUniform1f(renderingProgram, mShiLoc, matShi); } void setupVertices(void) { // pyramid definition numPyramidVertices = pyramid.getNumVertices(); //获取金字塔所有的顶点坐标个数 vector vert = pyramid.getVertices(); //获取金字塔所有的顶点个数 vector norm = pyramid.getNormals(); //获取金字塔所有法线 vector pyramidPValues; vector pyramidNValues; for (int i=0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3