圖形流水線中光柵化原理與實現

光柵化主要解決的問題

在傳統圖形學流水線,技術難點可以分爲兩大類:

着色(shading) 涉及到後續局部光照模型以及全局光照模型。這類知識點在以後我學明白後再寫文章介紹給大家。

光柵化原理

  • 那什麼是可見性問題呢?簡單來說就是處在後面的物體應該被前面的物體所遮擋,從而在渲染結果上表現爲不可見(對於不透明物體來說是這樣,對於透明物體來說是顏色混合)。

  • 結合文章(圖形流水線中座標變換詳解:模型矩陣、視角矩陣、投影矩陣),所有三角形點在經過模型矩陣變換、視角矩陣變換、投影矩陣變化以及透視除法後,座標都變換到NDC座標系下(x,y,z∈【-1, 1】)。而在知道輸出屏幕大小的情況下,通過窗口變換可將x/y變換到窗口大小下(x∈【0,width】 y∈【0,height】z不變)。至此我們即將所有三角形投射到raster_space中。
    將三角形投射到窗口上

  • 光柵化的作用是判斷哪些像素點與三角形“干涉”(在三角形內部),並插值出三角形內部點的屬性值(Z值、顏色、法向、紋理座標等)。

在這裏插入圖片描述

  • 光柵化解決可見性(visibility) 思想:藉助Z-Buffer, FrameBuffer

    • Z-Buffer是一個與光柵圖像大小一致的二維數組,記錄着每個像素點的深度值。
    • Z-Buffer中所有值初始化爲inifity,當三角形上點P的Z值小於Z-Buffer中記錄的值時,Z-Buffer中相應位置深度值更新爲P點的Z值。否則不進行更新。
    • FrameBuffer也是一個與光柵圖像大小一致的二維數據,記錄着每個像素點的顏色值
    • 當Z-Buffer深度值更新時,FrameBuffer對應處顏色值也進行更新。
  • 光柵化流程:

//遍歷所有三角形
FOR 每個已經轉換到窗口座標系(raster space)下的三角形:
  //內部兩重循環 遍歷所有像素點
  FOR row in raster space:
    FOR column in raster space:
      pixelCoord = vec2(row, column)
      IF pixelCoord 在三角形內部:
        插值pixelCoord 的Z值
        用Z值與Z-Buffer做深度測試
        IF 通過深度測試:
          更新Z-Buffer對應處的Z值
          插值pixelCoord的其他頂點屬性(color, normal, uv)
          將像素點處的顏色值寫入FrameBuffer

根據光柵化步驟,我們可以提出兩個關鍵點:

  1. 如何判斷某個像素在三角形內部
  2. 如何正確插值頂點屬性

判斷像素在三角形內部or外部

前提條件:三角形都已投影到raster space空間中

  • 該問題可以簡化爲,在二維平面中,如何判斷一個點在三角形內部。如果你學過計算幾何中凸包求解問題。你可能記得裏面有一個TO-LEFT測試:一條向量無限延長後可以將平面分成兩部分,分別稱爲其左側和右側
    在這裏插入圖片描述
    給定三角形旋向,三角形三條邊即可以確定三條向量,如果對於一個點P,對這三條向量來說,它都處於同一側(都在左側(或者邊上)、都在右側(或者邊上)),那個我們可以稱點P它在三角形內部。具體情況如下圖所示:
    在這裏插入圖片描述
  • 向量叉乘判斷點P在向量的左側或右側或在直線上
    在這裏插入圖片描述
    因此只需要判斷三次叉乘結果是否同號(或者=0)即可判斷點是否在上三角形上
  • 判斷像素在三角形內部or外部 僞代碼

//edge Funciton
bool edgeFunction(vec2 p, vec2 a, vec2 b, vec2 c){
  //三角形三個點abc構成三個向量ab bc ca
  vec2 ab = b - a;
  vec2 bc = c - b;
  vec2 ca = a - c;

  //待判斷點p分別於abc構成三個向量 ap, bp, cp
  vec2 ap = p - a;
  vec2 bp = p - b;
  vec2 cp = p - c;
  //得到三次叉乘結果
   result1 = ab * ap;
   result2 = bc * bp;
   result3 = ca * cp;
  //判斷是否同號
  float threshold = 1e-5;
  if(result1 > -threshold && result2 > -threshold && result3 > -threshold)//都爲非負數
    return true;
  if(result1 < threshold && result2 < threshold && result3 ><threshold)//都爲非正數
    return true;
  return false;
}

頂點屬性插值

流水線中我們只會給三角形頂點賦值某些頂點屬性,光柵化需要根據三個頂點信息插值三角形內部點的頂點屬性。

重心座標系

對於三角形內部點P來說,它可以由頂點V0/V1/V2唯一表示:
p = λ0 * V0 + λ1 * V1 + λ2 * V2 且 λ0 >=0 λ1 >=0 λ2 >=0; λ0 + λ1 + λ2 = 1
(λ0, λ1, λ2)即V0V1V2組成的三角形內部點P的重心座標

同理我們可以用重心座標來插值頂點屬性
P_attribute = λ0 * V0_attribute + λ1 * V1_attribute + λ2 * V2_attribute
在這裏插入圖片描述

  • 既然可以用重心座標系來插值所有頂點屬性信息,那麼又如何計算重心座標系呢?
    在這裏插入圖片描述
    觀察上圖,你可以從中看出,重心座標系值與V0V1P/V1V2P/V2V0P這三個三角形的面積有關。
    在這裏插入圖片描述
    由向量叉乘的集合意義可知: 叉乘得到的行列式的值,即兩個向量所圍成的平行四邊形面積
    在這裏插入圖片描述
    結合判斷點P是否在三角形內部的函數。我們可以在判斷點P是否在三角形內部時,同時計算出P點的重心座標系值。這是不是非常巧妙!

