修改了tinyrender的一些地方,tinyrender貌似说的也不清楚,把tinyrender的一些过程和opengl函数对照起来,主要用于光栅器原理阐述,下面粘贴修改的地方。这个软光栅器只是学习目的,没有依赖,用法是将github 的tinyrenderer下载下来 main.cpp tgaimage.h/tgaimage.cpp geometry.h/geometry.cpp model.h/model.cpp our_gl.h/our_gl.cpp 这9个文件对应的安照下面修改的修改,然后VC2015或者2019创建空工程,要C++11以上语法才行,VC2010估计都不支持了,将这9个文件添加到工程中编译就行了,注意文件要UTF-8 BOM编码格式,可以用notepad++改编码格式
model.h 将变量设为public, 用于自己创建model
#ifndef __MODEL_H__
#define __MODEL_H__
#include <vector>
#include <string>
#include "geometry.h"
#include "tgaimage.h"
class Model {
public:
std::vector<Vec3f> verts_;
std::vector<std::vector<Vec3i> > faces_; // attention, this Vec3i means vertex/uv/normal
std::vector<Vec3f> norms_;
std::vector<Vec2f> uv_;
TGAImage diffusemap_;
TGAImage normalmap_;
TGAImage specularmap_;
void load_texture(std::string filename, const char *suffix, TGAImage &img);
public:
Model(){}
Model(const char *filename);
~Model();
int nverts();
int nfaces();
Vec3f normal(int iface, int nthvert);
Vec3f normal(Vec2i uv);
Vec3f vert(int i);
Vec3f vert(int iface, int nthvert);
Vec2i uv(int iface, int nthvert);
TGAColor diffuse(Vec2i uv);
float specular(Vec2i uv);
std::vector<int> face(int idx);
};
#endif //__MODEL_H__
our_gl类实现几个opengl函数,其中
glutInit初始时设置colorbuffer, depthbuffer, 缺省shader
glDrawElements,是将model作为vbo数据传进去绘制
#ifndef __OUR_GL_H__
#define __OUR_GL_H__
#include "tgaimage.h"
#include "geometry.h"
#include "model.h"
#define TI_GL_CLEAR_COLOR_BUFFER 0x01
#define TI_GL_CLEAR_DEPTH_BUFFER 0x02
#define TI_GL_DEPTH_TEST 0x01
extern Matrix ModelView;
extern Matrix Viewport;
extern Matrix Projection;
struct IShader {
virtual ~IShader() = 0;
virtual Vec3i vertex(int iface, int nthvert) = 0;
virtual bool fragment(Vec3f bar, TGAColor &color) = 0;
};
namespace our_gl
{
void glutInit(int w, int h, TGAImage* colorImage, TGAImage* zImage, IShader* defaultShader);
void glutFinal();
void glFrustum(float l, float r, float b, float t, float n, float f);
void gluLookAt(Vec3f eye, Vec3f center, Vec3f up);
void glViewport(int x, int y, int w, int h);
void glDrawELements(Model* m);
void glClear(unsigned int flag);
void glEnable(unsigned int flag);
void glDisable(unsigned int flag);
}
#endif //__OUR_GL_H__
our_gl.cpp
#include <cmath>
#include <limits>
#include "our_gl.h"
Matrix ModelView;
Matrix Viewport;
Matrix Projection;
TGAImage* _colorImage = nullptr;
TGAImage*_zImage = nullptr;
float* _zbufreal = nullptr;
TGAColor clearColor(102,102,204);
TGAColor depthValue(255);
float depthvreal = 255.f;
unsigned int testflag = 0;
IShader* _defaultShader = nullptr;
void triangle(Vec3i *pts, IShader &shader, TGAImage &image, TGAImage &zbuffer, float* zbuf_real, unsigned int fragmentTestFalg = 0);
template <typename T> T CLAMP(const T& value, const T& low, const T& high)
{
return value < low ? low : (value > high ? high : value);
}
IShader::~IShader() {}
namespace our_gl
{
void glutInit(int w, int h, TGAImage* colorImage, TGAImage* zImage, IShader* defaultShader)
{
_zbufreal = new float[w*h];
_colorImage = colorImage;
_zImage = zImage;
_defaultShader = defaultShader;
}
void glutFinal()
{
if(_zbufreal)
{
delete[] _zbufreal;
}
}
void glViewport(int x, int y, int w, int h)
{
Viewport = Matrix::identity();
Viewport[0][3] = x+w/2.f;
Viewport[1][3] = y+h/2.f;
Viewport[2][3] = 255.f/2.f;
Viewport[0][0] = w/2.f;
Viewport[1][1] = h/2.f;
Viewport[2][2] = 255.f/2.f;
}
void glFrustum(float l, float r, float b, float t, float n, float f)
{
Projection = Matrix::identity();
auto& m = Projection;
m[0][0] = (2.f*n) / (r-l);
m[1][1] = (2.f*n) / (t-b);
m[0][2] = (r+l) / (r-l);
m[1][2] = (t+b) / (t-b);
m[2][2] = -(f+n) / (f-n);
m[3][2] = -1.f;
m[2][3] = -(2.f*f*n) / (f-n);
}
void gluLookAt(Vec3f eye, Vec3f center, Vec3f up)
{
Vec3f z = (eye-center).normalize();
Vec3f x = cross(up,z).normalize();
Vec3f y = cross(z,x).normalize();
ModelView = Matrix::identity();
auto& m = ModelView;
for (int i=0; i<3; i++)
{
m[0][i] = x[i];
m[1][i] = y[i];
m[2][i] = z[i];
}
m[0][3] = -(eye*x);
m[1][3] = -(eye*y);
m[2][3] = -(eye*z);
}
void glClear(unsigned int flag)
{
if (flag & TI_GL_CLEAR_COLOR_BUFFER)
{
int w = _colorImage->get_width();
int h = _colorImage->get_height();
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
_colorImage->set(i,j, clearColor);
}
}
}
if (flag & TI_GL_CLEAR_DEPTH_BUFFER)
{
int w = _zImage->get_width();
int h = _zImage->get_height();
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
_zImage->set(i,j, depthValue);
_zbufreal[i*w+j] = depthvreal;
}
}
}
}
void glEnable(unsigned int flag)
{
testflag |= flag;
}
void glDisable(unsigned int flag)
{
testflag &= ~flag;
}
void glDrawELements(Model* m)
{
for (int i=0; i<m->nfaces(); i++) {
Vec3i screen_coords[3];
for (int j=0; j<3; j++) {
screen_coords[j] = _defaultShader->vertex(i, j);
}
triangle(screen_coords, *_defaultShader, *_colorImage, *_zImage, _zbufreal,testflag);
}
}
}//end of namespace ti
Vec3f barycentric(Vec3i A, Vec3i B, Vec3i C, Vec3i P) {
Vec3i s[2];
for (int i=2; i--; ) {
s[i][0] = C[i]-A[i];
s[i][1] = B[i]-A[i];
s[i][2] = A[i]-P[i];
}
Vec3f u = cross(s[0], s[1]);
if (std::abs(u[2])>1e-2) // dont forget that u[2] is integer. If it is zero then triangle ABC is degenerate
return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z);
return Vec3f(-1,1,1); // in this case generate negative coordinates, it will be thrown away by the rasterizator
}
// triangle是三角形光栅化过程,隐蔽在GPU中,程序员接触不到
void triangle(Vec3i *pts, IShader &shader, TGAImage &image, TGAImage &zbuffer, float* zbuf_real, unsigned int fragmentTestFlag) {
// pts为经过 顶点着色器后三角形三个顶点的屏幕+深度坐标(整形,x,y为像素坐标,z为深度)
// 计算pts三角形在屏幕上的矩形区域
// boxmax
// *---*---*
// | / \ |
// | / \ |
// |/ \|
// *-------*
//boxmin
Vec2i bboxmin(std::numeric_limits<int>::max(), std::numeric_limits<int>::max());
Vec2i bboxmax(-std::numeric_limits<int>::max(), -std::numeric_limits<int>::max());
for (int i=0; i<3; i++)
{
for (int j = 0; j < 2; j++)
{
bboxmin[j] = std::min(bboxmin[j], pts[i][j]);
bboxmax[j] = std::max(bboxmax[j], pts[i][j]);
}
}
// 两个for遍历该矩形区域的所有像素P
// boxmax
// *---*---*
// | / \ |
// | / \ |
// |/ *p\|
// *-------*
//boxmin
Vec3i P;
TGAColor color;
for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++)
{
for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++)
{
// 计算P在屏幕三角形上的重心坐标
Vec3f c = barycentric(pts[0], pts[1], pts[2], P);
// 插值深度值, 得到P的深度值
float p_depth = pts[0].z*c.x + pts[1].z*c.y + pts[2].z*c.z;
// 由于深度buffer是用一张灰度图片存储的,要clamping到0-155
P.z = std::max(0, std::min(255, int(p_depth + .5)));
//P.z = std::max(0, std::min(255, int(pts[0].z*c.x + pts[1].z*c.y + pts[2].z*c.z + .5))); // clamping to 0-255 since it is stored in unsigned char
//if (c.x<0 || c.y<0 || c.z<0 || zbuffer.get(P.x, P.y)[0]>P.z) continue;
// 坐标的barycenti c都大于0, 则像素在三角形里面
// 否则略过该像素
if (c.x<0 || c.y<0 || c.z<0)
{
continue;
}
// 片元处理
bool discard = shader.fragment(c, color);
if (discard) {
continue;
}
float& pre_frag_d_r = zbuf_real[P.y*zbuffer.get_width() + P.x];
if (fragmentTestFlag& TI_GL_DEPTH_TEST)//是否深度测试
{
// zbuffer的该像素位置的深度值,前一个片元的深度
int pre_fragment_depth = zbuffer.get(P.x,P.y)[0];
// 如果P的深度比该屏幕位置的前一个片元的深度大
// 这里z是看的方向
if (p_depth >=pre_frag_d_r)
{
continue;
}
}
zbuffer.set(P.x, P.y, TGAColor(P.z));
pre_frag_d_r = p_depth;
image.set(P.x, P.y, color);
}
}
}
tgaimage.h tgaimage.cpp没有改动
main.cpp 很类似glut程序,先构造vbo,这里就是用model读取head.obj模型,也可以自己创建 test_createtriangle就是,调glutInit,传进去colorImage和depthImage两个图片,缺省的Gouraud光照shader
接着gluLookat设置观察方位, glFrustum设置透视参数,然后glDrawElements绘制vbo
main.cpp
#include <vector>
#include <iostream>
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include "our_gl.h"
const int width = 800;
const int height = 800;
Vec3f light_dir(1,1,1);
Vec3f eye(0.0,0.0,3);
Vec3f center(0.0,0.0,0);
Vec3f up(0,1,0);
TGAImage image(width, height, TGAImage::RGB);
TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);
struct GouraudShader : public IShader {
Vec3f varying_intensity; // written by vertex shader, read by fragment shader
Model* modelptr;
virtual Vec3i vertex(int iface, int nthvert) {
Vec4f gl_Vertex = embed<4>(modelptr->vert(iface, nthvert)); // read the vertex from .obj file
gl_Vertex = Viewport*Projection*ModelView*gl_Vertex; // transform it to screen coordinates
varying_intensity[nthvert] = std::max(0.f, modelptr->normal(iface, nthvert)*light_dir); // get diffuse lighting intensity
return proj<3>(gl_Vertex/gl_Vertex[3]); // project homogenious coordinates to 3d
}
virtual bool fragment(Vec3f bar, TGAColor &color) {
float intensity = varying_intensity*bar; // interpolate intensity for the current pixel
color = TGAColor(255, 255, 255)*intensity; // well duh
return false; // no, we do not discard this pixel
}
};
void test_createtriangle(Model*& model)
{
model = new Model();
model->verts_.push_back(Vec3f(-0.1,-0.1,-0.4));
model->verts_.push_back(Vec3f(0.1,-0.1,-0.4));
model->verts_.push_back(Vec3f(0.f,0.3,-0.4));
model->norms_.push_back(Vec3f(0.f,0.f,1.f));
model->norms_.push_back(Vec3f(0.f,0.f,1.f));
model->norms_.push_back(Vec3f(0.f,0.f,1.f));
model->faces_.resize(1);
model->faces_[0].push_back(Vec3i(0,0,0));
model->faces_[0].push_back(Vec3i(1,0,0));
model->faces_[0].push_back(Vec3i(2,0,0));
}
struct BoundBox3f
{
Vec3f _min;
Vec3f _max;
BoundBox3f()
{
_min = Vec3f(1e10,1e10,1e10);
_max = Vec3f(-1e10,-1e10,-1e10);
}
Vec3f getCenter()
{
return (_min+_max)*0.5f;
}
float getRadius()
{
Vec3f _v = _max - _min;
return sqrt(_v[0]*_v[0] + _v[1]*_v[1] +_v[2]*_v[2] )*0.5f;
}
};
void getModelBoundBox(Model* model, BoundBox3f& box)
{
auto& boxmin = box._min;
auto& boxmax = box._max;
boxmin = model->vert(0,0);
boxmax = boxmin;
for (int i=0; i<model->nfaces(); i++)
{
for (int j=0; j<3; j++)
{
auto v = model->vert(i,j);
for (int k = 0; k<3; k++)
{
if (v[k] > boxmax[k])
{
boxmax[k] = v[k];
}
if (v[k] < boxmin[k])
{
boxmin[k] = v[k];
}
}
}
}
}
using namespace our_gl;
int main(int argc, char** argv)
{
GouraudShader grdshader;
glutInit(width,height, &image, &zbuffer, &grdshader);
//构造vbo model阶段
Model* model = new Model("d:/models/obj/african_head.obj");
BoundBox3f model_box;
getModelBoundBox(model, model_box);
auto model_center = model_box.getCenter();
auto model_r = model_box.getRadius();
grdshader.modelptr = model;
float rv = model_r*0.66f;
Vec3f seeto = Vec3f(1.f,0.f,2.f).normalize();
Vec3f eyePos = Vec3f(0,0,0)+seeto*(model_r*3.5f);
gluLookAt(eyePos, Vec3f(0.f,0.f,0.f), up);
glViewport(0, 0, width, height);
glFrustum(-rv,rv,-rv,rv,model_r*2.f,model_r*4.5);
light_dir.normalize();
glClear(TI_GL_CLEAR_COLOR_BUFFER | TI_GL_CLEAR_DEPTH_BUFFER);
glEnable(TI_GL_DEPTH_TEST);
// glDrawElements
glDrawELements(model);
//
image.flip_vertically(); // to place the origin in the bottom left corner of the image
zbuffer.flip_vertically();
image.write_tga_file("output.tga");
zbuffer.write_tga_file("zbuffer.tga");
delete model;
glutFinal();
std::cout << "render model done.\n";
return 0;
}
最后write_tga_file将渲染(绘制)的图片保存到output.tga文件,打开如下