跳转到内容

GLSL 编程/Unity/投影仪

来自维基教科书,开放世界中的开放书籍
一个 overhead projector。

本教程涵盖投影仪的投影纹理贴图,它们是 Unity 的特定渲染组件。

它基于“Cookies”部分。如果你还没有阅读过本教程,建议你先阅读。

Unity 的投影仪

[编辑 | 编辑源代码]

Unity 的投影仪有点类似于聚光灯。事实上,它们可以用于类似的应用。然而,存在着重要的技术差异:对于聚光灯,所有被照亮对象的着色器必须计算聚光灯的光照,如“Cookies”部分所述。如果对象的着色器忽略了聚光灯,它就不会被聚光灯照亮。对于投影仪来说,情况不同:每个投影仪都与一个材质相关联,该材质具有一个应用于投影仪范围内的任何对象的着色器。因此,对象的着色器不需要处理投影仪;相反,投影仪会将它的着色器应用于它范围内的所有对象,作为额外的渲染通道,以实现某些效果,例如添加投影图像的光照或衰减对象的颜色以模拟阴影。事实上,通过使用投影仪着色器的不同混合方程,可以实现各种效果。(混合方程在“透明度”部分中讨论。)

人们甚至可以认为投影仪是实现光照的更“自然”的方式。然而,光照和材质之间的交互通常是特定于每种材质的,而投影仪的单个着色器无法处理所有这些差异。这限制了投影仪的可能性,只有三种基本行为:向物体添加光照,调制物体的颜色,或者两者兼而有之,添加光照和调制物体的颜色。我们将以向物体添加光照和衰减物体的颜色为例,它们是调制颜色的一种示例。

用于添加光照的投影仪

[编辑 | 编辑源代码]

为了创建一个投影仪,从主菜单中选择GameObject > Create Empty,然后(在仍然选中新对象的情况下)从主菜单中选择Component > Effects > Projector。现在你拥有了一个投影仪,可以像聚光灯一样进行操作。Inspector View中投影仪的设置在Unity 的参考手册中进行了说明。这里,唯一的设置是投影仪的材质,它将应用于它范围内的所有对象。因此,我们必须创建另一个材质,并将合适的着色器分配给它。这个着色器通常无法访问它应用到的游戏对象的材质;因此,它无法访问它们的纹理等。它也无法访问任何有关光源的信息。然而,它可以访问游戏对象顶点的属性及其自身的着色器属性。

一个向物体添加光照的着色器可以用来将任何图像投影到其他物体上,类似于 overhead projector 或电影放映机。因此,它应该使用类似于聚光灯 cookie 的纹理图像(参见“Cookies”部分),但纹理图像的 RGB 颜色应该被添加以允许彩色投影。我们可以通过将片段颜色设置为纹理图像的 RGBA 颜色,并使用混合方程来实现这一点

Blend One One

它只是将片段颜色添加到帧缓冲区中的颜色。(取决于纹理图像,最好使用Blend SrcAlpha One以去除任何不透明度为零的颜色。)

与聚光灯的 cookie 不同,我们应该使用 Unity 特定的统一矩阵_Projector 将位置从对象空间转换为投影仪空间,而不是矩阵_LightMatrix0。然而,投影仪空间中的坐标与光空间中的坐标非常相似——除了结果 坐标处于正确范围内;因此,我们不必担心添加 0.5。尽管如此,我们必须对 坐标进行除法运算(如投影纹理贴图中始终如此);可以通过显式地将 除以,或者使用texture2DProj来实现。

Shader "GLSL 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 
 
         GLSLPROGRAM

         // User-specified properties
         uniform sampler2D _ShadowTex; 

         // Projector-specific uniforms
         uniform mat4 _Projector; // transformation matrix 
            // from object space to projector space 
                  
         varying vec4 positionInProjSpace; 
            // position of the vertex (and fragment) in projector space

         #ifdef VERTEX

         void main()
         {                                         
            positionInProjSpace = _Projector * gl_Vertex;            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

         #endif

         #ifdef FRAGMENT

         void main()
         {
            if (positionInProjSpace.w > 0.0) // in front of projector?
            {
               gl_FragColor = texture2D(_ShadowTex , 
                  vec2(positionInProjSpace) / positionInProjSpace.w); 
               // alternatively: gl_FragColor = texture2DProj(  
               //    _ShadowTex, vec3(positionInProjSpace));
            }
            else // behind projector
            {
               gl_FragColor = vec4(0.0);
            }
         }

         #endif
         
         ENDGLSL
      }
   }  
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Projector/Light"
}