//改進 edge Funciton, 判斷點p是否在三角形abc上時,
//同時返回其重心座標系值
bool edgeFunction(vec2 p, vec2 a, vec2 b, vec2 c, vector<< float>& barycentricCoord ){
  //三角形三個點abc構成三個向量ab bc ca
  vec2 ab = b - a;
  vec2 bc = c - b;
  vec2 ca = a - c;
   //三角形面積的兩倍
  float triangleArea = abs(ab * bc);
  //待判斷點p分別於abc構成三個向量 ap, bp, cp
  vec2 ap = p - a;
  vec2 bp = p - b;
  vec2 cp = p - c;
  //得到三次叉乘結果
   result1 = ab * ap;
   result2 = bc * bp;
   result3 = ca * cp;
  //計算重心座標系
  barycentricCoord.push_back(abs(result2) / triangleArea);
  barycentricCoord.push_back(abs(result3) / triangleArea);
  barycentricCoord.push_back(abs(result1) / triangleArea);
  //判斷是否同號
  float threshold = 1e-5;
  if(result1 > -threshold && result2 > -threshold && result3 > -threshold)//都爲非負數
    return true;
  if(result1 < threshold && result2 < threshold && result3 ><threshold)//都爲非正數
    return true;
  return false;
}

插值深度

直接插值Z值的問題

根據上文結論可得,對於三角形內部點p,其Z值可以由此插值:
P_z = λ0 * V0_z + λ1 * V1_z + λ2 * V2_z
但實際上直接使用此式子是錯誤的,因爲經過投影變換後,Z值不再滿足線性變化。下圖可以清楚的展現這種錯誤:
在這裏插入圖片描述
在投影前,P的Z值爲:
P.z = V0.z * (1 - 0.666) + V1.z * 0.666 = 4.001;
投影后,P‘的Z值爲
P’.z = V0.z * (1 - 0.8333) + V1.z * 0.8333 = 4.499;

正確的深度插值公式推導

在這裏插入圖片描述
在這裏插入圖片描述
因此正確的深度插值公式爲:
1 / P_z = λ0 * 1 / V0_z + λ1 * 1 / V1_z + λ2 * 1 / V2_z

透視校正

同理在插值其他頂點屬性(如顏色、法相、紋理座標)時,如果直接用重心座標系,也不正確。
因此正確的方法是藉助已經正確插值的Z值,屬性與Z值滿足線性變化關係。
在這裏插入圖片描述
在這裏插入圖片描述
因此正確的頂點屬性插值公式爲:
Attr = Z * [Attr0 / V0_z * λ0 +Attr1 / V1_z * λ1 + Attr2 / V2_z * λ2 ]
下圖展示了使用正確的屬性插值與不正確的屬性插值的顏色誤差。
在這裏插入圖片描述

代碼實現

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
vector2.h

//vector2.h
#pragma once
#include<iostream>

using namespace std;

template <typename T>
class Vector2 {
public:
	T x, y;
	float z;

public:
	//Vector(){}
	~Vector2() {}
	Vector2(T xx = 0, T yy = 0, float zz = 0) :x(xx), y(yy), z(zz) {}
	Vector2(Vector2& t) { x = t.x; y = t.y; z = t.z; }


	//標量乘除
	Vector2 multiplayByScalar(float a, Vector2<T>& result) {
		result.x = x * a;
		result.y = y * a;
		return result;
	}

	Vector2 divideByScalar(float a, Vector2<T>& result) {
		result.x = x / a;
		result.y = y / a;
		return result;
	}

	//矢量運算
	Vector2<T> add(Vector2& t, Vector2<T>& result) {
		result.x = x + t.x;
		result.y = y + t.y;

		return result;
	}

	Vector2<T> divide(Vector2& t, Vector2<T>& result) {
		result.x = x - t.x;
		result.y = y - t.y;

		return result;
	}

	float cross(Vector2& t) {
		return y * t.x - x * t.y;
	}


	//模場
	float length() {
		return sqrt(x * x + y * y);
	}

	//歸一化
	void normalize() {
		float l = this->length();
		if (l < 1e-5) {
			cout << "向量模長爲0.0,不能歸一化" << endl;
			return;
		}
		x /= l;
		y /= l;
	}

	//流運算
	friend ostream& operator<<(ostream& os, const Vector2<T>& t) {
		os << "(x, y, z):" << "(" << t.x << ", " << t.y << ", " << t.z << ")" << endl;
		return os;
	}

};

Vector3.h

//Vector3
#pragma once
#include<iostream>

using namespace std;

template <typename T>
class Vector3 {
public:
	T x, y, z;
	float w;

public:
	//Vector(){}
	~Vector3() {}
	Vector3(T xx = 0, T yy = 0, T zz = 0, float ww = 1.0) :x(xx), y(yy), z(zz), w(ww) {}
	Vector3(Vector3& t) { x = t.x; y = t.y; z = t.z; w = t.w; }
	

	//標量乘除
	Vector3 multiplayByScalar(float a, Vector3<T>& result) {
		result.x = x * a;
		result.y = y * a;
		result.z = z * a;
		return result;
	}

