TPS相机及相机遮挡的一些处理方法

提要

第三人称相机有非常多种,今天要实现的一个第三人称射击游戏的相机。

如果对相机控制不是很了解,建议看一下上一篇博文FPS相机

控制思路

鼠标控制yaw和pitch,添加一个distance变量来记录角色和相机之间的距离。通过yaw和pitch来得到相机的position。

最后添加一个向右的位移和向上的位移量,将角色放在屏幕偏左边的位置。

transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);

characterModel.transform.forward = new Vector3(transform.forward.x, characterModel.transform.forward.y, transform.forward.z);
target.forward = new Vector3(transform.forward.x, 0, transform.forward.z);

float yaw = rotationX;
float pitch = rotationY;
float yawRed = Mathf.Deg2Rad * (yaw - 90f);
float pitchRed = Mathf.Deg2Rad * pitch;
Vector3 direction = new Vector3(-Mathf.Cos(yawRed) * Mathf.Cos(pitchRed), -Mathf.Sin(pitchRed), Mathf.Sin(yawRed) * Mathf.Cos(pitchRed));

transform.position = target.transform.position + distance * direction;
transform.position += transform.right + transform.up;

在这里,相机只控制了model的rotation。

direction是通过yaw和pitch计算出的角色到相机的Ray的方向。

一些问题的处理

角色往斜方向跑的动画处理

通常在TPS游戏中,角色的背面是始终对着摄像机的。当玩家希望角色往斜方向走的时候,不能直接播放角色往前走的动画,这时候就需要给角色Model一个额外的角度偏移量,这个偏移量由玩家的输入决定。

                                    

代码如下

	characterModel.transform.forward = new Vector3(transform.forward.x, characterModel.transform.forward.y, transform.forward.z);
				if (characterModel.transform.parent.GetComponent<Character>().characterFPAnimation.extraRotation == 0)
				{
					extraRot = Mathf.Lerp(extraRot, 0f, 10 * Time.deltaTime);
				}else
				{
					extraRot = Mathf.Lerp(extraRot, characterModel.transform.parent.GetComponent<Character>().characterFPAnimation.extraRotation, 10 * Time.deltaTime);
				}
				Quaternion targetRotation = characterModel.transform.rotation * Quaternion.AngleAxis(extraRot, Vector3.up);

				characterModel.transform.rotation = targetRotation;

添加了Lerp,让转身更加顺滑。


墙体遮挡

环境遮挡是第三人称摄像机一个经常遇到问题,下面是几个常见的方法。

解法一  射线检测,将相机移动到不被遮挡的位置。

在Unity官网的一个Tutorial里面,处理的方法是将相机慢慢上移,直到看到角色(游戏的场景是没有天花板的)

 bool ViewingPosCheck (Vector3 checkPos)
    {
        RaycastHit hit;

        // If a raycast from the check position to the player hits something...
        if(Physics.Raycast(checkPos, player.position - checkPos, out hit, relCameraPosMag))
            // ... if it is not the player...
            if(hit.transform != player)
                // This position isn‘t appropriate.
                return false;

        // If we haven‘t hit anything or we‘ve hit the player, this is an appropriate position.
        newPos = checkPos;
        return true;
    }

    void SmoothLookAt ()
    {
        // Create a vector from the camera towards the player.
        Vector3 relPlayerPosition = player.position - transform.position;

        // Create a rotation based on the relative position of the player being the forward vector.
        Quaternion lookAtRotation = Quaternion.LookRotation(relPlayerPosition, Vector3.up);

        // Lerp the camera‘s rotation between it‘s current rotation and the rotation that looks at the player.
        transform.rotation = Quaternion.Lerp(transform.rotation, lookAtRotation, smooth * Time.deltaTime);
    }

在Update里面的处理是这样的

  void FixedUpdate ()
    {
        // The standard position of the camera is the relative position of the camera from the player.
        Vector3 standardPos = player.position + relCameraPos;

        // The abovePos is directly above the player at the same distance as the standard position.
        Vector3 abovePos = player.position + Vector3.up * relCameraPosMag;

        // An array of 5 points to check if the camera can see the player.
        Vector3[] checkPoints = new Vector3[5];

        // The first is the standard position of the camera.
        checkPoints[0] = standardPos;

        // The next three are 25%, 50% and 75% of the distance between the standard position and abovePos.
        checkPoints[1] = Vector3.Lerp(standardPos, abovePos, 0.25f);
        checkPoints[2] = Vector3.Lerp(standardPos, abovePos, 0.5f);
        checkPoints[3] = Vector3.Lerp(standardPos, abovePos, 0.75f);

        // The last is the abovePos.
        checkPoints[4] = abovePos;

        // Run through the check points...
        for(int i = 0; i < checkPoints.Length; i++)
        {
            // ... if the camera can see the player...
            if(ViewingPosCheck(checkPoints[i]))
                // ... break from the loop.
                break;
        }

        // Lerp the camera‘s position between it‘s current position and it‘s new position.
        transform.position = Vector3.Lerp(transform.position, newPos, smooth * Time.deltaTime);

        // Make sure the camera is looking at the player.
        SmoothLookAt();
    }

