[Nori]番外-简单纹理映射

番外会记录一下个人拿nori扩展或者修改的一些特性,不保证正确性,如果有问题请各位大佬指出来,我会非常开心 :)

(没想到吧,除了nori本体的作业,还有会番外篇

啊对了,这里要特地说一下,nori本体是个C++工程,但是因为个人实在不想写C++,就拿C#复刻了一遍nori,基本一模一样,API都一样。不出意外的话,以后文章基本都是以C#为主。(想复刻回C++应该也比较简单吧(大概))

放一下C#版本的repo(顺便求个star):https://github.com/ksgfk/Pursuit

这是nori番外篇的第一篇文章。我们先来实现一个相对简单,而且效果爆炸的功能

纹理映射

这里有很多部分是跟着Ray Tracing: The Next Week走的

关于什么是纹理,GAMES101第9课已经为我们解释的非常清楚了。像是OpenGL这类图像API支持二维,三维的纹理。不过这里呢我们为了实现简单(挖坑),就只实现二维纹理了。以下说的纹理都是二维纹理。

这里照搬GAMES101那张令人印象深刻的图了

众所周知,纹理也有坐标系,横轴是u纵轴是v,称为纹理坐标,范围0到1。一般来说坐标原点默认是左下角(0, 0),右上角在(1, 1)。

所以我们只要获取到uv坐标,就可以对纹理进行采样了

一般来说,在建模软件里导出模型时,会同时导出模型的纹理坐标

我们只需要根据光线与三角形相交时的重心坐标,对模型自带的纹理坐标进行插值,就可以得到需要的uv了,和法线插值是一个道理

首先在Common/Intersection.cs里面添加一个uv字段(nori的Intersection声明在mesh.h里面)

1
2
3
4
5
6
7
8
public readonly struct Intersection
{
public readonly Point3F P;
public readonly float T;
public readonly IShape Shape;
public readonly Frame ShadingFrame;
public readonly Point2F Uv;
}

然后在Common/Mesh.csGetIntersectionInfo方法里照着法线插值的代码CV一份改一下变成纹理坐标插值(nori原版的相交信息计算都在accel.cpp里面,不太方便,个人觉得这些可以抽象出来交给图元计算,为以后更多图元类型做准备)

1
2
3
4
5
if (mesh.TexCoords.Count > 0)
{
var tc = mesh.GetTexCoord(index);
itsUv = bary.X * (Vector2)tc.T0 + bary.Y * (Vector2)tc.T1 + bary.Z * (Vector2)tc.T2;
}

别忘了uv坐标是要在BSDF里使用的,我们在BsdfQueryRecord里也新增一个uv字段,并且在各个积分器里填充

接下来设计纹理类。众所周知,一张贴图的RGB通道不一定都用来保存数据,像是PBR里面metallic,roughness纹理只有R通道保存了数据。也就是说采样的返回值可能有许多类型。我们可以使用泛型(C++模板一样的233)
Common/ITexture.cs

1
2
3
4
5
public interface ITexture { }
public interface ITexture<T> : ITexture where T : struct
{
T Sample(Point2F uv);
}

实现一个最简单的纯色纹理。
Common/ConstantTexture.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ConstantTexture<T> : ITexture<T> where T : struct
{
public T Color { get; set; }
public ConstantTexture(T color)
{
Color = color;
}
public T Sample(Point2F uv)
{
return Color;
}
}
public sealed class ConstantTexture3F : ConstantTexture<Color3F>
{
public ConstantTexture3F(Color3F color) : base(color) { }
}
public sealed class ConstantTexture1F : ConstantTexture<float>
{
public ConstantTexture1F(float color) : base(color) { }
}

The Next Week介绍了一种程序化纹理,看上去像是国际象棋棋盘。以前在shader抄过一个效果类似的…但是忘记出处在哪了…具体实现可以看Common/ChessboardTexture.cs,这里就不放代码了

接下来是从其他地方加载贴图作为纹理。C++可以用stb_image之类的库。C#加载纹理可以使用Magick.NET。巨硬文档不推荐使用System.Drawing

具体怎么加载就不写了,就算有库辅助还是有很多要注意的…总之,最终我们拿到了一个贴图Common/ImageTexture.cs。直接对它采样就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ImageTexture<T> : ITexture<T> where T : struct
{
public IColorBuffer<T> Image { get; set; }
protected ImageTexture(IColorBuffer<T> image) { Image = image; }
public T Sample(Point2F uv)
{
float du = uv.X * Image.Width;
float dv = uv.Y * Image.Height;
int u = Math.Clamp((int)(du), 0, Image.Width - 1);
int v = Math.Clamp((int)(dv), 0, Image.Width - 1);
T color = Image[u, v];
return color;
}
}

嗯,既然涉及到采样,那必然会出现失真现象。为了解决这种问题可以使用双线性插值,mipmap等一系列采样方法。不过这篇是简单纹理采样,就先不深入这些方法吧(主要是还不会233)

接下来就是去各种BSDF里,把可以用纹理的参数替换掉,这是个体力活。

在搞定纹理加载流程,搭建完测试场景后,首先来试一下Diffuse材质的纹理采样效果吧。我把GAMES202的202娘直接搬过来了,启动!

画面质量直接起飞

再来几张结果


[Nori]番外-简单纹理映射
https://ksgfk.github.io/2021/12/20/Nori番外-纹理映射/
作者
ksgfk
发布于
2021年12月20日
许可协议