在采样时使用的uv坐标我们之前已经知道了0到1的采样,那如何对坐标-1到0或者-2到0等越界的纹理坐标位置进行采样?

Repeat方式

超出范围的就重复采样相对的位置

Repeat算法

假设当前u坐标为1.2,取其小数部分得到0.2,即为所需要的坐标;
假设当前u坐标为-0.2,取其小数部分得到-0.2,加1得到0.8,即为所需坐标

图-1

代入数值验算:

图-2

图-3

图-4

图-5

Mirror方式

对于纹理坐标小于0或者大于1的采样行为可以选择镜像采样,即上下左右均对称

Mirror算法

直接用1减去repeat算法得到的结果就是Mirror的结果

图-6

代入数值验算:

图-7

图-8

代码实现

base.h

#define FRACTION(v)			((v) - (int)(v))

//...

#define TEXTURE_WRAP_REPEAT 0
#define TEXTURE_WRAP_MIRROR 1

gpu.h

新增:
(1) uint32_t mWrap{ TEXTURE_WRAP_REPEAT };
(2) setTextureWrap接口设置warp处理方式
(3) checkWrap处理函数

#pragma once
#include "../global/base.h"
#include "frameBuffer.h"
#include "../application/application.h"
#include "../application/image.h"
#include "../math/math.h"

#define sgl GPU::getInstance()

/*
* class GPU:
* 模拟GPU的绘图行为以及算法等
*/
class GPU {
public:
static GPU* getInstance();
GPU();

~GPU();

//接受外界传入的bmp对应的内存指针以及窗体的宽/高
void initSurface(const uint32_t& width, const uint32_t& height, void* buffer = nullptr);

//清除画布内容
void clear();

//传入像素位置,绘制成某种颜色
void drawPoint(const uint32_t& x, const uint32_t& y, const RGBA& color);

void drawLine(const Point& p1, const Point& p2);

void drawTriangle(const Point& p1, const Point& p2, const Point& p3);

void drawImage(const Image* image);

void drawImageWidthAlpha(const Image* image, const uint32_t& alpha);

//设置状态
void setBlending(bool enable);

void setBilinear(bool enable);

void setTexture(Image* image);

void setTextureWrap(uint32_t wrap);//设置使用哪种处理方式

private:
RGBA sampleNearest(const math::vec2f& uv);
RGBA sampleBilinear(const math::vec2f& uv);
void checkWrap(float& n);

private:
static GPU* mInstance;

bool mEnableBlending{ false };
bool mEnableBilinear{ false };
uint32_t mWrap{ TEXTURE_WRAP_REPEAT };//记录使用哪种方式来处理大于0小于1的情况

FrameBuffer* mFrameBuffer{ nullptr };

//纹理贴图
Image* mImage{ nullptr };
};

gpu.cpp

在sampleNearest和sampleBilinear中调用checkWrap,检查纹理坐标是否小于0大于1对其进行相应的处理

void GPU::checkWrap(float& n) {
if (n > 1.0f || n < 0.0f) {
n = FRACTION(n);
switch (mWrap) {
case TEXTURE_WRAP_REPEAT:
n = FRACTION(n + 1);
break;
case TEXTURE_WRAP_MIRROR:
n = 1.0f - FRACTION(n + 1);
break;
default:
break;
}
}
}

RGBA GPU::sampleNearest(const math::vec2f& uv) {
auto myUV = uv;

checkWrap(myUV.x);
checkWrap(myUV.y);

//四舍五入到最近整数
// u = 0 对应 x = 0,u = 1 对应 x = width - 1
// v = 0 对应 y = 0,v = 1 对应 y = height - 1
int x = std::round(myUV.x * (mImage->mWidth - 1));
int y = std::round(myUV.y * (mImage->mHeight - 1));

int position = y * mImage->mWidth + x;
return mImage->mData[position];
}

