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

本文介绍了一种实时渲染硬阴影的方法,通过将物体投影到平面上实现。教程详细讲解了顶点着色器如何计算投影,并提供了完整的着色器代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

投射到平面上的硬阴影

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

投影物体到一个平面上

这里写图片描述
为了渲染投影的阴影,我们必须把物体向一个平面投影。为了指定这个平面,我们将会使用默认平面的局部坐标系。这样,我们可以通过编辑平面对象很容易地修改平面的位置和方向,实际的平面就是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指令来移除,它在章节“裁剪”中有介绍。
  • 如果平面有纹理,那么这个纹理可以通过仅仅使用本地顶点坐标进行纹理查找(也在平面对象的着色器中)来合成,并且指定平面的纹理作为阴影投射对象的着色器属性。
  • 软阴影可以通过在这个着色器中计算平面的光照来伪造,并且根据阴影投射对象的表面法向量到光照方向的角度衰减,这个类似于章节“轮廓增强”中的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值