Pixel World

it's better be burning out than to fade away.

Instance

实例化这项技术能够让我们通过依次drawcall来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信开销,它只需要一次drwacall即可。使用实例化渲染,只需要将glDrawArrays和glDrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced就可以了。这些渲染函数的实例化版本需要一个额外的参数,即绘制的实例数量(Instance Count),它能够设置需要渲染的实例个数。这样只需要将必须的数据发送到GPU一次,然后使用一次drawcall用告诉GPU它应该如何绘制这些实例。GPU将会直接渲染这些实例,而不用频繁与CPU进行通信。

实例化对象属性传递的两种方式

阅读全文 »

MSAA

现代GPU具备通过光栅化硬件渲染点、线和三角形图元的能力。GPU上的光栅化管线以被渲染图元的顶点作为输入,其中顶点位置由某个投影矩阵变换后产生的齐次裁剪空间坐标提供。这些位置用于确定当前渲染目标中三角形可见的像素集合。这个可见集合由两个因素决定:覆盖(coverage)和遮挡(occlusion)。覆盖通过执行某种测试来确定图元是否与给定像素重叠。在GPU中,覆盖率的计算是通过测试图元是否与位于每个像素精确中心的单个采样点重叠来实现的。
遮挡(Occlusion)用于判断被某个图元覆盖的像素是否同时被其他三角形覆盖,在GPU中通过ZBuffer来处理。ZBuffer存储了每个像素位置上相对于摄像机最近的图元深度值。当图元被光栅化时,其插值后的深度值会与深度缓冲中的数值进行比较,以确定该像素是否被遮挡。如果深度测试通过,深度缓冲中对应像素的值就会更新为新的最近深度值。关于深度测试需要注意的一点是,虽然它通常被描述为发生在像素着色之后,但几乎所有现代硬件都支持在着色之前执行某种形式的深度测试。这是一种优化手段,使得被遮挡的像素可以跳过像素着色。GPU仍然支持在像素着色之后执行深度测试,以便处理某些提前深度测试会产生错误结果的情况。其中一种情况是像素着色器手动指定深度值,因为此时图元的深度在像素着色器运行之前是未知的。
覆盖和遮挡共同决定了图元的可见性。由于可见性可以定义为X和Y的二维函数,我们可以将其视为一种信号,并使用信号处理领域的概念来描述其行为。例如,由于覆盖测试和深度测试是在渲染目标的每个像素位置上执行的,因此可见性的采样率由该渲染目标的X和Y分辨率决定。我们还应注意到,三角形和线具有不连续性,这意味着该信号不是带限的,因此在一般情况下,任何采样率都不足以完全避免混叠(aliasing)。
根本原因:像素是离散的,而几何边缘是连续的。当三角形边缘穿过像素时,像素要么完全着色,要么完全不着色,导致阶梯状锯齿(Jaggies)。几何边缘产生的锯齿状本质原因是采样率不足导致的.

阅读全文 »

MipMap

MipMap

Mipmap(多级渐远纹理)是一种预先计算并存储的纹理图像序列,其中每个后续图像的分辨率都是前一个的一半。Mipmap一一种快速, 近似 方形的过滤
为什么需要 Mipmap?
当纹理在远处被渲染时,多个纹素(texel)会映射到单个像素上。如果直接使用原始高分辨率纹理:

  • 像素只采样纹理中的一个点
  • 忽略了该像素覆盖的纹理区域
  • 导致摩尔纹(Moiré patterns)和闪烁
特性 说明
存储开销 比原始纹理多约 33% 的内存(1 + 1/4 + 1/16 + … ≈ 1.33)
采样方式 根据像素在纹理空间的投影大小,选择合适的 Mipmap 层级
插值 可在相邻层级间进行三线性插值(Trilinear Filtering)
阅读全文 »

Face Culling

Face Culling

试着在脑海中想象一个 3D 立方体,数一数从任何方向你最多能看到多少个面。如果你的想象力不是太过丰富,你可能得出的最大数字是 3。你可以从任何位置和/或方向观察立方体,但你永远看不到超过 3 个面。那么我们为什么要浪费精力去绘制那些我们根本看不到的另外 3 个面呢?如果我们能以某种方式丢弃它们,我们就能节省这个立方体总片元着色器运行次数的 50% 以上!
我们说超过 50% 而不是 50%,是因为从某些角度可能只能看到 2 个甚至 1 个面。在这种情况下我们会节省更多。
这确实是一个很棒的想法,但有一个问题需要解决:我们如何知道物体的某个面从观察者的视角是否不可见? 如果我们想象任何封闭形状,它的每个面都有两侧。每一侧要么面向用户,要么背对用户。如果我们只渲染面向观察者的面会怎样?
这正是面剔除所做的。OpenGL 检查所有朝向观察者的正面,并渲染这些面,同时丢弃所有背向观察者的背面,为我们节省大量的片元着色器调用。我们确实需要告诉 OpenGL 我们使用的哪些面实际上是正面,哪些面是背面。OpenGL 通过分析顶点数据的环绕顺序(winding order)来使用一个巧妙的技巧解决这个问题。

