渲染综述

色彩空间

  • 线性空间:物理世界的色彩空间,如果光照强一倍,亮度也会增强一倍
  • 伽马(Gamma)空间: 显示器用于颜色矫正,通常值为2.2,对颜色进行灰度,亮度矫正
    打个比方,功率为50%的灰色,人眼实际感知亮度为:0.5的2.2开根 = 0.7297
    而人眼认为的50%中灰色,实际功率为:0.5的2.2次幂 = 0.2176
  • sRGB色彩空间:sRGB对应的是Gamma0.45所在的空间,如储存的照片相当于对图片先进行了Gamma0.45的矫正,在显示器输出时又进行了Gamma2.2矫正
    通常使用时,基础颜色等都勾选sRGB,如法线/粗糙度/金属度等贴图需要取消勾选sRGB

渲染技术术语

  • 坐标系:在渲染过程中需要将坐标点以及向量等在不同坐标空间转换(模型空间,齐次裁剪空间,屏幕空间,相机空间,世界空间,光源空间等)
  • 顶点,片段,像素:模型上的顶点,片段是光栅化的产物,像素是屏幕空间像素
  • 深度测试,蒙版测试:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    深度测试:
    ZTest 可取值为:Greater , GEqual , Less , LEqual , Equal , NotEqual , Always , Never , Off,默认是 LEqual,ZTest Off 等同于 ZTest Always。
    ZWrite 可取值为:On , Off,默认是 On。
    蒙版测试:
    Stencil
    {
    Ref 1 //Reference Value
    ReadMask 255 //读取的时候将该值 maskValue 与 referenceValue 和 stencilBufferValue 分别进行按位与(&)操作
    WriteMask 255 //写入的时候将该值与 referenceValue 和 stencilBufferValue 分别进行按位与(&)操作
    Comp Always //拿当前参考值与像素缓存值比较
    Pass Replace //两个测试都通过了 进行处理
    Fail Keep //两个测试都没通过 进行处理
    ZFail Replace //模板测试通过而深度测试没通过 进行处理
    }
  • Shader:

渲染管线

  • 裁剪(Culling):
  1. 对象级裁剪:类型裁剪,几何体裁剪,遮挡裁剪
  2. 顶点级裁剪:视口裁剪
  3. 像素级裁剪:EarlyZ
  • 渲染物件:
  1. 渲染状态
  2. 材质,纹理,各种参数
  3. 渲染顺序
  • 后处理(Post Processing):对屏幕渲染结果进行加工

光照框架

对于N个物体受M盏光的影响
框架|算法复杂度|优点|缺点
:–|:–|:–|:–
forward shading(前向渲染)|O(N*M)|实现简单,兼容性好|性能较低,光源个数限制严格
deffered shading(延迟渲染)|O(N+M)|支持多光源,性能较好|
forward+ shading(前向+渲染)|||

硬件

  • GPU:寄存器,Cache,显存 (GPU读取顺序:寄存器找不到->Cache->显存->CPU)
  • 带宽

图形API

  • DirectX,OpenGL,OpenGL ES,WebGL,Vulkan,Metal

渲染应用方式

  • 离线渲染:电影,动画,烘培LightMap
  • 实时渲染:游戏,VR

基础光照模型

  • Lambert: max(0,dot(L,N))
  • HalfLambert: max(0,dot(L,N)) * 0.5 + 0.5
  • Phong: pow(max(0,dot(reflect(-L,N), V)), Gloss)
  • BlinnPhong: pow(max(0,dot(normalize(L+V), N)), Gloss)

PBR

  • 满足以下几点的光照模型,符合PBR模型:
  1. 微表面:不同材质的平面,有很多不同朝向不一的微小平面
  2. 能量守恒:出射光的总量不超过入射光的总量
  3. 反射方程:使用基于物理的BRDF(双向反射分布函数)
  • PBR反射方程:L(o) = f(fr(p,wi,wo) * Li(p,wi) * dot(n,wi) * dwi)

  • brdf方程:

  1. fr(p,wi,wo) = k(d)*f(lambert) + K(s)*f(cook-torrance)
  2. f(lamber) = c / Π
  3. ∫(cook-torrance) = DFG / (4*dot(wo,n)*dot(wi,n))
  4. D:法线分布函数(NDF),估算微平面的整体取向,公式:a^2 / (Π * (NdotH^2) * (a^2-1) + 1)^2 (注:a表示粗糙度)
  5. F: 菲尼尔方程,用于描述表面反射光所占比例, 公式: F0 + (1 - F0) * pos(1 - cosTheta, 5) (注:F0表示不同材质的垂直方向的反射率,直接光照中cosTheta:HdotV或HdotL,间接光照中cosTheta:NdotV)
  6. G:几何函数,用于计算微表面,自阴影, 公式:NdotV / (NdotV(1.0 - k) + k) (注:k由粗糙度a计算,直接光照:k=(a+1)^2 / 8, 间接光照:k=a^2 / 2)
  7. K(d):(1-F)*(1-metallic)
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//计算直接光照
half3 CalcDirectLight(half metalness, half roughness, half3 albedo, half3 F0, half3 normal, half3 viewDir, half NoV, float3 worldPos)
{
//准备参数
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w); //获取光线方向
half3 halfDir = normalize(viewDir + lightDir); //计算半角方向
half NoL = saturate(dot(normal, lightDir)); //计算法线光线点积
half NoH = saturate(dot(normal, halfDir)); //计算法线半角点积
half HoL = saturate(dot(halfDir, lightDir)); //计算半角光线点积,同半角视线点积

