Code Repo: github.com/xuchi16/vis...
Project Path: github.com/xuchi16/vis...
本文主要包含以下内容:
- PBR 的基本概念
- Metallic, roughness 和 opacity 的改变对材质的影响
目标及设计
PBR(Physically based renderring)通过模拟真实世界中光线和材质的相互作用进行物体渲染。它有两种工作流用于定义材料表现:
- Metallic Roughness Workflow
- Specular-Glossiness Workflow
两者贴图参数有所区别,但效果相同。从下图可以发现,它们均包含了 6 种贴图,其中有 3 个是相同的(Ambient Occlusion, Normal 和 Height),而另外 3 个有所区别:
-
Metallic Roughness Workflow 使用 baseColor、metallic 和 roughness 贴图来表示材质的颜色、金属度和粗糙度
-
Specular-Glossiness Workflow 则使用 diffuse、glossiness 和 specular 贴图来表示材质的颜色、光泽度和镜面反射率
Apple 推荐了三种 Renderer:RealityKit,SceneKit 和 Storm,均支持 Metallic Roughness Workflow,后续也只详述该工作流。
Metallic Roughness Workflow
Metallic Roughness Workflow 工作流的着色器以金属、粗糙度和基础颜色值作为其核心输入。包含如下 6 种贴图
- Base Color:提供颜色信息
- Roughness:提供材料的粗糙程度,浅色表示粗糙,深色表示光滑,可以用来表示镜面上的指纹、桌上的杯子水渍等
- Metallic:提供对应位置材质是否是金属,如果是金属则为白色,如果不是金属则为黑色
- Ambient Occlusion:用于表示环境光遮蔽
- Normal:法线贴图,引擎会根据法线决定光的反射方向
- Height:表示材质表面的深浅
Metallic 值:0-1 之间,越接近 1,金属度越高
Roughness 值:0-1 之间,越接近 1,粗糙度越高
PBR 的 3 个主要理论
- 微平面理论:物体表面从微观上看总是粗糙的
- 能量守恒:出射光线能量不能超过入射光线能量
-
菲涅尔反射:入射角越大,反射率越高
- 入射角在 0°到 45°时反射率较低
- 45°到 75°时反射率逐渐升高
- 75°到 90°之间时反射率快速趋近于 100%
PBR 光照
Lighting = Ambient + Diffuse + Specular + Emissive
最终颜色 = 环境光 + 漫反射 + 高光反射 + 自发光
双向反射分布函数 BRDF: Bidirectional Reflectance Distribution Function
第一项表示的是物体自发光,后续积分项表示了其他光线的反射情况,其中 BRDF 决定了不同光线的反射方式。不同的光照采用不同的 BRDF 模型,如:
-
Lambertian BRDF:常用于计算漫反射 Diffuse
-
Cook-Torrance BRDF: 常用于计算高光反射 Specular
另外在 PBR 中,环境光通常用 IBL(Image Based Lighting)来计算,RealityKit 也支持 IBL 光照。IBL 的核心概念是生成一张环境贴图,其中包含了场景中的光照信息,在渲染时候引擎根据相机位置从贴图中获取到每个像素的光照信息,再结合物体材质属性,计算出场景中各个像素的光照。
示例基本实现
选定基准位置,在 3 个方向上分别对 roughness,metallic 和 opacity 进行改变即可。
- x 轴方向:roughness 改变
- y 轴方向:metallic 改变
- z 轴方向:opacity 改变
swift
private let radius: Float = 0.08
private let interval: Float = 0.2
private let initPosition = SIMD3<Float>(x: 0, y: 0.4, z: -3)
private let matrixSize = 5
for roughness in 0...matrixSize {
for metallic in 0...matrixSize {
for opacity in 0...matrixSize {
let r = Float(roughness)
let m = Float(metallic)
let o = Float(opacity)
let xOffset = interval * r
let yOffset = interval * m
let zOffset = interval * o
let position = SIMD3<Float>(x: initPosition.x + xOffset,
y: initPosition.y + yOffset, z: initPosition.z + zOffset)
var material = PhysicallyBasedMaterial()
material.baseColor = PhysicallyBasedMaterial.BaseColor(tint:.orange)
material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 0.2 * r)
material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.2 * m)
material.blending = .transparent(opacity: .init(floatLiteral: 0.2 * o))
let sphere = ModelEntity(
mesh: .generateSphere(radius: radius),
materials: [material],
collisionShape: .generateSphere(radius: radius),
mass: 0.0
)
sphere.components.set(InputTargetComponent(allowedInputTypes: .indirect))
sphere.position = position
outerEntity.addChild(sphere)
}
}
}
最终效果
参考
developer.apple.com/documentati...
developer.apple.com/documentati...
developer.apple.com/documentati...
help.sketchfab.com/hc/en-us/ar...