请注意,我们必须测试 是否为正数(即片段位于投影仪前面,而不是后面)。如果没有这个测试,投影仪也会向它后面的物体添加光照。此外,纹理图像必须是正方形,并且通常建议使用将包装模式设置为钳位的纹理。

如果你想知道:纹理的着色器属性叫做_ShadowTex,以便与投影仪的内置着色器兼容。

一个带有投影阴影的卡通角色。

用于调制颜色的投影仪

[编辑 | 编辑源代码]

创建调色投影仪的基本步骤与上面相同。唯一的区别在于着色器代码。以下示例通过衰减颜色(特别是地板的颜色)添加阴影。请注意,在实际应用中,阴影投射者的颜色不应该被衰减。这可以通过将阴影投射者分配到特定的 **层**(在游戏对象的 **Inspector View** 中)并在投影仪的 **Inspector View** 中的 **Ignore Layers** 下指定该层来实现。

为了使阴影具有特定的形状,我们使用纹理图像的 alpha 通道来确定阴影的深浅。(因此,我们可以使用标准资源中灯光使用的饼干纹理。)为了衰减帧缓冲器中的颜色,我们应该将其乘以 1 减去 alpha(即 alpha 等于 1 时为因子 0)。因此,合适的混合方程是

Blend Zero OneMinusSrcAlpha

Zero 表示我们不添加任何光线。即使阴影太暗,也不应添加光线;而是应该在片段着色器中减少 alpha 通道,例如通过将其乘以小于 1 的因子。为了独立地调制帧缓冲器中的颜色组件,我们需要 Blend Zero SrcColorBlend Zero OneMinusSrcColor

实际上,不同的混合方程是着色器代码与添加光线版本相比唯一的变化。

Shader "GLSL projector shader for drop shadows" {
   Properties {
      _ShadowTex ("Shadow Shape", 2D) = "white" {}
   }
   SubShader {
      Pass {      
         Blend Zero OneMinusSrcAlpha // attenuate color in framebuffer 
            // by 1 minus alpha of _ShadowTex
 
         GLSLPROGRAM

         // User-specified properties
         uniform sampler2D _ShadowTex; 

         // Projector-specific uniforms
         uniform mat4 _Projector; // transformation matrix 
            // from object space to projector space 
                  
         varying vec4 positionInProjSpace; 
            // position of the vertex (and fragment) in projector space

         #ifdef VERTEX

         void main()
         {                                         
            positionInProjSpace = _Projector * gl_Vertex;            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

         #endif

         #ifdef FRAGMENT

         void main()
         {
            if (positionInProjSpace.w > 0.0) // in front of projector?
            {
               gl_FragColor = texture2D(_ShadowTex , 
                  vec2(positionInProjSpace) / positionInProjSpace.w); 
               // alternatively: gl_FragColor = texture2DProj(
               //    _ShadowTex, vec3(positionInProjSpace));
            }
            else // behind projector
            {
               gl_FragColor = vec4(0.0);
            }
         }

         #endif
         
         ENDGLSL
      }
   }  
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Projector/Light"
}

总结

[edit | edit source]

恭喜,本教程到此结束。我们已经了解了

  • Unity 的投影仪是如何工作的。
  • 如何实现一个投影仪着色器来为物体添加光线。
  • 如何实现一个投影仪着色器来衰减物体的颜色。

进一步阅读

[edit | edit source]

如果您还想了解更多

  • 关于光空间(与投影空间非常相似),您可以阅读 “饼干”部分
  • 关于纹理映射,特别是 alpha 纹理映射,您可以阅读 “透明纹理”部分
  • 关于固定功能 OpenGL 中的投影纹理映射,您可以阅读 NVIDIA 的白皮书 “Projective Texture Mapping”,作者为 Cass Everitt(可在 在线 获取)。
  • 关于 Unity 的投影仪,您可以阅读 Unity 关于投影仪的文档


< GLSL Programming/Unity

除非另有说明,本页面上的所有示例源代码均为公有领域。
华夏公益教科书