當圖像已經渲染完成,相當於後期處理,這裡能夠拿到原始的圖片,然後通過3d數據進行圖像的修正。這個函數可以訪問當前正在渲染的圖像。這個函數只出現在Camera上的腳本
後期處理的過程:在onRenderImage裡面拿到當前的渲染圖像,然後Blit傳遞給shader進行渲染,渲染完成之後會再次通過onRenderImage進行回調,這時候可以通過Blit函數渲染到屏幕上。sourceTexture會成為material的mainTexture。Blit(sourceTarget,des,material),渲染的時候會使用material裡面的shader。
Mesh,網格 。 MeshFilter 用於獲取網格數據的組件。MeshRender,用於渲染網格的組件。因為mesh可能被多個模型使用,mesh是每個模型單獨的,sharedMesh則指的是共享的mesh,修改之後所有使用的mesh都會進行改變。Mesh實際上是三角形的點和邊的集合。
需要判斷ComputeShader是否支持,ComputeShader不能掛在mesh上面,只能在腳本裡面調用。SetTexture 傳遞數據,Dispatch 函數名字,線程組個數,每個線程組的線程數目,每次處理的像素個數,StructBuffer可以雙向傳遞數據albedo
4.參考資料
https:// github.com/SIZMW/unity- raytracer/blob/master
CPU計算,只計算了反射,但是對從模型獲取三角形,光源的處理有很強的借鑒意義。
http:// blog.three-eyed-games.com /2018/05/03/gpu-ray-tracing-in-unity-part-1/
實現了GPU求交,採樣等等,優點是能夠借鑒GPU處理光線追蹤的框架。缺點是求交運算太簡單,材質處理也太簡單
對光線追蹤和圖像渲染有一個高屋建瓴的認識,能夠快速建立起一個大概的印象,但是只能渲染球體,其他進階的處理還需要額外看資料
既有基本框架,也有高深的數據描述,但是沒有多少實踐部分
5.光線追蹤模型框架
參照前文所述的光線追蹤原理,我們可以得出一個光線追蹤模型的基本框架。
//參考 real time rending
rayTrace (){
for ( p in pixels ){
color of p = trace ( eye ray through p );
}
}
trace (){
pt = find last intersection ;
return shade ( pt );
}
shade ( point ){
color = 0 ;
for ( L in light sources ){
trace ( shadow ray to L );
color += evaluate BRDF ;
}
color += trace ( reflection ray );
color += trace ( refraction ray );
return color ;
}
6.法線插值計算
當渲染一個三角形的時候整個面的法線方向都是一致的,因此會出現稜角分明的效果,需要對法線進行插值。Mesh裡面包含了每個頂點的法線信息,頂點法線其實也是通過對面的計算然後加權平均計算的,raycast會返回重心坐標,這個坐標反應了重心分割的時候三塊面積的大小。以此對三角形面的法線進行插值。需要注意的是如何獲取頂點index,根據三角形的index,去mesh.triangles裡面查找。mesh裡面的信息都是本地坐標系統需要用transform進行轉換。
public void GetFixedNormal ( Mesh mesh , ref Vector3 normal , int hIndex , Transform transform , Vector3 barycentricCoordinate )
{
Vector3 [] normals = mesh . normals ;
int [] triangles = mesh . triangles ;
int trianglesLength = triangles . Length ;
int normalLength = normals . Length ;
int tIndex0 = hIndex * 3 + 0 ;
int tIndex1 = hIndex * 3 + 1 ;
int tIndex2 = hIndex * 3 + 2 ;
if ( tIndex0 >= 0 && tIndex0 < trianglesLength && tIndex1 < trianglesLength && tIndex2 < trianglesLength )
{
int vIndex0 = triangles [tIndex0] ;
int vIndex1 = triangles [tIndex1] ;
int vIndex2 = triangles [tIndex2] ;
// Extract local space normals of the triangle we hit
if ( vIndex0 < normalLength && vIndex1 < normalLength && vIndex2 < normalLength )
{
Vector3 n0 = normals [vIndex0] ;
Vector3 n1 = normals [vIndex1] ;
Vector3 n2 = normals [vIndex2] ;
// interpolate using the barycentric coordinate of the hitpoint
Vector3 baryCenter = barycentricCoordinate ;
// Use barycentric coordinate to interpolate normal
Vector3 interpolatedNormal = n0 * baryCenter . x + n1 * baryCenter . y + n2 * baryCenter . z ;
// normalize the interpolated normal
interpolatedNormal = interpolatedNormal . normalized ;
// Transform local space normals to world space
Transform hitTransform = transform ;
interpolatedNormal = hitTransform . TransformDirection ( interpolatedNormal );
normal = interpolatedNormal ;
}
}
}
7.局部光照模型簡單實現
using UnityEngine ;
using System.Collections ;
//光線處理工具
public class RayUtil
{
/*--------------------------------------局部光照模型------------------------------------
* 單一光源,特定BRDF下的推導。需要光線方向,光照強度,視線方向
*/
/*
* 反射光線在視線上的投影
* Phone模型 ks*ls*(v dot r)^n 高光係數*光照強度*(反射光線 點乘 視線 )^高光指數
*/
public static Vector3 PhoneLight ( Vector3 lightDirection , Vector3 lightColor , float specular , float lightStrength , Vector3 normal , Vector3 viewDirection , float alpha )
{
return lightColor * specular * lightStrength *
Mathf . Pow (
( Vector3 . Dot ( Vector3 . Normalize ( Vector3 . Reflect ( lightDirection , normal )), Vector3 . Normalize ( viewDirection ))),
alpha );
}
/*
* 漫反射,光照射到粗糙的表面的時候,均勻向四周反射,漫反射的光強與入射方向與法線的夾角餘弦成正比,因此此模型不涉及視線
*
* Lambert模型 kd*ld*(n dot l) 漫反射屬性*入射光強度*(入射單位法向量 dot 入射點指向光源的單位向量)
*/
public static Vector3 LambertLight ( Vector3 lightDirection , Vector3 lightColor , float albedo , Vector3 normal , float ligthStrength )
{
return lightColor * albedo * Mathf . Max (( Vector3 . Dot ( Vector3 . Normalize ( normal ), Vector3 . Normalize ( lightDirection )))* 0.5f + 0.5f , 0.0f );
}
public static Vector3 BlinnPhong ( Vector3 lightDirection , Vector3 lightColor , float specular , float lightStrength , Vector3 normal , Vector3 viewDirection , float alpha )
{
return lightColor * specular * lightStrength * Mathf . Pow ( Mathf . Max ( Vector3 . Dot ( Vector3 . Normalize ( lightDirection + viewDirection ), Vector3 . Normalize ( normal )), 0.0f ), alpha );
}
public static float SmoothnessToAlpha ( float s )
{
return Mathf . Pow ( 1000.0f , s * s );
}
}
8.軟陰影
在光線追蹤裡面,陰影的產生和判斷都是通過發射shadow ray判斷是否有障礙物來計算的,這樣的話其實陰影會有一個很明顯的邊界,在現實世界,光源不可能是一個點,方向光也不可能絕對平行,因此陰影會有一個平滑的過渡過程,這樣的陰影就是軟陰影。在光線追蹤裡面,軟陰影的實現採用發射shadow ray的時候在光源表面隨機採樣來實現,按理說應該用光源上的每個點進行計算,但是這樣計算量太大了。這裡光源就不是一個點(其他部分為了方便計算都抽象成一個點)。
Vector3 shadowRayDirection = ligthDirection + Random . onUnitSphere * 0.009f ;
if ( Physics . Raycast ( hit . point , shadowRayDirection , 500 ))
{
return new Vector3 ( 0.0f , 0.0f , 0.0f );
}
9.體積光渲染
體積光使用RayMarch的方式渲染,測試每一條光和光源之間的距離,採用合適的衰減和採樣函數來確定當前方向上的光線表現。實現如下:
目前光線的衰減函數是和距離平方成反比,這樣的函數衰減特別厲害,會導致光線到不了物體,後面需要優化。下圖是函數衰減曲線。
當物體在光線的陰影裡面的時候需要進行相交判斷,具體做法是從當前位置向光源發射一束光線,如果沒有碰撞說明沒有遮擋,這裡又個需要注意的地方,有可能會導致光源前後都形成遮擋區域這時候要根據碰撞距離來保留合適的那一個。
using System ;
using System.Collections.Generic ;
using UnityEngine ;
/*
* 處理髮光物的丁達爾效應,默認為球體光源
*/
public class LightObject
{
public BoundingSphere boundSphere ;
public static float MAX_LIGHT_DISTANCE = 2.0f ;
//物體最靠近的顏色
public static float MIN_DISTANCE = 1.0E-2f ;
public Vector3 mLightColor ;
public static int MAX_SAMPLE_COUNT = 180 ;
public float mStep = 0.0f ;
public float mInnerDistance = MIN_DISTANCE ;
public Vector3 mPosition ;
public void Init ( Vector3 color , Vector3 position , float length )
{
mPosition = position ;
mLightColor = color ;
mStep = 0.1f ;
mInnerDistance = length ;
}
/*
* 光線步進的方式獲取光照顏色
*/
public Vector3 RayMarch ( Ray ray , float minDistance , float maxDistance )
{
float t = minDistance ;
Vector3 result = new Vector3 ( 0.0f , 0.0f , 0.0f );
int realCount = 0 ;
for ( int i = 0 ; i < MAX_SAMPLE_COUNT && t >= minDistance && t <= maxDistance ; i ++)
{
Vector3 p = ray . GetPoint ( t );
if (! InShadow ( p ))
{
result += GetSampleColor ( Vector3 . Distance ( p , GetPosition ()));
realCount ++;
}
t += GetStep ();
}
result /= ( realCount + 0.0f );
return result ;
}
public Vector3 GetSampleColor ( float distance )
{
return mLightColor * ( mInnerDistance / ( distance + 0.0f ));
}
public Vector3 GetPosition ()
{
return mPosition ;
}
public float GetStep ()
{
return mStep ;
}
public Vector3 GetLightColor ()
{
return mLightColor ;
}
public bool InShadow ( Vector3 point )
{
Vector3 direction = ( mPosition - point ). normalized ;
RaycastHit hit ;
if ( Physics . Raycast ( point , direction , out hit , 100f ))
{
return hit . distance >= 0.0f ;
}
else
{
return false ;
}
}
}
11.初步結果
採用圖像二次採樣,即關閉所有光照,環境光調整為1,然後在需要圖像顏色的時候對原始圖片進行採樣,這樣其實不正確,但是也能得到一個近似結果
下面都是正常處理,採用Unity的材質系統處理顏色。單一方向光
alpha = 3.2f,specular = 1.1f,可以看到出現了光斑
12.後期的構想
一方面需要提升性能,因此我打算在GPU裡面實現相交,採樣等運算,Unity仍然負責提供材質的解析,一些初始化的工作。另一方面需要完善自發光物體的效果,因此引入了體積光渲染,但是要渲染一個不規則的自發光物體,還需要對體積光渲染進行修正,比如可以對發光物體先進行一次發光物體的求交運算。細節問題歡迎私信交流討論。
推薦閱讀: