内容

1、修改了GPU中的透视除法函数
2、加入剪裁函数trim
3、加入透视恢复函数
4、Raster插值,加入1/w以及depth
5、主流程更改
6、示例

修改GPU中的透视除法函数

dataStructures.h

修改了GPU中的透视除法函数,添加了mOneOverW代表当前点的1/w

#pragma once
#include "../global/base.h"
#include "../math/math.h"

//VAO之中,用于描述属性读取方式的Description
struct BindingDescription {
uint32_t mVboId{ 0 };
size_t mItemSize{ 0 };
size_t mStride{ 0 };
size_t mOffset{ 0 };
};

struct VsOutput {
float mOneOverW{ 0.0f };
math::vec4f mPosition{ 0.0f, 0.0f, 0.0f, 1.0f };
math::vec4f mColor;//此处颜色改为0.0-1.0之间表达0-255的量
math::vec2f mUV;
};

struct FsOutput {
math::vec2i mPixelPos;
float mDepth;
RGBA mColor;//此处使用0-255来进行颜色显示
};

加入剪裁函数trim

gpu.cpp

void GPU::perspectiveDivision(VsOutput& vsOutput)//输入剪裁空间的vsOutput,对所有顶点属性进行透视除法
{
vsOutput.mOneOverW = 1.0f / vsOutput.mPosition.w;

vsOutput.mPosition *= vsOutput.mOneOverW;
vsOutput.mPosition.w = 1.0f;

vsOutput.mColor *= vsOutput.mOneOverW;
vsOutput.mUV *= vsOutput.mOneOverW;

trim(vsOutput);
}

void GPU::trim(VsOutput& vsOutput) {
//修剪毛刺,边界求交点的时候,可能会产生超过-1到1的现象,修正回到-1到1之间
if (vsOutput.mPosition.x < -1.0f) {
vsOutput.mPosition.x = -1.0f;
}

if (vsOutput.mPosition.x > 1.0f) {
vsOutput.mPosition.x = 1.0f;
}

if (vsOutput.mPosition.y < -1.0f) {
vsOutput.mPosition.y = -1.0f;
}

if (vsOutput.mPosition.y > 1.0f) {
vsOutput.mPosition.y = 1.0f;
}

if (vsOutput.mPosition.z < -1.0f) {
vsOutput.mPosition.z = -1.0f;
}

if (vsOutput.mPosition.z > 1.0f) {
vsOutput.mPosition.z = 1.0f;
}
}

修改透视修复函数

图-1

对应代码:

void GPU::perspectiveRecover(VsOutput& vsOutput) {
vsOutput.mColor /= vsOutput.mOneOverW;
vsOutput.mUV /= vsOutput.mOneOverW;
}

Raster插值,加入1/w以及depth

图-1

对应代码:

void Raster::interpolantTriangle(const VsOutput& v0, const VsOutput& v1, const VsOutput& v2, VsOutput& p) {
auto e1 = math::vec2f(v1.mPosition.x - v0.mPosition.x, v1.mPosition.y - v0.mPosition.y);
auto e2 = math::vec2f(v2.mPosition.x - v0.mPosition.x, v2.mPosition.y - v0.mPosition.y);
float sumArea = std::abs(math::cross(e1, e2));

auto pv0 = math::vec2f(v0.mPosition.x - p.mPosition.x, v0.mPosition.y - p.mPosition.y);
auto pv1 = math::vec2f(v1.mPosition.x - p.mPosition.x, v1.mPosition.y - p.mPosition.y);
auto pv2 = math::vec2f(v2.mPosition.x - p.mPosition.x, v2.mPosition.y - p.mPosition.y);
//计算v0的权重

float v0Area = std::abs(math::cross(pv1, pv2));
float v1Area = std::abs(math::cross(pv0, pv2));
float v2Area = std::abs(math::cross(pv0, pv1));

float weight0 = v0Area / sumArea;
float weight1 = v1Area / sumArea;
float weight2 = v2Area / sumArea;

//插值1/w,用于透视恢复
p.mOneOverW = math::lerp(v0.mOneOverW, v1.mOneOverW, v2.mOneOverW, weight0, weight1, weight2);

//插值深度值
p.mPosition.z = math::lerp(v0.mPosition.z, v1.mPosition.z, v2.mPosition.z, weight0, weight1, weight2);

//对于颜色的插值
p.mColor = math::lerp(v0.mColor, v1.mColor, v2.mColor, weight0, weight1, weight2);

//对于uv坐标的插值
p.mUV = math::lerp(v0.mUV, v1.mUV, v2.mUV, weight0, weight1, weight2);
}

主流程更改

gpu.cpp

添加透视恢复阶段,调用perspectiveRecover