阅读全文 »

PCSS 基于两个经典技术: 1. Shadow Mapping(阴影贴图) [Williams 1978]:从光源视角生成深度图 2. Percentage-Closer Filtering(PCF,百分比渐近过滤) [Reeves et al. 1987]:在阴影边缘进行多重采样模糊 关键洞察:PCF 核(kernel)越大,阴影越软。PCSS 的核心就是动态调整 PCF 核的大小来模拟物理正确的软阴影。

阅读全文 »

Shadow Mapping

Basis

01.png

  • 第一个pass从光源位置和方向构造相机的viewProjectionMatrix,渲染深度图
  • 第二个pass将采样点转换到光照空间,比较深度判定遮挡性, 以下为shadowMap第二个pass计算阴影的关键代码:

VertexShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#version 460 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoords;

out vsOut
{
vec3 FragPos;
vec3 Normal;
vec3 TexCoords;
vec3 FragPosLightSpace;
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;

void main()
{
vs_out.FragPos = model * vec4(aPos, 1.0);
vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
vs_out.TexCoords = aTexCoords;
vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
gl_Position = projection * view * vec4(vs_out.FragPos, 1.0);
}

FragmentShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#version 460 core

in vsOut
{
vec3 FragPos;
vec3 Normal;
vec3 TexCoords;
vec3 FragPosLightSpace;
} fs_in;

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;
}
阅读全文 »

TBN矩阵和TBN空间

法线贴图采样得到的法线往往在切线空间,也就是顶点的“局部空间”,需要乘以TBN矩阵将法线从切线空间转换到世界空间下才能进行光照计算,而顶点中一般仅提供TBN中的T和N,Bitangent需要经过施密特正交化计算得出:

1
2
3
4
5
6
7
8
void main()
{
vec3 T = normalize(vec3(model * vec4(aTangent, 0.0)));
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0)));
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 TBN = mat3(T, B, N);
}
阅读全文 »

辐射度量学

上一章文章我们讨论了颜色, 一个重要结论就是光的颜色和其波长有关, 也就是电磁场的震动频率. 对于人眼的视觉系统来说, 只有很窄范围的波长是可见的, 被称为可见频谱.

01.png

但是渲染不仅仅只是关于颜色, 如果我们只知道一张图片上每个点的波长, 那么呈现的图片将是不完整的, 我们还需要知道多少光命中了该点, 或者说场景中有多少光量进入了摄像机. 从某种意义上说,这两个组件一起构成了我们最终的图像. 每个像素点的颜色是什么,以及强度是多少.

02.png

这就引出了一个问题, 我们如何量化光的测量?

$Radiometry$

$Radiometry$是关于电磁辐射(光)的测量单位和测量体系.其中涉及到了大量的术语,如果从微观角度来看的话, 需要深入掌握大量概念, 但是对于生成图像来说, 我们只需要一个宏观模型, 一个经过调整或建模的适合人类感知的模型.

03.png

阅读全文 »

Computer-Graphic08-空间数据结构

本篇文章我们讨论当场景复杂度大幅增加时,如何进行高效的几何查询。现实世界中的真实场景极其复杂,那么从计算的角度我们如何处理这些复杂的场景呢?其中很重要的一个用例就是光线追踪:用于光线和场景求交。

0.png

光线三角形求交

声明一个三角形,三个顶点分别是$p_0,p_1,p_2$, 定义一条光线,从$o$出发,沿着$d$方向行进,光线的参数化方程可以定义如下:

$\mathbf{r}(t) = \mathbf{o} + t\mathbf{d}$

这里需要解决两个问题,第一个光线是否与三角形相交,第二个如果相交那么交点在哪里?

1.png

首先将光线的参数化方程代入平面的隐式方程可得:

阅读全文 »

颜色

颜色是什么

光线就是震荡的电磁场, 颜色就是频率

01.png

电磁场震荡的频率决定了光线的颜色.

那么光线的波长和频率之间有什么关系呢?

波长和频率其实本质上表征的是一件事情, 但是使用了不同的方式来表达而已.

$wavelength = 1 / frequency$

$frequency = 1 / wavelength$

阅读全文 »
0%