那么先实现简单一些的
不知道为何,现在CSDN写博客会这么卡,我手提是高配版游戏本啊,真无语,因为我们到的现象是,左下角,每次提示自动保存,就会卡10秒左右。我觉得你们要是不决定这问题,迟早大家都换平台了
上面的效果有时会抖动,是因为我的shader参数是再update里每帧传过去的,有些不够及时
消融边缘,我们还可以加上顶点挤出效果,对顶点面比较多的效果比较好,应该可以用细分、几何着色器添加顶点也行
效果如下:
顶点shader代码如下:
v2f vert (appdata v) {
v2f o;
// 顶点在过渡的时候加一些法线挤出效果
float3 ObjPosDir = v.vertex - p0;
half distance = dot(ObjPosDir, upDir);
fixed4 tintColor;
if (distance > 0)
{
float t = max(0, 1 - saturate(distance / _CutRange));
v.vertex.xyz += v.normal * _OffsetRange * _OffsetStrengthen * t;
}
o.objPos = v.vertex;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = v.normal;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
vec3 p0, p1, p2; // 平面三点
vec3 normalDir = cross((p1-p0),(p2-p0));// 根据两切线,算出法线
// 确定PlaneJ那面
vec3 po2J = J - p0; // 指向J点
vec3 distance_and_facing = dot(po2J, normalDir); // 由点乘来计算:投影面向、距离
if (distance_and_facing >0)
{ // 在plane平面上面
}
else
{ // 在plane平面下面,即:PlaneK那面
}
在这,一个切面,我使用的是,点法式:已知一点,与平面的法线,即可。
P , N P, N P,N
P P P是平面内的随便一个点
N N N是平面法线
并将平面信息传导shader,对应脚本:
// jave.lin 2019.08.08
using UnityEngine;
public class TestScript : MonoBehaviour
{
private int p0Hash;
private int upDirHash;
private Material mat;
public TestMoveVertices verticeObj;
// Start is called before the first frame update
void Start()
{
mat = this.GetComponent<MeshRenderer>().material;
p0Hash = Shader.PropertyToID("p0");
upDirHash = Shader.PropertyToID("upDir");
}
// Update is called once per frame
void LateUpdate()
{
var p0 = verticeObj.p0T.transform.position;
var p1 = verticeObj.p1T.transform.position;
var p2 = verticeObj.p2T.transform.position;
var upDir = Vector3.Cross(p1 - p0, p2 - p0).normalized;
mat.SetVector(p0Hash, p0); // 传入平面的某个点
mat.SetVector(upDirHash, upDir); // 传入平面法线
}
}
我们将 P , N P,N P,N都传到了shader中,在片段着色器中,对片段判断是在切面的哪一边,使用:点乘即可:dot
如上面我手画的图中,将其中一个 P 0 P_0 P0点与法线传如shader中,然后再片段着色器中,使用当前模型空间的点 J J J减去模型空间的点 P 0 P_0 P0(这个点也是平面上的点,但我们*.cs脚本中将它转换到了模型空间中),即可得到指定该片段点的方向 P 0 J P_0J P0J。再使用该 P 0 J P_0J P0J与 N N N法线的点乘,就知道就在该平面的上面,还是下面(点乘>0:上面;点乘==0:共面;点乘<0:下面)。
脚本中将点,转换到对应模型空间:
// jave.lin 2019.08.08
using UnityEngine;
public class TestMoveVertices : MonoBehaviour
{
public Transform p0T;
public Transform p1T;
public Transform p2T;
private Mesh mesh;
public Transform target;
// Start is called before the first frame update
void Start()
{
mesh = GetComponent<MeshFilter>().mesh;
}
// Update is called once per frame
void LateUpdate()
{
var o2w = this.gameObject.transform.localToWorldMatrix; // 对象到世界
var w2o = target.transform.worldToLocalMatrix; // 世界到对象
// 先统一变换到世界空间
var p0 = o2w.MultiplyPoint(mesh.vertices[0]);
var p1 = o2w.MultiplyPoint(mesh.vertices[1]);
var p2 = o2w.MultiplyPoint(mesh.vertices[2]);
// 再变换到对应的target模型的空间,因为我们shader中在模型空间中处理计算的
p0T.position = w2o.MultiplyPoint(p0);
p1T.position = w2o.MultiplyPoint(p1);
p2T.position = w2o.MultiplyPoint(p2);
}
}
shader中求在哪一个面向:
float3 ObjPosDir = i.objPos - p0; // 片段模型空间与平面某个点的指向
half distance = dot(ObjPosDir, upDir); // 将该指向与法线点乘,就是再法线方向的投影长度,同时也是与平面的距离
fixed4 tintColor;
if (distance > 0) // 在上面
{...
}
else// 下面
{...
}
计算点与面的距离
使用 P 0 J P_0J P0J与法线求点乘,就可以算出点 J J J与平面的距离
推导如下图(参考:平面方程与点到平面的距离)
d = n ⋅ P 0 P 1 → ∣ n ∣ d=\frac{n \cdot \overrightarrow{P_0P_1}}{|n|} d=∣n∣n⋅P0P1
而我们的 n n n我传入shader前就单位化了,所以 ∣ n ∣ = = 1 |n|==1 ∣n∣==1,可以忽略
所以公式直接变换为: d d = n ⋅ P 0 P 1 → ∣ n ∣ = n ⋅ P 0 P 1 → 1 = n ⋅ P 0 P 1 → dd=\frac{n \cdot \overrightarrow{P_0P_1}}{|n|}=\frac{n \cdot \overrightarrow{P_0P_1}}{1}={n \cdot \overrightarrow{P_0P_1}} dd=∣n∣n⋅P0P1=1n⋅P0P1=n⋅P0P1,在shader代码就是: d = d o t ( n , P 0 P 1 → ) d={dot(n, \overrightarrow{P_0P_1})} d=dot(n,P0P1)
shader中求点与平面距离:
half distance = dot(ObjPosDir, upDir);
有了距离,剩下就是做lerp插值过渡处理
if (distance > 0) // 在平面上面,我们就处理alpha过渡
{
float t = 1 - saturate(distance / _CutRange);
tintColor = lerp(_PlaneUpColor, _PlaneIntersectedColor, t); // 上部颜色与交点部分颜色的插值
a = t;
}
else
{
float t = 1 - saturate(-distance / _CutRange);
tintColor = lerp(_PlaneDownColor, _PlaneIntersectedColor, t); // 下部颜色与交点部分颜色的插值
a = 1; // 下面alpha保持为1
}
// jave.lin 2019.08.08
Shader "Test/Cylinder" {
Properties {
_PlaneUpColor ("Plane Up Color", Color) = (1,0,0,1)
_PlaneDownColor ("Plane Down Color", Color) = (0,1,0,1)
_PlaneIntersectedColor ("Plane Intersected Color", Color) = (1,1,0,1)
_MainTex ("Texture", 2D) = "white" {}
_Alpha ("Alpha", Range(0, 1)) = 0.3
_CutRange("CutRange", Range(0, 1)) = 1
_OffsetRange("OffsetRange", Float) = 0.3
_OffsetStrengthen("OffsetStrengthen", Range(0, 1)) = 0.5
_Brightness("Brightness", Range(0, 100)) = 2
}
CGINCLUDE
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float3 worldPos : TEXCOORD01;
float3 objPos : TEXCOORD02;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _PlaneUpColor;
fixed4 _PlaneDownColor;
fixed4 _PlaneIntersectedColor;
float3 p0, upDir; // plane infos
float _Alpha;
float _CutRange;
float _OffsetRange;
float _OffsetStrengthen;
float _Brightness;
v2f vert (appdata v) {
v2f o;
// 顶点在过渡的时候加一些法线挤出效果
float3 ObjPosDir = v.vertex - p0;
half distance = dot(ObjPosDir, upDir);
fixed4 tintColor;
if (distance > 0)
{
float t = max(0, 1 - saturate(distance / _CutRange));
v.vertex.xyz += v.normal * _OffsetRange * _OffsetStrengthen * t;
}
o.objPos = v.vertex;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = v.normal;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float a;
/* =======1======
float3 posDir = normalize(i.objPos - p0);
half PdotU = dot(posDir, upDir);
fixed4 tintColor;
if (PdotU > 0) tintColor = _PlaneUpColor;
else tintColor = _PlaneDownColor;
*/
/* =======2======*/
// 求点与平面距离
// 参考:https://blog.csdn.net/qq_23869697/article/details/82688277
// 其实就是求投影,刚好就是点乘:dot就完事了
float3 ObjPosDir = i.objPos - p0; // 片段模型空间与平面某个点的指向
half distance = dot(ObjPosDir, upDir); // 将该指向与法线点乘,就是再法线方向的投影长度,同时也是与平面的距离
fixed4 tintColor;
half bri;
if (distance > 0) // 在平面上面,我们就处理alpha过渡
{
float t = 1 - saturate(distance / _CutRange);
tintColor = lerp(_PlaneUpColor, _PlaneIntersectedColor, t); // 上部颜色与交点部分颜色的插值
a = t * _Alpha;
bri = 1 + _Brightness * a;
}
else
{
float t = 1 - saturate(-distance / _CutRange);
tintColor = lerp(_PlaneDownColor, _PlaneIntersectedColor, t); // 下部颜色与交点部分颜色的插值
a = _Alpha; // 下面alpha保持为1
bri = 1;
}
// ambient
fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT;
// diffuse
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
half3 normal = UnityObjectToWorldNormal(i.normal);
half LdotN = dot(lightDir, normal) * 0.5 + 0.5;
fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * LdotN;
diffuse *= tintColor;
// specular
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
half3 halfVec = normalize(lightDir + viewDir);
half HdotN = max(dot(halfVec, normal), 0);
half3 specular = _LightColor0.rgb * pow(HdotN, 12) * LdotN;
return fixed4((ambient + diffuse + specular) * bri, a);
}
ENDCG
SubShader {
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Pass {
Cull Front
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass {
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
TestPlaneCut 提取码: qgnu