ARFoundation之路-AR阴影生成之三

版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。

  Unity自带的Shadowmap阴影与Projector投影阴影生成方式都具有普适的特点,适用范围广,并且均是通过物理的方式生成阴影,因此阴影能够投射到自身以及不规则的物体表面,与场景复杂度没有关系。但通过前面的学习我们也可以看到,这两种生成阴影的方法在带来较好效果的同时,性能开销也是比较大的,特别是在使用高分辨率高质量实时软阴影时,非常有可能成为性能瓶颈。

  在某些情况下,我们可能并不需要那么高质量、高通用性的阴影,或者由于性能制约不能使用那么高质量的阴影,因此可能会寻求一个“适用某些特定场合”的“看起来正确”的实时阴影以降低性能消耗。本节我们将要学习一种平面投影阴影(Planar Projected Shadows),以适应一些对性能要求非常高,以降低阴影质量换取性能的应用场景。

(一)数学原理

  阴影区域其实就是物体在投影平面上的投影区域,因此我们可以直接将物体的顶点投影到投影平面上,并在这些投影区域里用特定的颜色着色即可,如下图所示。

在这里插入图片描述

  因此,阴影计算转化为求物体在平面上的投影,求投影可以使用解释几何求投影的方法,也可以使用平面几何的方法。在上图中,阴影投影计算可以转化为数学模型,即已知空间内一个方向向量L(Lx,Ly,Lz)和一点P(Px,Py,Pz),求点P沿着L方向在平面y = h上的投影位置Q(Qx,Qy,Qz)的问题,下面我们使用平面几何的计算方式进行推导,为简体计算,只在二维空间内进行推导,对上图进行二维抽象成下图。

在这里插入图片描述

  根据相似三角形定理,可以得到下面公式:

− L y P y − h = L x Q x − P x -\frac{Ly}{Py-h}=\frac{Lx}{Qx-Px} PyhLy=QxPxLx

  因此

Q x = P x − L x ( P y − h ) ) L y Qx = Px - \frac{Lx(Py-h))}{Ly} Qx=PxLyLx(Pyh))

Q y = h Qy = h Qy=h

  坐标Q(Qx,Qy)的即是要求的投影点坐标,推广到三维,坐标Q计算公式如下:

Q x = P x − L x ( P y − h ) ) L y Qx = Px - \frac{Lx(Py-h))}{Ly} Qx=PxLyLx(Pyh))

Q y = h Qy = h Qy=h

Q z = P z − L z ( P y − h ) ) L z Qz = Pz - \frac{Lz(Py-h))}{Lz} Qz=PzLzLz(Pyh))

  该公式即为阴影投影公式。

(二)代码实现

  有了公式以后,我们就可以使用Shader对阴影进行渲染,为了与原有渲染流程统一,我们只需要在原来渲染的shader中多写一个pass进行阴影渲染即可,具体Shader如下所示。

