纹理映射
纹理映射说白了就是将一幅图像贴在我们所要贴的物体的表面。
为每个多边形顶点附一个纹理坐标,然后再纹理素材上取样贴在一个多边形上,然后达到所要的视觉效果。
如下为一个正方体箱子每个面都贴上纹理。
将上图当作一个颜色矩阵,每个颜色有一个坐标(U,V),而三维的立方体每个点都有一个三维坐标(x,y,z),我们利用坐标映射((x,y,z)-(U,V))为空白的立方体着色。
如何建立这种映射呢?
首先将3D网格映射为一个单一的2D图像(操作又名展开),获得2D网格视图
猴子展开图如下。
这里其实不是软渲染器的部分,需要一名设计师来完成。
设计师会在这样一个二维视图中画画以便于在我们的引擎中使用纹理。
这个结果看起来好像比较奇怪。但是,我们已经可以看到一些东西,纹理右下角是眼睛。
纹理映射简单步骤是
1. 加载纹理图
2. 获取或者建立纹理与3D坐标的映射关系X。
3. 在渲染像素p时依据X来从纹理采样并着色。
第二步中映射关系我们从Json中解析,其应由模型设计师提供。
第三步中具体到某个像素时我们采用类似高氏着色的方式进行插值计算即可。
补充概念:
纹理坐标我用【0-1】来表示,以防止更换不同精细程度的纹理图时不必改变纹理坐标。
纹理坐标作为顶点的重要属性,不会随着顶点的旋转平移缩放而改变,但是注意细分时面的正反面其实应该有两个纹理坐标。
纹理映射另一个重要的概念是滤波(filtering)。我们的【0-1】映射到纹理图时可能会出现浮点数,而真实纹理图中的坐标需要为整数,我们采用简单的四舍五入(nearest filtering)来处理这种情况,这可能导致最终结果不是非常好。PS:有关线性插值,双线性插值等的改进方法这里我不做展开。
纹理操作在OpenGL涉及四个对象:纹理对象,纹理单元,采样对象与采样器。
纹理对象主要包括我们的纹理原始素材图以及相关属性。
纹理对象不直接绑定到着色器,他通过纹理纹理单元与着色器绑定,通常着色器有多个纹理单元可用,数量取决于你的显卡能力。绑定纹理对象与纹理单元是首先获得一个空的纹理单元0,然后绑定到纹理对象A,以此类推另一个纹理单元1同样可以绑定到另一个纹理对象B。事实上,每个纹理单元其实可以同时挂载多个不同类的的纹理对象。
采样操作需要能访问纹理单元。采样对象包含纹理数据以及进行纹理操作的配置参数。
映射关系其实类似如下
当然纹理操作相关即使不仅仅这点东西,我在这里只做这些简单介绍。
加载纹理与纹理映射函数
void Texture::Load(const char *filename) {
buf = cv::imread(filename);
width = buf.size().width;
height = buf.size().height;
//cv::imshow("buf", buf);
}
Color Texture::Map(float tu, float tv) {
Color re;
re.Set(0x00000000, 1.0f); //默认为黑
if (buf.empty()) return re;
int u = (int)(tu*width) % width; //%为防止复用
int v = (int)(tv*height) % height;
if (u<0 || v<0)
cout << tu << ' ' << tv << endl;
u = u >= 0 ? u : -u;
v = v >= 0 ? v : -v;
cv::Vec3b tex_w = buf.at<cv::Vec3b>(v, u); //U是X,V是Y
re.argb[3] = 0x00;
re.argb[0] = tex_w[0]; //B
re.argb[1] = tex_w[1]; //G
re.argb[2] = tex_w[2]; //R
return re;
}
背面剔除
当你运行这个程序的时候,明显会发现渲染的有点慢了,这里我们引入一种加速的方式,原理也是非常简单的,就是不渲染我们看不到的部分。那么如何确定我们看不到的部分呢?类似于光照平面着色那部分的做法,我们可以简单的采取相机视向量与多边形的法向量的点乘积如果大于零,那么该多边形可以不渲染。
float Camera::plane_camera_cos(Vector4D center, Vector4D normal)
{
//依据法线计算与视线的夹角
Vector4D eye_dir;
center.Sub(pos, eye_dir); //获取光线向量
eye_dir.Normalize();
normal.Normalize();
return normal.Mul_Dot(eye_dir);
}
在DrawTriangleGouraudTexture()开始调用上述函数加以判断即可。