Cg Programming/Unity/Shadows on Planes平面上的阴影

本教程涵盖了投射到平面上的阴影。

它并没有基于任何特别的教程;但是,理解章节“顶点变换”还是很有用的。

投射到平面上的硬阴影

这里写图片描述
实时计算真实的阴影是困难的。但是,有些情况就要容易得多了。向平面投影一个硬阴影(即没有半影的阴影,查看章节“球体的软阴影”)就是其中之一。这个想法是通过用阴影接收平面之上被投影的顶点在阴影的颜色中渲染阴影投射物体来渲染阴影。

投影物体到一个平面上

这里写图片描述
为了渲染投影的阴影,我们必须把物体向一个平面投影。为了指定这个平面,我们将会使用默认平面的局部坐标系。这样,我们可以通过编辑平面对象很容易地修改平面的位置和方向,实际的平面就是y=0的平面,它是由xz轴组成的。

于是,顶点着色器就是这样的:

uniform float4x4 _World2Receiver; // transformation from 
            // world coordinates to the coordinate system of the plane

         [...]

         float4 vert(float4 vertexPos : POSITION) : SV_POSITION
         {
            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            float4 lightDirection;
            if (0.0 != _WorldSpaceLightPos0.w) 
            {
               // point or spot light
               lightDirection = normalize(
                  mul(modelMatrix, vertexPos - _WorldSpaceLightPos0));
            } 
            else 
            {
               // directional light
               lightDirection = -normalize(_WorldSpaceLightPos0); 
            }

            float4 vertexInWorldSpace = mul(modelMatrix, vertexPos);
            float distanceOfVertex = 
               mul(_World2Receiver, vertexInWorldSpace).y 
               // = height over plane 
            float lengthOfLightDirectionInY = 
               mul(_World2Receiver, lightDirection).y 
               // = length in y direction

           lightDirection = lightDirection 
               * (distanceOfVertex / (-lengthOfLightDirectionInY));

            return mul(UNITY_MATRIX_VP, 
               vertexInWorldSpace + lightDirection));
         }

uniform变量_World2Receiver是通过附着在阴影投射对象上的脚本来设置的:

@script ExecuteInEditMode()

public var plane : GameObject;

function Update () 
{
   if (null != plane)
   {
      GetComponent(Renderer).sharedMaterial.SetMatrix("_World2Receiver", 
         plane.GetComponent(Renderer).worldToLocalMatrix);
   }
}

这个脚本需要用户指定阴影接收的平面对象并且设置相应的uniform _World2Receiver

完整的着色器代码

对于这段完整的着色器代码,我们通过注意矩阵向量乘法的y坐标正好是矩阵和向量第二行(即从0开始时的第一行)的点乘这点来提升代码的性能。此外,我们通过不移动平面下方的顶点来提升代码的鲁棒性(译者注:鲁棒是Robust的音译,也就是健壮和强壮的意思。),光线向上的时候也不移动。另外,我们要用以下的指令努力确保阴影是在平面的上面:

Offset -1.0, -2.0

这会稍微减少光栅化三角形的深度,这样它们总是会遮挡相同深度的其它三角形。

着色器的第一个通道渲染了阴影投射对象,同时第二个通道渲染了投影的阴影。在一个实际的应用中,第一个通道会被一个或多个通道替换以便计算阴影投射对象的光照。

