Unity 2D Light (2) - 渲染

image

光與陰影的渲染

光與陰影的渲染須要兩類貼圖,LightMap(光照貼圖) 和 ShadowMap(陰影貼圖)。git

每一個光源都須要渲染一張LightMap、ShadowMap。LightMap 是光渲染結果,ShadowMap 是陰影渲染結果。算法

二者能夠同一次渲染,分開渲染是爲了以後作軟陰影或半影。c#

這裏目前僅簡單的混合疊加光源,因此將全部光源結果渲染到一張 LightMap 裏。cookie

Light Mesh

目前生成的LightMesh並不包含物體模型,生成的陰影貼圖便將物體也包括進去,而光照貼圖不包括物體。測試

image

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

image

可是會致使光部分接觸到的物體總體都被渲染,應該僅有光照到地方被渲染。code

image

須要進行分割。orm

通用的分割不談(應該是用的三角剖分Delaunay),一開始我想利用排序好的端點作分割(未作複雜外形的覆蓋測試),原理是:htm

  • 因爲端點是排好序的,因此能夠獲取物體掃描的第一個端點和最後一個端點。
  • 三角化的時候保存一個物體的全部射線弧度(ShadowMesh一個三角網格經過開始弧度、結束弧度和一個線段三角化)
  • 射線分端點射線和分割射線,端點射線通常去除便可,分割射線頂點替換成其擊穿物體與物體第二條線段相交的頂點。
  • 以後分如下幾種狀況:
  1. 兩條射線弧度同開始端點和結束端點弧度,則渲染整個物體
  2. 兩條射線僅一個同開始或結束端點,分割的那條射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點。再將物體大於開始射線和分割射線的全部端點加入三角化的輸出點。

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

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

image

  1. 多個射線,因爲只有成對的射線才能構成一個三角網格,因此兩兩判斷其是1-4的那種狀況,而後分別分割。

image

這個暫用方案並很差用,須要考慮的情況比較多(目前還不兼容拼接的物體),並且使用到了排序端點、線段數據,還須要不停的修改掃描後的三角網格頂點數據,簡而言之就是耦合度過高了。

分割物體方法靜態光源還好,有優化的餘地,若是是動態光源,光源數量一多實時分割的話計算量有點大了。

選擇不分割,若是沒什麼受光面高光之類的效果,實際渲染效果其實也能夠接受。

選擇將物體加入LightMesh,是爲了以後進行光着色。若是不須要物體受光影響或者經過其餘途徑着色(好比僅受環境光影響),這部分能夠省去。

貼圖繪製

我使用的是Unity2019版本,繪製貼圖使用的是CommandBuffer。舊版本可使用Graphics.DrawTexture或者GL。

ShadowMap

使用CommandBuffer.DrawMesh,將ShadowMesh繪製到目標渲染貼圖。由於暫時不作半影,因此使用的着色器很簡單,顏色置1,以後加個高斯模糊便可。深度之類的看狀況而定。

LightMap

須要採樣 ShadowMap, 若是不作軟陰影,陰影貼圖生成、採樣混合的步驟能夠省去(光直接使用使用lightMesh)。若是須要軟陰影或者以後的半影,那麼每一個光源都須要一個正方形的Mesh繪製光源,而後採樣陰影混合(通常相乘便可)生成LightMap。

光的衰減

通常來講都是使用CookieTexture。

  1. 經過程序

圓的面積是 πr²,光的強度爲 1 / (πr²) 簡化爲 1/d² ,d爲當前位置到光源的距離,爲防止d接近與0時結果無限大,最終結果爲 1 / (1 + d²)。

// 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)好處是能夠經過代碼動態修改。

image

  1. 經過CookieTexture

點光源CookieTexture:image

使用Cookie貼圖須要構建ShadowMesh時正確設定uvs。

好處是比較方便,構建複雜外形光源只須要更換CookieTexture就能夠了。

光的混合

光的混合直接疊加便可 Blend One One

物體着色

不定。我這邊是LightMap採樣光亮後,物體顏色與強度相乘(光的強度:rgb轉換成hsv取v值或者rgb的模估算),而後與光的顏色相加。

image

image

示例

// 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;
}

源碼

link

參考

Unity中實現2D光照系統

相關文章
相關標籤/搜索