Cg Programming/Unity/Projectors投影器

来源:互联网 发布:他趣聊天软件 编辑:程序博客网 时间:2024/06/07 05:17

本章涵盖了投影器的投影贴图映射,它是Unity的特殊渲染组件。

它基于章节“Cookies”。如果你还没有阅读那章,你应该首先看一下。

Unity的投影器

Unity的投影器类似于聚光灯。实际上,它们可能被用在相似的应用上。但是,这里有一项重要的技术是不一样的:对于聚光源,所有发光物体的着色器必须如章节“Cookies”讨论的一样通过聚光源来计算光照。如果一个对象的着色器忽略了聚光灯,它就不会被聚光灯点亮。这个跟投影器不一样:每个跟着色器材质相关的投影器会在投影器的范围内被应用到任意对象上。因此,一个对象的着色器并不需要处理投影器;相反,为了达到某种效果,比如添加投影贴图的光照或者衰减一个对象的颜色来伪造影子,投影器会作为额外的渲染通道在它的范围内把着色器应用到所有对象上。实际上,使用投影器着色器的不同混合等式可以达到不同的效果。(混合等式在章节“透明度”中有所讨论。)

人们可能甚至认为投影器是实现光照的最“自然”的方式。但是,光照和材质之间的相互作用通常针对每个材质,而一个投影器的着色器不能处理所有的差别。这个就把投影器的可能性限制到三种基本行为:添加光照到一个对象上,修改对象的颜色,或者同时添加光照并且修改对象颜色。接下来我们将会看到添加光照到一个对象上并且衰减它的颜色。

添加光照投影器

为了创建一个投影器,在菜单栏选择GameObject > Create Empty,然后(依然选中刚刚创建的对象)选择Component > Effects > Projector。现在你就有了一个跟聚光灯类似操作的投影器。Inspector Window中投影器的设置在Unity manual中有所讨论。这里,投影器材质中最重要的设置会在它的范围内被应用到所有对象上。这样,我们必须创建另外一个材质并且把一个合适的着色器指定给它;因此,它并没有访问它们的纹理等。它也没有访问关于光源的任何信息。但是,它已经访问游戏对象顶点的属性以及它自己的着色器属性。

向对象添加光照的着色器会被用来向其它对象投影任何图像,类似于投影仪或放映机。因此,它可以使用一张类似于聚光灯cookie(参考章节“Cookies”)的纹理贴图,除了纹理贴图的RGB颜色应该被添加以允许彩色投影。为此,我们把片元颜色设置为纹理贴图的RGBA颜色并且使用混合等式

Blend One One

这只是把片元颜色增加到帧缓冲中的颜色。(取决于纹理贴图,可能最好使用Blend SrcAlpha One来移除任何不透明度为零的颜色。)

另一个跟聚光灯cookies不同的地方在于我们应该使用Unity指定的uniform矩阵_Projector代替矩阵_LightMatrix0把位置从对象空间变换到投影空间。但是,投影空间中的坐标工作原理跟灯光空间的坐标非常类似—除了最终的x和y坐标在正确范围内;于是,我们不需要为增加0.5而烦恼。尽管如此,我们必须用w坐标执行除法(对于投影贴图映射也是这样);要么x和y明确除以w或者使用tex2Dproj

ZWrite Off这行代码保证了我们不会改变深度缓冲,因为我们只是把光照加入到已经光栅化的网格上去。Offset -1, -1稍微改变了深度,假装我们在添加光的网格前面。这有助于确保我们光栅化的东西没被那个网格遮挡。

