问题分析:

在绘制两个三角形的过程中,系统如何决定谁遮挡谁?

之前我们的设计是画家算法,即最早渲染的被最新渲染的遮挡,但不按照由远到近的顺序绘制时,就得不到我们像要的效果,如何直接根据图形的远近设置他们是否被遮挡,我们需要深度测试算法

深度

在屏幕空间变换后,我们得到的每个顶点的z坐标即顶点深度;在经过光栅化后得到的每个像素中都记录则经过插值的搭配的z值,即像素的深度值,用于描述当前像素距观察点有多远
深度取值范围是0-1

深度检测: 对于当前像素,都需要跟画布已有的像素深度值进行对比,如果被挡住则丢弃;如果靠前则保留

深度缓存:跟画布一样分辨率大小的内存空间,用于记录画布上已经绘制的每个像素的深度值

深度测试算法解析

图-1

深度缓存与颜色缓存分辨率一致,初始化为1.0f

图-2

三角形每个像素深度值与深度缓存中对应深度值对比;
如果三角形像素深度值较小(接近观测点)则通过测试;
通过测试的像素,使用新的像素深度值覆盖对应的深度值;
通过测试的像素,可以绘制到颜色缓存

图-3

API设计

深度检测可配置参数较多,我们只选取深度对比函数进行编写:
DEPTH_LESS: 代表比现存深度小才能通过检测
DEPTH_GREATER: 代表比现存深度大才能通过检测

代码实现

内容:
1、FrameBuffer加入深度缓存
2、GPU加入深度测试状态,clear中清除深度缓存
3、绘制流程加入深度测试
4、示例

frameBuffer.h

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

/*
* class FrameBuffer:
* 存储当前画布对应的bmp的内存指针,作为当前绘图画板
*/
class FrameBuffer {
public:
FrameBuffer(uint32_t width, uint32_t height, void* buffer = nullptr);
~FrameBuffer();
FrameBuffer(const FrameBuffer&) = delete;//不准拷贝复制

uint32_t mWidth{ 0 };
uint32_t mHeight{ 0 };
RGBA* mColorBuffer{ nullptr };
float* mDepthBuffer{ nullptr };//深度缓存<-------------------------------+
bool mExternBuffer{ false };
};

frameBuffer.cpp

#include "frameBuffer.h"

FrameBuffer::FrameBuffer(uint32_t width, uint32_t height, void* buffer) {
mWidth = width;
mHeight = height;

if (!buffer) {
buffer = new RGBA[width * height];
mExternBuffer = false;
}
else {
mExternBuffer = true;
}

mColorBuffer = (RGBA*)buffer;

mDepthBuffer = new float[width * height];
std::fill_n(mDepthBuffer, width * height, 1.0f);//深度缓存初始化为1.0f
}

FrameBuffer::~FrameBuffer() {
if (!mExternBuffer && mColorBuffer) {
delete[] mColorBuffer;
}

if (mDepthBuffer) {
delete[] mDepthBuffer;
}
}

base.h

#pragma once

#include<iostream>
#include<vector>
#include<map>
#include<cmath>
#include<assert.h>

#define PI 3.14159265358979323
#define DEG2RAD(theta) (0.01745329251994329 * (theta))
#define FRACTION(v) ((v) - (int)(v))

using byte = unsigned char;

struct RGBA {
byte mB;
byte mG;
byte mR;
byte mA;

RGBA(
byte r = 255,
byte g = 255,
byte b = 255,
byte a = 255)
{
mR = r;
mG = g;
mB = b;
mA = a;
}
};

#define ARRAY_BUFFER 0
#define ELEMENT_ARRAY_BUFFER 1

#define DRAW_LINES 0
#define DRAW_TRIANGLES 1

#define CULL_FACE 1
#define DEPTH_TEST 2

#define FRONT_FACE 0
#define BACK_FACE 1
#define FRONT_FACE_CW 0
#define FRONT_FACE_CCW 1


#define DEPTH_LESS 0 //<-------------------------------+
#define DEPTH_GREATER 1 //<-------------------------------+

gpu.h

class GPU {
//... ... ...
void enable(const uint32_t& value);

void disable(const uint32_t& value);

//depth test
void depthFunc(const uint32_t& depthFunc);

//... ... ...
private:
bool depthTest(const FsOutput& output);

private:
//... ... ...
//depth
bool mEnableDepthTest{ true };//是否开启深度检测
uint32_t mDepthFunc{ DEPTH_LESS };//深度检测条件
};

gpu.cpp

