
光與陰影的渲染
光與陰影的渲染需要兩類貼圖,LightMap(光照貼圖) 和 ShadowMap(陰影貼圖),
每個光源都需要渲染一張LightMap、ShadowMap,LightMap 是光渲染結果,ShadowMap 是陰影渲染結果,
兩者可以同一次渲染,分開渲染是為了之后做軟陰影或半影,
這里目前僅簡單的混合疊加光源,所以將所有光源結果渲染到一張 LightMap 里,
Light Mesh
目前生成的LightMesh并不包含物體模型,生成的陰影貼圖便將物體也包括進去,而光照貼圖不包括物體,

將光接觸的物體加入Mesh,結果:

但是會導致光部分接觸到的物體整體都被渲染,應該僅有光照到地方被渲染,

需要進行分割,
通用的分割不談(應該是用的三角剖分Delaunay),一開始我想利用排序好的端點做分割(未做復雜外形的覆寫測驗),原理是:
- 由于端點是排好序的,所以可以獲取物體掃描的第一個端點和最后一個端點,
- 三角化的時候保存一個物體的所有射線弧度(ShadowMesh一個三角網格通過開始弧度、結束弧度和一個線段三角化)
- 射線分端點射線和分割射線,端點射線一般去除即可,分割射線頂點替換成其擊穿物體與物體第二條線段相交的頂點,
- 之后分以下幾種情況:
- 兩條射線弧度同開始端點和結束端點弧度,則渲染整個物體
- 兩條射線僅一個同開始或結束端點,分割的那條射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點,再將物體大于開始射線和分割射線的所有端點加入三角化的輸出點,

3. 多條射線僅一個同開始或結束端點,其他射線未端點射線,分割的那條射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點,再將物體大于開始射線和分割射線的所有端點加入三角化的輸出點,去除所有中間射線的頂點,

4. 兩個射線都不同開始或結束端點(中空),兩個分割射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點,

- 多個射線,由于只有成對的射線才能構成一個三角網格,所以兩兩判斷其是1-4的那種情況,然后分別分割,

這個暫用方案并不好用,需要考慮的狀況比較多(目前還不兼容拼接的物體),而且使用到了排序端點、線段資料,還需要不停的修改掃描后的三角網格頂點資料,簡而言之就是耦合度太高了,
分割物體方法靜態光源還好,有優化的余地,如果是動態光源,光源數量一多實時分割的話計算量有點大了,
選擇不分割,如果沒什么受光面高光之類的效果,實際渲染效果其實也可以接受,
選擇將物體加入LightMesh,是為了之后進行光著色,如果不需要物體受光影響或者通過其他途徑著色(比如僅受環境光影響),這部分可以省去,
貼圖繪制
我使用的是Unity2019版本,繪制貼圖使用的是CommandBuffer,舊版本可以使用Graphics.DrawTexture或者GL,
ShadowMap
使用CommandBuffer.DrawMesh,將ShadowMesh繪制到目標渲染貼圖,因為暫時不做半影,所以使用的著色器很簡單,顏色置1,之后加個高斯模糊即可,深度之類的看情況而定,
LightMap
需要采樣 ShadowMap, 如果不做軟陰影,陰影貼圖生成、采樣混合的步驟可以省去(光直接使用使用lightMesh),如果需要軟陰影或者之后的半影,那么每個光源都需要一個正方形的Mesh繪制光源,然后采樣陰影混合(一般相乘即可)生成LightMap,
光的衰減
一般來說都是使用CookieTexture,
- 通程序式
圓的面積是 πr2,光的強度為 1 / (πr2) 簡化為 1/d2 ,d為當前位置到光源的距離,為防止d接近與0時結果無限大,最終結果為 1 / (1 + d2),
// shader frag
float3 lightVec = i.worldPos - _Light2DPos;
float rangeFactor = (_LightMaxRange - length(lightVec))/_LightMaxRange;
float atten = 1 / (1 + dot(lightVec,lightVec));
col.rgb = atten*rangeFactor*_LightColor*_Intensity;
這是個簡化的演算法,比較真實的渲染方程參考:用 C 語言畫光
同大多數程式式貼圖一樣(Procedural Texture)好處是可以通過代碼動態修改,

- 通過CookieTexture
點光源CookieTexture:
使用Cookie貼圖需要構建ShadowMesh時正確設定uvs,
好處是比較方便,構建復雜外形光源只需要更換CookieTexture就可以了,
光的混合
光的混合直接疊加即可 Blend One One,
物體著色
不定,我這邊是LightMap采樣光亮后,物體顏色與強度相乘(光的強度:rgb轉換成hsv取v值或者rgb的模估算),然后與光的顏色相加,


示例
// c#
MaterialPropertyBlock matPropBlock = new MaterialPropertyBlock();
bool first = true;
lightMapCmdBuffer.Clear();
lightMapCmdBuffer.GetTemporaryRT(shadowMapTmp, cam.pixelWidth, cam.pixelHeight, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBFloat);
foreach (var light in lights)
{
if (light.lightMesh != null)
{
var trs = Matrix4x4.TRS(light.transform.position, light.transform.rotation, light.transform.localScale);
// render shadow map
lightMapCmdBuffer.SetRenderTarget(shadowMapTex);
lightMapCmdBuffer.ClearRenderTarget(true, true, Color.black);
lightMapCmdBuffer.DrawMesh(light.lightMesh, trs, shadowMat);
blurMat.SetFloat("_BlurDownSample", blurDownSample);
lightMapCmdBuffer.Blit(shadowMapTex, shadowMapTmp, blurMat);
lightMapCmdBuffer.Blit(shadowMapTmp, shadowMapTex, blurMat);
// render light map
lightMapCmdBuffer.SetRenderTarget(lightMapTex);
if (first)
{
first = false;
lightMapCmdBuffer.ClearRenderTarget(true, true, Color.black);
}
matPropBlock.SetFloat("_Intensity", light.intensity);
matPropBlock.SetVector("_Light2DPos", light.transform.position);
matPropBlock.SetTexture("_ShadowMap", shadowMapTex); // 陰影采樣
matPropBlock.SetColor("_LightColor", light.color);
matPropBlock.SetTexture("_LightCookie", light.cookieTexture);
matPropBlock.SetFloat("_LightMaxRange", light.range);
// 根據光源的范圍生成一張正方形Mesh
// 如果不實作軟陰影,將_ShadowMap的采樣程序去掉,然后將light.GetQuadMesh() 改為 light.lightMesh
lightMapCmdBuffer.DrawMesh(light.GetQuadMesh(), trs, lightMat, 0, 0, matPropBlock);
}
}
lightMapCmdBuffer.ReleaseTemporaryRT(shadowMapTmp);
// light shader
Blend One One
vert {
o.screenPos = ComputeScreenPos(o.vertex);
}
frag {
fixed4 col = 1;
col.rgb = tex2D(_LightCookie, i.uv).r*_LightColor*_Intensity;
float shadow = tex2D(_ShadowMap, UNITY_PROJ_COORD(i.screenPos)).r;
col.rgb = col.rgb * shadow.r;
return col;
}
參考
Unity中實作2D光照系統
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/217328.html
標籤:其他