	Vector3 divideByScalar(float a, Vector3<T>& result) {
		result.x = x / a;
		result.y = y / a;
		result.z = z / a;
		return result;
	}
	
	//矢量運算
	Vector3<T> add(Vector3& t, Vector3<T>& result) {
		result.x = x + t.x;
		result.y = y + t.y;
		result.z = z + t.z;

		return result;
	}

	Vector3<T> divide(Vector3& t, Vector3<T>& result) {
		result.x = x - t.x;
		result.y = y - t.y;
		result.z = z - t.z;

		return result;
	}

	Vector3<T> cross(Vector3& t, Vector3<T>& result) {
		result.x = y * t.z - z * t.y;
		result.y = z * t.x - x * t.z;
		result.z = x * t.y - y * t.x;

		return result;
	}

	//比較運算
	bool equal(Vector3<T>& t) {
		float threshold = 1e-5;
		if (abs(x - t.x) < threshold && abs(y - t.y) < threshold && abs(z - t.z) < threshold)
			return true;
		return false;
	}

	//透視除法
	void perspectiveDivision() {
		x = x / T(w);
		y = y / T(w);
		z = z / T(w);
		w = 1.0f;
	}

	//模場
	float length() {
		return sqrt(x * x + y * y + z * z);
	}

	//歸一化
	void normalize() {
		float l = this->length();
		if (l < 1e-5) {
			cout << "向量模長爲0.0,不能歸一化" << endl;
			return ;
		}
		x /= l;
		y /= l;
		z /= l;
	}

	//流運算
	friend ostream& operator<<(ostream& os, const Vector3<T>& t) {
		os << "(x, y, z, w):" << "(" << t.x << ", " << t.y << ", " << t.z << ", " << t.w << ")" << endl;
		return os;
	}

};

Matrix4.h

//Matrix4.h
#pragma once
#include "Vector3.h"

class Matrix4
{
public:
	Matrix4();
	~Matrix4();

	Matrix4(float a0, float a1, float a2, float a3,
		float a4, float a5, float a6, float a7,
		float a8, float a9, float a10, float a11,
		float a12, float a13, float a14, float a15);

	Matrix4(Matrix4& t);

	//重置爲單位矩陣
	void setIdentityMatrix();
	//求矩陣的行列式的值
	float getDeterminant();
	//求矩陣的逆矩陣
	Matrix4 invert();
	//矩陣與向量/點相乘
	Vector3<float> multiplyByVector(Vector3<float> v);
	//矩陣與矩陣相乘
	Matrix4 multiplyByMatrix4(Matrix4& m);

	//交換矩陣的兩行
	void swapRow(int row1, int row2);

	//輸出矩陣
	friend ostream& operator<<(ostream& os, Matrix4& t);


public:
	float m[16];

};


Matrix4.cpp

//Matrix4.cpp
#include "Matrix4.h"


Matrix4::Matrix4()
{
	m[0] = m[5] = m[10] = m[15] = 1.0f;
	m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0.0f;
}

Matrix4::~Matrix4(){}

Matrix4::Matrix4(float a0, float a1, float a2, float a3,
	float a4, float a5, float a6, float a7,
	float a8, float a9, float a10, float a11,
	float a12, float a13, float a14, float a15) {
	m[0] = a0; m[1] = a1; m[2] = a2; m[3] = a3;
	m[4] = a4; m[5] = a5; m[6] = a6; m[7] = a7;
	m[8] = a8; m[9] = a9; m[10] = a10; m[11] = a11;
	m[12] = a12; m[13] = a13; m[14] = a14; m[15] = a15;
}

Matrix4::Matrix4(Matrix4& t) {
	for (int i = 0; i < 16; i++)
		m[i] = t.m[i];
}

//重置爲單位矩陣
void Matrix4::setIdentityMatrix() {
	m[0] = m[5] = m[10] = m[15] = 1.0f;
	m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0.0f;
}

//求矩陣的行列式的值
float Matrix4::getDeterminant() {
	return m[0] * m[5] * m[10] * m[15] + m[1] * m[6] * m[11] * m[12] + m[2] * m[7] * m[8] * m[13] + m[3] * m[4] * m[9] * m[14]
		- m[3] * m[6] * m[9] * m[12] - m[7] * m[10] * m[13] * m[0] - m[11] * m[14] * m[1] * m[4] - m[15] * m[2] * m[5] * m[8];
}

