Cg Programming/Unity/Silhouette Enhancement轮廓增强

一个半透明的水母. 注意轮廓处逐渐增加的不透明度一个半透明的水母. 注意轮廓处逐渐增加的不透明度。
本章介绍了表面法线向量的变换。本章假设读者熟悉章节“透明度”中讨论的alpha混合以及章节“世界空间中的着色器编写”中讨论的着色器属性。本章的目标就是为了达到以上照片中的效果:半透明物体的轮廓往往比物体的其余部分更不透明。这样增加了即使没有光照的三维形状的印象。事实证明,转换法线是获得这种效果的关键。

光滑表面的轮廓

这里写图
在一小块表面上的表面法线向量(简称法线)。
以光滑表面为例,轮廓处表面上的点以与平行于观察平面的法线向量为特征,因此与观察者的方向正交。如上图所示,在图上轮廓处蓝色的法线向量平行于观察平面,同时其它的法线向量更多是指向观察者(或者摄像机)方向。通过计算观察者的方向和法线向量以及测试它们是否互相垂直,我们可以测试一个点是否(大概)在轮廓上面。
更具体地说,如果V是指向观察者的归一化的方向并且N是归一化的表面法向量,当这两个向量点积为0时它们就是垂直的:V.N = 0。实际上,很少会出现这种情况。但是,如果点积V·N的结果接近于0,我们就可以假设这个点在轮廓上。

提高轮廓不透明度

为了实现我们要的效果,我们应该在点积V·N的值接近0的时候提升不透明度alpha。对于观察者方向和法线向量的点积,我们有多种提高不透明度的方法。其中的一种方法(实际上背后是一个物理模型,在章节5.1中有过描述)是通过材质的标准不透明度来计算不透明度的提升:
这里写图片描述
考虑到接近轮廓的点:V·N ≈ 0,检查方程的极端情况还是很有道理的。在这种情况下,规则的不透明度这里写图片描述会除以一个很小的正数。(注意GPUs通常会优雅地处理除数为0的情况;因此,我们无需担心这点。)所以不论这里写图片描述多大,这里写图片描述与小正数的比值会很大。min函数会确保这里写图片描述的值不大于1。
另一方面,对于远离轮廓的点我们有V·N ≈ 1。在这种情况下,α’ ≈ min(1, α) ≈ α,也就是说那些点的不透明度不会有太大变化。这正是我们想要的。因此,我们刚刚核实的方程至少是合理的。

在着色器中实现方程

为了在着色器中实现一个类似于如上alpha的方程,第一个问题是:在顶点着色器还是在片元着色器中实现?在某些情况下,答案是显而易见的,因为方案实施需要的纹理映射通常只在片元着色器中才有效。然而在大多数情况下并没有定论。在顶点着色器中实现会更快(因为相比片元着色器顶点通常会少)但图象质量较低(因为顶点法向量和其它顶点属性会突然发生改变)。所以,如果你更关心性能,在顶点着色器中实现方程是一个更好的选择。另一方面,如果你更关心图象质量,在片元着色器中实现方程会是一个更好的选择。同样的权衡也存在于逐顶点光照(即“镜面高光”章节中讨论的高洛德着色)和逐片元光照(即章节“光滑镜面高光”讨论的Phone着色)。
下一个问题是:方程应该在哪个坐标系中实现?(参见章节“顶点转换”中标准坐标系的介绍。)同样这也没有定论。但是,Unity中在世界坐标系中实现是一个不错的选择,因为有很多统一的变量都是在世界坐标系中指定的。(在其它环境下在观察坐标系中实现也很常见。)
在实现方程前的最后一个问题是:我们从哪里获取方程的参数?规则的不透明度alpha是由着色器的一个属性(查阅章节“世界空间中的着色器编程”)指定的(用一个RGBA颜色)。法向量normal是一个标准顶点输入参数(参见章节“着色器调试”)。观察者的方向可以在顶点着色器中计算,即从世界空间中的顶点位置到世界空间中摄像机位置_WorldSpaceCameraPos(Unity中提供)构成的向量。
因此,在实现方程之前我们只需把顶点位置和法线向量转换到世界空间。转换矩阵_Object2World将模型空间转换到世界空间,它的逆矩阵_World2Object由Unity提供并在章节“世界空间中的着色器编程”中讨论过。点和法线向量的变换矩阵的应用在章节“应用矩阵转换”中有详细讨论。基本结果就是将点和方向通过转换矩阵相乘来转换,例如:用modelMatrix设置_Object2World:
output.viewDir = normalize(_WorldSpaceCameraPos - mul(modelMatrix, input.vertex).xyz);
另一方面,法向量通过乘以转置逆矩阵来变换。既然Unity为我们提供了变换矩阵(_World2Object),一个更好地选择是法向量左乘逆矩阵(译者注://将mesh传递过来的顶点法向量 乘以 模型–>世界坐标系矩阵 得到世界坐标系中的法向量 , 然后单位化。
output.normal = normalize(mul(ModelMatrix,float4(v.normal,0.0)).xyz);),这个等价于右乘章节“应用矩阵变换”中讨论的转置逆矩阵:
output.normal = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
现在我们就有了编写这个着色器所需要的所有部分。

