결과물
쉐이더
바다 자체는 에셋 스토어에서 구하였고 주로 물체를 바다 위에 띄우는 작업을 하였다.
바다에 물체를 뛰우기 위해서는 바다 버텍스의 y위치를 구해야 한다.
그러기 위해서 쉐이더를 일부 수정하였다.
가장 중요한 것이 버텍스의 y값을 정하는 텍스처의 uv값을 월드 좌표계를 이용하여 뽑아내야 한다는 것이다.
그 외에도 쉐이더를 어떻게 구현했는지에 따라 수정해야 하는 부분이 더 생길 수 있다.
쉐이더를 수정한 후 버텍스의 y높이를 구하는 부분을 스크립트로
따로 계산하여 쉽게 사용할 수 있게 해주어야 한다.
스크립트
쉐이더 수정이 끝나면 스크립트로 쉐이더에서 수행한 연산을 따로 구현해주면 된다.
public class Ocean : MonoBehaviour
{
public static Ocean Instance { get; private set; }
[SerializeField] private Transform ocean;
private float waveSpeed;
private float waveIntensity;
private Texture2D noise;
private Material oceanMat;
private void Awake()
{
Instance = this;
oceanMat = ocean.GetComponent<Renderer>().sharedMaterial;
waveSpeed = oceanMat.GetFloat("_WaveSpeed");
waveIntensity = oceanMat.GetFloat("_WaveIntensity");
noise = (Texture2D)oceanMat.GetTexture("_Noise");
}
public float GetWaterHeight(Vector3 position)
{
Vector2 uv = new Vector2(position.x, position.z) * waveIntensity + (Vector2.one * Time.time * waveSpeed);
float r = noise.GetPixelBilinear(uv.x, uv.y).r;
Vector3 mul = Vector3.up * r;
return mul.y + ocean.position.y;
}
}
노드를 그대로 스크립트로 옮기면 된다. 노드 별 계산 방법은 유니티 도큐먼트에 자세히 나와있다.
GetWaterHeight의 매개변수로 월드 포지션을 넘기면 그 위치에 물의 y치가 계산되어 나온다.
이것을 위해 쉐이더 그래프에서 uv값을 월드 좌표계로 뽑아낸 것이다.
부유물
부력 공식은 (유체의 밀도) * (부유물이 잠긴 부피) * (중력)이다. 현실적으로 부력을 구현하면 좋지만 계산하기 까다롭고 자유롭게 설정하기 위해 (부유물이 잠긴 대력적인 정도) * (지정한 특정 값)으로 부력을 구현하였다.
public class BuoyancyObject : MonoBehaviour
{
float addPower = 0f;
public float gravity = -9.81f;
public float floatingPower = 30f;
public Rigidbody rb;
private void FixedUpdate()
{
addPower = 0f;
//잠긴 정도
float diff = transform.position.y - Ocean.Instance.GetWaterHeight(transform.position);
//물체가 잠겼는가?
if (diff < 0f)
{
//부력
addPower += floatingPower * Mathf.Abs(diff);
}
rb.AddForce(Vector3.up * addPower, ForceMode.Acceleration);
}
}
그다음 부력과 중력이 작용될 위치를 설정해 주어야 한다. 지금은 현실과 다르게 물체의 중심에만 힘이 적용되어 물에 떠있는 것이 어색하기 때문이다.
추가로 저항을 추가하면 스크립크 완성된다.
[RequireComponent(typeof(Rigidbody))]
public class BuoyancyObject : MonoBehaviour
{
public Transform[] floaters;
public float underWaterDrag = 3f;
public float underWaterAngularDrag = 1f;
public float airDrag = 0f;
public float airAngularDrag = 0.05f;
public float floatingPower = 15f;
public float gravity = -9.81f;
private Rigidbody rb;
private bool isUnderWater;
private int floatersUnderWater;
private float addPower = 0f;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
floatersUnderWater = 0;
addPower = 0;
for (int i = 0; i < floaters.Length; i++)
{
float diff = floaters[i].position.y - Ocean.Instance.GetWaterHeight(floaters[i].position);
//중력
addPower += gravity / floaters.Length * Time.fixedDeltaTime;
//잠겼는가?
if (diff < 0f)
{
floatersUnderWater++;
//부력
addPower += floatingPower * Mathf.Abs(diff) / floaters.Length;
if (!isUnderWater)
{
isUnderWater = true;
SwitchState(isUnderWater);
}
}
//특정 위치에 힘을 가함
rb.AddForceAtPosition(Vector3.up * addPower, floaters[i].position, ForceMode.Acceleration);
}
if (isUnderWater && floatersUnderWater == 0)
{
isUnderWater = false;
SwitchState(isUnderWater);
}
}
//저항
private void SwitchState(bool isUnderWater)
{
if(isUnderWater)
{
rb.drag = underWaterDrag;
rb.angularDrag = underWaterAngularDrag;
}
else
{
rb.drag = airDrag;
rb.angularDrag = airAngularDrag;
}
}
}
따로 설정한 위치에 힘을 적용시켜야 하기 때문에 AddForceAtPosition함수를 사용하였다.
마무리로 변수들을 조정하고 위치 설정을 해주면 완성이다
'유니티' 카테고리의 다른 글
[유니티] 유령 쉐이더 (1) | 2024.03.23 |
---|---|
[유니티] Active Ragdoll (0) | 2023.12.22 |