Context
I've been trying to create a buoyancy script that samples the position of a point, tests if it's under a certain level (the "water level"), and adds a force on that position based on depth. Separately, I worked on creating a nice looking water shader in Shadergraph, and had the bright idea to add in waves using the Simple Noise node + vertex displacement.
However, the only way (I could think of) to use those displaced values as the float "water level" was to rewrite the entire node tree in C#, and use that to sample the "water level" at that position.
Problem
For some reason, the final displaced mesh and the calculated positions are different, causing the buoyancy script to assume that the "water level" is higher/lower than it is. The difference isn't large, so I'm assuming there's an error somewhere within either the C# Node Graph or C# Simple Noise translation.
Is that correct? If so, where and what's my misunderstanding? If not, what else could have gone wrong?
Approach
Node Graph
Image of the node graph for the wave vector displacement
*If you need zoomed in pictures, let me know!
All things considered, it's relatively simple. It:
Takes the world position as a UV, and offsets and tiles it.
Feeds the UV to a Simple Noise node, and multiplies the noise by a strength.
Clamps the output.
Repeats 1-3 again and adds both together for more detail.
Replaces the Y value of the vertex position with the combined wave value.
C# Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaveHeightCalculator : MonoBehaviour
{
// Step 1
[SerializeField] Material _waterMaterial;
[Header("Waves")]
[SerializeField] float _waveTiling;
[SerializeField] float _waveOffset;
[SerializeField] float _waveMin;
[SerializeField] float _waveMax;
[Header("Small Waves")]
[SerializeField] float _wavesSmallScale;
[SerializeField] float _wavesSmallStrength;
[SerializeField] Vector2 _wavesSmallVelocity;
[Header("Large Waves")]
[SerializeField] float _wavesLargeScale;
[SerializeField] float _wavesLargeStrength;
[SerializeField] Vector2 _wavesLargeVelocity;
// Step 2
private void OnValidate()
{
_waterMaterial = GetComponent<Renderer>().sharedMaterial;
SetVariables();
}
void SetVariables()
{
_waveTiling = _waterMaterial.GetFloat("_Wave_Tiling");
_waveOffset = _waterMaterial.GetFloat("_Wave_Offset");
_waveMin = _waterMaterial.GetFloat("_Wave_Min");
_waveMax = _waterMaterial.GetFloat("_Wave_Max");
_wavesSmallScale = _waterMaterial.GetFloat("_Waves_Small_Scale");
_wavesSmallStrength = _waterMaterial.GetFloat("_Waves_Small_Strength");
_wavesSmallVelocity = _waterMaterial.GetVector("_Waves_Small_Velocity");
_wavesLargeScale = _waterMaterial.GetFloat("_Waves_Large_Scale");
_wavesLargeStrength = _waterMaterial.GetFloat("_Waves_Large_Strength");
_wavesLargeVelocity = _waterMaterial.GetVector("_Waves_Large_Velocity");
}
// Step 3
public float GetWaveHeightAtPosition(Vector3 position)
{
Vector2 noiseMapUV;
noiseMapUV = new Vector2(position.x, position.z) * _waveTiling;
// Calculate Small Waves
Vector2 wavesSmallUVOffset = (Time.time / 20) * _wavesSmallVelocity;
float noiseValueAtUVPlusOffset = UnitySimpleNoiseAtUV(noiseMapUV + wavesSmallUVOffset, _wavesSmallScale);
float wavesSmall = noiseValueAtUVPlusOffset * _wavesSmallStrength;
// Calculate Large Waves
Vector2 wavesLargeUVOffset = (Time.time / 20) * _wavesLargeVelocity;
noiseValueAtUVPlusOffset = UnitySimpleNoiseAtUV(noiseMapUV + wavesLargeUVOffset, _wavesLargeScale);
float wavesLarge = noiseValueAtUVPlusOffset * _wavesLargeStrength;
// Combine
float waveHeight = wavesSmall + wavesLarge;
// Clamp
waveHeight = Mathf.Clamp(waveHeight, _waveMin, _waveMax);
// Offset
waveHeight += _waveOffset;
return waveHeight;
}
In the C# script, a couple of things are going on. Here's my thought process for it:
It assigns the relevant material properties to member variables.
It sets those variables in the OnValidate() function.
It uses those variables to calculate the wave value; equivalent to the "water level".
The script also contains and relies on my best attempt at translating the Simple Noise node from "Show Generated Code", which looked liked this.
Generated Code
inline float Unity_SimpleNoise_RandomValue_float (float2 uv)
{
float angle = dot(uv, float2(12.9898, 78.233));
#if defined(SHADER_API_MOBILE) && (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3) || defined(SHADER_API_VULKAN))
// 'sin()' has bad precision on Mali GPUs for inputs > 10000
angle = fmod(angle, TWO_PI); // Avoid large inputs to sin()
#endif
return frac(sin(angle)*43758.5453);
}
inline float Unity_SimpleNnoise_Interpolate_float (float a, float b, float t)
{
return (1.0-t)*a + (t*b);
}
inline float Unity_SimpleNoise_ValueNoise_float (float2 uv)
{
float2 i = floor(uv);
float2 f = frac(uv);
f = f * f * (3.0 - 2.0 * f);
uv = abs(frac(uv) - 0.5);
float2 c0 = i + float2(0.0, 0.0);
float2 c1 = i + float2(1.0, 0.0);
float2 c2 = i + float2(0.0, 1.0);
float2 c3 = i + float2(1.0, 1.0);
float r0 = Unity_SimpleNoise_RandomValue_float(c0);
float r1 = Unity_SimpleNoise_RandomValue_float(c1);
float r2 = Unity_SimpleNoise_RandomValue_float(c2);
float r3 = Unity_SimpleNoise_RandomValue_float(c3);
float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x);
float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x);
float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y);
return t;
}
void Unity_SimpleNoise_float(float2 UV, float Scale, out float Out)
{
float t = 0.0;
float freq = pow(2.0, float(0));
float amp = pow(0.5, float(3-0));
t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
freq = pow(2.0, float(1));
amp = pow(0.5, float(3-1));
t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
freq = pow(2.0, float(2));
amp = pow(0.5, float(3-2));
t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
Out = t;
}
/* WARNING: $splice Could not find named fragment 'CustomInterpolatorPreVertex' */
// Graph Vertex
// GraphVertex: <None>
/* WARNING: $splice Could not find named fragment 'CustomInterpolatorPreSurface' */
// Graph Pixel
struct SurfaceDescription
{
float4 Out;
};
Translated Code
float float_frac(float x) { return x - Mathf.Floor(x);}
Vector2 frac(Vector2 x) { return x - new Vector2(Mathf.Floor(x.x), Mathf.Floor(x.y));}
float sin(float x) { return Mathf.Sin(x);}
float dot(Vector2 a, Vector2 b) { return a.x * b.x + a.y * b.y;}
float float_floor(float x) { return Mathf.Floor(x);}
Vector2 floor(Vector2 x) { return new Vector2(Mathf.Floor(x.x), Mathf.Floor(x.y));}
float float_abs(float x) { return Mathf.Abs(x);}
Vector2 abs(Vector2 x) { return new Vector2(Mathf.Abs(x.x), Mathf.Abs(x.y));}
float pow (float x, float y) { return Mathf.Pow(x, y);}
float Unity_SimpleNoise_RandomValue_float (Vector2 uv)
{
float angle = dot(uv, new Vector2(12.9898f, 78.233f));
return float_frac(sin(angle) * 43758.5453f);
}
float Unity_SimpleNnoise_Interpolate_float (float a, float b, float t)
{
return (1.0f - t) * a + (t * b);
}
float Unity_SimpleNoise_ValueNoise_float (Vector2 uv)
{
Vector2 i = floor(uv);
Vector2 f = frac(uv);
f = (f * f) * (new Vector2 (3.0f, 3.0f) - new Vector2(2.0f, 2.0f) * f);
uv = abs(frac(uv) - new Vector2 (0.5f, 0.5f));
Vector2 c0 = i + new Vector2(0.0f, 0.0f);
Vector2 c1 = i + new Vector2(1.0f, 0.0f);
Vector2 c2 = i + new Vector2(0.0f, 1.0f);
Vector2 c3 = i + new Vector2(1.0f, 1.0f);
float r0 = Unity_SimpleNoise_RandomValue_float(c0);
float r1 = Unity_SimpleNoise_RandomValue_float(c1);
float r2 = Unity_SimpleNoise_RandomValue_float(c2);
float r3 = Unity_SimpleNoise_RandomValue_float(c3);
float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x);
float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x);
float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y);
return t;
}
float UnitySimpleNoiseAtUV(Vector2 UV, float Scale)
{
float t = 0.0f;
float freq = pow(2.0f, 0);
float amp = pow(0.5f, 3-0);
t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
freq = pow(2.0f, 1);
amp = pow(0.5f, 3-1);
t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x * Scale / freq, UV.y * Scale / freq)) * amp;
freq = pow(2.0f, 2);
amp = pow(0.5f, 3-2);
t += Unity_SimpleNoise_ValueNoise_float(new Vector2(UV.x * Scale / freq, UV.y * Scale / freq)) * amp;
return t;
}
Related
The problem is that when I tried converting height map to normal map. The results are wrong. For some reason there is 3 light sources that is emitting from top (green), right (red), and left (blue) in the texture.
This is the GeoMath.hlsl code that I am using
static const float PI = 3.141592653589793238462643383279;
float2 longitudeLatitudeToUV(float2 longLat) {
float longitude = longLat[0];
float latitude = longLat[1];
float u = longitude / (2 * PI) + 0.5;
float v = latitude / PI + 0.5;
return float2(u,v);
}
float3 longitudeLatitudeToPoint(float2 longLat) {
float longitude = longLat[0];
float latitude = longLat[1];
float x;
float y;
float z;
y = sin(latitude);
float r = cos(latitude);
x = sin(longitude) * r;
z = -cos(longitude) * r;
return float3(x, y, z);
}
float2 uvToLongitudeLatitude(float2 uv) {
float longitude = (uv.x - 0.5) * (2 * PI);
float latitude = (uv.y - 0.5) * PI;
return float2(longitude, latitude);
}
float2 pointToLongitudeLatitude(float3 p) {
float longitude = atan2(p.x, p.z);
float latitude = asin(p.y);
return float2(longitude, latitude);
}
float2 pointToUV(float3 p) {
p = normalize(p);
return longitudeLatitudeToUV(pointToLongitudeLatitude(p));
}
This is the compute shader I am using to convert height map into normal map.
#pragma kernel CSMain
#include "GeoMath.hlsl"
Texture2D<float> _HeightMap;
RWTexture2D<float4> _NormalMap;
int _TextureSize_Width;
int _TextureSize_Height;
float _WorldRadius;
float _HeightMultiplier;
float3 CalculateWorldPoint(uint2 texCoord)
{
float2 uv = texCoord / float2(_TextureSize_Width - 1, _TextureSize_Height - 1);
float2 longLat = uvToLongitudeLatitude(uv);
float3 spherePoint = longitudeLatitudeToPoint(longLat);
float height01 = _HeightMap[texCoord].r + 1.0;
float worldHeight = _WorldRadius + height01 * _HeightMultiplier;
return spherePoint * worldHeight;
}
uint2 WrapIndex(uint2 texCoord)
{
texCoord.x = (texCoord.x + _TextureSize_Width) % _TextureSize_Width;
texCoord.y = max(min(_TextureSize_Height - 1, texCoord.y), 0);
return texCoord;
}
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
float3 normalVector;
float3 posNorth = CalculateWorldPoint(WrapIndex(id.xy + uint2(0, 1)));
float3 posSouth = CalculateWorldPoint(WrapIndex(id.xy + uint2(0, -1)));
float3 posEast = CalculateWorldPoint(WrapIndex(id.xy + uint2(1, 0)));
float3 posWest = CalculateWorldPoint(WrapIndex(id.xy + uint2(-1, 0)));
float3 dirNorth = normalize(posNorth - posSouth);
float3 dirEast = normalize(posEast - posWest);
normalVector = normalize(cross(dirNorth, dirEast));
_NormalMap[id.xy] = float4(normalVector, 1.0);
}
And this is the result I am getting is down below height map (top), generated normal map from the code above (bottom)
I believe that you are trying to get object space normals.
But there is tiny detail is missing.
Possible values for normalized vector3 are -1..1 for each axis.
And possible values for pixel: 0..1.
You just need to adjust ranges.
This line roughly fixes problem:
_NormalMap[id.xy] = float4(normalVector / 2 + float3(0.5, 0.5, 0.5), 1.0);
Result
I need to shoot a ball from any height and make it bounce on a target position defined by the user. The angle of launch is also given. I've tried a couple of solutions so far:
Vector3 calcBallisticVelocityVector(Vector3 source, Vector3 target, float angle) {
Vector3 direction = target - source;
float h = direction.y;
direction.y = 0;
float distance = direction.magnitude;
float a = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(a);
distance += h/Mathf.Tan(a);
// calculate velocity
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2*a));
return velocity * direction.normalized;
}
Vector3 calcBallisticVelocityVector2(Vector3 source, Vector3 target, float angle) {
float distance = (target.Planar() - source.Planar()).magnitude;
float a = target.y - source.y - distance;
float halfGravity = -Physics.gravity.magnitude * 0.5f;
float distanceSquared = distance * distance;
float theta = Mathf.Deg2Rad * angle;
float cosSquared = Mathf.Cos(theta) * Mathf.Cos(theta);
float b = distanceSquared / cosSquared;
float speed = Mathf.Sqrt((halfGravity * b) / a);
Vector3 velocity = (target.Planar() - source.Planar()).normalized * Mathf.Cos(theta);
velocity.y = Mathf.Sin(theta);
return velocity * speed;
}
The results I'm getting is that even the ball does go into the direction is expected, it falls earlier than it should be so the speed calculated by these methods seems to be lower than what is actually required to hit the target position.
Rigidbody's mass is set to 1, Gravity is (0, -98, 0), rigid body's drag and angular drag is set to 0. What other variables could be affecting this behavior?
EDIT: One thing I forgot to mention is that I'm setting the resulting vector as rigid body's velocity, so I'm not using via the apply force method.
I adapted code gotten from here: https://answers.unity.com/questions/1131176/projectile-motion.html and now I'm getting the results I was expecting. I can always hit the target position at whatever angle I input.
private Vector3 calcBallisticVelocityVector(Vector3 initialPos, Vector3 finalPos, float angle)
{
var toPos = initialPos - finalPos;
var h = toPos.y;
toPos.y = 0;
var r = toPos.magnitude;
var g = -Physics.gravity.y;
var a = Mathf.Deg2Rad * angle;
var vI = Mathf.Sqrt (((Mathf.Pow (r, 2f) * g)) / (r * Mathf.Sin (2f * a) + 2f * h * Mathf.Pow (Mathf.Cos (a), 2f)));
Vector3 velocity = (finalPos.Planar() - initialPos.Planar()).normalized * Mathf.Cos(a);
velocity.y = Mathf.Sin(a);
return velocity * vI;
}
I want to proceduraly generate meshes. I created a method to supply the vertices of a circle. The idea is that it creates a shape in 2d and then rotates it in 3d assuming that "rotation" is the vector of the shapes normal axis.
public List<Vector3> Loop (Vector3 center, Vector3 rotation, float radius, int divisions)
{
List<Vector3> loop = new List<Vector3>();
for(int p = 0; p < divisions; p++)
{
float u = (float)Math.Cos(2 * Math.PI * p / divisions) * radius;
float v = (float)Math.Sin(2 * Math.PI * p / divisions) * radius;
float x = 0;
float y = 0;
float z = 0;
// Apply rotation to u & v to get x, y, z
loop.Add(new Vector3(x, y, z));
}
return loop;
}
Creating the circle in 2d (u & v) was super easy but when I looked into applying 3d rotations, it seemed to be a complete rabbit hole completely beyond my comprehension.
Is there a way to use existing API to do this?
I would pass in an axis parameter that you are rotating rotation around, then use Cross products to find the "up" direction for the "forward" that is the normal of the circle.
Use Quaternion.LookRotation, then Quaternion * Vector3 to apply the rotation to the position:
public List<Vector3> Loop (Vector3 center, Vector3 rotation, Vector3 axis, float radius, int divisions)
{
List<Vector3> loop = new List<Vector3>();
for(int p = 0; p < divisions; p++)
{
float u = (float)Math.Cos(2 * Math.PI * p / divisions) * radius;
float v = (float)Math.Sin(2 * Math.PI * p / divisions) * radius;
Vector3 fromPosition = new Vector3(u, v, 0f);
Vector3 up = Vector3.Cross(rotation.normalized, axis.normalized);
Quaternion rot = Quaternion.LookRotation(rotation, up);
loop.Add(rot * fromPosition);
}
return loop;
}
So you could do something like: List<Vector3> res = Loop(Vector3.zero, Vector3.up, Vector3.right, 10f, 20);
I'm writing a script for an Object in Unity3D.
I want to get the volume of my object.
rigidBody = GetComponent<Rigidbody>();
I'm looking in the documentation at the attributes contained in Rigidbody but I don't see anything I can use.
I tried using bounds but I found that rotation of an object changed those values even without size changing:
int getSize(Vector3 bounds)
{
float size = bounds[0] * bounds[1] * bounds[2] * 1000;
Debug.Log("size value = " + (int)size);
return (int)size;
}
What properties can I use to calculate the volume of an object?
The math is explained here.
In C# for convencience:
float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
{
float v321 = p3.x * p2.y * p1.z;
float v231 = p2.x * p3.y * p1.z;
float v312 = p3.x * p1.y * p2.z;
float v132 = p1.x * p3.y * p2.z;
float v213 = p2.x * p1.y * p3.z;
float v123 = p1.x * p2.y * p3.z;
return (1.0f / 6.0f) * (-v321 + v231 + v312 - v132 - v213 + v123);
}
float VolumeOfMesh(Mesh mesh)
{
float volume = 0;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
for (int i = 0; i < mesh.triangles.Length; i += 3)
{
Vector3 p1 = vertices[triangles[i + 0]];
Vector3 p2 = vertices[triangles[i + 1]];
Vector3 p3 = vertices[triangles[i + 2]];
volume += SignedVolumeOfTriangle(p1, p2, p3);
}
return Mathf.Abs(volume);
}
Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
Debug.Log(VolumeOfMesh(mesh));
So I'm trying to create a realistic trampoline jump instead of the player falling through the trampoline and then slingshotting back up, whilst instead allowing the player to instantly shoot upon contact with the trampoline and come down a relative gravity.
Where am I going wrong and what can I do to fix it?
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(CharacterController))]
public class small_bounce_script: MonoBehaviour {
public float speed = 6.0F;
public float jumpSpeed = 8.0F;
public float gravity = 20.0F;
private Vector3 moveDirection = Vector3.zero;
private Vector3 bounce = Vector3.zero;
void Update() {
CharacterController controller = GetComponent<CharacterController>();
if (controller.isGrounded) {
if (bounce.sqrMagnitude > 0) {
moveDirection = bounce;
bounce = Vector3.zero;
} else {
moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= speed;
}
if (Input.GetButton("Jump"))
moveDirection.y = jumpSpeed;
}
moveDirection.y -= gravity * Time.deltaTime;
controller.Move(moveDirection * Time.deltaTime);
}
void OnTriggerEnter(Collider other) {
Debug.Log ("Controller collider hit");
Rigidbody body = other.attachedRigidbody;
// Only bounce on static objects...
if ((body == null || body.isKinematic) && other.gameObject.controller.velocity.y < -1f) {
float kr = 0.5f;
Vector3 v = other.gameObject.controller.velocity;
Vector3 n = other.normal;
Vector3 vn = Vector3.Dot(v,n) * n;
Vector3 vt = v - vn;
bounce = vt -(vn*kr);
}
}
}
A trampoline reacts like a spring device. Let's assume gravity is in Y direction and the trampoline surface is positionied in the X,Z plane.
Then your Y coordinate y is proportional to a sine function during OnTriggerStay. Velocity v in Y direction as 1st derivative of y is then a cosine function, while X and Z velocity remain constant.
y (t) = yMax * sin (f * t)
v (t) = yMax * f * cos (f * t)
Considering conservation of energy, we have:
E = 0.5 * m * vMax² = 0.5 * k * yMax²
=> yMax = ± SQRT (k / m) * vMax
vMax := speed in Y direction when hitting the trampoline. ± because for landing and starting
yMax := maximum amplitude when v == 0, i.e. hwo deep should the player sink before returning
k := spring constant defining trampoline behaviour i.e. how strong it is
m := player's mass
f := SQRT (k / m)
So all you need to do is playing around with the spring constant and have something like this in your Update method:
Vector3 velocity = rigidbody.velocity;
float elapsedTime = Time.time - timestampOnEnter;
velocity.y = YMax * FConst * Mathf.cos (FConst * elapsedTime);
rigidbody.velocity = velocity;
Member var timestampOnEnter is taken in OnTriggerEnter, FConst is the constant we called f in the maths part.