//求矩陣的逆矩陣
/* C++實現矩陣求逆 採用  Gauss-Jordan elimination method
** 原理:利用行變換和新建一個單位陣,當前矩陣通過行變換變爲單位陣時,單位陣通過同樣的行變換會變成矩陣的逆
** 行變換:1.任意交換兩行	2.一行加減除了這行的其他行  3.一行乘除一個數

** 步驟:
** 1.通過交換行將對角線上所有值變爲非0值
** 2.通過行變換按列從左到右將非對角線上的值變換爲0
** 3.通過行縮放,將對角線上的值變爲1
*/
Matrix4 Matrix4::invert() {
	Matrix4 invertM = Matrix4();
	int row = 0, column = 0;
	float tempM[16];
	for (int i = 0; i < 16; i++)
		tempM[i] = m[i];
	int matrixSize = 4;
	float threshold = 1e-5f;
	//1.通過交換行將對角線上所有值變爲非0值
	for (column = 0; column < matrixSize; column++) {
		if (abs(tempM[column * matrixSize + column]) <= threshold) {
			//對角線上值爲0 需要交換
			int swapRow = column;
			for (int i = 0; i < matrixSize; i++) {
				if (abs(tempM[i * matrixSize + column]) > threshold && abs(tempM[column * matrixSize + i]) > threshold)
					swapRow = i;
			}
			
			if (swapRow == column) {
				cout << "該矩陣沒有逆矩陣" << endl;
				return invertM;
			}
			else {
				//交換
				for (int i = 0; i < matrixSize; i++) {
					float temp = tempM[column * matrixSize + i];
					tempM[column * matrixSize + i] = tempM[swapRow * matrixSize + i];
					tempM[swapRow * matrixSize + i] = temp;
				}
				invertM.swapRow(column, swapRow);
			}

		}

		//2.通過行變換按列從左到右將非對角線上的值變換爲0
		//記錄對角線的值
		float pivotsValue = tempM[column * matrixSize + column];

		for (row = 0; row < matrixSize; row++) {
			//如果處於對角線上 則跳過
			if (column == row)
				continue;
			//如果不處於對角線上 則將這一列上其他所有值變爲0.0
			float coeff = tempM[row * matrixSize + column] / pivotsValue;

			//
			for (int i = 0; i < matrixSize; i++) {
				tempM[row * matrixSize + i] -= coeff * tempM[column * matrixSize + i];
				invertM.m[row * matrixSize + i] -= coeff * invertM.m[column * matrixSize + i];
			}

			tempM[row * matrixSize + column] = 0.0f;
		}


	}

	//3.通過行縮放,將對角線上的值變爲1
	for (row = 0; row < matrixSize; row++) {
		float coeff = 1.0f / tempM[row * matrixSize + row];
		for (int i = 0; i < matrixSize; i++) {
			tempM[row * matrixSize + i] *= coeff;
			invertM.m[row * matrixSize + i] *= coeff;
		}
	}

	return invertM;
}

//矩陣與向量/點相乘
/* 矩陣是行主序,右乘 this * v
** 
*/

Vector3<float> Matrix4::multiplyByVector(Vector3<float> v) {
	Vector3<float> reusltV;
	reusltV.x = float(m[0] * v.x + m[1] * v.y + m[2] * v.z + m[3] * v.w);
	reusltV.y = float(m[4] * v.x + m[5] * v.y + m[6] * v.z + m[7] * v.w);
	reusltV.z = float(m[8] * v.x + m[9] * v.y + m[10] * v.z + m[11] * v.w);
	reusltV.w = float(m[12] * v.x + m[13] * v.y + m[14] * v.z + m[15] * v.w);

	return reusltV;
}


//矩陣與矩陣相乘 this * M
Matrix4 Matrix4::multiplyByMatrix4(Matrix4& M) {
	Matrix4 resultM;
	resultM.m[0] = m[0] * M.m[0] + m[1] * M.m[4] + m[2] * M.m[8] + m[3] * M.m[12];
	resultM.m[1] = m[0] * M.m[1] + m[1] * M.m[5] + m[2] * M.m[9] + m[3] * M.m[13];
	resultM.m[2] = m[0] * M.m[2] + m[1] * M.m[6] + m[2] * M.m[10] + m[3] * M.m[14];
	resultM.m[3] = m[0] * M.m[3] + m[1] * M.m[7] + m[2] * M.m[11] + m[3] * M.m[15];

	resultM.m[4] = m[4] * M.m[0] + m[5] * M.m[4] + m[6] * M.m[8] + m[7] * M.m[12];
	resultM.m[5] = m[4] * M.m[1] + m[5] * M.m[5] + m[6] * M.m[9] + m[7] * M.m[13];
	resultM.m[6] = m[4] * M.m[2] + m[5] * M.m[6] + m[6] * M.m[10] + m[7] * M.m[14];
	resultM.m[7] = m[4] * M.m[3] + m[5] * M.m[7] + m[6] * M.m[11] + m[7] * M.m[15];

	resultM.m[8] = m[8] * M.m[0] + m[9] * M.m[4] + m[10] * M.m[8] + m[11] * M.m[12];
	resultM.m[9] = m[8] * M.m[1] + m[9] * M.m[5] + m[10] * M.m[9] + m[11] * M.m[13];
	resultM.m[10] = m[8] * M.m[2] + m[9] * M.m[6] + m[10] * M.m[10] + m[11] * M.m[14];
	resultM.m[11] = m[8] * M.m[3] + m[9] * M.m[7] + m[10] * M.m[11] + m[11] * M.m[15];

	resultM.m[12] = m[12] * M.m[0] + m[13] * M.m[4] + m[14] * M.m[8] + m[15] * M.m[12];
	resultM.m[13] = m[12] * M.m[1] + m[13] * M.m[5] + m[14] * M.m[9] + m[15] * M.m[13];
	resultM.m[14] = m[12] * M.m[2] + m[13] * M.m[6] + m[14] * M.m[10] + m[15] * M.m[14];
	resultM.m[15] = m[12] * M.m[3] + m[13] * M.m[7] + m[14] * M.m[11] + m[15] * M.m[15];

	return resultM;
}


//交換矩陣的兩行
void Matrix4::swapRow(int row1, int row2) {
	int matrixSize = 4;

	for (int i = 0; i < matrixSize; i++) {
		float temp = m[row1 * matrixSize + i];
		m[row1 * matrixSize + i] = m[row2 * matrixSize + i];
		m[row2 * matrixSize + i] = temp;
	}
}

