目录
引入UV纹理坐标及三角形绘制设置
纹理过滤
上图其实不是很直观。
UV坐标要解决的问题就是:
假设我有一张500×500的纹理图片;
我要把它映射到一张200×200的图片中;
这个问题要怎么去解决。
这里提出的UV坐标,就是一种”比例“,用(u,v)直接乘以原图的像素坐标(x,y),那么我们就可以直接获得原像素的颜色值,从而映射到目标图片中。
我们在Canvas画布类中添加纹理相关的属性与方法:
同理的,绘制中颜色既然要插值,uv坐标同样需要插值:
void Canvas::drawTriange(Point p1, Point p2, Point p3){
...
float s = (float)(newPoint.m_y - ptMin.m_y) / (float)(ptMax.m_y - ptMin.m_y);
newPoint.m_color = colorLerp(ptMin.m_color, ptMax.m_color, s);
newPoint.m_uv = uvLerp(ptMin.m_uv, ptMax.m_uv, s);
...
}
void Canvas::drawTriangeFlat(Point pFlat1, Point pFlat2, Point pt) {
//两边直线斜率
float k1 = 0.0;
float k2 = 0.0;
if (pFlat1.m_x != pt.m_x) {
k1 = (float)(pFlat1.m_y - pt.m_y) / (float)(pFlat1.m_x - pt.m_x);
}
if (pFlat2.m_x != pt.m_x) {
k2 = (float)(pFlat2.m_y - pt.m_y) / (float)(pFlat2.m_x - pt.m_x);
}
//两直线b值
float b1 = (float)pt.m_y - (float)pt.m_x * k1;
float b2 = (float)pt.m_y - (float)pt.m_x * k2;
//做垂线
int yStart = MIN(pt.m_y, pFlat1.m_y);
int yEnd = MAX(pt.m_y, pFlat1.m_y);
if (yStart < 0)yStart = 0;
if (yEnd > m_height)yEnd = m_height - 1;
//颜色插值相关
RGBA colorStart1, colorEnd1;
RGBA colorStart2, colorEnd2;
floatV2 uvStart1, uvEnd1;
floatV2 uvStart2, uvEnd2;
if (pt.m_y < pFlat1.m_y) {
yStart = pt.m_y;
yEnd = pFlat1.m_y;
colorStart1 = pt.m_color;
colorEnd1 = pFlat1.m_color;
colorStart2 = pt.m_color;
colorEnd2 = pFlat2.m_color;
uvStart1 = pt.m_uv;
uvEnd1 = pFlat1.m_uv;
uvStart2 = pt.m_uv;
uvEnd2 = pFlat2.m_uv;
}
else {
yStart = pFlat1.m_y;
yEnd = pt.m_y;
colorStart1 = pFlat1.m_color;
colorEnd1 = pt.m_color;
colorStart2 = pFlat2.m_color;
colorEnd2 = pt.m_color;
uvStart1 = pFlat1.m_uv;
uvEnd1 = pt.m_uv;
uvStart2 = pFlat2.m_uv;
uvEnd2 = pt.m_uv;
}
float yColorStep = 1.0 / ((float)(yEnd - yStart));
int yColorStart = yStart;
for (int y = yStart; y <= yEnd; ++y) {
int x1 = 0;
//判断是否为直角三角形
if (k1 == 0) {
x1 = pFlat1.m_x;
}
else {
x1 = ((float)y - b1) / k1;
}
//剪裁
if (x1 < 0)x1 = 0;
if (x1 > m_width)x1 = m_width - 1;
int x2 = 0;
//判断是否为直角三角形
if (k2 == 0) {
x2 = pFlat2.m_x;
}
else {
x2 = ((float)y - b2) / k2;
}
//剪裁
if (x2 < 0)x2 = 0;
if (x2 > m_width)x2 = m_width - 1;
//构建这两个点
float s1 = (float)(y - yColorStart) * yColorStep;
RGBA _color1 = colorLerp(colorStart1, colorEnd1, s1);
RGBA _color2 = colorLerp(colorStart2, colorEnd2, s1);
floatV2 _uv1 = uvLerp(uvStart1, uvEnd1, s1);
floatV2 _uv2 = uvLerp(uvStart2, uvEnd2, s1);
Point pt1(x1, y, _color1, _uv1);
Point pt2(x2, y, _color2, _uv2);
//绘制直线
drawLine(pt1, pt2);
}
}
void GT::Canvas::drawLine(Point pt1, Point pt2) {
int disX = abs(pt2.m_x - pt1.m_x);
int disY = abs(pt2.m_y - pt1.m_y);
int xNow = pt1.m_x;
int yNow = pt1.m_y;
int stepX = 0;
int stepY = 0;
//判断两个方向步进的正负
stepX = pt1.m_x < pt2.m_x ? 1 : -1;
stepY = pt1.m_y < pt2.m_y ? 1 : -1;
//对比xy偏移量,决定步进方向选取x or y
int sumStep = disX;
bool useXStep = true;
if (disX < disY) {
sumStep = disY;
useXStep = false;
SWAP_INT(disX, disY);
}
//初始化P
int p = 2 * disY - disX;
RGBA _color;
for (int i = 0; i <= sumStep; ++i) {
float _scale = 0;
if (useXStep) {
if (pt2.m_x == pt1.m_x)
_scale = 0;
else
_scale = (float)(xNow - pt1.m_x) / (float)(pt2.m_x - pt1.m_x);
}
else {
if (pt1.m_y == pt2.m_y)
_scale = 0;
else
_scale = (float)(yNow - pt1.m_y) / (float)(pt2.m_y - pt1.m_y);
}
//启用纹理
if (m_enableTexture) {
floatV2 _uv = uvLerp(pt1.m_uv, pt2.m_uv, _scale);
if (m_texture) {
_color = m_texture->getColorbyUV(_uv.x, _uv.y);
}
else {
_color = colorLerp(pt1.m_color, pt2.m_color, _scale);
}
}
else {
_color = colorLerp(pt1.m_color, pt2.m_color, _scale);
}
drawPoint(xNow, yNow, _color);
if (p >= 0) {
if (useXStep) {
yNow += stepY;
}
else {
xNow += stepX;
}
p = p - 2 * disX;
}
//步进主坐标
if (useXStep) {
xNow += stepX;
}
else {
yNow += stepY;
}
p = p + 2 * disY;
}
}
测试一下:
//===========================Render==================================================
void Render() {
_canvas->clear();
GT::Point ptArray[] = {
{0,0,GT::RGBA(255,0,0),GT::floatV2(0,0)},
{500,0,GT::RGBA(255,0,0),GT::floatV2(1.0,0)},
{250,300,GT::RGBA(255,0,0),GT::floatV2(0.5,1.0)}
};
_canvas->enableTexture(true);
_canvas->bindTexture(_bkImage);
_canvas->drawTriange(ptArray[0], ptArray[1], ptArray[2]);
//在这里画到设备上,hMem相当于缓冲区
BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}
直接上代码看效果。其实就是OpenGL里的纹理过滤:纹理 - LearnOpenGL CN (learnopengl-cn.github.io)
void Render() {
_canvas->clear();
GT::Point ptArray[] = {
{0,0, GT::RGBA(255,0,0),GT::floatV2(0,0)},
{500,0, GT::RGBA(255,0,0),GT::floatV2(1.0,0)},
{500,300, GT::RGBA(255,0,0),GT::floatV2(1.0,1.0)}
};
GT::Point ptArray1[] = {
{0,0, GT::RGBA(255,0,0),GT::floatV2(0,0)},
{0,300, GT::RGBA(255,0,0),GT::floatV2(0.0,1.0)},
{500,300, GT::RGBA(255,0,0),GT::floatV2(1.0,1.0)}
};
for (int i = 0; i < 3; i++) {
ptArray[i].m_uv.x += 0.5;
ptArray1[i].m_uv.x += 0.5;
}
_canvas->enableTexture(true);
_canvas->bindTexture(_bkImage);
_canvas->setTextureType(GT::Image::TX_REPEAT);
_canvas->drawTriange(ptArray[0], ptArray[1], ptArray[2]);
_canvas->drawTriange(ptArray1[0], ptArray1[1], ptArray1[2]);
//在这里画到设备上,hMem相当于缓冲区
BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}
TX_REPEAT过滤:
TX_CLAMP_TO_EDGE过滤