//计算方程参数
half3 F = FresnelSchlick(HoL, F0); //计算菲涅尔
half G = GeometrySmith(NoV, NoL, pow(roughness + 1.0, 2) / 8.0); //计算遮挡
half D = DistributionGGX(NoH, roughness * roughness); //计算分布
half3 kD = (1.0 - F) * (1.0 - metalness); //计算漫反射系数

//计算直接光照结果
half3 directDiffuse = kD * albedo / BRDF_PI; //计算漫反射
half3 directSpecular = F * (D * G) / (4.0 * max(NoV * NoL, EPSILSON)); //计算高光
half3 directLightIn = _LightColor0.rgb * BRDF_PI; //获取直接光照颜色
return (directDiffuse + directSpecular) * NoL * directLightIn;
}
//菲涅尔函数
half3 FresnelSchlick(half NoV, half3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - NoV, 5);
}
//几何函数
half GeometrySchlickGGX(half NoV, half k)
{
return NoV / max(NoV * (1.0 - k) + k, EPSILSON);
}
//几何函数
half GeometrySmith(half NoV, half NoL, half k)
{
return GeometrySchlickGGX(NoV, k) * GeometrySchlickGGX(NoL, k);
}
//分布函数 alpha=roughness*roughness
half DistributionGGX(half NoH, half alpha)
{
half a2 = alpha * alpha;
half denom = pow2(NoH * NoH * (a2 - 1.0) + 1.0);
return a2 / max(denom * BRDF_PI, EPSILSON);
}
  • IBL间接光照
  1. CubeMap IrradianceMap 或者ShadeSH9() 计算间接光照的diffuse
  2. 预高光积分图,或者高光积分算法计算间接光照的specular
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
28
29
30
31
32
33
34
35
36
37
38
39
//计算间接光照
half3 CalcIndirectLight(half metalness, half roughness, half3 albedo, half3 F0, half3 normal, half3 viewDir, half NoV)
{
//准备参数
half3 F = FresnelSchlick(NoV, F0); //计算菲涅尔
half3 kD = (1.0 - F) * (1.0 - metalness); //计算漫反射系数

//计算间接漫反射
//half3 indirectDiffuse = texCUBE(_IrradianceCube, normal).rgb;
half3 indirectDiffuse = ShadeSH9(half4(normal, 1.0));
indirectDiffuse *= kD * albedo;

//计算间接高光
half mip = GetMipLevelFromRoughness(roughness); //计算粗糙度对应MIP
half3 reflDir = reflect(-viewDir, normal); //计算视线反射方向
//half3 indirectSpecular = texCUBElod(_RadianceCube, half4(reflDir, mip)).rgb;
half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir, mip);
half3 indirectSpecular = DecodeHDR(rgbm, unity_SpecCube0_HDR);

//高光积分
#if USE_BRDF_INTEGRATION_MAP
half2 envBRDF = tex2D(_BRDFIntegrationMap, half2(NoV, roughness)).rg;
#else
half2 envBRDF = IntegrateSpecularBRDF(NoV, roughness);
#endif
indirectSpecular *= F * envBRDF.x + envBRDF.y;

//计算间接光照结果
return (indirectDiffuse + indirectSpecular) * _IndirectIntensity;
}

half2 IntegrateSpecularBRDF(half NoV, half roughness)
{
const half4 c0 = half4(-1, -0.0275, -0.572, 0.022);
const half4 c1 = half4(1, 0.0425, 1.04, -0.04);
half4 r = roughness * c0 + c1;
half a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
return half2(-c1.z, c1.z) * a004 + r.zw;
}
  • PBR两种工作流
  1. metaillic/roughness工作流(金属/粗糙度)
  2. specular/glossness工作流(高光/光泽度)
    区别:

渲染管线

Bulid-in

内置管线,不可自定义

SRP

可编程的渲染管线

URP

通用渲染管线:Unity 2019.2.0版本后支持Universal Render Pipeline

HDRP

高清渲染管线

LWRP

URP的前身

后处理

  • LUT(ColorGrading) 颜色分级(调整暖色/饱和度这种)
  • Bloom 全屏泛光
  • GaussianBlur 高斯模糊
  • DepthOfField 景深模糊
  • RadialBlur 径向模糊

Post-ProcessStack

角色渲染

头发

  • 各项异性光照
  • Kajiya-Kay光照模型:使用切线/副切线计算高光,高光计算两层,往法线方向偏移。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 计算高光值
float StrandSpecular (float3 T, float3 V, float3 L, float p)
{
float3 H = normalize(L + V);
float dotTH = dot(T, H);
float sinTH = sqrt(1.0 - dotTH*dotTH);
float dirAtten = smoothstep(-1.0, 0.0, dotTH);
return dirAtten * pow(sinTH, p);
}

// 计算偏移值 传入StrandSpecular中的T
float3 ShiftTangent(float3 T,float3 N,float shift)
{
return normalize(T + shift * N);
}

皮肤

  • 次表面散射(SSS)

阴影渲染

  • 标准阴影算法步骤:
    (1) 以光源空间绘制:生成深度图
    (2) 以相机空间绘制:将顶点坐标转换值光源空间的顶点z值得到d值
    (3) 以顶点再光源空间的xy值为uv采样ShadowMap,获取阴影z值
    (4) d >= z 阴影内, d < z阴影外

  • 软阴影:通常使用PCF,PCSS

  • PCF:从目标点周围多个点做偏移采样,取平均阴影值

  • PCSS:

  • 阴影失真:Shadow Acne,Peter Panning

  • 级联阴影 Cascade Shadowmap:

  • ScreenSpaceShadow:

  • ShadowVolume:

GI

  • Ray Tracing
  • Path Tracing

Shader调试工具

Frame Debuger

Renderdoc

XCode

环境效果

动画与特效

NPR(Non Photorealistic Rendering) 非真实渲染