天天看點

軟體光栅化渲染器知識總結

簡單的CVV裁剪

經過了透視變換,坐标被變換到CVV空間,此時仍然是齊次坐标,我們正常應該是判斷在裁剪的立方體内,不過齊次坐标我們也就是直接比較xyz值和w的值即可,DX模式的話,z需要比較0和w。這個是非常重要的,因為我們預設為了友善是把投影平面放到了眼睛前面,但是真的有在投影平面後面的東西,如果不剔除z<0的内容,就會導緻這一部分按照不對的透視公式進行計算導緻結果錯誤。而且更重要的一點在于,相機空間z = 0的時候(也就是齊次空間的w = 0)的這種情況,在我們透視除法的時候會有除0的問題。是以要把這個剔除掉。

比如一個齊次空間的頂點,我們可以按照上述方式判斷其是否在CVV内:

// 判斷點vertex是否在CVV内,在的話傳回false
bool SimpleCVVCullCheck(const Vertex& vertex)
{
	float w = vertex.pos.w;
	// x,y,z隻要有一個不符合範圍就傳回true
	if (vertex.pos.x < -w || vertex.pos.x > w)
		return true;
	if (vertex.pos.y < -w || vertex.pos.y > w)
		return true;
	if (vertex.pos.z < 0.0f || vertex.pos.z > w)
		return true;
	return false;
}
           

CVV裁剪個人感覺是一個比較有争議的地方,現代的GPU到底如何去做裁剪,我不敢妄加推測,看了知乎上的讨論,也是分為幾個派别。有認為裁剪的,有人為隻剔除不裁剪的。不過個人倒比較贊同,重新建構一個三角形對于GPU來說還不如把整個三角形都畫了好,畢竟實際運用時,三角形的密度很大,面積很小,都繪制了也要比裁剪可能還省。對于CVV中比較好處理的主要在于我們可以在透視除法前就把完全不可見的三角形直接剔除掉。是以我隻實作了最簡單的三頂點均不在CVV内剔除的方案

參考 從迷你光栅化軟渲染器的實作看渲染流水線

背面剔除

正向背向面剔除可以在NDC中進行,先計算三角形表面法向量,根據法向量和view向量的夾角進行剔除:

enum Face {
	Back,
	Front
};
//面剔除,剔除正向面或者逆向面
bool FaceCull(Face face, const glm::vec4 &v1, const glm::vec4 &v2, const glm::vec4 &v3) {

	glm::vec3 tmp1 = glm::vec3(v2.x - v1.x, v2.y - v1.y, v2.z - v1.z);
	glm::vec3 tmp2 = glm::vec3(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z);

	//叉乘得到法向量
	glm::vec3 normal = glm::normalize(glm::cross(tmp1, tmp2));
	//glm::vec3 view = glm::normalize(glm::vec3(v1.x - camera->Position.x, v1.y - camera->Position.y, v1.z - camera->Position.z));
	//NDC中觀察方向指向+z
	glm::vec3 view = glm::vec3(0, 0, 1);
	if (face == Back)
		return glm::dot(normal, view) > 0;
	else
		return glm::dot(normal, view) < 0;
}
           

參考 一篇文章徹底弄懂齊次裁剪

深度測試

ZBuffer

透視插值

三角形面上的正确插值不是線性的,這是由于在投影平面上的相同步長随着三角形面與相機之間的距離增加而在三角形面上産生了更大的步長。圖形處理器必須采用非線性插值的方法來計算紋理坐标映射,以避免紋理映射圖的扭曲變形。

透視插值的公式:

軟體光栅化渲染器知識總結
軟體光栅化渲染器知識總結

Phong着色

頂點屬性經過插值後,在像素着色器中進行着色

切線空間法線貼圖

用三角形的頂點坐标以及紋理坐标可以計算切線向量和副切線向量。然後可以建構可以把切線坐标空間的向量轉換到世界坐标空間的矩陣TBN矩陣。TBN矩陣這三個字母分别代表tangent、bitangent和normal向量:這三個互相垂直的向量,它們沿一個表面的法線貼圖對齊于:上、右、前。

參考 法線貼圖

陰影映射

方向光陰影

第一步,我們需要生成一張深度貼圖(Depth Map)。深度貼圖是從光的透視圖裡渲染的深度紋理,用它計算陰影。因為我們使用的是一個所有光線都平行的定向光。出于這個原因,我們将為光源使用正交投影矩陣,透視圖将沒有任何變形。

第二步,判斷是否為陰影。先把世界空間頂點位置轉換為光空間,然後把光空間片段位置轉換為裁切空間的标準化裝置坐标,做透視除法後得到[-1,1]範圍的NDC坐标,變換到[0,1]後,用x和y采用深度貼圖,采樣出來的深度closestDepth和片段的目前深度也就是z坐标比較,若 z > closestDepth,則為陰影片段。

陰影失真(Shadow Acne)問題

因為陰影貼圖受限于分辨率,在距離光源比較遠的情況下,多個片段可能從深度貼圖的同一個值中去采樣。有些在地闆上面,有些在地闆下面,這樣我們所得到的陰影就有了差異。因為這個,有些片段被認為是在陰影之中,有些不在,由此産生了圖檔中的條紋樣式。

可以用一個叫做陰影偏移(shadow bias)的技巧來解決這個問題,我們簡單的對表面的深度(或深度貼圖)應用一個偏移量,這樣片段就不會被錯誤地認為在表面之下了

軟體光栅化渲染器知識總結

偏移量計算:

我們有一個偏移量的最大值0.05,和一個最小值0.005,它們是基于表面法線和光照方向的。這樣像地闆這樣的表面幾乎與光源垂直(與光線平行),得到的偏移就很小,而比如立方體的側面這種表面得到的偏移就更大。

懸浮(Peter Panning)問題

使用shadow bias時偏移值過大導緻的。使用普通的偏移值通常就能避免peter panning。

陰影鋸齒問題

深度貼圖有一個固定的分辨率,多個片段對應于一個紋理像素。結果就是多個片段會從深度貼圖的同一個深度值進行采樣,這幾個片段便得到的是同一個陰影,這就會産生鋸齒邊。

可以使用PCF緩解:一個簡單的PCF的實作是簡單的從紋理像素四周對深度貼圖采樣,然後把結果平均起來。

點光源陰影

算法和定向陰影映射差不多:我們從光的透視圖生成一個深度貼圖,基于目前fragment位置來對深度貼圖采樣,然後用儲存的深度值和每個fragment進行對比,看看它是否在陰影中。定向陰影映射和萬向陰影映射的主要不同在于深度貼圖的使用上。

對于深度貼圖,我們需要從一個點光源的所有渲染場景,普通2D深度貼圖不能工作;改為使用立方體貼圖,立方體貼圖可以儲存6個面的環境資料,它可以将整個場景渲染到立方體貼圖的每個面上,把它們當作點光源四周的深度值來采樣。

參考 陰影映射 和 點光源陰影

Blinn-Phong反射模型

參考 圖形學/OpenGL/3D數學/Unity 第43條。

SSAO

對于鋪屏四邊形上的每一個片段,我們都會根據周邊深度值計算一個遮蔽因子。這個遮蔽因子之後會被用來減少或者抵消片段的環境光照分量。遮蔽因子是通過采集片段周圍法向半球型核心(Kernel)的多個深度樣本,并和目前片段深度值對比而得到的。高于片段深度值樣本的個數就是我們想要的遮蔽因子。

繼續閱讀