Shader "Cg planar shadow" {
   Properties {
      _Color ("Object's Color", Color) = (0,1,0,1)
      _ShadowColor ("Shadow's Color", Color) = (0,0,0,1)
   }
   SubShader {
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // rendering of object

         CGPROGRAM

         #pragma vertex vert 
         #pragma fragment frag

         // User-specified properties
         uniform float4 _Color; 

         float4 vert(float4 vertexPos : POSITION) : SV_POSITION 
         {
            return mul(UNITY_MATRIX_MVP, vertexPos);
         }

         float4 frag(void) : COLOR
         {
            return _Color; 
         }

         ENDCG 
      }

      Pass {   
         Tags { "LightMode" = "ForwardBase" } 
            // rendering of projected shadow
         Offset -1.0, -2.0 
            // make sure shadow polygons are on top of shadow receiver

         CGPROGRAM

         #pragma vertex vert 
         #pragma fragment frag

         #include "UnityCG.cginc"

         // User-specified uniforms
         uniform float4 _ShadowColor;
         uniform float4x4 _World2Receiver; // transformation from 
            // world coordinates to the coordinate system of the plane

         float4 vert(float4 vertexPos : POSITION) : SV_POSITION
         {
            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 
            float4x4 viewMatrix = 
               mul(UNITY_MATRIX_MV, modelMatrixInverse);

            float4 lightDirection;
            if (0.0 != _WorldSpaceLightPos0.w) 
            {
               // point or spot light
               lightDirection = normalize(
                  mul(modelMatrix, vertexPos - _WorldSpaceLightPos0));
            } 
            else 
            {
               // directional light
               lightDirection = -normalize(_WorldSpaceLightPos0); 
            }

            float4 vertexInWorldSpace = mul(modelMatrix, vertexPos);
            float4 world2ReceiverRow1 = 
               float4(_World2Receiver[1][0], _World2Receiver[1][1], 
               _World2Receiver[1][2], _World2Receiver[1][3]);
            float distanceOfVertex = 
               dot(world2ReceiverRow1, vertexInWorldSpace); 
               // = (_World2Receiver * vertexInWorldSpace).y 
               // = height over plane 
            float lengthOfLightDirectionInY = 
               dot(world2ReceiverRow1, lightDirection); 
               // = (_World2Receiver * lightDirection).y 
               // = length in y direction

            if (distanceOfVertex > 0.0 && lengthOfLightDirectionInY < 0.0)
            {
               lightDirection = lightDirection 
                  * (distanceOfVertex / (-lengthOfLightDirectionInY));
            }
            else
            {
               lightDirection = float4(0.0, 0.0, 0.0, 0.0); 
                  // don't move vertex
            }

            return mul(UNITY_MATRIX_VP,  
               vertexInWorldSpace + lightDirection);
         }

         float4 frag(void) : COLOR 
         {
            return _ShadowColor;
         }

         ENDCG 
      }
   }
}

片元着色器的进一步优化

有一些东西可以改进,特别是在片元着色器中:

  • 矩形平面对象外部的阴影片段可以用discard指令来移除,它在章节“裁剪”中有介绍。
  • 如果平面有纹理,那么这个纹理可以通过仅仅使用本地顶点坐标进行纹理查找(也在平面对象的着色器中)来合成,并且指定平面的纹理作为阴影投射对象的着色器属性。
  • 软阴影可以通过在这个着色器中计算平面的光照来伪造,并且根据阴影投射对象的表面法向量到光照方向的角度衰减,这个类似于章节“轮廓增强”中的方法。
展开阅读全文

Secrets in Shadows

12-30

DescriptionnnLong long ago, there were several identical columns (or cylinders) built vertically in a big open space near Yokohama (Fig. 1). In the daytime, the shadows of the columns were moving on the ground as the sun moves in the sky. Each column was very tall so that its shadow was very long. The top view of the shadows is shown in Fig. 2.nnnFig. 1: Columns (or cylinders)nnnFig. 2: Top view of the columns (Fig. 1) and their shadowsnnThe directions of the sun that minimizes and maximizes the widths of the shadows of the columns were said to give the important keys to the secrets of ancient treasures.nnThe width of the shadow of each column is the same as the diameter of the base disk. But the width of the whole shadow (the union of the shadows of all the columns) alters according to the direction of the sun since the shadows of some columns may overlap those of other columns.nnFig. 3 shows the direction of the sun that minimizes the width of the whole shadow for the arrangement of columns in Fig. 2.nnnFig. 3: The direction of the sun for the minimal width of the whole shadownnFig. 4 shows the direction of the sun that maximizes the width of the whole shadow. When the whole shadow is separated into several parts (two parts in this case), the width of the whole shadow is defined as the sum of the widths of the parts.nnnFig. 4: The direction of the sun for the maximal width of the whole shadownnA direction of the sun is specified by an angle θ defined in Fig. 5. For example, the east is indicated by θ=0, the south by θ=π/2, and the west by θ=π. You may assume that the sun rises in the east (θ=0) and sets in the west (θ=π).nnYour job is to write a program that, given an arrangement of columns, computes two directions θmin and θmax of the sun that give the minimal width and the maximal width of the whole shadow, respectively.nnThe position of the center of the base disk of each column is specified by its (x,y) coordinates. The x-axis and y-axis are parallel to the line between the east and the west and that between the north and the south, respectively. Their positive directions indicate the east and the north, respectively.nnYou can assume that the big open space is a plane surface.nnnFig. 5: The definition of the angle of the direction of the sunnnThere may be more than one θmin or θmax for some arrangements in general, but here, you may assume that we only consider the arrangements that have unique θmin and θmax in the range 0<=θmin 问答

没有更多推荐了,返回首页