Shader "Davidwang/PlanarShadow"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white" {}
		_ShadowColor("_ShadowColor",color) = (0.5, 0.5, 0.5, 1.0)
		_ShadowFalloff("ShadowInvLen", float) = 1.2
	}

    SubShader
	{
		Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+10" }
		LOD 100

		Pass
		{
				CGPROGRAM

				#pragma vertex vert
				#pragma fragment frag
				// make fog work
				#pragma multi_compile_fog

				#include "UnityCG.cginc"

				struct appdata
				{
					float4 vertex : POSITION;
					float2 uv : TEXCOORD0;
				};

				struct v2f
				{
					float2 uv : TEXCOORD0;
					UNITY_FOG_COORDS(1)
					float4 vertex : SV_POSITION;
				};

				sampler2D _MainTex;
				float4 _MainTex_ST;

				v2f vert(appdata v)
				{
					v2f o;
					o.vertex = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);
					UNITY_TRANSFER_FOG(o,o.vertex);
					return o;
				}

				fixed4 frag(v2f i) : SV_Target
				{
					fixed4 col = tex2D(_MainTex, i.uv);
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				return col;
			}

			ENDCG
		}

		Pass
		{
			//用使用模板测试以保证alpha显示正确
			Stencil
			{
				Ref 0
				Comp equal
				Pass incrWrap
				Fail keep
				ZFail keep
			}

			//透明混合模式
			Blend SrcAlpha OneMinusSrcAlpha

			//关闭深度写入
			ZWrite off

			//深度稍微偏移防止阴影与地面穿插
			Offset -1 , 0

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float4 color : COLOR;
			};

			float4 _LightDir;
			float4 _ShadowColor;
			float _ShadowFalloff;

			float3 ShadowProjectPos(float4 vertPos)
			{
				float3 shadowPos;

				//得到顶点的世界空间坐标
				float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;

				//灯光方向
				float3 lightDir = normalize(_LightDir.xyz);

				//阴影的世界空间坐标(低于地面的部分不做改变)
				shadowPos.y = min(worldPos.y , _LightDir.w);
				shadowPos.xz = worldPos.xz - lightDir.xz * max(0 , worldPos.y - _LightDir.w) / lightDir.y;

				return shadowPos;
			}

			v2f vert(appdata v)
			{
				v2f o;

				//得到阴影的世界空间坐标
				float3 shadowPos = ShadowProjectPos(v.vertex);

				//转换到裁切空间
				o.vertex = UnityWorldToClipPos(shadowPos);

				//得到中心点世界坐标
				float3 center = float3(unity_ObjectToWorld[0].w , _LightDir.w , unity_ObjectToWorld[2].w);
				//计算阴影衰减
				float falloff = 1 - saturate(distance(shadowPos , center) * _ShadowFalloff);

				//阴影颜色
				o.color = _ShadowColor;
				o.color.a *= falloff;

				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				return i.color;
			}
				ENDCG
		}
	}
}

  第一个pass是正常的物体着色渲染,第二个pass负责阴影投影着色,其中_LightDir.xyz是灯光方向,_LightDir.w是接受阴影的平面高度,_ShadowColor为阴影颜色,_LightDir我们使用脚本代码将值传过来,因为不能提前知道灯光方向和检测到的平面(接受阴影)的高度信息。使用模板与混合是为了营造阴影衰减的效果,如下图所示。

在这里插入图片描述
  计算中心点世界坐标代码:float3 center =float3( unity_ObjectToWorld[0].w , _LightPos.w , unity_ObjectToWorld[2].w);该公式中unity_ObjectToWorld这个矩阵每一行的第四个分量分别对应物体Transform的xyz。float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff)为计算衰减因子,以便于后面的混合。

  相应的,我们需要修改AppContoller控制脚本如下。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

[RequireComponent(typeof(ARRaycastManager))]
public class AppControler : MonoBehaviour
{
    public  GameObject spawnPrefab;
    public GameObject ARPlane;
    public Light mLight;

    static List<ARRaycastHit> Hits;
    private ARRaycastManager mRaycastManager;
    private GameObject spawnedObject = null;
    private float mARCoreAngle = 180f;
    private List<Material> mMatList = new List<Material>();
    private float mPlaneHeight = 0.0f;

    private void Start()
    {
        Hits = new List<ARRaycastHit>();
        mRaycastManager = GetComponent<ARRaycastManager>();
    }

    void Update()
    {
        if (Input.touchCount == 0)
            return;
        var touch = Input.GetTouch(0);
        if (mRaycastManager.Raycast(touch.position, Hits, TrackableType.PlaneWithinPolygon | TrackableType.PlaneWithinBounds))
        {
            var hitPose = Hits[0].pose;            
            if (spawnedObject == null)
            {
                spawnedObject = Instantiate(spawnPrefab, hitPose.position, hitPose.rotation);
                spawnedObject.transform.Rotate(Vector3.up, mARCoreAngle);
                spawnedObject.transform.Translate(0, 0.02f, 0);
                var p = Instantiate(ARPlane, hitPose.position, hitPose.rotation);
                mPlaneHeight = hitPose.position.y;
                p.transform.parent = spawnedObject.transform;

                GameObject spider = GameObject.FindGameObjectWithTag("spider");
                SkinnedMeshRenderer[] renderlist = spider.GetComponentsInChildren<SkinnedMeshRenderer>();
                foreach (var render in renderlist)
                {
                    if (render == null)
                        continue;

                    mMatList.Add(render.material);
                }
            }
            else
            {
                spawnedObject.transform.position = hitPose.position;
                spawnedObject.transform.Translate(0, 0.02f, 0);
                mPlaneHeight = hitPose.position.y + 0.02f;
            }
        }
        if(spawnedObject != null)
            UpdateShader();
    }

