Last active
December 13, 2022 20:39
-
-
Save HK-SHAO/2dbb8c717fb3fb137c5cd97a456d659d to your computer and use it in GitHub Desktop.
Godot Ray Tracing PBR Shader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type canvas_item; | |
// 传入统一值 | |
uniform vec3 camera_position = vec3(0.0, 0.0, 4.0); // 传入摄像机的位置 | |
uniform mat3 camera_rotation = mat3(1); // 摄像机的旋转 | |
uniform float camera_aspect = 2.0; // 画布长宽比 | |
uniform float camera_vfov = 30.0; // 摄像机的纵向视野 | |
uniform float camera_focus = 2.0; // 摄像机的对焦距离 | |
uniform float camera_aperture = 0.005; // 摄像机的光圈大小 | |
uniform float camera_exposure = 1.0; // 摄像机曝光值 | |
uniform float camera_gamma = 2.2; // gamma 矫正值 | |
uniform float light_quality = 0.2; // 间接光质量 | |
uniform float frame = 1.0; // 降噪帧 | |
// 配置常量 | |
const float TMIN = 0.001; // 光开始传播的起始偏移,避免光线自相交 | |
const float TMAX = 2000.0; // 最大单次光线传播距离 (相当于可见范围) | |
const float PRECISION = 0.0001; // 必须要小于 TMIN,否则光线会自相交产生阴影痤疮 | |
const float VISIBILITY = 0.001; // 亮度可见度 | |
const uint MAX_RAYMARCH = 512U; // 最大光线步进次数 | |
const uint MAX_RAYTRACE = 512U; // 最大光线追踪次数 | |
const float ENV_IOR = 1.000277; // 环境的折射率 | |
// 枚举形状 | |
const int SHAPE_SPHERE = 0; | |
const int SHAPE_BOX = 1; | |
const int SHAPE_CYLINDER = 2; | |
const float NONE = 0.0; | |
// 光线 | |
struct ray { | |
vec3 origin; // 光的起点 | |
vec3 direction; // 光的方向 | |
vec3 color; // 光的颜色 | |
}; | |
// 物体材质 | |
struct material { | |
vec3 albedo; // 反照率 | |
vec3 emission; // 自发光 | |
vec3 normal; // 切线空间法线 | |
float roughness; // 粗糙度 | |
float metallic; // 金属度 | |
float transmission; // 透明度 | |
float ior; // 折射率 | |
}; | |
// 物体变换 | |
struct transform { | |
vec3 position; // 位置 | |
vec3 rotation; // 旋转 | |
vec3 scale; // 缩放 | |
}; | |
// SDF 物体 | |
struct object { | |
int shape; // 形状 | |
float dis; // 距离物体表面 | |
transform trs; // 变换 | |
material mtl; // 材质 | |
}; | |
// 光子击中的记录 | |
struct record { | |
object obj; // 物体 | |
vec3 pos; // 击中的位置 | |
bool hit; // 是否击中 | |
}; | |
// 摄像机 | |
struct camera { | |
vec3 lookfrom; // 视点位置 | |
vec3 lookat; // 目标位置 | |
vec3 vup; // 向上的方向 | |
float vfov; // 视野 | |
float aspect; // 传感器长宽比 | |
float aperture; // 光圈大小 | |
float focus; // 对焦距离 | |
}; | |
// 对三维向量进行哈希 | |
float rand13(vec3 seed) { | |
uvec3 p = floatBitsToUint(seed); | |
p = 1103515245U * ((p.xyz >> 1U) ^ (p.yzx)); | |
uint h32 = 1103515245U * ((p.x ^ p.z) ^ (p.y >> 3U)); | |
uint n = h32 ^ (h32 >> 16U); | |
return float(n) * (1.0 / float(2147483647U*2U)); | |
} | |
// 生成归一化随机数 | |
float rand11(float seed) { | |
uvec2 n = floatBitsToUint(seed) * uvec2(1597334673U, 3812015801U); | |
uint q = (n.x ^ n.y) * 1597334673U; | |
return float(q) * (1.0 / float(2147483647U*2U)); | |
} | |
vec2 rand21(float seed) { | |
uvec2 n = floatBitsToUint(seed) * uvec2(1597334673U, 3812015801U); | |
n = (n.x ^ n.y) * uvec2(1597334673U, 3812015801U); | |
return vec2(n) * (1.0 / float(2147483647U*2U)); | |
} | |
// 光子在射线所在的位置 | |
vec3 at(ray r, float t) { | |
return r.origin + t * r.direction; | |
} | |
// 单位圆内随机取一点 | |
vec2 random_in_unit_disk(inout float seed) { | |
vec2 r = rand21(seed++) * vec2(1.0, TAU); | |
return sqrt(r[0]) * vec2(sin(r[1]), cos(r[1])); | |
} | |
// 从摄像机获取光线 | |
ray get_ray(camera c, vec2 uv, vec3 color, inout float seed) { | |
// 根据 VFOV 和显示画布长宽比计算传感器长宽 | |
float theta = radians(c.vfov); | |
float half_height = tan(theta * 0.5); | |
float half_width = c.aspect * half_height; | |
// 以目标位置到摄像机位置为 Z 轴正方向 | |
vec3 z = normalize(c.lookfrom - c.lookat); | |
// 计算出摄像机传感器的 XY 轴正方向 | |
vec3 x = normalize(cross(c.vup, z)); | |
vec3 y = cross(z, x); | |
vec3 hwfx = half_width * c.focus * x; | |
vec3 hhfy = half_height * c.focus * y; | |
vec3 lower_left_corner = c.lookfrom - hwfx - hhfy - c.focus * z; | |
vec3 horizontal = 2.0 * hwfx; | |
vec3 vertical = 2.0 * hhfy; | |
// 模拟光进入镜头光圈 | |
float lens_radius = c.aperture * 0.5; | |
vec2 rud = lens_radius * random_in_unit_disk(seed); | |
vec3 offset = x * rud.x + y * rud.y; | |
// 计算光线起点和方向 | |
vec3 ro = c.lookfrom + offset; | |
vec3 po = lower_left_corner + uv.x * horizontal | |
+ uv.y * vertical; | |
vec3 rd = normalize(po - ro); | |
return ray(ro, rd, color); | |
} | |
// SDF 球体 | |
float sd_sphere(vec3 p, float s) { | |
return length(p) - s; | |
} | |
// SDF 盒子 | |
float sd_box(vec3 p, vec3 b) { | |
vec3 q = abs(p) - b; | |
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); | |
} | |
// SDF 圆柱 | |
float sd_cylinder(vec3 p, vec2 rh) { | |
vec2 d = abs(vec2(length(p.xz),p.y)) - rh; | |
return min(max(d.x,d.y), 0.0) + length(max(d, 0.0)); | |
} | |
// 欧拉角转旋转矩阵 | |
mat3 angle(vec3 a) { | |
vec3 s = sin(a), c = cos(a); | |
return mat3(vec3( c.z, s.z, 0), | |
vec3(-s.z, c.z, 0), | |
vec3( 0, 0, 1)) * | |
mat3(vec3( c.y, 0, -s.y), | |
vec3( 0, 1, 0), | |
vec3( s.y, 0, c.y)) * | |
mat3(vec3( 1, 0, 0), | |
vec3( 0, c.x, s.x), | |
vec3( 0, -s.x, c.x)); | |
} | |
// 计算有向距离 (物体内部距离为负) | |
float signed_distance(object obj, vec3 pos) { | |
vec3 position = obj.trs.position; | |
vec3 rotation = obj.trs.rotation; | |
vec3 scale = obj.trs.scale; | |
vec3 p = pos - position; | |
// 会重复的将欧拉角转换成旋转矩阵,实际上只用在第一次计算就行了 | |
// 也有可能被编译器优化掉了 | |
p *= angle(radians(rotation)); | |
switch(obj.shape) { | |
case SHAPE_SPHERE: | |
return sd_sphere(p, scale.x); | |
case SHAPE_BOX: | |
return sd_box(p, scale); | |
case SHAPE_CYLINDER: | |
return sd_cylinder(p, scale.xy); | |
default: | |
return sd_sphere(p, scale.x); | |
} | |
} | |
// 地图 | |
const object[] map = { | |
object(SHAPE_SPHERE, NONE, | |
transform( vec3(0, -100.5, 0), | |
vec3(0, 0, 0), | |
vec3(100, 0, 0) | |
), | |
material( vec3(1.0, 1.0, 1.0)*0.2, // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.2, // 粗糙度 | |
0.5, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
), | |
object(SHAPE_SPHERE, NONE, | |
transform( vec3(0, 0, 0), | |
vec3(0, 0, 0), | |
vec3(0.5, 0, 0) | |
), | |
material( vec3(1.0, 1.0, 1.0), // 基础色 | |
vec3(0.1, 1.0, 0.1)*10.0, // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
1.0, // 粗糙度 | |
0.0, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
), | |
object(SHAPE_CYLINDER, NONE, | |
transform( vec3(-1.0, -0.3, 0), | |
vec3(0, 0, 0), | |
vec3(0.3, 0.33, 0) | |
), | |
material( vec3(1.0, 0.1, 0.1), // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.9, // 粗糙度 | |
0.1, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
), | |
object(SHAPE_SPHERE, NONE, | |
transform( vec3(1.0, -0.2, 0), | |
vec3(0, 0, 0), | |
vec3(0.3, 0, 0) | |
), | |
material( vec3(0.1, 0.1, 1.0), // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.2, // 粗糙度 | |
1.0, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
), | |
object(SHAPE_SPHERE, NONE, | |
transform( vec3(0.0, -0.24, 3), | |
vec3(0, 0, 0), | |
vec3(0.3, 0, 0) | |
), | |
material( vec3(1.0, 1.0, 1.0)*0.9, // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.0, // 粗糙度 | |
0.0, // 金属度 | |
1.0, // 透明度 | |
1.5 // 折射率 | |
) | |
), | |
object(SHAPE_BOX, NONE, | |
transform( vec3(0, 0, 5), | |
vec3(0, 0, 0), | |
vec3(2, 1, 0.2) | |
), | |
material( vec3(1.0, 1.0, 1.0), // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.0, // 粗糙度 | |
1.0, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
), | |
object(SHAPE_BOX, NONE, | |
transform( vec3(0, 0, -1), | |
vec3(0, 0, 0), | |
vec3(2, 1, 0.2) | |
), | |
material(vec3(1.0, 1.0, 1.0), // 基础色 | |
vec3(1), // 自发光 | |
vec3(0, 0, 1), // 切线空间法线 | |
0.0, // 粗糙度 | |
1.0, // 金属度 | |
0.0, // 透明度 | |
1.0 // 折射率 | |
) | |
) | |
}; | |
// 找到最近的物体并计算距离 | |
object nearest_object(vec3 p) { | |
object o; o.dis = TMAX; | |
for (int i = 0; i < map.length(); i++) { | |
object oi = map[i]; | |
oi.dis = abs(signed_distance(oi, p)); | |
if (oi.dis < o.dis) o = oi; | |
} | |
return o; | |
} | |
// 计算物体法线 | |
vec3 calc_normal(object obj, vec3 p) { | |
vec2 e = vec2(1, -1) * 0.5773 * 0.0005; | |
return normalize( e.xyy*signed_distance(obj, p + e.xyy) + | |
e.yyx*signed_distance(obj, p + e.yyx) + | |
e.yxy*signed_distance(obj, p + e.yxy) + | |
e.xxx*signed_distance(obj, p + e.xxx) ); | |
} | |
// 用世界坐标下的法线计算 TBN 矩阵 | |
mat3 TBN(vec3 N) { | |
vec3 T, B; | |
if (N.z < -0.99999) { | |
T = vec3(0, -1, 0); | |
B = vec3(-1, 0, 0); | |
} else { | |
float a = 1.0 / (1.0 + N.z); | |
float b = -N.x*N.y*a; | |
T = vec3(1.0 - N.x*N.x*a, b, -N.x); | |
B = vec3(b, 1.0 - N.y*N.y*a, -N.y); | |
} | |
return mat3(T, B, N); | |
} | |
// 光线步进 | |
record raycast(ray r) { | |
record rec; rec.hit = false; float t = TMIN; | |
for(uint i = 0U; i < MAX_RAYMARCH && t < TMAX && !rec.hit; i++) { | |
rec.pos = at(r, t); | |
rec.obj = nearest_object(rec.pos); | |
rec.hit = rec.obj.dis < PRECISION; | |
t += rec.obj.dis; | |
} | |
return rec; | |
} | |
// 采样天空 | |
vec4 sky(ray r) { | |
float t = 0.5 + 0.5 * r.direction.y; | |
vec4 bottom = vec4(1.0, 1.0, 1.0, 1.0); | |
vec4 top = vec4(0.3, 0.5, 1.0, 1.0); | |
return mix(bottom, top, t); | |
} | |
// 快速计算五次方 | |
float pow5(float x) { | |
float t = x*x; t *= t; | |
return t*x; | |
} | |
// 用粗糙度计算菲涅尔近似值 | |
float fresnel_schlick_roughness(float cosine, float F0, float roughness) { | |
return F0 + (max(1.0 - roughness, F0) - F0) * pow5(abs(1.0 - cosine)); | |
} | |
// 以 n 为法线进行半球采样 | |
vec3 hemispheric_sampling(vec3 n, inout float seed) { | |
vec2 r = rand21(seed++) * vec2(1.0, TAU); | |
float rz = sqrt(r[0]); | |
vec2 v = vec2(cos(r[1]), sin(r[1])); | |
vec2 rxy = sqrt(1.0 - r[0]) * v; | |
return TBN(n) * vec3(rxy, rz); | |
} | |
// 用粗糙度采样沿向量 n 半球采样 | |
vec3 hemispheric_sampling_roughness(vec3 n, float roughness, inout float seed) { | |
vec2 r = rand21(seed++) * vec2(1.0, TAU); | |
float shiny = pow5(roughness); // 光感越大高光越锐利 | |
float rz = sqrt((1.0 - r[0]) / (1.0 + (shiny - 1.0) * r[0])); | |
vec2 v = vec2(cos(r[1]), sin(r[1])); | |
vec2 rxy = sqrt(abs(1.0 - rz*rz)) * v; | |
return TBN(n) * vec3(rxy, rz); | |
} | |
// 应用 PBR 材质 | |
ray PBR(ray r, record rec, inout float seed) { | |
// 材质参数 | |
vec3 albedo = rec.obj.mtl.albedo; | |
float roughness = rec.obj.mtl.roughness; | |
float metallic = rec.obj.mtl.metallic; | |
float transmission = rec.obj.mtl.transmission; | |
vec3 normal = rec.obj.mtl.normal; | |
float ior = rec.obj.mtl.ior; | |
// 光线和物体表面参数 | |
vec3 I = r.direction; | |
vec3 V = -r.direction; | |
vec3 P = rec.pos; | |
vec3 N = TBN(normal) * calc_normal(rec.obj, P); | |
vec3 C = r.color; | |
vec3 L; | |
normal = N; // 备份一下原始法线 (永远朝向物体外面) | |
float NoV = dot(N, V); | |
float outer = sign(NoV); // 如果处于 SDF 物体内部就反过来 | |
NoV *= outer; | |
N *= outer; | |
float eta = outer > 0.0 ? ENV_IOR / ior : ior / ENV_IOR; // 计算折射率之比 | |
float F0 = (eta - 1.0) / (eta + 1.0); F0 *= 2.0*F0; // 让透明材质的反射更明显一些 | |
float F = fresnel_schlick_roughness(NoV, F0, roughness); // 菲尼尔 | |
vec2 rand2 = rand21(seed++); | |
if (rand2[0] < transmission) { | |
N = hemispheric_sampling_roughness(N, roughness, seed); | |
float k = 1.0 - eta * eta * (1.0 - NoV * NoV); // 小于 0 为全反射 | |
if (rand2[1] < F + metallic || k < 0.0) { | |
L = I + 2.0 * NoV * N; // 菲涅尔反射或全反射 | |
} else { | |
L = eta * I - (sqrt(k)- eta * NoV) * N; // 斯涅尔折射 | |
} | |
} else { | |
// 反射或者漫反射 | |
if (rand2[1] < F + metallic) { | |
N = hemispheric_sampling_roughness(N, roughness, seed); | |
L = reflect(I, N); // 镜面反射 | |
} else { | |
L = hemispheric_sampling(N, seed); // 漫反射 | |
} | |
// 如果光穿入表面就直接吸收掉 | |
C *= (sign(dot(L, normal)) + 1.0) * 0.5; | |
} | |
C *= albedo; | |
// 更新光的方向和颜色 | |
r.color = C; | |
r.origin = P; | |
r.direction = L; | |
return r; | |
} | |
// RGB 亮度 | |
float brightness(vec3 rgb) { | |
return dot(rgb, vec3(0.299, 0.587, 0.114)); | |
} | |
// 光线追踪 | |
ray raytrace(ray r, inout float seed) { | |
for (uint i = 0U; i < MAX_RAYTRACE; i++) { | |
// 俄罗斯轮盘赌概率,防止光线过分的反复反射 | |
float inv_pdf = exp(float(i) * light_quality); | |
float roulette_prob = 1.0 - (1.0 / inv_pdf); | |
// 光被吸收掉或者光线毙掉就不用继续了 | |
float visible = brightness(r.color); | |
if (visible <= VISIBILITY || rand11(seed++) < roulette_prob) { | |
r.color *= roulette_prob; | |
break; | |
} | |
// 能量守恒 | |
r.color *= inv_pdf; | |
// 与地图求交 | |
record rec = raycast(r); | |
// 没击中物体就肯定击中天空 | |
if (!rec.hit) { | |
vec4 color = sky(r) * sin(TIME) * 2.0; | |
r.color *= color.rgb * color.a; | |
break; | |
} | |
// 处理自发光 | |
r.color *= rec.obj.mtl.emission; | |
if (brightness(rec.obj.mtl.emission) > 1.0) { | |
break; | |
} | |
// 应用 PBR 材质 | |
r = PBR(r, rec, seed); | |
} | |
return r; | |
} | |
// 一次采样 | |
vec3 sample(camera cam, vec2 uv, vec3 color, inout float seed) { | |
// 获取光线并逆向追踪光线 | |
ray r = get_ray(cam, uv, color, seed); | |
r = raytrace(r, seed); | |
return r.color; | |
} | |
// ACES 色调映射 | |
vec3 aces_approx(vec3 v) { | |
v *= 0.6; | |
const float a = 2.51f; | |
const float b = 0.03f; | |
const float c = 2.43f; | |
const float d = 0.59f; | |
const float e = 0.14f; | |
return (v*(a*v + b)) / (v*(c*v + d) + e); | |
} | |
// 片段着色器程序入口 | |
void fragment() { | |
// 计算并修正 UV 坐标系 (左手系,以左下角为原点) | |
vec2 uv = vec2(UV.x, 1.0 - UV.y); | |
// 计算摄像机方位和视线 | |
vec3 lookfrom = camera_position; | |
vec3 direction = camera_rotation * vec3(0, 0, -1); | |
vec3 lookat = lookfrom + direction; | |
// 初始化摄像机 | |
camera cam; | |
cam.lookfrom = lookfrom; | |
cam.lookat = lookat; | |
cam.aspect = camera_aspect; | |
cam.vfov = camera_vfov; | |
cam.vup = vec3(0, 1, 0); | |
cam.focus = camera_focus; | |
cam.aperture = camera_aperture; | |
// 用 UV 和时间初始化随机数发生器种子 | |
float seed = rand13(vec3(uv, TIME)); | |
// 超采样 | |
uv += rand21(seed++) * SCREEN_PIXEL_SIZE; | |
// 对每个光子经过的表面采样一次 | |
vec3 color = sample(cam, uv, vec3(1), seed); | |
// 色调映射 | |
color *= camera_exposure; | |
color = aces_approx(color); | |
// 伽马矫正 | |
color = pow(color, vec3(1.0 / camera_gamma)); | |
// 混合历史的帧来降噪 | |
vec3 prev = texture(SCREEN_TEXTURE, SCREEN_UV).rgb; | |
color = mix(prev, clamp(color, 0, 1), 1.0 / max(1.0, frame)); | |
COLOR = vec4(color, 1.0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment