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光照系统

相关文章
相关标签/搜索