颜色混合
不考虑其他后处理步骤的话,颜色混合是图形管线的最后一步标准操作。
颜色混合的原理,在之前的文章 《图片和纹理》 里,已经介绍过了。总体原理就是,使用 RGBA 中的 alpha 分量,当作源颜色和目标颜色的混合权重,得到两者混合的颜色。
我们这边直接使用 alpha 当作混合因子,并当作权重进行加权计算,其实是做了简化。在 OpenGL 中,颜色混合的设置参数更多,可以指定不同的混合方程和混合因子:glBlendFunc 函数可以指定源和目标的混合因子;glBlendEquation 函数可以设置源和目标颜色的混合方程。
代码实现
首先,如代码清单 1 所示,我们和 OpenGL 接口一样,在 Enable 处增加颜色混合开启。
- void TSoftRenderer::Enable(TEnableCap cap)
- {
- if (cap == TEnableCap::CullFace)
- m_state.SetCulling(true);
- else if (cap == TEnableCap::DepthTest)
- m_state.SetDepthTest(true);
- else if (cap == TEnableCap::Blend)
- m_state.SetBlend(true);
- }
混合的逻辑,在之前的设计中已经实现好了。我们再来梳理一下。如代码清单 2 所示,我们在光栅化画点的时候,如果开启了颜色混合,就会调用 BlendPixel 进行混合。
- void TRasterizer::SetPixel(int x, int y, TRGBA color)
- {
- if (x < 0 || x >= m_width || y < 0 || y >= m_height)
- return;
- if (m_state->IsBlendEnabled())
- BlendPixel(x, y, color.r, color.g, color.b, color.a);
- else
- m_pBits[y * m_width + x] = color.ToBGR888();
- }
- void TRasterizer::BlendPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
- {
- BGRA* dstPixel = reinterpret_cast<BGRA*>(&m_pBits[y * m_width + x]);
- float srcAlpha = a / 255.0f;
- float dstAlpha = 1.0f - srcAlpha;
- dstPixel->b = (b * srcAlpha + dstPixel->b * dstAlpha);
- dstPixel->g = (g * srcAlpha + dstPixel->g * dstAlpha);
- dstPixel->r = (r * srcAlpha + dstPixel->r * dstAlpha);
- }
测试
最后,我们编写测试样例,来验证实现的混合功能。如代码清单 3 所示,我们画三个三角形。因为开启了深度测试,所以由远到近画,让新画的颜色能和底色混合起来。
- #include "TBlendTestRenderTask.h"
- TBlendTestRenderTask::TBlendTestRenderTask(TBasicWindow& win)
- {
- float vertices[] = {
- // 第一个三角形
- 0.3f, 0.0f, 0.8f,
- 0.8f, 0.0f, 0.8f,
- 0.45f, 0.5f, 0.8f,
- // 第二个三角形
- 0.5f, 0.0f, 0.5f,
- 1.0f, 0.0f, 0.5f,
- 0.75f, 0.5f, 0.5f,
- // 第三个三角形
- -0.5f, 0.0f, 0.0f,
- 0.5f, 0.0f, 0.0f,
- 0.25f, 0.5f, 0.0f,
- };
- float colors[] = {
- // 第一个三角形
- 1.0f, 1.0f, 0.0f, 1.0f,
- 1.0f, 1.0f, 0.0f, 1.0f,
- 1.0f, 1.0f, 0.0f, 1.0f,
- // 第二个三角形
- 0.0f, 0.0f, 1.0f, 0.5f,
- 0.0f, 0.0f, 1.0f, 0.5f,
- 0.0f, 0.0f, 1.0f, 0.5f,
- // 第三个三角形
- 1.0f, 0.0f, 0.0f, 0.3f,
- 0.0f, 1.0f, 0.0f, 0.3f,
- 0.0f, 0.0f, 1.0f, 0.3f,
- };
- uint32_t indices[] = {
- // 第一个三角形
- 0, 1, 2,
- // 第二个三角形
- 3, 4, 5,
- // 第三个三角形
- 6, 7, 8,
- };
- TSoftRenderer& sr = win.GetRenderer();
- uint32_t vao, vboPosition, vboColor, vboUv, ebo;
- sr.GenVertexArrays(1, &vao);
- sr.BindVertexArray(vao);
- sr.GenBuffers(1, &vboPosition);
- sr.BindBuffer(TBufferType::ArrayBuffer, vboPosition);
- sr.BufferData(TBufferType::ArrayBuffer, sizeof(vertices), vertices);
- sr.VertexAttribPointer(0, 3, 3 * sizeof(float), 0);
- sr.GenBuffers(1, &vboColor);
- sr.BindBuffer(TBufferType::ArrayBuffer, vboColor);
- sr.BufferData(TBufferType::ArrayBuffer, sizeof(colors), colors);
- sr.VertexAttribPointer(1, 4, 4 * sizeof(float), 0);
- sr.GenBuffers(1, &ebo);
- sr.BindBuffer(TBufferType::ElementArrayBuffer, ebo);
- sr.BufferData(TBufferType::ElementArrayBuffer, sizeof(indices), indices);
- sr.PrintVAO(vao);
- ////
- int width = win.GetWindowWidth();
- int height = win.GetWindowHeight();
- float aspect = (float)width / height;
- m_shader.projectionMatrix = tmath::PerspectiveMatrix(tmath::degToRad(60.0f), aspect, 0.1f, 100.0f);
- m_shader.viewMatrix = tmath::TranslationMatrix(0.0f, 0.0f, 2.0f);
- m_shader.modelMatrix.ToIdentity();
- sr.UseProgram(&m_shader);
- ////
- sr.Enable(TEnableCap::DepthTest);
- sr.DepthFunc(TDepthFunc::Less);
- sr.Enable(TEnableCap::Blend);
- }
- void TBlendTestRenderTask::Render(TSoftRenderer& sr)
- {
- sr.ClearColor({ 0,0,0 });
- sr.ClearDepth(1.0f);
- sr.DrawElements(TDrawMode::Triangles, 9, 0);
- }
图 1 是和底色依次混合的结果;图 2 是没有开启混合的结果。
本节的完整代码在 tag/blending。

