路径追踪之重要性采样
困扰路径追踪的最大问题就在于–采样.重要性采样就是把宝贵的算力资源投入在收益更高的地方–好钢用在刀刃上.本文默认读者对路径追踪有基本了解,下文会介绍各种重要性采样.
光源重要性采样(Sampling Light)
对于p点来说,如果有一片区域是直接照亮p点的,那么可以通过将积分区域分为紫色和黄色两部分.令整个区域为$\Omega_+$紫色区域为$M$黄色区域为$\Omega_-$
那么我们就可以改写渲染方程:
$L = \int_{\Omega^-} L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (n \cdot \omega_i) \mathrm{d}\omega_i +\int_M L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) (n \cdot \omega_i) \mathrm{d}\omega_i$
光源重要性采样是无偏的.
$\cos\theta$重要性采样(Cosine-Weighted Sampling)路径追踪中我们使用蒙特卡洛方法进行积分.积分效率最高(收敛最快)的情况为:p ...
Split Sum原理介绍
基于图像的光照(Image based lighting, IBL)是一类光照技术的集合.其光源不是如点光源等直接光源,而是将周围环境整体视为一个大光源.
那么在shader中我们有了一张cubeMap(也可能是别的形式),如何用它来实现PBS(Physically Based Shading)呢?
或者说,我们如何用一张环境贴图来解渲染方程呢?
首先回顾渲染方程:
$$L(p, \omega_o) = \int_{H^2} f(p, \omega_i \cdot \omega_o) L_i(p, \omega_i) V(p,wi) \cos \theta_i dw_i$$
在环境光照中,我们不考虑Visibility即不考虑阴影(任何方向光照都能到达),所以可以去除这一项:
$$L(p, \omega_o) = \int_{H^2} f(p, \omega_i \cdot \omega_o) L_i(p, \omega_i) \cos \theta_i dw_i$$
我们可以用蒙特卡洛方法来积分:
$$L(p, \omega_o) \approx \frac{1} ...
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: 如果片段的深度值小于深度缓冲中的值,则通过测 ...