回顾

在绘制透明或者半透明物体时,需要将当前物体生成的像素与画布上已有的像素做计算,得到新的像素颜色值的过程

图-1

混合方式

每个像素由RGBA构成,其中Alpha(0-1)通道就记录了当前颜色的透明度,那么可以如下计算最终像素:

图-2

此时,srcAlpha就代表了当前颜色占最终颜色的比重,越大越明显

混合绘制次序

先绘制不透明物体,保证底色正确;
从后往前绘制半透明物体,保证近距离透明物体不会遮挡远距离透明物体(按摄像机距离排序)

图-3

代码实现

内容:
1、颜色混合状态
2、颜色混合加入绘制流程
3、示例

gpu.h

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 printVAO(const uint32_t& vaoID);

uint32_t genBuffer();
void deleteBuffer(const uint32_t& bufferID);
void bindBuffer(const uint32_t& bufferType, const uint32_t& bufferID);
void bufferData(const uint32_t& bufferType, size_t dataSize, void* data);

uint32_t genVertexArray();
void deleteVertexArray(const uint32_t& vaoID);
void bindVertexArray(const uint32_t& vaoID);
void vertexAttributePointer(
const uint32_t& binding,
const uint32_t& itemSize,
const uint32_t& stride,
const uint32_t& offset);

void useProgram(Shader* shader);

void enable(const uint32_t& value);

void disable(const uint32_t& value);

//cull face
void frontFace(const uint32_t& value);

void cullFace(const uint32_t& value);

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

void depthWrite(bool value);

void drawElement(const uint32_t& drawMode, const uint32_t& first, const uint32_t& count);

private:
void vertexShaderStage(
std::vector<VsOutput>& vsOutputs,
const VertexArrayObject* vao,
const BufferObject* ebo,
const uint32_t first,
const uint32_t count);

void perspectiveDivision(VsOutput& vsOutput);
void perspectiveRecover(VsOutput& vsOutput);
void screenMapping(VsOutput& vsOutput);

void trim(VsOutput& vsOutput);

bool depthTest(const FsOutput& output);

RGBA blend(const FsOutput& output);//颜色混合<-------------------------------+

private:
static GPU* mInstance;
FrameBuffer* mFrameBuffer{ nullptr };

//VBO相关/EBO也存在内部
uint32_t mCurrentVBO{ 0 };
uint32_t mCurrentEBO{ 0 };
uint32_t mBufferCounter{ 0 };
std::map<uint32_t, BufferObject*> mBufferMap;

//VAO相关
uint32_t mCurrentVAO{ 0 };
uint32_t mVaoCounter{ 0 };
std::map<uint32_t, VertexArrayObject*> mVaoMap;

Shader* mShader{ nullptr };
math::mat4f mScreenMatrix;

//cull face
bool mEnableCullFace{ true };
uint32_t mFrontFace{ FRONT_FACE_CCW };
uint32_t mCullFace{ BACK_FACE };

//depth
bool mEnableDepthTest{ true };
bool mEnableDepthWrite{ true };
uint32_t mDepthFunc{ DEPTH_LESS };

//blending
bool mEnableBlending{ false };//是否开启颜色混合<-------------------------------+
};

gpu.cpp

void GPU::enable(const uint32_t& value) {
switch (value)
{
case CULL_FACE:
mEnableCullFace = true;
break;
case DEPTH_TEST:
mEnableDepthTest = true;
break;
case BLENDING:
mEnableBlending = 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;
case BLENDING:
mEnableBlending = false;
break;
default:
break;
}
}

RGBA GPU::blend(const FsOutput& output) {//颜色混合<-------------------------------+
RGBA result;

uint32_t pixelPos = output.mPixelPos.y * mFrameBuffer->mWidth + output.mPixelPos.x;
RGBA dst = mFrameBuffer->mColorBuffer[pixelPos];
RGBA src = output.mColor;

float weight = static_cast<float>(src.mA) / 255.0f;

result.mR = static_cast<float>(src.mR) * weight + static_cast<float>(dst.mR) * (1.0f - weight);
result.mG = static_cast<float>(src.mG) * weight + static_cast<float>(dst.mG) * (1.0f - weight);
result.mB = static_cast<float>(src.mB) * weight + static_cast<float>(dst.mB) * (1.0f - weight);
result.mA = static_cast<float>(src.mA) * weight + static_cast<float>(dst.mA) * (1.0f - weight);

return result;
}

绘制流程中增加颜色混合

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;//丢弃未通过深度测试的像素点(被遮挡)
}

RGBA color = fsOutput.mColor;
if (mEnableBlending) {
color = blend(fsOutput);// 颜色混合<-----------------------------------------------------------------+
}

mFrameBuffer->mColorBuffer[pixelPos] = color;
}
}

示例(绘制三个前后位置的三角形叠加透明效果)

uint32_t WIDTH = 800;
uint32_t HEIGHT = 600;

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

uint32_t colorVbo0 = 0;
uint32_t colorVbo1 = 0;
uint32_t colorVbo2 = 0;

uint32_t uvVbo = 0;

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

//两个三角形专属vao
uint32_t vao0 = 0;
uint32_t vao1 = 0;
uint32_t vao2 = 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);

//非透明物体必须先绘制,z为-0.8
sgl->bindVertexArray(vao1);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->drawElement(DRAW_TRIANGLES, 0, 3);

//透明物后绘制,z为-0.5
sgl->bindVertexArray(vao2);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->drawElement(DRAW_TRIANGLES, 0, 3);

//透明物后绘制,z为0
sgl->bindVertexArray(vao0);
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->enable(BLENDING);


//第一个三角形
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, 0.3f,
0.0f, 1.0f, 0.0f, 0.3f,
0.0f, 0.0f, 1.0f, 0.3f,
};

//第二个三角形
float positions1[] = {
0.3f, 0.0f, -0.8f,
0.8f, 0.0f, -0.8f,
0.45f, 0.5f, -0.8f,
};

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 positions2[] = {
0.5f, 0.0f, -0.5f,
1.0f, 0.0f, -0.5f,
0.75f, 0.5f, -0.5f,
};

float colors2[] = {
0.0f, 0.0f, 1.0f, 0.5f,
0.0f, 0.0f, 1.0f, 0.5f,
0.0f, 0.0f, 1.0f, 0.5f,
};

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);

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

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

//color0
colorVbo2 = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, colorVbo2);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 12, colors2);
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;
}