opengl 学习 之 16 lesson
opengl 学习 之 16 lesson
简介
阴影贴图,在tutorial15中,我们学会了去创建了光照贴图,使用的是静态光源。它产生了很好的阴影,但是它不可以处理动态光照。
link
http://www.opengl-tutorial.org/uncategorized/2017/06/07/website-update/
http://www.opengl-tutorial.org/cn/intermediate-tutorials (还有中文版在一直没有察觉)
https://zhuanlan.zhihu.com/p/144025113 (知乎大佬)
基础的阴影贴图算法
基础的阴影贴图算法由两部分组成。第一,这个场景场景是由点光源渲染的。只要计算每个碎片的深度。第二,场景以普通的方式渲染,用而外的测试去看当前的片段是否在阴影中。
是否在阴影中的测试十分简单,如果当前的采样比光存储的阴影贴图中对应的点远。表示场景有一个对象比当前要渲染出来的片段离光更近。
渲染阴影贴图
在当前教程中,我们将会去只考虑直线光源 (类似于太阳光,所有的光线是平行投射的)。因此,渲染阴影贴图我们要用正交投影(TIPS:还有一个是透视投影,一共两个投影?)一个正交矩阵,就像一个透视投影举证,除了没有透视考虑在内。(正常,先得到正交矩阵才能得到透视矩阵)。一个对象将会看起来一样无论它离摄像头是远是近。
设置渲染对象和MVP矩阵
在教程14中,你知道了如何取渲染场景进入纹理中为了在渲染中读取它?(其实14并没有特别懂)。
我们这里使用一个1024x1024 16位的深度纹理去包含阴影贴图。16位通常足够生成阴影贴图了。我们使用深度纹理,而不是一个深度的渲染矩阵,因为我们将要去在之后采样他。
// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
GLuint FramebufferName = 0;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
// Depth texture. Slower than a depth buffer, but you can sample it later in your shader
GLuint depthTexture;
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT16, 1024, 1024, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0);
glDrawBuffer(GL_NONE); // No color buffer is drawn to.
// Always check that our framebuffer is ok
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
return false;
模型视图投影矩阵渲染沉降从点光源的位置:
- 投影矩阵是一个正交矩阵将包含坐标轴空间的所有的东西,X的空间范围(-10,10),Y的空间范围(-10,10),Z的空间范围是(-10,20)。这些值被设置然后所有可以看到的东西都会被看到。
- 视图矩阵旋转这个世界,光线朝向是-Z
- 模型矩阵可以是任何你想要的
glm::vec3 lightInvDir = glm::vec3(0.5f,2,2);
// Compute the MVP matrix from the light's point of view
glm::mat4 depthProjectionMatrix = glm::ortho<float>(-10,10,-10,10,-10,20);
glm::mat4 depthViewMatrix = glm::lookAt(lightInvDir, glm::vec3(0,0,0), glm::vec3(0,1,0));
glm::mat4 depthModelMatrix = glm::mat4(1.0);
glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix;
// Send our transformation to the currently bound shader,
// in the "MVP" uniform
glUniformMatrix4fv(depthMatrixID, 1, GL_FALSE, &depthMVP[0][0])
渲染器
在此过程使用渲染器十分简单。顶点着色器是一个传递着色器,简单计算顶点的坐标在齐次坐标系中:
#version 330 core
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
// Values that stay constant for the whole mesh.
uniform mat4 depthMVP;
void main(){
gl_Position = depthMVP * vec4(vertexPosition_modelspace,1);
}
片段着色器也很简单:它简单的写入片段的深度
#version 330 core
// Ouput data
layout(location = 0) out float fragmentdepth;
void main(){
// Not really needed, OpenGL does it anyway
fragmentdepth = gl_FragCoord.z;
}
渲染阴影贴图是两倍快对于普通的渲染,因为只有第分辨率的深度被写入了,而不是深度和颜色;内存带宽通常是最大的变现问题在GPU中。
使用阴影贴图
基础渲染器
现在我们回到我们通常的渲染器,对于每个片段我们计算的,我们必须测试他是否在阴影贴图后面。
为了做这个,我们需要去计算当前的片段位置在相同的空间我们曾经创建阴影贴图的地方。所以我们需要去转换它一次使用普通的MVP矩阵,另一次使用深度MVP矩阵。
但是这有一个小窍门。深度MVP矩阵乘以顶点坐标会得到齐次结果,结果在[-1,1]之间;但是纹理采样在[0,1] 之间。
举个例子,一个片段在屏幕的中间会是(0,0) 在齐次坐标;但是因为被采样在纹理坐标,UVs是(0.5,0.5)。
这可以被修复通过扭转得到的坐标直接应用于片段着色器,但是更困难去乘以齐次坐标通过以下的矩阵,简单的除2(不太懂,但是例子好像有点懂如下,先将得到[-1,1] / 2 得到 [-0.5, 0.5],然后平移得到[0,1])
glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
glm::mat4 depthBiasMVP = biasMatrix*depthMVP;
现在我们可以卸下顶点着色器。和之前一样但是,我们现在处处两个坐标而不是1个:
- gl_Position 是顶点的坐标通过当前摄像头看到的坐标
- ShadowCoord是顶点的坐标我们通过上一个摄像头看到的(光源的位置)
// Output position of the vertex, in clip space : MVP * position
gl_Position = MVP * vec4(vertexPosition_modelspace,1);
// Same, but with the light's view matrix
ShadowCoord = DepthBiasMVP * vec4(vertexPosition_modelspace,1);
碎片着色器也很简单
- 纹理(阴影贴图,阴影坐标).z 是距离在管线和最近的物体
- 阴影坐标是距离在管线和当前碎片着色器
...所以如果当前的随便比最近的物体元,这意味着我们在阴影中:
float visibility = 1.0;
if ( texture( shadowMap, ShadowCoord.xy ).z < ShadowCoord.z){
visibility = 0.5;
}
我们仅仅需要去使用知识去修改我们的着色器。当然,环境色不需要求改,因为环境光的目的在生活中是模拟一些入射关键机试我们在阴影之中,否则阴影中将会变为纯黑色。
color =
// Ambient : simulates indirect lighting
MaterialAmbientColor +
// Diffuse : "color" of the object
visibility * MaterialDiffuseColor * LightColor * LightPower * cosTheta+
// Specular : reflective highlight, like a mirror
visibility * MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5);
result

