Re: 0x01. 从零开始的光线追踪实现-光线、相机及背景
目标
书接上文,之前已经实现一个铺满整个窗口的红色填充,这趟来实现光线、相机及背景。
本节最终效果
计算物体在窗口坐标的位置
其实这个光追的思维模式很简单,就是从相机处开始发射一束射线,射线撞到哪些“物体”,就计算跟该“物体”相交的颜色。如图所示,从相机处发射射线,以左上角开始逐像素扫一遍,计算对应像素的颜色
我们再看看 viewport 的坐标,假设一个窗口大小是 (没错,PSP 的分辨率😁)的宽高,那么 的区间就是 , 的区间就是
现在我们要来处理一个标准化的像素坐标,处理像素在屏幕中的 2D 位置
struct Vertex {
float4 position [[position]];
};
fragment float4 fragmentFn(Vertex in [[stage_in]]) {
auto uv = in.position.xy / float2(float(480 - 1), float(272 - 1));
// ...
}
上面这一步的作用是把像素级的屏幕坐标转成区间 的归一化坐标。
假设现在有一个物体,它的坐标是 ,通过上面的计算式子可以得出
,说明它在屏幕的中间
接着我们假定相机的位置是原点 ,相机距离 viewport 。我们计算出宽高的比例再套进这个计算 (2 * uv - float2(1))
,等于讲把 映射成 的范围,其实就是
原始 uv | 变换后 |
---|---|
(0, 0) | (-1, -1) 左下角 |
(1, 0) | (1, -1) 右下角 |
(0.5, 0.5) | (0, 0) 居中 |
(1, 1) | (1, 1) 右上角 |
再把 (2 * uv - float2(1))
跟 float2(aspect_ratio, -1)
相乘等于讲横向乘以 aspect_ratio
用来做等比例变换
至于纵向乘以 -1
,那是因为在 Metal 中, 轴是向下为正,乘一下 -1
就可以把 轴翻转变成向上为正,接下来计算方向就简单多了,因为 轴面向相机,其实就是相机距离取反,上面假定相机距离为 1
,所以取反再跟 放一块就是方向,同时我们又假定相机的位置是原点 ,那么求光线就很容易了
struct Ray {
float3 origin;
float3 direction;
};
fragment float4 fragmentFn(Vertex in [[stage_in]]) {
// ...
const auto focus_distance = 1.0;
// ...
const auto direction = float3(uv, -focus_distance);
Ray ray = { origin, direction };
}
现在既然有了光线,再就是要计算一下光线的颜色,因为目前场景中没有物体,所以就默认计算背景色,我们先把光线从 映射回 ,然后再线性插值计算渐变天空颜色,所以先要让光线经过归一化操作到
// [-1, 1]
normalize(ray.direction)
然后再给该向量加
// [-1, 1] + 1 = [0, 2]
normalize(ray.direction) + 1
然后把 乘以 就转成 了,之后再代入线性插值公式计算结果,具体渐变色值可以根据自己的需求调整,我这里直接使用 Ray Tracing in One Weekend 的色值 float3(0.5, 0.7, 1)
float3 sky_color(Ray ray) {
const auto a = 0.5 * (normalize(ray.direction).y + 1);
return (1 - a) * float3(1) + a * float3(0.5, 0.7, 1);
}
最后总结一下代码
struct Ray {
float3 origin;
float3 direction;
};
float3 sky_color(Ray ray) {
const auto a = 0.5 * (normalize(ray.direction).y + 1);
return (1 - a) * float3(1) + a * float3(0.5, 0.7, 1);
}
fragment float4 fragmentFn(Vertex in [[stage_in]]) {
const auto origin = float3(0);
const auto focus_distance = 1.0;
const auto aspect_ratio = 480 / 272;
auto uv = in.position.xy / float2(float(480 - 1), float(272 - 1));
uv = (2 * uv - float2(1)) * float2(aspect_ratio, -1);
const auto direction = float3(uv, -focus_distance);
Ray ray = { origin, direction };
return float4(sky_color(ray), 1);
}