Linux OpenGL 实践篇-11-shadow
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

Linux OpenGL 实践篇-11-shadow的更多相关文章
- Linux OpenGL 实践篇-6 光照
经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反 ...
- Linux OpenGL 实践篇-5 纹理
纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...
- Linux OpenGL 实践篇-4 坐标系统
OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...
- Linux OpenGL 实践篇-3 绘制三角形
本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...
- Linux OpenGL 实践篇-2 创建一个窗口
OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...
- Linux OpenGL 实践篇-1 OpenGL环境搭建
本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...
- Linux OpenGL 实践篇-16 文本绘制
文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...
- Linux OpenGL 实践篇-15-图像数据操作
OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...
- Linux OpenGL 实践篇-14-多实例渲染
多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...
随机推荐
- hdu-2066 一个人的旅行(最短路spfa)
题目链接: 一个人的旅行 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Pr ...
- B - Mike and Fun
Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d & %I64u Description Mike a ...
- Event Handling Guide for iOS--(二)---Gesture Recognizers
Gesture Recognizers 手势识别器 Gesture recognizers convert low-level event handling code into higher-leve ...
- 3、HTML的body内标签1
一.特殊符号的表示 #代指空格 < #代指,< > #代指,> ...... #这玩意有很多,记也记不完,用的时候查一下即可: 二.p和br标签 <p>< ...
- POJ - 1661 - Help Jimmy - 简单dp
http://poj.org/problem?id=1661 一般化处理,把一开始的落地和大地都视作平台,设计平台类的属性.dp的时候显然是从上往下dp的,而且要小心Jimmy不能够穿过平台,也就是从 ...
- hdu2476【区间DP,未完待续】
好难搞得东西.... 题意都懒得写了,看题解的巨巨莫怪啊,未完待续未完待续,回去睡觉.
- 组合数学1.4&3.10 By cellur925
本文引用于清华大学出版社卢开澄.卢华明<组合数学第五版>. 今天我们稍微讨论下圆排列以及$n$对夫妻的问题. 1.4圆周排列 这个问题是:从$n$个人中取$r$个在圆周上,我们用$Q(n, ...
- Selenium | 简单使用
需求分析: 登录百度首页,对百度首页进行截屏操作,保存文件 核心代码如下: //配置浏览器 System.setProperty("webdriver.chrome.driver" ...
- 跟我一起玩Win32开发(19):浏览和打开文件
在应用程序中,我们很经常要实现的功能,是Open文件或保存文件对话框,让用户来选择一个或N个文件.本文我将介绍两种思路,第一种方法较为复杂,第二种方法较为简单. 方法一:老规矩 这是一种传统方法,使用 ...
- AtCoder Grand Contest 011 F - Train Service Planning
题目传送门:https://agc011.contest.atcoder.jp/tasks/agc011_f 题目大意: 现有一条铁路,铁路分为\(1\sim n\)个区间和\(0\sim n\)个站 ...