unity手搓光线追踪渲染管线(SRP) 第二篇 光线、追踪、抗锯齿
一、光线
回顾上一篇,我们配置好了项目,接下来我们探究射线:
如果为原点,
为方向,t为时间,
为射线上一点,则有
,
其中,
是单位向量,
这样t就是射线传播的长度。
我们在RayTracing.compute中写这个结构体和它的构造函数:
struct Ray
{
float3 O, D;
};
Ray newRay(float3 o, float3 d)
{
Ray r;
r.O = o;
r.D = d;
return r;
}
我们要让它创建相机光线,所以在RTRP.cs每个相机的循环中,asset.RT调度前写:
asset.RT.SetMatrix("CTW", cam.cameraToWorldMatrix);
asset.RT.SetMatrix("CIP", cam.projectionMatrix.inverse);
在RTRP.cs每个相机的循环前写:
asset.RT.SetVector("Screen", new Vector2(Screen.width, Screen.height));
在RayTracing.compute开头写:
float4x4 CTW, CIP;
float2 Screen;
在RayTracing.compute中间写:
Ray CamRay(float2 uv)
{
float3 o = mul(CTW, float4(0.0, 0.0, 0.0, 1.0)).xyz;
float3 d = mul(CIP, float4(uv * 2.0 - 1.0, 0.0, 1.0)).xyz;
d = mul(CTW, float4(d, 0.0)).xyz;
d = normalize(d);
return newRay(o, d);
}
将RayTracing.compute的核函数Main内容改为:
Ray r = CamRay(id.xy / Screen);
tex[id.xy] = float4(r.D * 0.5 + 0.5, 0.0);
懵逼了吧?你先别懵逼,我们慢慢说起。
变量Screen是渲染分辨率,不用每个相机设置一遍,所以在相机循环前写;
函数CamRay将被用于生成相机光线(我们探究反向光线追踪,从相机找光源),

图片中大写O是世界坐标原点,小写o由第一行算出,第二行算出在相机坐标系下的(uv*2.0-1.0是将屏幕uv转换到CIP矩阵使用的uv),第三行将
从相机坐标系变换的世界坐标系,第四行将
归一化;
核函数中r.D*0.5+0.5是因为方向的每个分量属于[-1, 1]经过这个计算变换入[0, 1]。
最后你会看到:

二、追踪
打开RayTracing.compute写入:
struct Hit
{
float3 P, N;
float2 UV;
float t;
};
Hit newHit()
{
Hit h;
h.P = 0.0;
h.N = 0.0;
h.UV = 0.0;
h.t = 1.#INF;
return h;
}
bool IntersectTriangle_MT97(Ray r, float3 vert0, float3 vert1, float3 vert2, inout float t, inout float u, inout float v)
{
float3 edge1 = vert1 - vert0;
float3 edge2 = vert2 - vert0;
float3 pvec = cross(r.D, edge2);
float det = dot(edge1, pvec);
if (det < 1e-8)
return false;
float inv_det = 1.0f / det;
float3 tvec = r.O - vert0;
u = dot(tvec, pvec) * inv_det;
if (u < 0.0 || u > 1.0f)
return false;
float3 qvec = cross(tvec, edge1);
v = dot(r.D, qvec) * inv_det;
if (v < 0.0 || u + v > 1.0f)
return false;
t = dot(edge2, qvec) * inv_det;
return true;
}
void IntersectTriangle(Ray r, float3 v0, float3 v1, float3 v2, inout Hit h)
{
float t, u, v;
if (IntersectTriangle_MT97(r, v0, v1, v2, t, u, v))
{
if (t > 0.0 && t < h.t)
{
h.t = t;
h.P = r.O + t * r.D;
h.N = normalize(cross(v1 - v0, v2 - v0));
h.UV = float2(u, v);
}
}
}
Hit Trace(Ray r)
{
Hit h = newHit();
IntersectTriangle(r, float3(0.0, 0.0, 0.0), float3(2.0, 2.0, 2.0), float3(5.0, 0.0, 0.0), h);
return h;
}
然后将核函数改为:
Ray r = CamRay(id.xy / Screen);
Hit h = Trace(r);
tex[id.xy] = float4(h.N * 0.5 + 0.5, 0.0);
上面代码中,我们定义了结构体Hit以储存相交结果,P是交点坐标,N是交点法线,UV是交点几何UV,t是光线时间,或是说距离,为了方便,Hit的构造函数中t是无穷大,最后你会看到:

三、抗锯齿
如果放大图像,会发现有很明显的锯齿,为了减少锯齿,我们使用超采样抗锯齿。
我们在RTRPA.cs和RayTracing.compute内定义SSC(SuperSamplingCount,可以看出我为少写几个字使用缩写)
我们在每个像素内随即确定一个点来实现单次采样,因此我们要先写随机数生成器:
float seed, pix_x, pix_y, pix_z;
float rand()
{
seed = frac(cos(dot(float3(pix_x, pix_y, pix_z), float3(0.954731, 0.57743, 0.6432)) + seed) * 43758.5453);
return seed;
}
float2 randBox()
{
return float2(rand(), rand());
}
上面四个浮点数是生成随机数的参数,seed由RTRP.cs传入随机非负浮点数,其中float3(0.954731, 0.57743, 0.6432)每个分量小于1否则随机值转颜色后有明显的条纹,影响效果,pix_x、pix_y、pix_z由核函数Main设置:
float3 col = 0.0;
pix_x = id.x;
pix_y = id.y;
for (int a = 0; a < SSC; a++)
{
pix_z = a;
Ray r = CamRay((id.xy + randBox()) / Screen);
Hit h = Trace(r);
col += h.N * 0.5 + 0.5;
}
tex[id.xy] = float4(col / SSC, 0.0);
每次采样得到的颜色加给col,最后col / SSC就是求了几次采样的平均值,最后你会看到:

更多推荐

所有评论(0)