Image may be NSFW. Clik here to view. 图 5.预计算遮挡剔除例子。
静态网格体Actor合并(Static Mesh Actor Merging)
UE4里的“Merge Actors”工具可以自动将多个静态网格体合并成一个网格体来减少绘制调用,在设置里可以根据实际需要选择是否合并材质、光照贴图或物理数据,设置流程可以参考[8]。此外,UE4里有另一个工具-分层细节级别(Hierarchical Level of Detail,HLOD)也有类似的Actor合并效果[9],差别在于HLOD只会对远距离发生LOD的对象做合并。
Image may be NSFW. Clik here to view.带有该符号的元素表示关于可通过提高 CPU 功率改进VR体验的指南。
身体基础
Image may be NSFW. Clik here to view.身体基础指南是在 VR 体验中建立任何沉浸感的基本要求。要使玩家足够信任系统以体验沉浸感,玩家必须感到安全、舒适,且不受外界干扰因素的影响。Oculus*、Microsoft* 和 HTC* 等硬件提供商制定的许多技术指南均属于此类别。
Image may be NSFW. Clik here to view.安全和健康得到保证后,营造沉浸式体验的下一步是创造一个令人信服的世界,以供用户探索。在最好的情况下,用户期望和虚拟世界之间的明显不匹配将在潜意识里阻止他们完全沉浸其中。在最坏的情况下,这些不匹配成为明显的干扰因素,破坏沉浸感,就像是电影剪辑师没有将电影场景中的麦克风架或摄像机支架剪掉。
Image may be NSFW. Clik here to view.人们很容易就会认为上述良好设备和沉浸式 VR 功能是软件产品成功所需的全部要素。虽然此类产品具备显著优势,但还不够。我们可以用电影院的 3-D 效果进行类比。无论雪花飘落或岩石向观众砸来的效果多么酷炫,如果电影无法提供值得观看的体验(即,好的角色、有趣的故事框架和观众想听的对话),这些新鲜感将很快消失。
void Update () {
if (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && (Time.Time > nextfire))
{
//If the Primary Index trigger is pressed on the touch controller we fire at the targets
nextfire = Time.Time + fireRate;
audioSource.Play();
// Teleporting is set to false here
RayCastShoot(false); }
else if (OVRInput.Get(OVRInput.RawButton.A) && (Time.time > nextfire))
{
// Teleporting is set to true when Button A is pressed on the controller
nextfire = Time.time + fireRate;
RayCastShoot(true);
}
else if (OVRInput.Get(OVRInput.RawButton.B) && (Time.time > nextfire))
{
// If Button B is pressed on the controller player is reset to his original position
nextfire = Time.time + fireRate;
player.transform.position = resetPosition;
}
}
在以下示例中,我将僵尸添加为敌人(僵尸可以从 Unity Asset Store 中下载),还添加了一些目标,如岩石和手榴弹,以增加场景的粒子效果,如爆炸、岩石撞击等。我还根据该教程创建了一个简单的僵尸动画。
//Enemy.cs
public class Enemy :MonoBehaviour {
//public GameObject explosionPrefab;
public int fullLife = 5;
public void enemyhit(int life)
{
//subtract life when Damage function is called
fullLife -= life;
//Check if full life has fallen below zero
if (fullLife <= 0)
{
//Destroy the enemy if the full life is less than or equal to zero
Destroy(gameObject);
}
}
}
// if the hit object is the enemy
//Raycastexample.cs from where we are calling the enemyhit function
if (enemy != null)
{
enemy.enemyhit(1);
//Checks the health of the enemy and resets to max again
//Instantiates the blood effect prefab for each hit
var bloodEffect = Instantiate(bloodPrefab);
bloodEffect.transform.position = hit.point;
if (enemy.fullLife <= 0)
{
enemy.fullLife = 5;
}
}
//If the hit targets are the targets other than the enemy like the mud, Rocks , Grenades on the terrain
else
{
var impactEffect = Instantiate(impactEffectPrefab);
impactEffect.transform.position = hit.point;
Destroy(impactEffect, 4);
// If the Target is the ground
if ((hit.collider.gameObject.CompareTag("Mud")))
{
var mudeffect = Instantiate(mudPrefab);
mudeffect.transform.position = hit.point;
}
// If the Target is Rocks
else if ((hit.collider.gameObject.CompareTag("Rock")))
{
var rockeffect = Instantiate(rockPrefab);
rockeffect.transform.position = hit.point;
}
// If the Target is the Grenades
else if ((hit.collider.gameObject.CompareTag("Grenade")))
{
var grenadeEffect = Instantiate(explosionPrefab);
grenadeEffect.transform.position = hit.point;
Destroy(grenadeEffect, 4);
}
}
}
if (teleport)
{
//If the player needs to be teleported to the hit point
// Vector3 newposition = hit.point;
//player.transform.position = new Vector3(newposition.x, player.transform.position.y, newposition.z);
//If the player needs to be teleported to the teleport points that are created in the Unity scene.Below code teleports the player
// to one of the points randomly
var teleportPoints = GameObject.FindGameObjectsWithTag("Teleport");
var newPosition = teleportPoints[Random.Range(0, teleportPoints.Length)];
player.transform.position = newPosition.transform.position;
return;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Raycastexample :MonoBehaviour {
//Audio clip to play
public AudioClip clip;
public AudioSource audioSource;
//rate of firing at the targets
public float fireRate = .25f;
// Range to which Raycast will detect the collision
public float weaponRange = 300f;
//Prefab for Impacts at the target
public GameObject impactEffectPrefab;
//Prefab for Impacts for grenade explosions
public GameObject explosionPrefab;
//Prefab at gun transform position
public GameObject GunfirePrefab;
//Prefab if the target is the terrain
public GameObject mudPrefab;
// Prefab when hits the Zombie
public GameObject bloodPrefab;
// prefabs when hits the rocks
public GameObject rockPrefab;
// Player transform that is used in teleporting
public Transform player;
private float nextfire;
//transform at the Gun end to show some muzzle effects when firing
public Transform gunTransform;
// Position to reset the player to its original position when "B" is pressed on the touch controller
private Vector3 resetPosition;
// Use this for initialization
void Start () {
// Play the Audio clip while firing
audioSource = GetComponent();
audioSource.clip = clip;
// Reset position after teleporting to set the position to his original position
resetPosition = transform.position;
}
// Update is called once per frame
void Update () {
//If the Primary Index trigger is pressed on the touch controller we fire at the targets
if (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && (Time.time > nextfire))
{
nextfire = Time.time + fireRate;
audioSource.Play();
// Teleporting is set to false here
RayCastShoot(false);
}
else if (OVRInput.Get(OVRInput.RawButton.A) && (Time.time > nextfire))
{
// Teleporting is set to true when Button A is pressed on the controller
nextfire = Time.time + fireRate;
RayCastShoot(true);
}
else if (OVRInput.Get(OVRInput.RawButton.B) && (Time.time > nextfire))
{
// If Button B is pressed on the controller player is reset to his original position
nextfire = Time.time + fireRate;
player.transform.position = resetPosition;
}
}
private void RayCastShoot(bool teleport)
{
RaycastHit hit;
//Casts a ray against the targets in the scene and returns the "hit" object.
if (Physics.Raycast(gunTransform.position, gunTransform.forward, out hit, weaponRange))
{
if (teleport)
{
//If the player needs to be teleported to the hit point
// Vector3 newposition = hit.point;
//player.transform.position = new Vector3(newposition.x, player.transform.position.y, newposition.z);
//If the player needs to be teleported to the teleport points that are created in the Unity scene.Below code teleports the player
// to one of the points randomly
var teleportPoints = GameObject.FindGameObjectsWithTag("Teleport");
var newPosition = teleportPoints[Random.Range(0, teleportPoints.Length)];
player.transform.position = newPosition.transform.position;
return;
}
//Attach the Enemy script as component to the enemy
Enemy enemy = hit.collider.GetComponentInParent();
// Muzzle effects of the Gun and its tranfrom poisiton is the Gun
var GunEffect = Instantiate(GunfirePrefab);
GunfirePrefab.transform.position = gunTransform.position;
// if the hit object is the enemy
if (enemy != null)
{
enemy.enemyhit(1);
//Checks the health of the enemy and resets to max again
//Instantiates the blood effect prefab for each hit
var bloodEffect = Instantiate(bloodPrefab);
bloodEffect.transform.position = hit.point;
if (enemy.fullLife <= 0)
{
enemy.fullLife = 5;
}
}
//If the hit targets are the targets other than the enemy like the mud, Rocks , Grenades on the terrain
else
{
var impactEffect = Instantiate(impactEffectPrefab);
impactEffect.transform.position = hit.point;
Destroy(impactEffect, 4);
// If the Target is the groud
if ((hit.collider.gameObject.CompareTag("Mud")))
{
Debug.Log(hit.collider.name + ", " + hit.collider.tag);
var mudeffect = Instantiate(mudPrefab);
mudeffect.transform.position = hit.point;
}
// If the Target is Rocks
else if ((hit.collider.gameObject.CompareTag("Rock")))
{
var rockeffect = Instantiate(rockPrefab);
rockeffect.transform.position = hit.point;
}
// If the Target is the Grenades
else if ((hit.collider.gameObject.CompareTag("Grenade")))
{
var grenadeEffect = Instantiate(explosionPrefab);
grenadeEffect.transform.position = hit.point;
Destroy(grenadeEffect, 4);
}
}
}
}
}
//Enemy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy :MonoBehaviour {
//public GameObject explosionPrefab;
public int fullLife = 5;
// Use this for initialization
void Start () {
}
public void enemyhit(int life)
{
//subtract life when Damage function is called
fullLife -= life;
//Check if full life has fallen below zero
if (fullLife <= 0)
{
//Destroy the enemy if the full life is less than or equal to zero
Destroy(gameObject);
}
}
}
然而,改变游戏的核心技术绝非易事。最初,《Wilson’s Heart》是一款具有第一人称 PC 游戏,现在需要调整它的功能,以满足全新的游戏要求。“制作虚拟现实版本意味着我们基本上需要从头开始。”Bear 表示,“我们甚至重新评估了黑白色的图形审美:是否在虚拟现实中略显怪异?是否合适,是否让玩家感到不真实?”
在目前的技术发展阶段,影响虚拟现实体验的主要问题无疑是晕动症。从 PC 转变为虚拟现实的过程中,移动是必须处理的设计更改。“我们不希望玩家产生眩晕感,我们希望玩家尽可能长时间投入游戏中。如果您在游戏中,屏幕变成黑色,您仍可以听到 Wilson 的呼吸声、脚步声或其他声效,您追寻这些声音前行。”Bear 表示,“在虚拟现实中,我们需要从不同角度考虑所有问题。”
除了大牌明星彼得•威勒的加盟外,柯特伍德•史密斯也为游戏倾情献声,令所有《机械战警》粉丝兴奋不已。“尽管他们不在同一个房间内,但是,能同时邀请到两人非常难得。”《机械战警》铁杆粉丝 Bear 表示,“柯特伍德甚至情不自禁地说出《机械战警》的台词,我在 VO 室里都压抑不住内心的激动之情!我们在游戏中采用了一句台词。”
这听起来就像是一个速成班,但 Raissi 接受本计划在四个月内完成的游戏项目向后拖延如此之久。尽管他承认自己并不希望整个职业生涯单打独斗,但他没有停下一个人制作游戏的脚步。尽管有 3D 艺术创作的经验,但他很难在大型发行公司谋求一份美术方面的工作,因为他没有任何具体可证明的游戏开发经验,因为军事模拟的工作是无法展示的。
Liu, Wei, et al., SSD: Single Shot MultiBox Detector, European conference on computer vision. Springer, Cham, 2016.
Lin, Kevin, et al., Learning compact binary descriptors with unsupervised deep neural networks, Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 2016.