光與陰影的渲染須要兩類貼圖,LightMap(光照貼圖) 和 ShadowMap(陰影貼圖)。git
每一個光源都須要渲染一張LightMap、ShadowMap。LightMap 是光渲染結果,ShadowMap 是陰影渲染結果。算法
二者能夠同一次渲染,分開渲染是爲了以後作軟陰影或半影。c#
這裏目前僅簡單的混合疊加光源,因此將全部光源結果渲染到一張 LightMap 裏。cookie
目前生成的LightMesh並不包含物體模型,生成的陰影貼圖便將物體也包括進去,而光照貼圖不包括物體。測試
將光接觸的物體加入Mesh,結果:優化
可是會致使光部分接觸到的物體總體都被渲染,應該僅有光照到地方被渲染。code
須要進行分割。orm
通用的分割不談(應該是用的三角剖分Delaunay),一開始我想利用排序好的端點作分割(未作複雜外形的覆蓋測試),原理是:htm
3. 多條射線僅一個同開始或結束端點,其餘射線未端點射線,分割的那條射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點。再將物體大於開始射線和分割射線的全部端點加入三角化的輸出點。去除全部中間射線的頂點。blog
4. 兩個射線都不一樣開始或結束端點(中空),兩個分割射線所獲取的頂點替換成射線穿過物體與物體第二個線段的相交點。
這個暫用方案並很差用,須要考慮的情況比較多(目前還不兼容拼接的物體),並且使用到了排序端點、線段數據,還須要不停的修改掃描後的三角網格頂點數據,簡而言之就是耦合度過高了。
分割物體方法靜態光源還好,有優化的餘地,若是是動態光源,光源數量一多實時分割的話計算量有點大了。
選擇不分割,若是沒什麼受光面高光之類的效果,實際渲染效果其實也能夠接受。
選擇將物體加入LightMesh,是爲了以後進行光着色。若是不須要物體受光影響或者經過其餘途徑着色(好比僅受環境光影響),這部分能夠省去。
我使用的是Unity2019版本,繪製貼圖使用的是CommandBuffer。舊版本可使用Graphics.DrawTexture或者GL。
使用CommandBuffer.DrawMesh,將ShadowMesh繪製到目標渲染貼圖。由於暫時不作半影,因此使用的着色器很簡單,顏色置1,以後加個高斯模糊便可。深度之類的看狀況而定。
須要採樣 ShadowMap, 若是不作軟陰影,陰影貼圖生成、採樣混合的步驟能夠省去(光直接使用使用lightMesh)。若是須要軟陰影或者以後的半影,那麼每一個光源都須要一個正方形的Mesh繪製光源,而後採樣陰影混合(通常相乘便可)生成LightMap。
通常來講都是使用CookieTexture。
圓的面積是 π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)好處是能夠經過代碼動態修改。
點光源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; }