问题
Shaow acne :阴影交错

猜测,因为管线贴图的分辨率不是特别大。所以上图 lightmap pixel 代表的距离,导致了一段黑的更接近光(其实我认为黑的应该是亮的,图中是否绘制错误了呢),一段黄的比light远,应该绘制为黑色(我觉得图中画反了)。我们增加一个偏移:
float bias = 0.005;
float visibility = 1.0;
if ( texture( shadowMap, ShadowCoord.xy ).z < ShadowCoord.z-bias){
visibility = 0.5;
}
修改 simple里面的面片着色器
float bias = 0.005;
float visibility = texture( shadowMap, vec3(ShadowCoord.xy, (ShadowCoord.z - bias)/ShadowCoord.w) );

发现边缘还是不是特别好,发现tutorial里面也是这种效果。
但是,你发现因为我们的偏移,虚假在底面和墙上变得更糟糕了。更进一步,偏移0.005看起来在地面上足够了,但是不太足够在曲线曲面上:一些伪像存在圆柱和球上。
一个基本的方法是根据倾斜度修改偏移:
float bias = 0.005*tan(acos(cosTheta)); // cosTheta is dot( n,l ), clamped between 0 and 1
bias = clamp(bias, 0,0.01);
这个实验就先不做了~
阴影交错现象消失了,即使是在曲线和曲面上。
另一个敲门,可能会产生效果也可能不会产生效果基于集合,仅仅去渲染背面在渲染贴图。这使我们有一个更好的集合,看下一章(Perter Panning)。哦这个窍门是计算物体背面的渲染贴图,同时增加集合的厚度。
当我们渲染影音贴图的时候,剔除前面的三角形:
// We don't use bias in the shader, but instead we draw back faces,
// which are already separated from the front faces by a small distance
// (if your geometry is made this way)
glCullFace(GL_FRONT); // Cull front-facing triangles -> draw only back-facing triangles
当我们渲染长的的时候,正常渲染(剔除背面)
glCullFace(GL_BACK); // Cull back-facing triangles -> draw only front-facing triangles
Peter Panning(让墙起飞的现象)
我们没有了阴影交错了,但是我们任然有错误的底面渲染,让墙开起来仿佛在飞翔。事实上,增加偏移让它看起来更加糟糕。
这个很容易修复:简单避免很薄的集合体,这有两个优势:
- 第一,它解决了Perter Panning:
- 第二,他可以开启背面去除当渲染光照贴图,因为现在,这有一个多边形墙面对着光,将会咬合另一边。
缺点是你有更多的三角形面片需要被渲染(两倍时间)
Aliasing(混叠)
即使用了这两个敲门你也会注意到,球的边界上仍然有一些点亮边上点暗。
PCF
最简单的方法是提升这个去改变阴影贴图采样器类型为sample2DShadow.结果是当你采样阴影贴图,硬件将会采样周围的纹理,作比较,然后返回一个float值在[0,1]之间带有一个双线性过滤对于比较的结果。
举个例子,0.5代表2个采样在阴影,两个在光线之中。
表示这不和但采样滤波深度贴图!比较通常返回true或者false;PCF给出要给插值对于4个“true or false".
正如你看到的,阴影边界开始变得顺滑,但是阴影贴图纹理依旧可见。
Poisson Sampling(泊松采样)
一个简单的方法去解决这个是去赛扬阴影贴图N次而不是一次。使用PCF混合,这将产生很好的结果,及时用一个小一点的N。这有代码用了四次泊松采样。
for (int i=0;i<4;i++){
if ( texture( shadowMap, ShadowCoord.xy + poissonDisk[i]/700.0 ).z < ShadowCoord.z-bias ){
visibility-=0.2;
}
}
poissonDisk 是一个静态数组定义,举个例子如下所示
vec2 poissonDisk[4] = vec2[](
vec2( -0.94201624, -0.39906216 ),
vec2( 0.94558609, -0.76890725 ),
vec2( -0.094184101, -0.92938870 ),
vec2( 0.34495938, 0.29387760 )
);
生成的片段着色器汇编的更加亮或者更加暗依据阴影贴图的采样次数。
700这个常数定一个了样本分布的范围。
分层泊松采样
我们可以移除这个条纹通过选择不同的采样对于每个像素。这有两个主要的方法:分层泊松或者旋转泊松。分层泊松选择不同的样本;旋转泊松总是使用相同的采样,但是带有一个随机的旋转,所以他们看起来不同。在这个教程中我将只会解释分层泊松。
和之前的版本的位移不同我们将使用index用一个随机的值
for (int i=0;i<4;i++){
int index = // A random number between 0 and 15, different for each pixel (and each i !)
visibility -= 0.2*(1.0-texture( shadowMap, vec3(ShadowCoord.xy + poissonDisk[index]/700.0, (ShadowCoord.z-bias)/ShadowCoord.w) ));
}
我们可以生成一个随机的数字,返回一个随机的数字从[0,1]:
float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
return fract(sin(dot_product) * 43758.5453);
在我们的例子中,seed4将是i的组合和...其他东西。我们可以使用gl_FragCoord像素的位置在屏幕上,或者世界坐标系的位置:
// - A random sample, based on the pixel's screen location.
// No banding, but the shadow moves with the camera, which looks weird.
int index = int(16.0*random(gl_FragCoord.xyy, i))%16;
// - A random sample, based on the pixel's position in world space.
// The position is rounded to the millimeter to avoid too much aliasing
//int index = int(16.0*random(floor(Position_worldspace.xyz*1000.0), i))%16;
完美的噪音比条纹稍微令人接受。
其他提升的方法
Early bailing
Spot lights
......
opengl 学习 之 16 lesson的更多相关文章
- OpenGL学习之路(一)
1 引子 虽然是计算机科班出身,但从小对几何方面的东西就不太感冒,空间想象能力也较差,所以从本科到研究生,基本没接触过<计算机图形学>.为什么说基本没学过呢?因为好奇(尤其是惊叹于三维游戏 ...
- OpenGL学习之路(三)
1 引子 这些天公司一次次的软件发布节点忙的博主不可开交,另外还有其它的一些事也占用了很多时间.现在坐在电脑前,在很安静的环境下,与大家分享自己的OpenGL学习笔记和理解心得,感到格外舒服.这让我回 ...
- OpenGL学习之路(四)
1 引子 上次读书笔记主要是学习了应用三维坐标变换矩阵对二维的图形进行变换,并附带介绍了GLSL语言的编译.链接相关的知识,之后介绍了GLSL中变量的修饰符,着重介绍了uniform修饰符,来向着色器 ...
- OpenGL学习之windows下安装opengl的glut库
OpenGL学习之windows下安装opengl的glut库 GLUT不是OpenGL所必须的,但它会给我们的学习带来一定的方便,推荐安装. Windows环境下的GLUT下载地址:(大小约为15 ...
- OpenGL学习进程(10)第七课:四边形绘制与动画基础
本节是OpenGL学习的第七个课时,下面以四边形为例介绍绘制OpenGL动画的相关知识: (1)绘制几种不同的四边形: 1)四边形(GL_QUADS) OpenGL的GL_QUADS图 ...
- OpenGL学习进程(7)第五课:点、边和图形(二)边
本节是OpenGL学习的第五个课时,下面介绍OpenGL边的相关知识: (1)边的概念: 数学上的直线没有宽度,但OpenGL的直线则是有宽度的.同时,OpenGL的直线必须是有限长度,而不是像数学概 ...
- OpenGL学习进程(6)第四课:点、边和图形(一)点
本节是OpenGL学习的第四个课时,下面介绍OpenGL点的相关知识: (1)点的概念: 数学上的点,只有位置,没有大小.但在计算机中,无论计算精度如何提高,始终不能表示一个无穷小的点 ...
- OpenGL学习笔记3——缓冲区对象
在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段.由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU ...
- OpenGL学习进程(12)第九课:矩阵乘法实现3D变换
本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈. (1)3D变换: OpenGL中绘制3D世界的空间变换包括:模型变换.视图变换.投影变换和视口 ...
- OpenGL学习进程(11)第八课:颜色绘制的详解
本节是OpenGL学习的第八个课时,下面将详细介绍OpenGL的颜色模式,颜色混合以及抗锯齿. (1)颜色模式: OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. R ...
随机推荐
- C#高性能开发之类型系统:从C# 7.0 到C# 14的类型系统演进全景
自C# 7.0以来,C#语言在类型系统方面引入了众多新数据类型.类型构造和语言特性,以提升性能.类型安全性和开发效率.本文全面整理了从C# 7.0到C# 14.0(截至2025年4月,C# 14.0为 ...
- Spring Kafka: UnknownHostException: 34bcfcc207e0
参考: https://stackoverflow.com/questions/69527813/spring-kafka-unknownhostexception-34bcfcc207e0 我遇到的 ...
- 阿里云Ansible自动化运维平台部署
以下是在阿里云平台上基于Ansible实现自动化运维的完整实践指南,整合所有核心操作流程和命令,适配指定的服务器规划: 一.环境规划 主机名 IP地址 角色 操作系统 manage01 192.168 ...
- k8s二进制安装
各节点安装docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-rep ...
- 新纪元:"老"新人
博客园注册很久了,但从未发布过内容.终于开通博客,记录自己,也支持博客园! 另外,这次苹果秋季发布会真的好无聊!︎
- systemctl服务文件管理指南
systemctl命令概述 systemctl是 Linux 系统中用于管理系统服务的命令,是systemd初始化系统的一部分.它可以用于启动.停止.重启和重新加载服务,查看服务状态以及设置默认启动级 ...
- 【MySQL】字符串截取函数substring_index
业务场景描述:如何根据分隔符切割字符串?使用函数SUBSTRING_INDEX()根据指定分隔符切割,分隔符可以是字符串等. 语法:substring_index(str,delim,count) 说 ...
- AWK用法全解
一.awk介绍 awk是Linux自带的一个逐行扫描的文本处理工具,支持正则表达式.循环控制.条件判断.格式化输出.AWK自身带有一些变量,可以在书写脚本时调用. 二.基本语法格式 2.1.在shel ...
- mysql的递归写法:部门层级
前言 详细的可以参考:https://cloud.tencent.com/developer/article/2106748 这里用 WITH RECURSIVE 实现递归,需要 MySQL 8.0 ...
- Flannel相关问题记录
k8s的Pod无法分配IP 报错信息 E0222 07:22:36.762074 83 remote_runtime.go:113] RunPodSandbox from runtime servic ...