颜色混合

不考虑其他后处理步骤的话,颜色混合是图形管线的最后一步标准操作。

颜色混合的原理,在之前的文章 《图片和纹理》 里,已经介绍过了。总体原理就是,使用 RGBA 中的 alpha 分量,当作源颜色和目标颜色的混合权重,得到两者混合的颜色。

我们这边直接使用 alpha 当作混合因子,并当作权重进行加权计算,其实是做了简化。在 OpenGL 中,颜色混合的设置参数更多,可以指定不同的混合方程和混合因子:glBlendFunc 函数可以指定源和目标的混合因子;glBlendEquation 函数可以设置源和目标颜色的混合方程。

代码实现

首先,如代码清单 1 所示,我们和 OpenGL 接口一样,在 Enable 处增加颜色混合开启。

代码清单 1 Enable
  1. void TSoftRenderer::Enable(TEnableCap cap)
  2. {
  3.     if (cap == TEnableCap::CullFace)
  4.         m_state.SetCulling(true);
  5.     else if (cap == TEnableCap::DepthTest)
  6.         m_state.SetDepthTest(true);
  7.     else if (cap == TEnableCap::Blend)
  8.         m_state.SetBlend(true);
  9. }

混合的逻辑,在之前的设计中已经实现好了。我们再来梳理一下。如代码清单 2 所示,我们在光栅化画点的时候,如果开启了颜色混合,就会调用 BlendPixel 进行混合。

代码清单 2 Blend
  1. void TRasterizer::SetPixel(int x, int y, TRGBA color)
  2. {
  3.     if (x < 0 || x >= m_width || y < 0 || y >= m_height)
  4.         return;
  5.  
  6.     if (m_state->IsBlendEnabled())
  7.         BlendPixel(x, y, color.r, color.g, color.b, color.a);
  8.     else
  9.         m_pBits[y * m_width + x] = color.ToBGR888();
  10. }
  1. void TRasterizer::BlendPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
  2. {
  3.     BGRA* dstPixel = reinterpret_cast<BGRA*>(&m_pBits[y * m_width + x]);
  4.  
  5.     float srcAlpha = a / 255.0f;
  6.     float dstAlpha = 1.0f - srcAlpha;
  7.  
  8.     dstPixel->b = (b * srcAlpha + dstPixel->b * dstAlpha);
  9.     dstPixel->g = (g * srcAlpha + dstPixel->g * dstAlpha);
  10.     dstPixel->r = (r * srcAlpha + dstPixel->r * dstAlpha);
  11. }

测试

最后,我们编写测试样例,来验证实现的混合功能。如代码清单 3 所示,我们画三个三角形。因为开启了深度测试,所以由远到近画,让新画的颜色能和底色混合起来。

代码清单 3 测试
  1. #include "TBlendTestRenderTask.h"
  2.  
  3. TBlendTestRenderTask::TBlendTestRenderTask(TBasicWindow& win)
  4. {
  5.     float vertices[] = {
  6.         // 第一个三角形
  7.         0.3f,  0.0f, 0.8f,
  8.         0.8f,  0.0f, 0.8f,
  9.         0.45f, 0.5f, 0.8f,
  10.  
  11.         // 第二个三角形
  12.         0.5f,  0.0f, 0.5f,
  13.         1.0f,  0.0f, 0.5f,
  14.         0.75f, 0.5f, 0.5f,
  15.  
  16.         // 第三个三角形
  17.         -0.5f, 0.0f, 0.0f,
  18.         0.5f,  0.0f, 0.0f,
  19.         0.25f, 0.5f, 0.0f,
  20.     };
  21.  
  22.     float colors[] = {
  23.         // 第一个三角形
  24.         1.0f, 1.0f, 0.0f, 1.0f,
  25.         1.0f, 1.0f, 0.0f, 1.0f,
  26.         1.0f, 1.0f, 0.0f, 1.0f,
  27.  
  28.         // 第二个三角形
  29.         0.0f, 0.0f, 1.0f, 0.5f,
  30.         0.0f, 0.0f, 1.0f, 0.5f,
  31.         0.0f, 0.0f, 1.0f, 0.5f,
  32.  
  33.         // 第三个三角形
  34.         1.0f, 0.0f, 0.0f, 0.3f,
  35.         0.0f, 1.0f, 0.0f, 0.3f,
  36.         0.0f, 0.0f, 1.0f, 0.3f,
  37.     };
  38.  
  39.     uint32_t indices[] = {
  40.         // 第一个三角形
  41.         0, 1, 2,
  42.  
  43.         // 第二个三角形
  44.         3, 4, 5,
  45.  
  46.         // 第三个三角形
  47.         6, 7, 8,
  48.     };
  49.  
  50.     TSoftRenderer& sr = win.GetRenderer();
  51.  
  52.     uint32_t vao, vboPosition, vboColor, vboUv, ebo;
  53.     sr.GenVertexArrays(1, &vao);
  54.     sr.BindVertexArray(vao);
  55.  
  56.     sr.GenBuffers(1, &vboPosition);
  57.     sr.BindBuffer(TBufferType::ArrayBuffer, vboPosition);
  58.     sr.BufferData(TBufferType::ArrayBuffer, sizeof(vertices), vertices);
  59.     sr.VertexAttribPointer(0, 3, 3 * sizeof(float), 0);
  60.  
  61.     sr.GenBuffers(1, &vboColor);
  62.     sr.BindBuffer(TBufferType::ArrayBuffer, vboColor);
  63.     sr.BufferData(TBufferType::ArrayBuffer, sizeof(colors), colors);
  64.     sr.VertexAttribPointer(1, 4, 4 * sizeof(float), 0);
  65.  
  66.     sr.GenBuffers(1, &ebo);
  67.     sr.BindBuffer(TBufferType::ElementArrayBuffer, ebo);
  68.     sr.BufferData(TBufferType::ElementArrayBuffer, sizeof(indices), indices);
  69.  
  70.     sr.PrintVAO(vao);
  71.  
  72.     ////
  73.     int width = win.GetWindowWidth();
  74.     int height = win.GetWindowHeight();
  75.  
  76.     float aspect = (float)width / height;
  77.  
  78.     m_shader.projectionMatrix = tmath::PerspectiveMatrix(tmath::degToRad(60.0f), aspect, 0.1f, 100.0f);
  79.     m_shader.viewMatrix = tmath::TranslationMatrix(0.0f, 0.0f, 2.0f);
  80.     m_shader.modelMatrix.ToIdentity();
  81.  
  82.     sr.UseProgram(&m_shader);
  83.  
  84.     ////
  85.     sr.Enable(TEnableCap::DepthTest);
  86.     sr.DepthFunc(TDepthFunc::Less);
  87.  
  88.     sr.Enable(TEnableCap::Blend);
  89. }
  90.  
  91. void TBlendTestRenderTask::Render(TSoftRenderer& sr)
  92. {
  93.     sr.ClearColor({ 0,0,0 });
  94.     sr.ClearDepth(1.0f);
  95.  
  96.     sr.DrawElements(TDrawMode::Triangles, 9, 0);
  97. }

图 1 是和底色依次混合的结果;图 2 是没有开启混合的结果。

本节的完整代码在 tag/blending

图1 开启混合
图2 没有混合