    private void UpdateShader()
    {
        Vector4 projdir = new Vector4(mLight.transform.forward.x, mLight.transform.forward.y, mLight.transform.forward.z, mPlaneHeight);
        foreach (var mat in mMatList)
        {
            if (mat == null)
                continue;
            mat.SetVector("_LightDir", projdir);
        }
    }
}


  为了方便找到小蜘蛛的Mesh Renderer,我们给该Mesh加了个Tag “spider”,同时为了实时的反映虚拟物体的位置变化与光照变化,我们还对光照方向与阴影投影平面进行了实时更新,运行效果如下。

在这里插入图片描述
在这里插入图片描述

  从上图可以看到,阴影的渲染效果整体还是不错的,更关键的是性能与使用实时软阴影相比提升非常大,这也是当前很多流行游戏使用平面投影阴影代替内置阴影的主要原因。

  从平面投影阴影数学原理可以看到,该阴影只会投影到指定的平整平面上,不能投影到凸凹不平的平面,同时该阴影不会截断,即会穿插到其他物体或墙体时面去,但在AR中,这些问题都不是很严重的问题,为了提高AR应用性能使用该阴影是一个不错的选择。

参考文献

1、使用顶点投射的方法制作实时阴影 使用顶点投射的方法制作实时阴影

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
课程介绍 本套课程从技术理念到项目实践,教大家系统掌握ARKit技术开发,随心打造iOS端AR增强现实应用。由一开始的开发环境搭建,到Unity ARKit Plugin、ARFoundation ARKit等不同时期技术的讲解。从平面模型放置、识别图片、手势交互、3D物体识别、面部AR贴纸、光照估计、环境探针、多人AR技术,甚至包含ARKit3.0的动作捕捉技术等。除了以上课程内容,更加入了随着技术更新与时俱进更新的ARKit连载技术教学内容。课程收益 轻松理解ARKit的技术原理,从零到一创建自己的第一个AR项目。学习UnityARKit Plugin经典技术,了解ARKit中的常见概念和原理。学会在 ARFoundation 中使用ARKit,进行企业级AR项目开发。学会如何在AR项目里播放模型动画,通过触屏手势交互实现模型的旋转和缩放。 了解ARKit的图片识别,掌握AR卡片、AR书籍的实现方法。 学会使用面部增强技术,实现热门短视频应用的AR贴纸效果,实现面部表情追踪。学会使用ARKit物体检测技术,实现3D物体识别,可以广泛应用于工业、建筑、古董、玩具手办、艺展览等不同场景。学习ARKit中的经典交互案例,优化AR项目的用户体验。熟练使用环境纹理、光照估计技术,让AR内容随当前现实场景自动变化调节,看起来更加真实。基于WorldMap、CollaborativeSession AR技术,实现AR场景的持久化及多人AR体验效果。
ARFoundation是一个用于在移动设备上创建现实增强应用程序的开发框架。人脸跟踪编程是ARFoundation系列教程中的一部分。 人脸跟踪是通过相机捕捉实时视频流并使用AR技术来检测和跟踪人脸的过程。这个过程涉及到计算机视觉和人工智能的技术,可以在移动设备上实时地识别人脸,进而应用各种效果和交互。 在ARFoundation系列教程中学习人脸跟踪编程,你将学习如何使用ARFoundation和Unity引擎来构建具有人脸追踪功能的应用程序。教程会向你介绍如何在Unity中创建一个AR项目,并使用ARFoundation的API来实现人脸追踪功能。 首先,你需要在Unity中导入ARFoundation库并设置相机权限。然后,你可以创建一个3D模型来代表人脸,并将其与人脸跟踪的数据进行关联。在跟踪开始后,你可以通过获取人脸的特征点信息和姿势来实时地更新模型的位置和角度。 此外,你还可以根据人脸的表情特征,例如眨眼、微笑等,来触发应用程序中的效果或交互。例如,你可以通过检测到用户眨眼的动作来实现快门的触发,拍摄照片或录制视频。 通过学习ARFoundation系列教程中的人脸跟踪编程,你将能够掌握如何使用AR技术在移动设备上实现实时人脸跟踪功能。这将为你开发创新的增强现实应用程序提供基础,并且能够为用户提供更加沉浸式和交互性的体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_DavidWang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值