从角色的脚到头,分四个地方都进行了射线检测,最后的结果是这样的

 

类似的还可以将相机拉到被遮挡的墙前面。

检测的代码如下

void ShelterTest()
	{
		RaycastResult result = new RaycastResult();
		float characterHeight = GameManager.GetInstance().character.height * 0.4f;
		Vector3 targetHeadPos = new Vector3(target.position.x, target.position.y + characterHeight, target.position.z);

		 Ray[] testRays = new Ray[5];
		 testRays[0] = new Ray(targetHeadPos, transform.position + 0.8f * transform.right + 0.5f * transform.up - targetHeadPos);
		 testRays[1] = new Ray(targetHeadPos, transform.position + 0.8f * transform.right - 0.5f * transform.up - targetHeadPos);
		 testRays[2] = new Ray(targetHeadPos, transform.position - 0.8f * transform.right + 0.5f * transform.up - targetHeadPos);
		 testRays[3] = new Ray(targetHeadPos, transform.position - 0.8f * transform.right - 0.5f * transform.up - targetHeadPos);

		 testRays[4] = new Ray(transform.position, transform.position - targetHeadPos);

		float castDist = (transform.position - targetHeadPos).magnitude;
		float[] dists = new float[5];
		for (int i = 0; i < 5; i++)
		{
			if (RaycastHelper.RaycastAll(testRays[i], castDist, true, GameManager.GetInstance().character.floorMask, out result))
			{
				Debug.DrawLine(targetHeadPos, result.point, Color.red);
				dists[i] = Vector3.Distance(result.point, targetHeadPos);
			}else
			{
				Debug.DrawLine(targetHeadPos, targetHeadPos + castDist * testRays[i].direction, Color.blue);
				dists[i] = castDist;
			}
		}

		float minDist0 = Mathf.Min(dists[0], dists[1]);
		float minDist1 = Mathf.Min(dists[2], dists[3]);
		float minDist2 = Mathf.Min(minDist0, minDist1);
		float minDist = Mathf.Min(minDist2, dists[4]);

		transform.position = targetHeadPos + minDist * testRays[4].direction.normalized;

	}

用了5根射线来检测,为了避免fov穿墙的问题。注意是从角色射向摄像机。

解法二  半透明掉中间遮挡的物体

用raycast进行检测,然后动态替换掉材质就可以了。

解法三 利用Stencil对角色进行重绘

对Stencil Buffer 不了解的请参考这一篇 : Stencil buffer

通过Ztest将角色被遮挡部分的Stencial标记出来,然后就可以对这部分的像素进行处理。要么用一种单色绘制出来,要么绘制成透明,要么绘制一个发光的描边,都可以。

简单的效果如下:

这里分三个pass处理,第一遍绘制利用ZTest写Stencil