RGBA GPU::sampleBilinear(const math::vec2f& uv) {
RGBA resultColor;

auto myUV = uv;
checkWrap(myUV.x);
checkWrap(myUV.y);

float x = myUV.x * static_cast<float>(mImage->mWidth - 1);
float y = myUV.y * static_cast<float>(mImage->mHeight - 1);

int left = std::floor(x);
int right = std::ceil(x);
int bottom = std::floor(y);
int top = std::ceil(y);

//对上下插值,得到左右
float yScale = 0.0f;
if (top == bottom) {
yScale = 1.0f;
}
else {
yScale = (y - static_cast<float>(bottom)) / static_cast<float>(top - bottom);
}

int positionLeftTop = top * mImage->mWidth + left;
int positionLeftBottom = bottom * mImage->mWidth + left;
int positionRightTop = top * mImage->mWidth + right;
int positionRightBottom = bottom * mImage->mWidth + right;

RGBA leftColor = Raster::lerpRGBA(mImage->mData[positionLeftBottom], mImage->mData[positionLeftTop], yScale);
RGBA rightColor = Raster::lerpRGBA(mImage->mData[positionRightBottom], mImage->mData[positionRightTop], yScale);

//对左右插值,得到结果
float xScale = 0.0f;
if (right == left) {
xScale = 1.0f;
}
else {
xScale = (x - static_cast<float>(left)) / static_cast<float>(right - left);
}

resultColor = Raster::lerpRGBA(leftColor, rightColor, xScale);


return resultColor;
}

main.cpp

跑马灯效果,让图片纹理滚动起来

Image* texture;
Point p1;
Point p2;
Point p3;

Point q1;
Point q2;
Point q3;

float speed = 0.01;
void changeUV() {
p1.uv.x += speed;
p2.uv.x += speed;
p3.uv.x += speed;
q1.uv.x += speed;
q2.uv.x += speed;
q3.uv.x += speed;
}

void render() {
changeUV();

sgl->clear();
sgl->setTexture(texture);
sgl->setTextureWrap(TEXTURE_WRAP_MIRROR);

sgl->drawTriangle(p1, p2, p3);
sgl->drawTriangle(q1, q2, q3);
}

void prepare() {
texture = Image::createImage("assets/textures/goku.jpg");

p1.x = 0;
p1.y = 0;
p1.color = RGBA(255, 0, 0, 255);
p1.uv = math::vec2f(0.0f, 0.0f);

p2.x = 400;
p2.y = 300;
p2.color = RGBA(0, 255, 0, 255);
p2.uv = math::vec2f(1.0f, 1.0f);

p3.x = 400;
p3.y = 0;
p3.color = RGBA(0, 0, 255, 255);
p3.uv = math::vec2f(1.0f, 0.0f);

q1.x = 0;
q1.y = 0;
q1.color = RGBA(255, 0, 0, 255);
q1.uv = math::vec2f(0.0f, 0.0f);

q2.x = 0;
q2.y = 300;
q2.color = RGBA(0, 255, 0, 255);
q2.uv = math::vec2f(0.0f, 1.0f);

q3.x = 400;
q3.y = 300;
q3.color = RGBA(0, 0, 255, 255);
q3.uv = math::vec2f(1.0f, 1.0f);
}


int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, //本应用程序实例句柄,唯一指代当前程序
_In_opt_ HINSTANCE hPrevInstance, //本程序前一个实例,一般是null
_In_ LPWSTR lpCmdLine, //应用程序运行参数
_In_ int nCmdShow) //窗口如何显示(最大化、最小化、隐藏),不需理会
{
if (!app->initApplication(hInstance, 800, 600)) {
return -1;
}

//将bmp指向的内存配置到sgl当中
sgl->initSurface(app->getWidth(), app->getHeight(), app->getCanvas());

prepare();

bool alive = true;
while (alive) {
alive = app->peekMessage();
render();
app->show();
}

Image::destroyImage(texture);

return 0;
}