void GPU::drawElement(const uint32_t& drawMode, const uint32_t& first, const uint32_t& count) {
if (mCurrentVAO == 0 || mShader == nullptr || count == 0) {
return;
}

//1 get vao
auto vaoIter = mVaoMap.find(mCurrentVAO);
if (vaoIter == mVaoMap.end()) {
std::cerr << "Error: current vao is invalid!" << std::endl;
return;
}

const VertexArrayObject* vao = vaoIter->second;
auto bindingMap = vao->getBindingMap();

//2 get ebo
auto eboIter = mBufferMap.find(mCurrentEBO);
if (eboIter == mBufferMap.end()) {
std::cerr << "Error: current ebo is invalid!" << std::endl;
return;
}

const BufferObject* ebo = eboIter->second;

/*
* VertexShader处理阶段
* 作用:
* 按照输入的EBO的index顺序来处理顶点,
* 依次通过vsShader得到的输出结果按序放入vsOutputs中
*/
std::vector<VsOutput> vsOutputs{};
vertexShaderStage(vsOutputs, vao, ebo, first, count);

if (vsOutputs.empty()) return;

/*
* Clip Space剪裁处理阶段
* 作用:
* 在剪裁空间,对所有输出的图元进行剪裁拼接等
* 后面的处理阶段都使用剪裁结果clipOutputs来处理
*/
std::vector<VsOutput> clipOutputs{};
Clipper::doClipSpace(drawMode, vsOutputs, clipOutputs);
if (clipOutputs.empty()) return;

vsOutputs.clear();

/*
* NDC处理阶段
* 作用:
* 将顶点转化到NDC下
*/
for (auto& output : clipOutputs) {
perspectiveDivision(output);//透视除法,除以W
}

/*
* 屏幕映射处理阶段
* 作用:
* 将NDC下的点通过screenMatrix,转换到屏幕空间
*/
for (auto& output : clipOutputs) {
screenMapping(output);
}

/*
* 光栅化处理阶段
* 作用:
* 离散出所有需要的Fragment
*/
std::vector<VsOutput> rasterOutputs;
Raster::rasterize(rasterOutputs, drawMode, clipOutputs);


if (rasterOutputs.empty()) return;

/*
* 透视恢复阶段
* 作用:
* 离散出来的像素插值结果,需要乘以自身的w值恢复到正常状态
*/
for (auto& output : rasterOutputs) {
perspectiveRecover(output);
}

/*
* 颜色输出处理阶段
* 作用:
* 将颜色进行输出
*/
FsOutput fsOutput;
uint32_t pixelPos = 0;
for (uint32_t i = 0; i < rasterOutputs.size(); ++i) {
mShader->fragmentShader(rasterOutputs[i], fsOutput);
pixelPos = fsOutput.mPixelPos.y * mFrameBuffer->mWidth + fsOutput.mPixelPos.x;
mFrameBuffer->mColorBuffer[pixelPos] = fsOutput.mColor;
}
}

示例(旋转三角形,观察颜色变化)

做不做透视修正的区别: 不做的话,三角形旋转时,颜色始终是均匀的,但是做了透视修正后就就是一个颜色驱赶另一种颜色的真实视觉效果

uint32_t WIDTH = 800;
uint32_t HEIGHT = 600;

//三个属性对应vbo
uint32_t positionVbo = 0;
uint32_t colorVbo = 0;
uint32_t uvVbo = 0;

//三角形的indices
uint32_t ebo = 0;

//本三角形专属vao
uint32_t vao = 0;

//使用的Shader
DefaultShader* shader = nullptr;

//mvp变换矩阵
math::mat4f modelMatrix;
math::mat4f viewMatrix;
math::mat4f perspectiveMatrix;


float angle = 0.0f;
float cameraZ = 2.0f;
void transform() {
angle += 0.003f;
//cameraZ -= 0.01f;

//模型变换
modelMatrix = math::rotate(math::mat4f(1.0f), angle, math::vec3f{ 0.0f, 1.0f, 0.0f });

//视图变换
auto cameraModelMatrix = math::translate(math::mat4f(1.0f), math::vec3f{ 0.0f, 0.0f, cameraZ });
viewMatrix = math::inverse(cameraModelMatrix);
}

void render() {
transform();
shader->mModelMatrix = modelMatrix;
shader->mViewMatrix = viewMatrix;
shader->mProjectionMatrix = perspectiveMatrix;

sgl->clear();
sgl->useProgram(shader);
sgl->bindVertexArray(vao);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->drawElement(DRAW_TRIANGLES, 0, 3);
}

void prepare() {
shader = new DefaultShader();

perspectiveMatrix = math::perspective(60.0f, (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f);

auto cameraModelMatrix = math::translate(math::mat4f(1.0f), math::vec3f{ 0.0f, 0.0f, cameraZ });
viewMatrix = math::inverse(cameraModelMatrix);

float positions[] = {
-0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
};

float colors[] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
};

float uvs[] = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
};

uint32_t indices[] = { 0, 1, 2 };

//生成indices对应ebo
ebo = sgl->genBuffer();
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->bufferData(ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * 3, indices);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, 0);

//生成vao并且绑定
vao = sgl->genVertexArray();
sgl->bindVertexArray(vao);

//生成每个vbo,绑定后,设置属性ID及读取参数
auto positionVbo = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, positionVbo);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 9, positions);
sgl->vertexAttributePointer(0, 3, 3 * sizeof(float), 0);

auto colorVbo = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, colorVbo);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 12, colors);
sgl->vertexAttributePointer(1, 4, 4 * sizeof(float), 0);

auto uvVbo = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, uvVbo);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 6, uvs);
sgl->vertexAttributePointer(2, 2, 2 * sizeof(float), 0);

sgl->bindBuffer(ARRAY_BUFFER, 0);
sgl->bindVertexArray(0);
}

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

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

prepare();

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

delete shader;

return 0;
}