OpenGL 阴影

在三维场景中,为了使场景看起来更加的真实,通常需要为其添加阴影,OpenGL可以使用很多种技术实现阴影,其中有一种非常经典的实现是使用一种叫阴影贴图的实现,在本节中我们将使用阴影贴图来实现一个简单场景的阴影,场景是一个简单的box和plane,box阴影投射在plane上,光源使用平行光。

原理

使用阴影贴图实现阴影,原理就是使用OpenGL渲染到贴图的方式把当前场景通过深度测试的片元的深度值渲染到一张深度贴图中,然后再次渲染物体时通过深度比较判断片元是否在阴影中。

实现步骤

主要分为两个步骤:

1.从光源的角度渲染场景,这一次的渲染我们不关心场景看起来像什么,只是为了获取片元的深度值,并把这个深度值存储到一张深度贴图中,这个深度表示的是光源的光线所能达到的最大深度;

2.从摄像机的角度再次渲染场景,在渲染片元时同时计算片元在光源坐标系下的深度值,使用这个深度值和深度贴图中存储的同一片元的深度值比较,如果小于或等于深度贴图中的深度值,则表示不在阴影中,否则就是在阴影中。

代码

创建深度贴图,同时作为渲染附件附加到帧缓存中。

        //创建帧缓存
        glGenFramebuffers(1,&fbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo);     //创建深度纹理
glGenTextures(,&depthTex);
glBindTexture(GL_TEXTURE_2D,depthTex); glTexImage2D(GL_TEXTURE_2D,,GL_DEPTH_COMPONENT,width,height,,GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC,GL_LEQUAL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); //绑定
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,depthTex,);

绑定帧缓存,把本次的渲染结果存储到深度贴图中,首先在渲染之前禁用颜色的写入。

     //禁止渲染颜色
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

然后再渲染场景。

       //渲染阴影贴图
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo);
glViewport(,,width,height);
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //打开多边形偏移,以避免深度数据的zfighting问题
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(2.0f,4.0f); //渲染
tShader->Use();
tShader->SetMatrix("model",boxModelMat.Get());
glBindVertexArray(boxVao);
glDrawArrays(GL_TRIANGLES,,);
tShader->SetMatrix("model",planeModelMat.Get());
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES,,); glDisable(GL_POLYGON_OFFSET_FILL);

渲染阴影贴图所使用的着色器,只是最简单的着色器,片元着色器甚至什么都不干。

shadow.vert

#version  core 

layout(location=) in vec3 iPos;
uniform mat4 model;
uniform mat4 lightSpace; void main()
{
gl_Position = lightSpace * model * vec4(iPos,1.0);
}

shadow.frag

#version  core 

void main()
{
//gl_FragDepth = gl_FragCoord.z;
}

也可以把注释的代码放开表示显示设置片元的深度,但注释掉后更有效率,因为底层无论如何都会设置深度缓冲。

完成阴影贴图渲染后,再次渲染场景,并使用阴影贴图。

        //回到摄像机视角
glBindFramebuffer(GL_FRAMEBUFFER,);
glViewport(,,width,height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //绘制场景
planeShader->Use();
glBindTexture(GL_TEXTURE_2D,tt);
planeShader->SetMatrix("model",boxModelMat.Get());
glBindVertexArray(boxVao);
glDrawArrays(GL_TRIANGLES,,);
planeShader->Use();
glBindTexture(GL_TEXTURE_2D,depthTex);
modelShader->SetMatrix("model",planeModelMat.Get());
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES,,);
glBindTexture(GL_TEXTURE_2D,);

  阴影贴图着色器,注意在本次的实践中,因为只渲染了最简单的box,没有使用纹理,所以使用了默认的glBindTexture来绑定阴影贴图;但在复杂模型渲染时,一定要注意阴影贴图的绑定(在着色器中使用多张纹理)。

  那阴影贴图如何采样? 思路是把片元的坐标转换为纹理坐标来对阴影贴图进行采样。在本场景中使用的光源是平行光,光源的投影矩阵使用正投影,代码如下所示:

      //光源矩阵