Shader "Cg projector shader for adding light" {   Properties {      _ShadowTex ("Projected Image", 2D) = "white" {}   }   SubShader {      Pass {               Blend One One             // add color of _ShadowTex to the color in the framebuffer          ZWrite Off // don't change depths         Offset -1, -1 // avoid depth fighting         CGPROGRAM         #pragma vertex vert           #pragma fragment frag          // User-specified properties         uniform sampler2D _ShadowTex;          // Projector-specific uniforms         uniform float4x4 _Projector; // 从对象空间到投影空间的变换矩阵           struct vertexInput {            float4 vertex : POSITION;            float3 normal : NORMAL;         };         struct vertexOutput {            float4 pos : SV_POSITION;            float4 posProj : TEXCOORD0;//投影空间中的位置         };         vertexOutput vert(vertexInput input)          {            vertexOutput output;            output.posProj = mul(_Projector, input.vertex);            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);            return output;         }          float4 frag(vertexOutput input) : COLOR         {            if (input.posProj.w > 0.0) // 是否在投影器的前面            {               return tex2D(_ShadowTex ,input.posProj.xy / input.posProj.w);                // alternatively: return tex2Dproj(                 //    _ShadowTex, input.posProj);            }            else // behind projector            {               return float4(0.0, 0.0, 0.0, 0.0);            }         }         ENDCG      }   }     Fallback "Projector/Light"}

注意我们必须检查w是否是正向的(也就是片元是在投影器的前面而不是后面)。没有这个检查,投影器也会把光添加到对象后面。此外,纹理贴图必须是正方形,并且通常使用循环模式为clamp的纹理是个好主意。

万一你好奇:纹理使用的着色器属性叫做_ShadowTex,为了跟投影器的内置着色器兼容。

就如章节“Cookies”中描述的,投影贴图映射有时会带来一些负面影响:在投影的边缘处,GPU会使用高级mip map层级,它会导致一个可见的边界(特别对于有夹紧纹理坐标的纹理贴图)。防止这种情况最简单的方法是不激活纹理贴图的mip maps:在Project Window中找到并选中纹理贴图;然后在Inspector Window中设置Texture Type为Advanced ,并且取消选中Generate Mip Maps。不要忘记点击Apply button。

调节颜色的投影器

这里写图片描述有阴影的卡通角色

创建一个投影器来调节颜色的基本步骤跟上面的一模一样。唯一的不同就是着色器代码。下面的例子通过衰减颜色增加一个下拉阴影,特别是地板的颜色。注意在实际的应用中,阴影投射的颜色不应该被衰减。这可以通过把阴影投射指向一个特殊的层级上来实现(在游戏对象的Inspector Window中),并且在投影器的Inspector Window的Ignore Layers下面指定这个层级。

为了给阴影指定某种形状,我们使用纹理贴图的alpha分量决定阴影的黑暗程度。(这样我们可以在标准资源下对灯光使用cookie贴图。)为了在帧缓冲中衰减颜色,我们应该把它乘以1减去alpha(也就是alpha因子O等于1)。因此,合适的混合等式如下所示:

Blend Zero OneMinusSrcAlpha

Zero表示我们没有添加任何光源。即使阴影太黑了,没有灯光应该被添加;相反,在片元着色器中alpha分量应该被减小,比如乘以一个小于1的因子。对于在帧缓冲颜色分量的独立调整来说,我们应该使用Blend Zero SrcColor或者Blend Zero OneMinusSrcColor

跟添加光照的版本相比,不同的混合等式在着色器代码中实际只有很小的改变:

Shader "Cg projector shader for drop shadows" {   Properties {      _ShadowTex ("Projected Image", 2D) = "white" {}   }   SubShader {      Pass {               Blend Zero OneMinusSrcAlpha // 在帧缓冲中衰减颜色,通过1-_ShadowTex的alpha         ZWrite Off // 不改变深度缓冲         Offset -1, -1 // 避免深度冲突         CGPROGRAM         #pragma vertex vert           #pragma fragment frag          // 用户自定义属性         uniform sampler2D _ShadowTex;          // Projector-specific uniforms         uniform float4x4 _Projector; // transformation matrix             // from object space to projector space           struct vertexInput {            float4 vertex : POSITION;            float3 normal : NORMAL;         };         struct vertexOutput {            float4 pos : SV_POSITION;            float4 posProj : TEXCOORD0;               // position in projector space         };         vertexOutput vert(vertexInput input)          {            vertexOutput output;            output.posProj = mul(_Projector, input.vertex);            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);            return output;         }         float4 frag(vertexOutput input) : COLOR         {            if (input.posProj.w > 0.0) // in front of projector?            {               return tex2D(_ShadowTex ,input.posProj.xy / input.posProj.w);                // alternatively: return tex2Dproj(                 //    _ShadowTex, input.posProj);            }            else // behind projector            {               return float4(0.0, 0.0, 0.0, 0.0);            }         }         ENDCG      }   }     Fallback "Projector/Light"}

总结

恭喜,你完成了本教程的学习。我们看到:

  • Unity的投影器是如何工作的。
  • 如何实现一个投影器的着色器来向对象添加光照。
  • 如何实现一个投影器的着色器来衰减对象的颜色。

译者注

关于Offset -1, -1的理解:
1.参考
2.参考
3.z-fighting在unity中的解决方式
总结一下,大概有三种方法解决这个问题:

  • 使用模板测试。
  • 修改模型中产生深度冲突的两个面。
  • 使用Offset。
  • 修改照相机Clipping Planes的near和far。即尽量放大near的值,尽量减小far的值。因为z缓冲的位数是有限的,24位或者32位。打个比方,1/10表示的精度比1/100表示的精度高。
原创粉丝点击