void GPU::enable(const uint32_t& value) {
switch (value)
{
case CULL_FACE:
mEnableCullFace = true;
break;
case DEPTH_TEST://<-------------------------------+是否开启深度检测
mEnableDepthTest = true;
break;
default:
break;
}
}

void GPU::disable(const uint32_t& value) {
switch (value)
{
case CULL_FACE:
mEnableCullFace = false;
break;
case DEPTH_TEST:
mEnableDepthTest = false;//<-------------------------------+是否取消深度检测
break;
default:
break;
}
}

void GPU::depthFunc(const uint32_t& depthFunc) {
mDepthFunc = depthFunc;
}

bool GPU::depthTest(const FsOutput& output) {
uint32_t pixelPos = output.mPixelPos.y * mFrameBuffer->mWidth + output.mPixelPos.x;
float oldDepth = mFrameBuffer->mDepthBuffer[pixelPos];
switch (mDepthFunc)
{
case DEPTH_LESS:
if (output.mDepth < oldDepth) {
mFrameBuffer->mDepthBuffer[pixelPos] = output.mDepth;
return true;
}
else {
return false;
}
break;
case DEPTH_GREATER:
if (output.mDepth > oldDepth) {
mFrameBuffer->mDepthBuffer[pixelPos] = output.mDepth;
return true;
}
else {
return false;
}
break;
default:
return false;
break;
}

}

绘制流程中增加深度测试

gpu.cpp

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
}

/*
* 背面剔除阶段
* 作用:
* 背向我们的三角形需要剔除
*/
std::vector<VsOutput> cullOutputs = clipOutputs;
if (drawMode == DRAW_TRIANGLES && mEnableCullFace) {
cullOutputs.clear();
for (uint32_t i = 0; i < clipOutputs.size() - 2; i += 3) {
if (Clipper::cullFace(mFrontFace, mCullFace, clipOutputs[i], clipOutputs[i + 1], clipOutputs[i + 2])) {
auto start = clipOutputs.begin() + i;
auto end = clipOutputs.begin() + i + 3;
cullOutputs.insert(cullOutputs.end(), start, end);
}
}
}

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

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


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;

//深度测试 <-----------------------------------------------------------------+
if (mEnableDepthTest && !depthTest(fsOutput)) {
continue;//丢弃未通过深度测试的像素点(被遮挡)
}

mFrameBuffer->mColorBuffer[pixelPos] = fsOutput.mColor;
}
}

示例(绘制两个三角形看遮挡效果)

uint32_t WIDTH = 800;
uint32_t HEIGHT = 600;

//两个三角形,三个属性对应vbo
uint32_t positionVbo0 = 0;
uint32_t positionVbo1 = 0;
uint32_t colorVbo0 = 0;
uint32_t colorVbo1 = 0;

uint32_t uvVbo = 0;

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

//两个三角形专属vao
uint32_t vao0 = 0;
uint32_t vao1 = 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.01f;
//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(vao0);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->drawElement(DRAW_TRIANGLES, 0, 3);

sgl->bindVertexArray(vao1);
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);

sgl->enable(CULL_FACE);
sgl->frontFace(FRONT_FACE_CCW);
sgl->cullFace(BACK_FACE);

//sgl->disable(DEPTH_TEST);

//第一个三角形
float positions0[] = {
-0.5f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f,
0.25f, 0.5f, 0.0f,
};

float colors0[] = {
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 positions1[] = {
0.3f, 0.0f, -0.3f,
0.8f, 0.0f, -0.3f,
0.45f, 0.5f, -0.3f,
};

float colors1[] = {
1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.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);

//生成uv对应的vbo,二者公用
uvVbo = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, uvVbo);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 6, uvs);
sgl->bindBuffer(ARRAY_BUFFER, 0);

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

//position0
positionVbo0 = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, positionVbo0);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 9, positions0);
sgl->vertexAttributePointer(0, 3, 3 * sizeof(float), 0);

//color0
colorVbo0 = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, colorVbo0);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 12, colors0);
sgl->vertexAttributePointer(1, 4, 4 * sizeof(float), 0);

//uv
sgl->bindBuffer(ARRAY_BUFFER, uvVbo);
sgl->vertexAttributePointer(2, 2, 2 * sizeof(float), 0);

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


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

//position1
positionVbo1 = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, positionVbo1);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 9, positions1);
sgl->vertexAttributePointer(0, 3, 3 * sizeof(float), 0);

//color0
colorVbo1 = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, colorVbo1);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 12, colors1);
sgl->vertexAttributePointer(1, 4, 4 * sizeof(float), 0);

//uv
sgl->bindBuffer(ARRAY_BUFFER, uvVbo);
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;
}