//輸出矩陣
ostream& operator<<(ostream& os, Matrix4& t) {
	os << "[" << t.m[0] << ",\t" << t.m[1] << ",\t" << t.m[2] << ",\t" << t.m[3] << "]" << endl;
	os << "[" << t.m[4] << ",\t" << t.m[5] << ",\t" << t.m[6] << ",\t" << t.m[7] << "]" << endl;
	os << "[" << t.m[8] << ",\t" << t.m[9] << ",\t" << t.m[10] << ",\t" << t.m[11] << "]" << endl;
	os << "[" << t.m[12] << ",\t" << t.m[13] << ",\t" << t.m[14] << ",\t" << t.m[15] << "]" << endl;
	return os;
}

Camera .h

//Camera .h
#pragma once

#include "Vector3.h"
#include "Matrix4.h"
const float PI = 3.14159f;

class Camera {
public :
	Vector3<float> position;
	Vector3<float> direction;
	Vector3<float> up;

	float nearClippingPlane;
	float farClippingPlane;
	float horizonFov;
	float verticalFov;

	Matrix4 cameraCoord;

public:
	Camera();
	~Camera();
	Camera(Vector3<float>& p);

	//設置direction 和 up
	void lookAt(Vector3<float> d, Vector3<float> u);

	//設置前後裁剪面
	void setClippingPlane(float n, float f);

	//設置hf 和 vf
	void setHFandVF(float hf, float vf);
	
	//計算視角矩陣函數羣
	Matrix4 getCameraMatrix();
	Matrix4 getViewMatrix();

	//計算投影矩陣函數羣
	//Matrix4 getProjectionMatrix_rasterSpace();
	Matrix4 getProjectionMatrix_NDCSpace();


};

Camera.cpp

//Camera.cpp
#include "Camera.h"
Camera::Camera() {
	position = Vector3<float>(0.0f, 0.0f, 0.0f);
	direction = Vector3<float>(0.0f, 0.0f, -1.0f, 0.0f);
	up = Vector3<float>(0.0f, 1.0f, 0.0f, 0.0f);
	nearClippingPlane = 1.0f;
	farClippingPlane = 100.0f;
	horizonFov = verticalFov = 90.0f;
}

Camera::~Camera() {}

Camera::Camera(Vector3<float>& p) {
	position = Vector3<float>(p);
	direction = Vector3<float>(0.0f, 0.0f, -1.0f, 0.0f);
	up = Vector3<float>(0.0f, 1.0f, 0.0f, 0.0f);
	nearClippingPlane = 1.0f;
	farClippingPlane = 100.0f;
	horizonFov = verticalFov = 90.0f;
}

//設置direction 和 up
void Camera::lookAt(Vector3<float> d, Vector3<float> u) {
	direction = Vector3<float>(d);
	up = Vector3<float>(u);
}

//設置前後裁剪面
void Camera::setClippingPlane(float n, float f) {
	nearClippingPlane = n;
	farClippingPlane = f;
}

//設置hf 和 vf
void  Camera::setHFandVF(float hf, float vf) {
	horizonFov = hf;
	verticalFov = vf;
}

//計算相機座標系
/* 得到相機座標系基底的矩陣表示
** 視角座標系Z方向與direction方向相反  W = -normalize(direction)
** 視角座標系X方向 U = Up * W
** 視角座標系Y方向 V = W * U
*/
Matrix4 Camera::getCameraMatrix() {
	Vector3<float> W(1.0, 1.0, 1.0, 0.0);

	direction.multiplayByScalar(-1.0, W);
	W.normalize();
	up.normalize();

	Vector3<float> U(1.0, 1.0, 1.0, 0.0);
	up.cross(W, U);
	U.normalize();

	Vector3<float> V(1.0, 1.0, 1.0, 0.0);
	W.cross(U, V);
	V.normalize();

	Matrix4 cameraCoord = Matrix4();
	/*
	 [U.x. V.x, W.x, camera.x ]
	 [U.y, V.y, W.y, camera.y ]
	 [U.z, V.z, W.z, camera.z ]
	 [0.0, 0.0, 0.0, 1.0      ]
	*/
	cameraCoord.m[0] = U.x;
	cameraCoord.m[4] = U.y;
	cameraCoord.m[8] = U.z;
	cameraCoord.m[12] = 0.0;

	cameraCoord.m[1] = V.x;
	cameraCoord.m[5] = V.y;
	cameraCoord.m[9] = V.z;
	cameraCoord.m[13] = 0.0;

	cameraCoord.m[2] = W.x;
	cameraCoord.m[6] = W.y;
	cameraCoord.m[10] = W.z;
	cameraCoord.m[14] = 0.0;

	cameraCoord.m[3] = position.x;
	cameraCoord.m[7] = position.y;
	cameraCoord.m[11] = position.z;
	cameraCoord.m[15] = 1.0;

	this->cameraCoord = cameraCoord;
	return cameraCoord;
}

//計算圖形流水線中模型變化(世界座標系--->視角座標系)的 視角矩陣
Matrix4 Camera::getViewMatrix() {
	Matrix4 cameraCoord = this->getCameraMatrix();
	return cameraCoord.invert();
}

////計算圖形流水線中模型變化(視角座標系--->rasterSpace)的投影矩陣
//Matrix4 Camera::getProjectionMatrix_rasterSpace() {
//	
//}