Matrix4x4 othProjMat = Ortho(-,,-,,,);
//Matrix4x4 othProjMat = Frustum(-2,2,-2,2,1,10);
Matrix4x4 lightView = Matrix4x4::Identity();
lightView.Translate(Vector3(,-2.2,2.1));
lightView.Rotate(Vector3(,,),);
Matrix4x4 lightSpace = lightView * othProjMat;

片元经过一系列的坐标转换和透视除法后的坐标取值范围变换到[-1,1]中,片元的位置与阴影贴图的纹素对应,所以我们使用坐标的(x,y)来对阴影贴图进行采样,而z表示当前片元的深度。纹理坐标的取值范围为[0,1],所以采样之前需要把片元的坐标通过projCoords = projCoords * 0.5 + 0.5;转换到[0,1],然后使用转换坐标采样得到该片元位置光线所能达到的最大深度,与片元的当前z值相比即可判断片元是否在阴影中。着色器代码如下:

plane.vert

#version  core

layout(location=) in vec3 iPos;
layout(location=) in vec2 iTexcoords; uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
uniform mat4 lightView; out VS_OUT {
vec3 fragPos;
vec4 fragPosLightSpace;
}vs_out; out vec2 texcoords; void main()
{
vs_out.fragPos = vec3(model * vec4(iPos,1.0));
vs_out.fragPosLightSpace = lightView * vec4(vs_out.fragPos,1.0);
texcoords = iTexcoords;
gl_Position = proj * view * model * vec4(iPos,1.0);
}

plane.frag

#version  core                                                           

in VS_OUT {
vec3 fragPos;
vec4 fragPosLightSpace;
} fs_in; in vec2 texcoords;
uniform sampler2D shadowMap;
out vec4 color;

//计算片元是否在阴影中
float ShadowCalculation(vec4 fragPosLightSpace)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5; float closestDepth = texture(shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z; float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
return shadow;
} void main()
{
vec4 fragPosLightSpace = fs_in.fragPosLightSpace;
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5; float shadow = ShadowCalculation(fs_in.fragPosLightSpace);
vec3 red = vec3(,,);
vec3 lighting = vec3(0.1,0.1,0.1) + (-shadow) * red;
color = vec4(lighting,1.0);
}

效果

完整代码:

https://github.com/xin-lover/opengl-learn/tree/master/chapter-11-shadow

最新文章

  1. CodeBlocks及LCM应用
  2. maven生命周期和插件
  3. High Memory in the Linux Kernel
  4. 配置安装DVWA
  5. SQL Server中的事务日志管理(2/9):事务日志架构概述
  6. merge into 的用法
  7. Android Studio开发JNIproject
  8. 从IT的角度思考BIM(三):敏捷开发
  9. 初次接触GWT,知识点总括
  10. stm32的FSMC
  11. 28.按要求编写一个Java应用程序: (1)定义一个类,描述一个矩形,包含有长、宽两种属性,和计算面积方法。 (2)编写一个类,继承自矩形类,同时该类描述长方体,具有长、宽、高属性, 和计算体积的方法。 (3)编写一个测试类,对以上两个类进行测试,创建一个长方体,定义其长、 宽、高,输出其底面积和体积。
  12. Type Archive for required library: 'C:/Users/EuphemiaShaw/.m2/repository/org/apache/hadoop/hadoop-hdfs/2.6.5/hadoop-hdfs-2.6.5.jar' in project 'mapreduce' cannot be read or is not a valid ZIP file
  13. StandardContext
  14. NodeJS笔记(五) 使用React Native 创建第一个 Android APP
  15. C++实现简单学生管理系统
  16. library Makefiles
  17. Dependency Walker使用说明[转]
  18. js和java的参数传递方式实际都是一样的,都是按值传递
  19. js把mysql传过来的时间格式化为:0000-00-00 00:00:00
  20. 【刷题】Search in a Big Sorted Array

热门文章

  1. [Selenium] Grid 介绍
  2. [Selenium] Automation Test Manual(Selenium)
  3. 高可用性和PyMongo
  4. Hibernate自定义字段查询
  5. java web url编码解码问题(下载中文名文件)
  6. 基础总结篇之二:Activity的四种launchMode (转载)
  7. mq4参考
  8. 洛谷 - P1434 - 滑雪 - 有向图最长链
  9. Python %s和%r的区别
  10. CH#56C(LCA+dfs序)