原文链接
正文
脚本和材质的参数设定如下
效果演示
物理原理
- 质点弹簧系统
GAMES101-现代计算机图形学入门-闫令琪_哔哩哔哩
完整的质点弹簧系统可以看闫老师的课, 这里简单阐述一下用到的简单质点弹簧, 只需要高中物理基础即可:
ab
是一根没有长度的弹簧, 所以现在看起来是重合的
设 a
为 主动点(我们拽着拖动的点), b
为 从动点 (被弹力拖着动的点).
计算 从动点 b
的受力, 胡克定律:
s
为b
指向a
的向量k
为 弹簧劲度系数, 自己设定
此时已经可以模拟无限长弹簧的运动了, 但由于没有阻力, 所以一旦有了外力(玩家拖动), b
会一直运动停不下来.
为弹簧添加阻力. 弹簧阻力为 b
速度反向乘以 阻力系数(阻尼)
- 阻尼自己设定
此时已经可以完全模拟弹簧效果了
将模型根据世界空间各个顶点 y
坐标, 作为 插值因子, 来决定模型上下各部分该处于何处(更靠近主动点, 还是更靠近从动点)
完整代码
C# 代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MassObj : MonoBehaviour
{
private Material mat;
private Vector3 followPos = Vector3.zero; // 从动点位置
private Vector3 massVelocity = Vector3.zero; // 从动点速度
public float stiffness = 60f; // 劲度系数
public float damping = 2f; // 阻尼系数
private float max, min; // 模型在模型空间最高, 最低点的 y 值
private void Start()
{
mat = GetComponent<MeshRenderer>().sharedMaterial;
followPos = transform.position; // 虚拟抽象一个从动点
// 距离轴心的物体空间距离
max = GetComponent<MeshFilter>().sharedMesh.bounds.max.y;
min = GetComponent<MeshFilter>().sharedMesh.bounds.min.y;
mat.SetFloat("_MeshH", max - min);// 模型总高度
}
private void Update()
{
// 进行一些受力, 加速度, 速度, 路程, 运动 的数值计算
Vector3 force = GetMainForce(); // 弹力
force += GetDampingForce(); // 阻力
massVelocity += force * Time.deltaTime;// 将固定质量为 1, 则 force 数值等于加速度数值
followPos += massVelocity * Time.deltaTime;// 从动点的移动
// 为 shader 传入数据
SetMatData();
}
private Vector3 GetMainForce()
{
// 胡克定律
Vector3 forceDir = transform.position - followPos;
return forceDir * stiffness;
}
private Vector3 GetDampingForce()
{
return -massVelocity * damping;// 弹簧阻尼
}
private void SetMatData()
{
mat.SetVector("_MainPos", transform.position);// 主动点
mat.SetVector("_FollowPos", followPos);// 从动点
mat.SetFloat("_W_Bottom", transform.position.y + min);// 模型最低点y值
}
}
Shader 代码
这里使用 surface shader
, 它能提供 PBR 光照模型, 主要关注顶点 shader 即可, 放在 unlit 也是一样的
Shader "MassPoint/MassObj"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows addshadow vertex:vert
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
half _Glossiness;
half _Metallic;
fixed4 _Color;
float4 _MainPos, _FollowPos;///< world space
float _MeshH, _W_Bottom;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float3 mainPos = mul(unity_WorldToObject, _MainPos).xyz;///< 主动点在模型空间的位置
float3 follow = mul(unity_WorldToObject, _FollowPos).xyz;///< 从动点在模型空间的位置
float3 offDir = follow - mainPos;///< 偏移方向
float3 followVert = v.vertex.xyz + offDir;///< 从动的模型顶点进行位置偏移
float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;///< 模型的世界坐标
float mask = (wPos.y - _W_Bottom) / max(0.00001, _MeshH);///< 将模型世界顶点y值, 映射[0, 1], 作为上下的遮罩
v.vertex.xyz = lerp(v.vertex.xyz, followVert, mask); ///< 用遮罩来插值顶点该的主动点坐标, 还是从动点坐标
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}