//計算圖形流水線中模型變化(視角座標系--->NDCSpace)的投影矩陣
Matrix4 Camera::getProjectionMatrix_NDCSpace() {
	/*
	** 投影矩陣的具體推導請看 https://blog.csdn.net/qq_27161673
	** [ 2N / (r - l), 0           ,  (r + l)/(r - l),  0 ]
	** [ 0           , 2N / (t - b),  (t + b)/(t - b),  0 ]
	** [ 0           , 0           ,  (N + F)/(N - F),  2NF / (N - F)   ]
	** [ 0           , 0           ,  -1             ,  0               ]
	*/

	//角度轉弧度
	float horizonFov_radian = horizonFov * PI / 180.0f;
	float verticalFov_radian = verticalFov * PI / 180.0f;

	/*
	** r = N * tan(horizonFov_radian / 2);
	** l = -r;
	** t = N * tan(verticalFov_radian / 2);
	** t = -b;
	*/
	float r = nearClippingPlane * tan(horizonFov_radian / 2.0f);
	float l = -r;
	float t = nearClippingPlane * tan(verticalFov_radian / 2.0f);
	float b = -t;

	Matrix4 projectionMatrix = Matrix4();

	projectionMatrix.m[0] = nearClippingPlane / r;
	projectionMatrix.m[1] = 0.0f;
	projectionMatrix.m[2] = 0.0f;
	projectionMatrix.m[3] = 0.0f;

	projectionMatrix.m[4] = 0.0f;
	projectionMatrix.m[5] = nearClippingPlane / t;
	projectionMatrix.m[6] = 0.0f;
	projectionMatrix.m[7] = 0.0f;

	projectionMatrix.m[8] = 0.0f;
	projectionMatrix.m[9] = 0.0f;
	projectionMatrix.m[10] = (nearClippingPlane + farClippingPlane) / (nearClippingPlane - farClippingPlane);
	projectionMatrix.m[11] = 2.0f * nearClippingPlane * farClippingPlane / (nearClippingPlane - farClippingPlane);

	projectionMatrix.m[12] = 0.0f;
	projectionMatrix.m[13] = 0.0f;
	projectionMatrix.m[14] = -1.0f;
	projectionMatrix.m[15] = 0.0f;

	return projectionMatrix;
}

main.cpp

/************************************************************************/
/*2019-12-29 自己的光柵化程序                                           */
/************************************************************************/

/*
** 分成兩個階段
** 一、實現流水線中座標變化過程:包括模型矩陣、視角矩陣以及投影矩陣。
** 二、實現光柵化:包括隱藏面消除和頂點屬性插值
** 
** 本程序分爲以下幾個類
**
*/

#include "Camera.h"
#include "Vector2.h"
#include <vector>
#include <fstream>

bool edgeFunction(Vector2<float> p, Vector2<float>a, Vector2<float>b, Vector2<float>c, vector<float>& barycentricCoord);

