一.前言
在传统的固定渲染管线中,渲染中的一系列操作如:坐标转换、光照、投影、剪裁、扫描转换、纹理贴图、可见性测试、帧缓冲像一个流水线一样按部就班的按序执行。每一道工序上都有一些不同的参数,开发者为了完成不一样的效果就是去改变每个工序的参数。
然而人们总是不满足于这样有限的控制;于是就出现了后面的可编程渲染管线,将渲染中的一些工序交给shader来完成。你可以把shader看作是一段小程序,只不过是用作渲染中的一些操作。
所以在可编程渲染管线中,能够更自由的去通过编程得到想要的效果。shader的种类有很多,担任者不同的作用。不过在这里我们只用了解顶点着色器(Vertex Shader)和片元着色器(Fragement Shader)。
具体想更多了解固定管线和可编程渲染管线的区别可以看下知乎的这个问答
二.顶点着色器(Vertex Shader)
一般来说,顶点着色器需要做的事情,就是将模型的顶点坐标从物体空间转换到裁剪空间下,还有就是传输其他shader所需要的顶点数据。当然,如果想在片元着色器做光照模型可能需要顶点的法线;一般来说,我们都是在世界坐标系中做光照计算,所以有的时候我们也可以通过顶点着色器计算一些我们后面Shader所需要的数据。
三.片元着色器(Fragment Shader)
片元着色器一般是光栅化后,通过前面Shader计算或者是外部传入的一些数据计算来得到一个像素”最终”的颜色,其实这里的颜色并不一定是最终显示在屏幕的颜色,因为还会经过深度测试、模板测试、混合。
四.代码
根据以上的描述我们可以简单的建立一个Shader的基类,来描述一个Shader的基本操作:
class BaseShader
{
public:
BaseShader() {}
virtual ~BaseShader() {}
public:
virtual easym::VertexOut VS(const easym::VertexIn& vin) = 0;
virtual easym::Vector4 PS (easym::VertexOut& vout) = 0;
virtual void SetTexture(const easym::Texture2D& tex) = 0;
virtual void SetWorld(const easym::Matrix& world) = 0;
virtual void SetView(const easym::Matrix& view) = 0;
virtual void SetProject(const easym::Matrix& project) = 0;
virtual void SetEyePos(const easym::Vector3& eyePos) = 0;
virtual void SetMaterial(const easym::light::Material& material) = 0;
virtual void SetLight(const easym::light::Light& dirLight) = 0;
};
在上面的代码中,VS,FS分别表示顶点着色器和片元着色器,其余的方法是用来设置Shader的一些参数。值得注意的是VS函数的参数和返回值,参数我们在上一节见过,是顶点属性结构体,用来表示一个顶点的属性;而返回值VertexOut则为顶点着色器的输出数据,用于后面Shader的使用,我们把VertexOut定义如下:
//经过顶点着色器输出的结构
class VertexOut
{
public:
Vector4 posTrans; //世界变换后的坐标
Vector4 posH; //投影变换后的坐标
Vector2 tex; //纹理坐标
Vector3 normal; //法线
Vector4 color; //颜色
real oneDivZ; //1/z用于深度测试
VertexOut() = default;
VertexOut(Vector4 _posT, Vector4 _posH, Vector2 _tex, Vector3 _normal, Vector4 _color, real _oneDivZ)
:posTrans(_posT), posH(_posH), tex(_tex), normal(_normal), color(_color), oneDivZ(_oneDivZ) {}
VertexOut(const VertexOut& rhs) :posTrans(rhs.posTrans), posH(rhs.posH), tex(rhs.tex), normal(rhs.normal),
color(rhs.color), oneDivZ(rhs.oneDivZ) {}
VertexOut& operator= (const VertexOut& rhs)
{
if (this == &rhs)
return *this;
this->normal = rhs.normal;
this->posH = rhs.posH;
this->posTrans = rhs.posTrans;
this->tex = rhs.tex;
this->color = rhs.color;
this->oneDivZ = rhs.oneDivZ;
return *this;
}
};
inline VertexOut Lerp(const VertexOut& a, const VertexOut& b, real t)
{
return VertexOut(
easym::Lerp(a.posTrans, b.posTrans, t),
easym::Lerp(a.posH, b.posH, t),
easym::Lerp(a.tex, b.tex, t),
easym::Lerp(a.normal, b.normal, t),
easym::Lerp(a.color, b.color, t),
easym::Lerp(a.oneDivZ, b.oneDivZ, t)
);
}
我们给VertexOut添加了一个Lerp方法,是用来在后面光栅化的时候插值使用。
本节相关代码:Mesh.h、BaseShader.h