练习项目(六):Back facing描边法

编程入门 行业动态 更新时间:2024-10-26 01:33:42

练习<a href=https://www.elefans.com/category/jswz/34/1771421.html style=项目(六):Back facing描边法"/>

练习项目(六):Back facing描边法

概述

描边,在卡通渲染中是一个非常重要的主题。目前比较流行的描边方法有两种:一种是基于后处理的描边,这种方式相对不容易定制,适用于对复杂场景的描边;一种是过程式描边,通过两次绘制,一次绘制本体,一次绘制描边。 本文主要介绍第二种描边方式,在《GUILTY GEAR Xrd》中称其为Back Facing法。

一、基本的实现描边

基本思路是通过两次绘制,一次绘制本体,一次绘制描边。

这里就有个问题,两次绘制的顺序怎么处理呢?

经过试验可以发现,两种顺序可以得到相同的结果。

本文使用的顺序是先绘制本体,再绘制描边。参考下图,在片元着色器之前,有个Depth Test操作,这样,在后绘制描边的时候可以通过深度检测过滤掉本体覆盖的像素,效率更高。

在URP中,如果没有设置LightMode,那么URP默认使用SRPDefaultUnlit。所以,可以将绘制本体Pass的LightMode设为SRPDefaultUnlit,而将绘制描边Pass的LightMode设为UniversalForward。这样,就可以实现先绘制本体,再绘制描边的功能了。主要的代码如下:

            Varyings vert(Attributes input){Varyings output = (Varyings)0;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);output.vertex = TransformObjectToHClip(input.positionOS.xyz);float3 normal = TransformObjectToWorldNormal(input.normalOS);float2 offset = TransformWorldToHClipDir(normal).xy;output.vertex.xy += offset * _Outline;return output;}

此时,可以得到如下的结果:

查看Frame Debugger,可以发现,确实是先绘制本体,再绘制描边。

二、到相机距离造成的描边粗细问题

上面的步骤,得到了一个基本的描边效果。但是当物体远离相机时,可以发现,描边会变细。我们希望得到的,是描边宽度不随物体距离相机远近而变化的效果。

这里就需要多提一个知识点。物体变换到投影空间后,x、y代表投影空间下的横纵坐标,z代表投影空间下的深度,w等于-z,w用于后面的齐次除法。我们希望得到的,是在屏幕上显示固定宽度的描边,那么顶点向外延伸的距离就应该是NDC空间下的固定距离,而不是投影空间下的固定距离。于是,在投影空间下计算向外延伸的距离的时候,乘上w的值,这样,在之后的齐次除法中会将坐标值除以w,得到的就是不会随距离相机远近不同的描边宽度了。代码如下:

                output.vertex = TransformObjectToHClip(input.positionOS.xyz);float3 normal = TransformObjectToWorldNormal(input.normalOS);float2 offset = TransformWorldToHClipDir(normal).xy;output.vertex.xy += offset * output.vertex.w * _Outline;

三、屏幕分辨率造成的非等比缩放问题

上面两个部分,得到的都是宽度一致的描边。但是,当试着对得到的offset进行归一化时,就会出现下面这种问题。代码如下:

                output.vertex = TransformObjectToHClip(input.positionOS.xyz);float3 normal = TransformObjectToWorldNormal(input.normalOS);float2 offset = normalize(TransformWorldToHClipDir(normal).xy);output.vertex.xy += offset * output.vertex.w * _Outline;

这是因为,观察空间变换到投影空间xy会非等比缩放,所以正常的法线在投影空间就是非归一化的。在投影空间下,output.vertex.xy未归一化,如果法线offset归一化了,最后计算得到的偏移值在xy方向上的拉伸程度就会不同。所以,这里不需要对offset归一化。

当然,也可以对offset归一化后,再根据屏幕的宽高比计算出一个系数,将offset.y乘以这个系数,得到一个新的法线,这样也可以解决上面的问题。

                output.vertex = TransformObjectToHClip(input.positionOS.xyz);float3 normal = TransformObjectToWorldNormal(input.normalOS);float2 offset = normalize(TransformWorldToHClipDir(normal).xy);//将近裁剪面右上角位置的顶点变换到观察空间float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));//求得屏幕宽高比float aspect = abs(nearUpperRight.x / nearUpperRight.y);offset.y *= aspect;output.vertex.xy += offset * output.vertex.w * _Outline;

当然,这种方式相对上一种方式有点麻烦,只是提供一种思路。

完整代码

四、其它的一些技巧

在《罪恶装备-Xrd》的分享中,也提到了在卡通渲染中,其他的一些提升描边质量的方法。比如,使用顶点色存储描边粗细、颜色等,可以更加精细地控制描边。最近在实际的工作中,也遇到一种提升描边效果的方式:根据顶点距离相机的距离,计算出一个参数,在代表描边宽度的渐变贴图中采样,这样,可以定制各种不同距离的描边宽度。

参考

  • [1] URP ShaderLab Pass tags
  • [2] OpenGL Projection Matrix
  • [3] 【02】卡通渲染基本光照模型的实现
  • [4] 【01】从零开始的卡通渲染-描边篇

更多推荐

练习项目(六):Back facing描边法

本文发布于:2024-03-09 22:02:21,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1726222.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:项目   facing   描边法

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!