着色器代码

Shader "Cg silhouette enhancement" {
   Properties {
      _Color ("Color", Color) = (1, 1, 1, 0.5) 
         // 用户自定义的包括不透明度的RGBA颜色
   }
   SubShader {
      Tags { "Queue" = "Transparent" } 
         // 在所有不透明几何体绘制后绘制
      Pass { 
         ZWrite Off // 不遮挡其它物体
         Blend SrcAlpha OneMinusSrcAlpha // 标准alpha混合

         CGPROGRAM 

         #pragma vertex vert  
         #pragma fragment frag 

         #include "UnityCG.cginc"

         uniform float4 _Color; // 定义着色器属性

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float3 normal : TEXCOORD;
            float3 viewDir : TEXCOORD1;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            output.normal = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.viewDir = normalize(_WorldSpaceCameraPos 
               - mul(modelMatrix, input.vertex).xyz);

            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            float3 normalDirection = normalize(input.normal);
            float3 viewDirection = normalize(input.viewDir);

            float newOpacity = min(1.0, _Color.a 
               / abs(dot(viewDirection, normalDirection)));
            return float4(_Color.rgb, newOpacity);
         }

         ENDCG
      }
   }
}

newOpacity 的赋值几乎就是方程的表面意思
这里写图片描述
注意,我们归一化顶点着色器中的顶点输出参数output.normaloutput.viewDir(因为我们要在两者之间插值而不会考虑到对它们或多或少地权重)以及片元着色器开始的几行(因为插值可以在一定程度上扭曲我们的归一化)。但是,大多数情况下顶点着色器中的output.normal的归一化并不是必要的。类似的,在片元着色器中的output.viewDir归一化也不是必要的。

更加艺术地控制

虽然所描述的轮廓加强是基于物理模型的,它仍缺乏艺术控制;也就是说,一个CG艺术家并不能轻易地创建一个比物理模型更薄或更厚的轮廓。为了能够更好地艺术控制,你应该引入另一个(正的)浮点数参数,并且在如上方程中使用它之前用点积|V·N| 作为这个数的幂次方(使用内置的Cg函数pow(float x, float y))。这将允许CG艺术家独立地创建基于基础颜色不透明度的更薄或更厚的轮廓。

总结

恭喜你,你完成了这个教程。我们讨论了:

  • 如何找出光滑平面的轮廓(使用法向量和观察方向的点积)。
  • 如何增强轮廓的不透明度。
  • 如何在着色器中实现方程。
  • 如何把点和向量从模型空间向世界空间转换(对法向量使用转置逆模型矩阵)。
  • 如何计算观察方向(摄像机位置到顶点位置的差异)。
  • 如何插值归一化方向(也就是归一化两次:在顶点着色器中和在片元着色器中)。
  • 如何在轮廓的厚度上提供更多的艺术控制。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值