Distance Field soft shadows
在介绍Distance Field soft shadows之前,我们首先得了解距离场.
距离场:对于每一个点,距离场的值表示该点最近的物体的距离.
距离场一大好处在于混合时的特性.
这样就可以非常方便地去做物体的Blending.
SDF被广泛使用在实时渲染的如RayMarching等的各种技术中.
如果我们能用SDF描述场景,比如这样:
可以发现一个关键点:
我们可以用SDF转换为”安全角度”–即有多少角度不会被遮挡,进而求Visibility.
即: SDF -> 安全角度 -> Visibility
而这正是Distance Field soft shadows的核心思想.
计算安全角度对于我们的Shading point ,我们向光源做RayMarching.
然后在RayMarching过程中,每一步都可以计算出一个安全角度,取最小的角度.
计算角度很显然可以使用arcsin(SDF(p)/(p-o))然后进一步计算visibility.但是实时渲染比起准确,更追求效率.因此实际上使用的公式是min(k*SDF(p)/(p-0),1.0). ...
OpenGL学习笔记-十四-帧缓冲
创建帧缓冲unsigned int fbo;glGenFramebuffers(1, &fbo); // 第一个参数为生成的帧缓冲个数,第二个参数为帧缓冲ID的指针
绑定帧缓冲glBindFramebuffer(GL_FRAMEBUFFER, fbo);
此时绑定的fbo能读能写,长相十分英俊
我们也可以单独设置读取的fbo和写入的fbo
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
帧缓冲需要满足的条件通过执行上面的步骤,我们可以创建一个fbo,但是还不能使用它,因为一个完整的帧缓冲需要满足以下条件:
附加至少一个缓冲(颜色,深度,模板)
至少有一个Attachment(颜色附件)
所有附件都必须是完整的(有内存)
每个缓冲应该都有相同的样本数
我们可以使用glCheckFramebufferStatus函数来检查帧缓冲的状态,如果状态为GL_FRAMEBUFFER_COMPLETE,则说明帧缓冲满足条件,可以用了.
GLenum sta ...
OpenGL学习笔记-十三-面剔除
我们人类是低级的三维生物,无法看到物体背后的东西,但我们却老老实实地渲染了背后的三角形然后在深度测试中丢弃掉.这难道不是一种算力浪费吗?
这就是剔除技术的用武之处了,我们可以通过剔除掉那些看不见的三角形来节省渲染时间和算力.理论上可以节省50%以上的时间.
开启面剔除在OpenGL中,使用以下代码开启面剔除:
glEnable(GL_CULL_FACE);
我们可以设置剔除的面(可能有的外星人只能看到背面而看不到正面)
glCullFace(GL_FRONT); //剔除正面glCullFace(GL_BACK); //剔除背面glCullFace(GL_FRONT_AND_BACK); //剔除正背面
OpenGL学习笔记-十二-混合
在之前的文章中提到,fragshader最终输出一个vec4的颜色值.然而,对于一个颜色我们只需要rgb三个分量就够了,最后一个分量是干嘛的呢?
最后一个通道是alpha通道,表示颜色的透明程度.对于一些图片来说,不如说草,我们不要其中的一些像素,这是透明通道就派上用场了.
上图草的背景我们是不需要的,设置透明度为0,需要的部分则为1.
但是我们并没有告诉OpenGL如何处理透明通道.所以在片段着色器中我们写下如下代码:
vec4 texColor = texture(texture1, TexCoords);if(texColor.a < 0.1)    discard;FragColor = texColor;
使用discard命令丢弃透明度小于0.1的像素,这样就能达到透明效果.
但是对于一些别的半透明物品,比如玻璃,我们不能直接丢弃像素,而是需要按照某种规则进行和混合.
为了达到效果,我们首先要开启混合
glEnable(GL_BLEND);
启用混合后,我们需要告诉OpenGL如何混合颜色.OpenGL使用这个方程混合颜色:
其中:
Cs:源颜色
Cd:目标 ...
从PCF到MSM
在实时渲染中, 有一个常常被使用的约等式:
问题在于,什么时候这个等式比较正确呢?
积分域比较小时
g(x)在积分域中变化不大时
在实时渲染中,对Rendering Equation有另一种解释:
多了一项V(p,wi),表示着色点到光源的Visibility.
那么我们就可以更改Rendering Equation为:
在RTR中,由于光源大部分是点光源或方向光,使得积分域小.而brdf是diffuse的时候变化也不大.这时近似就比较准确.总结来说:
点光源,方向光
diffuse brdf/constant radiance area light
PCF(Percentage Closer Filtering)PCF是用于解决shadowmap的锯齿问题的一种方法.
PCF的步骤如下:
对于每个着色像素
获取周围n*n个像素的深度值
用每个深度值与shadowmap的深度值进行比较得到一堆1和0
对所有1,0取平均值得到Visibility
效果如下:
注意:
不是对ShadowMap进行模糊
问题在于: 这开销肉眼的变大了不少,肯定会很慢.
人们发现,P ...
OpenGL学习笔记-十一-模板测试
先看这个效果:
发现奇怪之处了吗?
明明是3D的场景,但却没有厚度.这使用到了模板测试
模板测试模板测试原理如下大致如下:
开启模板测试使用以下代码开启模板测试:
glEnable(GL_STENCIL_TEST);
注意别忘了清除模板缓冲
glClear(GL_STENCIL_BUFFER_BIT);
模板函数和深度测试一样,模板测试也需要设置模板函数.使用以下代码设置模板函数:
glStencilFunc(GL_ALWAYS, 1, 0xFF);
其中参数意义如下:
func: 选择模板测试函数,这里选择GL_ALWAYS,表示测试时总是通过.
ref: 参考值,这里设置为1.
mask: 掩码值,这里设置为0xFF,表示所有位都可以修改,即启用模板缓冲写入.设置为0x00则禁止写入.
在模板测试后会进行深度测试.我们可以设置不同情况下OpenGl如何更新模板缓冲.使用以下代码设置模板缓冲更新规则:
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
其中三个参数如下:
sfail: 发生模板测试失败,深度测试成功时,模板缓冲的操作.这里 ...
OpenGL学习笔记-十-深度测试
深度测试深度测试用于解决像素的遮挡问题,即:哪个像素应该被显示在屏vanishing.cc20040422幕上.
深度测试原理如下:
用一张纹理来存储信息
将每个片段与深度缓冲中的值信息进行深度测试,如果测试通过,则更新深度缓冲中的值信息,否则丢弃该片段.
启用深度测试在OpenGL中,使用以下代码来启用深度测试:
glEnable(GL_DEPTH_TEST);
为了防止渲染帧使用上一帧的深度缓冲,你应该在每个渲染迭代之前清除深度缓冲:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
禁用写入深度缓冲有时你可能需要禁止写入深度缓冲,例如,当你只需要渲染一个透明物体时,你可能不希望它影响深度测试.
在OpenGL中,使用以下代码来禁止写入深度缓冲:
glDepthMask(GL_FALSE);
深度测试函数深度测试函数用于控制OpenGL如何判断是否通过深度测试.
有以下几种深度测试函数:
GL_ALWAYS: 总是通过测试
GL_NEVER: 总是丢弃片段
GL_LESS: 如果片段的深度值小于深度缓冲中的值,则通过测 ...
Early-Z and Pre-Z -- 介绍与区别
Depth Testing 深度测试在介绍Early-Z和Pre-Z之前,我们首先得了解深度测试.
深度测试是用于解决物体可见性问题(是否被遮挡)的技术.在渲染管线中位于片元着色器之后
但是深度测试会带来一个问题:
即有的片元在经历了着色后,在深度测试环节被直接抛弃了,这显然是对算力的浪费–我们完全可以不用计算这些片元.我们把这一问题称为OverDraw问题.
Early-Z就是这个问题的一种解决方案.
Early-ZEarly-Z是在光栅化之后,片元着色器之前增加的一步操作
在这一流程中,会进行一次深度测试,剔除掉所有不通过的片元,这样就减少了算力 的浪费.
读者可能会想,如果提前进行了深度测试,那最后不就不需要再进行深度测试了吗?
事实上,由于渲染管线的复杂性,我们需要再最后再次进行深度测试(用于区分可以叫做z-check)以支持透明物体,抗锯齿等等操作.
Pre-ZEarly-Z也存在一些问题:
物体由远及近渲染时,Early-Z效率较低.为提高效率需要CPU排序
不支持透明物体
Pre-Z可以解决这些问题.方法是,使用两个pass渲染:第一个pass不输出任何颜色,只写入深 ...
启发式算法
Intro首先考虑一个经典问题:旅行商问题(Traveling Salesman Problem, TSP).
或许你的第一想法式dijastra算法.但问题在于dijastra解决的是点到点的最短路径,而旅行商要转一圈回到原点,这是dijastra算法无法解决的问题.
或许你还会想到贪心算法.事实上,贪心算法正是启发式算法的一个子类.但是在这个问题中,什么贪心策略都无法保证全局最优解.
为什么我能这么肯定呢?因为旅行商问题是一个$NP难(NP-hard)$问题,要想得到最优解,就必须把所有可能枚举出来然后再做比较.
如果你能做到不枚举所有可能就得到最优解,那么今年的菲尔兹奖,图灵奖等等等等你肯定是要拿到手软了.
我们继续分析旅行商问题.如果有$n$个城市,则路径有$(n-1)!$种可能,显然当n变大时,计算量就要爆炸了.
当我们无法接受计算量带来的成本时,我们就需要找到一种虽然成本较低,但最终结果仍然不错的算法,而这就是$启发式算法$
启发式算法启发式算法是基于直观或经验构造的算法,在可接受的计算时间和空间条件下,给出待解决优化问题的一个可行解.
启发式算法并不保证找到最优解, ...
STL的神奇用法
元素去重set示例:
vector<int> nums = {1, 2, 3, 2, 1, 4, 5, 4, 6};set<int> s(nums.begin(), nums.end());nums.assign(s.begin(), s.end());
原理:
set(集合)–每个元素只出现一次,且元素有序(默认升序).
将vector中的元素赋值给set,得到一个元素唯一的集合.
将set中的元素赋值给vector,得到一个元素唯一且有序的vector.
消耗:
时间复杂度: O(n log n)
空间复杂度: O(n)
二分查找lower_bound()示例:
vector<int> nums = {1, 3, 5, 7, 9};int pos = lower_bound(nums.begin(), nums.end(), 5) - nums.begin();cout << "The position of 5 is " << pos << ...