int main() {
	/************************************************************************/
	/* 設定窗口大小                                                                     */
	/************************************************************************/
	int screen_width = 400;
	int screen_height = 400;

	/************************************************************************/
	/* 設定相機                                                                      */
	/************************************************************************/
	// 設定相機位置
	Vector3<float> cameraPosition = Vector3<float>(0.0f, 0.0f, 3.0f, 1.0f);
	Camera camera = Camera(cameraPosition);
	//設定相聚 看向方向direction 和相機上方向up
	camera.lookAt(Vector3<float>(0.0f, 0.0f, -1.0f, 0.0f), Vector3<float>(0.0f, 1.0f, 0.0f, 0.0f));
	//設定視域前/後裁剪面
	camera.setClippingPlane(1.0f, 10.0f);
	//設定水平/垂直視域角度
	camera.setHFandVF(90.0f, 90.0f);

	/************************************************************************/
	/* 給定一個三角形數據(默認世界座標系)                                                                     */
	/************************************************************************/
	Vector3<float> A_world = Vector3<float>(-2.0f, -1.0f, 0.0f, 1.0f);
	Vector3<float> B_world = Vector3<float>(2.0f, -1.0f, 1.0f, 1.0f);
	Vector3<float> C_world = Vector3<float>(-1.0f, 1.0f, -3.0f, 1.0f);
	cout << "A_world" << A_world << endl;
	cout << "B_world" << B_world << endl;
	cout << "C_world" << C_world << endl;
	Vector3<float> D_world = Vector3<float>(-2.0f, -1.0f, 1.0f, 1.0f);
	Vector3<float> E_world = Vector3<float>(2.0f, -1.0f, 0.0f, 1.0f);
	Vector3<float> F_world = Vector3<float>(1.0f, 1.0f, -3.0f, 1.0f);

	/************************************************************************/
	/* 轉換到視角座標系: p_view = viewMatrix * p_world                 */
	/************************************************************************/
	Matrix4 viewMatrix = camera.getViewMatrix();
	Vector3<float> A_view = viewMatrix.multiplyByVector(A_world);
	Vector3<float> B_view = viewMatrix.multiplyByVector(B_world);
	Vector3<float> C_view = viewMatrix.multiplyByVector(C_world);
	cout << "A_view" << A_view << endl;
	cout << "B_view" << B_view << endl;
	cout << "C_view" << C_view << endl;
	Vector3<float> D_view = viewMatrix.multiplyByVector(D_world);
	Vector3<float> E_view = viewMatrix.multiplyByVector(E_world);
	Vector3<float> F_view = viewMatrix.multiplyByVector(F_world);

	/************************************************************************/
	/* 轉換到NDC座標系 :p_pro = projectionMatrix * p_view                                              */
	/************************************************************************/
	Matrix4 projectionMatrix = camera.getProjectionMatrix_NDCSpace();
	Vector3<float> A_pro = projectionMatrix.multiplyByVector(A_view);
	Vector3<float> B_pro = projectionMatrix.multiplyByVector(B_view);
	Vector3<float> C_pro = projectionMatrix.multiplyByVector(C_view);
	cout << "透視除法前 A_pro" << A_pro << endl;
	cout << "透視除法前 B_pro" << B_pro << endl;
	cout << "透視除法前 C_pro" << C_pro << endl;
	//透視除法
	A_pro.perspectiveDivision();
	B_pro.perspectiveDivision();
	C_pro.perspectiveDivision();
	cout << "A_pro" << A_pro << endl;
	cout << "B_pro" << B_pro << endl;
	cout << "C_pro" << C_pro << endl;
	Vector3<float> D_pro = projectionMatrix.multiplyByVector(D_view);
	Vector3<float> E_pro = projectionMatrix.multiplyByVector(E_view);
	Vector3<float> F_pro = projectionMatrix.multiplyByVector(F_view);
	D_pro.perspectiveDivision();
	E_pro.perspectiveDivision();
	F_pro.perspectiveDivision();

	/************************************************************************/
	/* 轉換到視圖座標系:p_screen = width/height * p_pro                                                                     */
	/************************************************************************/
	float half_screen_width = screen_width / 2;
	float half_screen_height = screen_height / 2;
	Vector2<float> A_screen = Vector2<float>(float((A_pro.x + 1.0f) * half_screen_width), float((1.0f - A_pro.y) * half_screen_height), A_view.z);
	Vector2<float> B_screen = Vector2<float>(float((B_pro.x + 1.0f) * half_screen_width), float((1.0f - B_pro.y) * half_screen_height), B_view.z);
	Vector2<float> C_screen = Vector2<float>(float((C_pro.x + 1.0f) * half_screen_width), float((1.0f - C_pro.y) * half_screen_height), C_view.z);
	cout << "A_screen" << A_screen << endl;
	cout << "B_screen" << B_screen << endl;
	cout << "C_screen" << C_screen << endl;
	Vector2<float> D_screen = Vector2<float>(float((D_pro.x + 1.0f) * half_screen_width), float((1.0f - D_pro.y) * half_screen_height), D_view.z);
	Vector2<float> E_screen = Vector2<float>(float((E_pro.x + 1.0f) * half_screen_width), float((1.0f - E_pro.y) * half_screen_height), E_view.z);
	Vector2<float> F_screen = Vector2<float>(float((F_pro.x + 1.0f) * half_screen_width), float((1.0f - F_pro.y) * half_screen_height), F_view.z);

	//點數組
	vector<Vector2<float>> vertice = vector<Vector2<float>>(6);
	vertice[0] = (A_screen);
	vertice[1] = (B_screen);
	vertice[2] = (C_screen);
	vertice[3] = (D_screen);
	vertice[4] = (E_screen);
	vertice[5] = (F_screen);

	//索引數組
	vector<unsigned int> indices = vector<unsigned int>();
	indices.push_back(0);
	indices.push_back(1);
	indices.push_back(2);
	indices.push_back(3);
	indices.push_back(4);
	indices.push_back(5);

	//點顏色數組
	Vector3<unsigned char> red = Vector3<unsigned char>(255, 0,0);
	Vector3<unsigned char> green = Vector3<unsigned char>(255, 255, 0);
	Vector3<unsigned char> blue = Vector3<unsigned char>(255, 0, 255);
	Vector3<unsigned char> red1 = Vector3<unsigned char>(0, 0, 255);
	Vector3<unsigned char> green1 = Vector3<unsigned char>(0, 255, 255);
	Vector3<unsigned char> blue1 = Vector3<unsigned char>(255, 0, 255);
	vector<Vector3<unsigned char>> colors = vector<Vector3<unsigned char>>(6);
	colors[0] = (red);
	colors[1] = (green);
	colors[2] = (blue);
	colors[3] = (red1);
	colors[4] = (green1);
	colors[5] = (blue1);

	/************************************************************************/
	/* 光柵化
	** 1.判斷每個像素點是否在screen space空間的三角形中
	** 2.如果在 計算其三角形重心座標,並進行頂點屬性插值
	** 3.將顏色信息寫入frame buffer中
	** 
	*/
	/************************************************************************/

	//創建Z-Buffer 和 FrameBuffer
	//創建Z-Buffer 記錄FrameBuffer對應像素點的深度值
	int bufferSize = screen_width * screen_height;
	float* zBuffer = new float[bufferSize];
	//創建FrameBuffer
	vector<vector<float>> framebuffer(bufferSize); 
	//Z-Buffer初始化爲最大值  FrameBuffer初始化爲黑色
	for (int i = 0; i < bufferSize; i++) {
		zBuffer[i] = FLT_MAX;
		framebuffer[i] = vector<float>(4);
		for (int j = 0; j < 4; j++)
			framebuffer[i][j] = 0.0f;
	}

	
	

	//遍歷所有像素
	for (int row = 0; row < screen_height; row++) {
		for (int column = 0; column < screen_width; column++) {
			
			//當前像素點座標
			Vector2<float> pixelCoord = Vector2<float>(float(column) + 0.5f, float(row) + 0.5f);

			//遍歷所有三角形
			int triangleNum = indices.size() / 3;
			for (int triangleIndex = 0; triangleIndex < triangleNum ; triangleIndex++) {
				//三角形三個點座標(screen space座標系下) 默認逆時針方向
				Vector2<float> a = vertice[indices[triangleIndex * 3]];
				Vector2<float> b = vertice[indices[triangleIndex * 3 + 1]];
				Vector2<float> c = vertice[indices[triangleIndex * 3 + 2]];

				//三角形三個點的顏色
				Vector3<unsigned char> a_color = colors[indices[triangleIndex * 3]];
				Vector3<unsigned char> b_color = colors[indices[triangleIndex * 3 + 1]];
				Vector3<unsigned char> c_color = colors[indices[triangleIndex * 3 + 2]];

				//記錄像素點的重心座標
				vector<float> barycentricCoord = vector<float>();


				//edge Function
				if (edgeFunction(pixelCoord, a, b, c, barycentricCoord) == false)
					continue;
				else {

					//插值像素點的Z座標
					// 1 / z = λ * 1/ z0 + (1-λ) * 1 /z1
					pixelCoord.z = 1.0f / (barycentricCoord[0] / a.z + barycentricCoord[1] / b.z + barycentricCoord[2] / c.z);

					//深度測試 只有Z座標小於0的物體才能被看到
					if (pixelCoord.z < 1e-5 && abs(pixelCoord.z) < zBuffer[row * screen_width + column] && (-pixelCoord.z) >= camera.nearClippingPlane && (-pixelCoord.z) <= camera.farClippingPlane) {
						zBuffer[row * screen_width + column] = abs(pixelCoord.z);
	
						//未使用透視矯正
						/*float R = (barycentricCoord[0] * a_color.x + barycentricCoord[1] * b_color.x  + barycentricCoord[2] * c_color.x );
						float G = (barycentricCoord[0] * a_color.y + barycentricCoord[1] * b_color.y  + barycentricCoord[2] * c_color.y );
						float B = (barycentricCoord[0] * a_color.z + barycentricCoord[1] * b_color.z  + barycentricCoord[2] * c_color.z );*/
						
						//插值像素點處的頂點屬性(目前只有顏色)  使用透視矯正
						// attribute = z * [attribute0 / z0 * λ + attribute1 / z1 * (1-λ)]
						float R = pixelCoord.z * (barycentricCoord[0] * float(a_color.x) / a.z + barycentricCoord[1] * float(b_color.x) / b.z + barycentricCoord[2] * float(c_color.x) / c.z);
						float G = pixelCoord.z * (barycentricCoord[0] * float(a_color.y) / a.z + barycentricCoord[1] * float(b_color.y) / b.z + barycentricCoord[2] * float(c_color.y )/ c.z);
						float B = pixelCoord.z * (barycentricCoord[0] * float(a_color.z) / a.z + barycentricCoord[1] * float(b_color.z) / b.z + barycentricCoord[2] * float(c_color.z) / c.z);

			
						//更新frameBuffer中顏色值
						vector<float> piexlColor = vector<float>(4);
						piexlColor[0] = R / 255.0f; piexlColor[1] = G / 255.0f; piexlColor[2] = B / 255.0f; piexlColor[3] = 1.0f;
						framebuffer[row * screen_width + column] = piexlColor;

					}
					
				}

			}

		}
	}

	//將framebuffer中的數據寫入圖片中
	std::ofstream ofs;
	ofs.open("./output.ppm");
	ofs << "P3\n" << screen_width << ' ' << screen_height << "\n255\n";
	for (int i = 0; i < screen_height; i++)
	{
		for (int j = 0; j < screen_width; j++)
		{
			float r = framebuffer[i * screen_width + j][0];
			float g = framebuffer[i * screen_width + j][1];
			float b = framebuffer[i * screen_width + j][2];
			int ir = int(255.99 * r);
			int ig = int(255.99 * g);
			int ib = int(255.99 * b);
			ofs << ir << ' ' << ig << ' ' << ib << '\n';
		}
	}
	ofs.close();
	delete[] zBuffer;

	return 0;
}