Shader "Custom/Player" {

	Properties {
		_MaskValue("Mask Value", int) = 2
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}

	SubShader {
	Tags { "RenderType"="Opaque" }
	LOD 200

	Stencil {
		Ref [_MaskValue]
		Comp always
		Pass replace
		ZFail keep
    }

		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

再加一个Shader来清掉ZTest

Shader "Custom/ClearZbuffer" {
	Properties {
		_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Transparent"  "Queue"="Transparent+100"}
		LOD 80
		ColorMask 0
		ZTest Greater
		ZWrite On

		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = half4(1,0,0,1);
			o.Alpha = 0.3;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

最后用一个Shader对被Stencil标记出来的像素进行处理

Shader "Custom/StencilTransparent" {
	Properties {
	    _MaskValue("Mask Value", int) = 2
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_TransVal ("Transparency Value", Range(0,1)) = 1.0
		_ColorAdd ("Color (Add, RGB)", Color) = (0.5,0,0)
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Transparent+100"}
		LOD 80

		Stencil {
		Ref [_MaskValue]
		Comp notequal
		Pass keep
		}

		ZTest LEqual
		ZWrite On
		Blend SrcAlpha OneMinusSrcAlpha
		BlendOp Add

		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;
		fixed _TransVal;
		half4 _ColorAdd;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			//o.Albedo = c.rgb * half4(1,0,0,1);
			//o.Alpha = 1;
			o.Albedo = c.rgb * _ColorAdd;
			o.Alpha = _TransVal;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

遮挡处理的方法并不是说哪一种最好,可以进行混合使用达到最好的效果。

参考

Real-Time Cameras:A Guide for Game Designers and Developers

Unity tutorial stealth - http://unity3d.com/learn/tutorials/projects/stealth-tutorial-4x-only

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-18 18:39:57

TPS相机及相机遮挡的一些处理方法的相关文章

防止fixed元素遮挡其他元素的方法

有多个页面,有的有固定的头部(设置了postion:fixed的元素),有的没有固定的头部,这时就有个问题,有固定头部的页面,头部会遮挡下面的内容,那怎么解决呢? <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <ti

相机标定 matlab opencv ROS三种方法标定步骤(1)

一 .理解摄像机模型,网上有很多讲解的十分详细,在这里我只是记录我的整合出来的资料和我的部分理解 计算机视觉领域中常见的三个坐标系:图像坐标系,相机坐标系,世界坐标系,实际上就是要用矩阵来表示各个坐标系下的转换 首先在图像坐标系下与相机坐标系的关系 可得出   Xcam=x/dx+x0,    Ycam=y/dy+y0  表示为矩阵形式 Xcam           1/dx   0      x0          x Ycam      =    0     1/dy   y0    *  

iOS开发进阶 - 实现类似美颜相机的相机启动动画

如果移动端访问不佳,可以访问我的个人博客 最近在写一个相册的demo,偶尔看到了美拍的相机过载动画觉得很有意思,就想在我的相册demo中加入一个这种特效,下面把我的想法和实现过程给大家分享一下 先上效果图:(demo地址) 步骤分析 这个动效看起来很有特色但是实现起来是非常简单的,只需要用到CALayer和CAShapeLayer做为展示层,然后通过CABasicAnimation实现动画就行了~ 用两个CALayer来呈现启动的image 通过UIBezierPath和CAShapeLayer

IOS7以上navigationBar遮挡页面的解决方法.

在IOS7以上,navigationbar车档界面的解决方法.在viewDidload中.添加以下代码. if( ([[[UIDevice currentDevice] systemVersion] doubleValue]>=7.0)) { self.edgesForExtendedLayout = UIRectEdgeNone; self.extendedLayoutIncludesOpaqueBars = NO; self.modalPresentationCapturesStatusBa

第 19 章 相机 I:取景器

请参考教材,全面理解和完成本章节内容... ... 记录办公室陋习时,如果能以现场照片佐证,问题解决起来就会容易很多.接下来的两章,使用系统自带的Camera API,为CriminalIntent应用添加拍摄作案现场照片的功能. Camera API功能虽然强大,但要用好它并不容易.不仅要编写大量的实现代码,还要苦苦挣扎着学习和理解一大堆全新概念.因此,很容易产生的一个疑问就是:"只是拍张快照,难道就没有便捷的标准接口可以使用吗?" 答案是肯定的.我们可以通过隐式intent与照相机

【Unity】2.8 相机(Camera)

分类:Unity.C#.VS2015 创建日期:2016-03-31 一.简介 Unity的相机用来向玩家呈现游戏世界.你在场景中始终至少有一个相机,但也可以有多个.多个相机可以带给您双人分屏效果或创建高级的自定义效果.您可以让相机动起来,或者用物理(组件)控制它们.您能想到的任何东西,几乎都可以通过相机变成可能,为了配合您的游戏风格,还可以使用典型或独特的相机. 相机 (Camera)是为玩家捕捉并展示世界的一种设备.通过自定义和操作相机,可以使您的游戏演示真正与众不同.您可以在一个场景中使用

WorldWind源码剖析系列:相机类[未完]

PluginSDK中的相机类CameraBase是三维计算机图形学中的概念.观察者在三维场景中漫游时,通过眼睛看到的场景和相机拍摄过程非常一致.实际上,Direct3D和OpenGL都是先通过对现实世界中的场景先进行世界变换,再通过设置观察矩阵以在场景中安置一个虚拟相机,构建一个视景体来裁剪场景的可见区域,然后在通过投影变换(平行投影或透视投影),获取三维场景的“像”,最后再通过视口变换,将场景的“像”光栅化输出到二维显示屏幕上.如下图所示. 在三维地形系统中,通常定义一个虚拟相机来实现对三维场

Android调用系统相机、自定义相机、处理大图片

Android调用系统相机和自定义相机实例 本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显示出来,该例子也会涉及到Android加载大图片时候的处理(避免OOM),还有简要提一下有些人SurfaceView出现黑屏的原因. Android应用拍照的两种方式,下面为两种形式的Demo展示出来的效果.    知识点: 一.调用系统自带的相机应用 二.自定义我们自己的拍照界面 三.关于计算机解析图片原理(如何正确加载图片到Android应用中) 所需

【转载】推导相机变换矩阵

原文:推导相机变换矩阵 一些网友写信给我希望能够了解固定流水线中世界空间到相机空间变换矩阵的具体推导过程.其实之前我在<向量几何在游戏编程中的使用6>中已经简单的把相机变换作为一个使用基理论的例子进行了说明,但可能仍然不够具体.这篇文章中,我会尽力阐述相机变换的整个来龙去脉.希望能够对正在学习固定流水线的朋友们有所帮助.这里我们仍然会在推导相机变换之前介绍几个理论知识,目的是为了更好的理解推导过程.我们马上开始! 什么是相机变换? 在流水线中,当物体从模型坐标通过世界矩阵变换到世界空间之后,它