学完games101课程的人都会想手写一个光栅化渲染器,本系列文章将介绍如何手写一个光栅化渲染器。
ps:作者也是只学了games101,边学边写的状态难免写的不好。
大致流程如下:
渲染器
- 创建图片文件
- 像素缓冲区(存放图片像素信息)
- 设置指定位置像素信息(setPixel函数)
- 保存图片(将像素缓冲区写入图片格式)
光栅化器
- 光栅化直线
- 光栅化圆形
- 光栅化三角形
- 顶点数组对象
- 顶点缓冲区
- 元素缓冲区
- 顶点布局设置
- 顶点着色器(各种图形变换)
- z-buffer深度缓冲
- MSAA抗锯齿
- 片段着色器
- 纹理贴图
- 模型加载
创建一个渲染器
创建文件图片可以使用读写二进制文件格式的方式创建图片,可以使用Bmp文件格式,这个文件格式好创建。
有兴趣的可以看这篇文章
或者看看我之前写废的BMP光栅化渲染器,当然是借鉴games101作业写的。
这里就使用png图片格式了,这里使用vspng这个简单的png文件库,该库只有短短的30几行的代码。
创建项目目录
├── CMakeLists.txt
├── build
├── include
│ ├── Rasterizer.hpp
│ └── svpng
│ ├── svpng.h
└── src
├── main.cpp
└── svpng
└── svpng.cpp
将svpng中的svpng函数分文件编写不然会触发重复定义的错误CMakeLists.txt
内容如下
cmake_minimum_required(VERSION 3.10.0)
project(Rasterization)
include_directories("./include/svpng/")
include_directories("./include/")
file(GLOB_RECURSE CPP_FILE "${PROJECT_SOURCE_DIR}/src/*.cpp")
file(GLOB_RECURSE SVPNG_FILE "${PROJECT_SOURCE_DIR}/src/svpng/*.cpp")
add_executable(${PROJECT_NAME} ${CPP_FILE} ${SVPNG_FILE})
vspng.cpp
内容如下
#include "svpng.h"
SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha) {
static const unsigned t[] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
/* CRC32 Table */ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
unsigned a = 1, b = 0, c, p = w * (alpha ? 4 : 3) + 1, x, y, i; /* ADLER-a, ADLER-b, CRC, pitch */
#define SVPNG_U8A(ua, l) for (i = 0; i < l; i++) SVPNG_PUT((ua)[i]);
#define SVPNG_U32(u) do { SVPNG_PUT((u) >> 24); SVPNG_PUT(((u) >> 16) & 255); SVPNG_PUT(((u) >> 8) & 255); SVPNG_PUT((u) & 255); } while(0)
#define SVPNG_U8C(u) do { SVPNG_PUT(u); c ^= (u); c = (c >> 4) ^ t[c & 15]; c = (c >> 4) ^ t[c & 15]; } while(0)
#define SVPNG_U8AC(ua, l) for (i = 0; i < l; i++) SVPNG_U8C((ua)[i])
#define SVPNG_U16LC(u) do { SVPNG_U8C((u) & 255); SVPNG_U8C(((u) >> 8) & 255); } while(0)
#define SVPNG_U32C(u) do { SVPNG_U8C((u) >> 24); SVPNG_U8C(((u) >> 16) & 255); SVPNG_U8C(((u) >> 8) & 255); SVPNG_U8C((u) & 255); } while(0)
#define SVPNG_U8ADLER(u) do { SVPNG_U8C(u); a = (a + (u)) % 65521; b = (b + a) % 65521; } while(0)
#define SVPNG_BEGIN(s, l) do { SVPNG_U32(l); c = ~0U; SVPNG_U8AC(s, 4); } while(0)
#define SVPNG_END() SVPNG_U32(~c)
SVPNG_U8A("\x89PNG\r\n\32\n", 8); /* Magic */
SVPNG_BEGIN("IHDR", 13); /* IHDR chunk { */
SVPNG_U32C(w); SVPNG_U32C(h); /* Width & Height (8 bytes) */
SVPNG_U8C(8); SVPNG_U8C(alpha ? 6 : 2); /* Depth=8, Color=True color with/without alpha (2 bytes) */
SVPNG_U8AC("\0\0\0", 3); /* Compression=Deflate, Filter=No, Interlace=No (3 bytes) */
SVPNG_END(); /* } */
SVPNG_BEGIN("IDAT", 2 + h * (5 + p) + 4); /* IDAT chunk { */
SVPNG_U8AC("\x78\1", 2); /* Deflate block begin (2 bytes) */
for (y = 0; y < h; y++) { /* Each horizontal line makes a block for simplicity */
SVPNG_U8C(y == h - 1); /* 1 for the last block, 0 for others (1 byte) */
SVPNG_U16LC(p); SVPNG_U16LC(~p); /* Size of block in little endian and its 1's complement (4 bytes) */
SVPNG_U8ADLER(0); /* No filter prefix (1 byte) */
for (x = 0; x < p - 1; x++, img++)
SVPNG_U8ADLER(*img); /* Image pixel data */
}
SVPNG_U32C((b << 16) | a); /* Deflate block end with adler (4 bytes) */
SVPNG_END(); /* } */
SVPNG_BEGIN("IEND", 0); SVPNG_END(); /* IEND chunk {} */
}
vspng.h
内容如下,注释太多已去除
#ifndef SVPNG_INC_
#define SVPNG_INC_
#ifndef SVPNG_LINKAGE
#define SVPNG_LINKAGE
#endif
#ifndef SVPNG_OUTPUT
#include <stdio.h>
#define SVPNG_OUTPUT FILE* fp
#endif
#ifndef SVPNG_PUT
#define SVPNG_PUT(u) fputc(u, fp)
#endif
SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha);
#endif /* SVPNG_INC_ */
然后创建Render.hpp 和 Render.cpp
#ifndef RENDER_HPP
#define RENDER_HPP
#define OPEN_ARGB true
#define CLOSE_ARGB false
#include "svpng.h"
class Render{
private:
unsigned int m_width,m_height;
unsigned char* m_buffer;
FILE* m_fp;
bool m_rgba_state = CLOSE_ARGB;
unsigned int m_size = 3;
public:
Render();
Render(const unsigned int w,const unsigned int h,
bool rgba = CLOSE_ARGB);
~Render();
unsigned int getWidth();
unsigned int getHeight();
void setPixel(const unsigned int x,
const unsigned int y,
const unsigned int r,
const unsigned int g,
const unsigned int b,
const unsigned int a=0);
bool createFile(const char* path);
bool saveFile();
};
#endif
#include "Render.hpp"
#include <cstdio>
Render::Render()
{
}
Render::Render(
const unsigned int w,
const unsigned int h,
bool rgba)
: m_width(w)
, m_height(h)
, m_rgba_state(rgba)
{
if (m_rgba_state)
m_size = 4;
m_buffer = new unsigned char[w * h * m_size]();
}
Render::~Render()
{
delete[] m_buffer;
}
bool Render::createFile(const char* path)
{
m_fp = fopen(path, "wb");
if (m_fp == nullptr)
return false;
else
return true;
}
void Render::setPixel(
const unsigned int x,
const unsigned int y,
const unsigned int r,
const unsigned int g,
const unsigned int b,
const unsigned int a)
{
m_buffer[(y * m_width + x) * m_size + 0] = r;
m_buffer[(y * m_width + x) * m_size + 1] = g;
m_buffer[(y * m_width + x) * m_size + 2] = b;
if (m_rgba_state)
m_buffer[(y * m_width + x) * m_size + 3] = a;
}
bool Render::saveFile()
{
svpng(m_fp, m_width, m_height, m_buffer, m_rgba_state);
return fclose(m_fp);
}
unsigned int Render::getWidth(){return m_width;}
unsigned int Render::getHeight(){return m_height;}
然后就大功告成了