/************************************************************************/
/* 判斷一個點是否在三角形內部                                           */
/************************************************************************/
bool edgeFunction(Vector2<float> p, Vector2<float>a, Vector2<float>b, Vector2<float>c, vector<float>& barycentricCoord) {
	//根據旋向和三個點座標組成三個向量
	Vector2<float> ab = Vector2<float>(b.x - a.x, b.y - a.y);
	Vector2<float> bc = Vector2<float>(c.x - b.x, c.y - b.y);
	Vector2<float> ca = Vector2<float>(a.x - c.x, a.y - c.y);

	Vector2<float> ap = Vector2<float>(p.x - a.x, p.y - a.y);
	Vector2<float> bp = Vector2<float>(p.x - b.x, p.y - b.y);
	Vector2<float> cp = Vector2<float>(p.x - c.x, p.y - c.y);

	float result1 = ab.cross(ap);
	float result2 = bc.cross(bp);
	float result3 = ca.cross(cp);
	float yuzhi = .1f;
	if ((result1 > -yuzhi && result2 > -yuzhi && result3 > -yuzhi) || (result1 < yuzhi && result2 < yuzhi && result3 < yuzhi) ) {
		//計算重心座標系
		float tirangle_area = abs(ab.cross(bc));
		/*cout << tirangle_area << endl;*/
		barycentricCoord.push_back(abs(result2 / tirangle_area));
		barycentricCoord.push_back(abs(result3 / tirangle_area));
		barycentricCoord.push_back(abs(result1 / tirangle_area));
		return true;
	}

	return false;

}
發佈了76 篇原創文章 · 獲贊 83 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章