《Shader入门精要》第11章-11.3.offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y

顶点移动 --- 水流效果

顶点移动就是指在shader中真的将顶点进行了偏移,所以模型的顶点数决定了最终效果,顶点数越多,模拟的效果越好,细节越多,模拟什么呢?其实就是模拟一个函数的图像,比如模拟一个2D的水的波纹,哪个函数图像和波纹像呢?说到底就是sincos的函数图像像水波纹,所以我们需要将一个长方形平面的边上的点,经过顶点移动后变成sin或者cos的图像即可,这样模型的形状就会被我们改变.顶点移动是改变的形状,想要让里面的贴图动起来,让采样uv动起来即可

Shader "Unity Shaders Book/Chapter 11/Water" {

Properties {

_MainTex ("Main Tex", 2D) = "white" {}

_Color ("Color Tint", Color) = (1, 1, 1, 1)

_Magnitude ("Distortion Magnitude", Float) = 1

                 _Frequency ("Distortion Frequency", Float) = 1

                 _InvWaveLength ("Distortion Inverse Wave Length", Float) = 10

                 _Speed ("Speed", Float) = 0.5

}

SubShader {

Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}

 

Pass {

Tags { "LightMode"="ForwardBase" }

ZWrite Off

Blend SrcAlpha OneMinusSrcAlpha

Cull Off

 

CGPROGRAM 

#pragma vertex vert

#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;

float4 _MainTex_ST;

fixed4 _Color;

float _Magnitude;//sin函数的振幅

float _Frequency;//sin函数的频率

float _InvWaveLength;//sin函数的波长

float _Speed;//采样uv的速度

struct a2v {

float4 vertex : POSITION;

float4 texcoord : TEXCOORD0;

};

struct v2f {

float4 pos : SV_POSITION;

float2 uv : TEXCOORD0;

};

v2f vert(a2v v) {

v2f o;

float4 offset;

offset.yzw = float3(0.0, 0.0, 0.0);//只需要在x轴上进行形状改变,所以其他轴偏移量都设置为0即可

offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;

//简单理解一下吧(详细的在下面),其实只要一个 v.vertex.z * _InvWaveLength就行,因为模型平面是在xz坐标轴上形成的,模型顶点位置取决于xz,xz轴互相影响

o.pos = UnityObjectToClipPos(v.vertex + offset);//x轴上模拟sin函数,并进行sin函数偏移

//,这里的_Frequency * _Time.y,为了让x轴上的顶点随时间变化左右移动起来,他后面的一串是为了让他在模型空间下模拟sin函数图像

o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

//o.uv +=  float2(0.0, _Time.y * _Speed);//这里是负责贴图里面是否移动的,而上面是负责改变模型顶点形状并移动的

return o;

}

fixed4 frag(v2f i) : SV_Target {

fixed4 c = tex2D(_MainTex, i.uv);

c.rgb *= _Color.rgb;

return c;

}

ENDCG

}

}

FallBack "Transparent/VertexLit"

}

详细讲解一下

offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;

分析流程

首先经过测试发现去掉 v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength 也能实现同样的效果,代码如下

offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;

那就说明 v.vertex.z * _InvWaveLength起到了影响offset.x的决定性作用,为什么是z轴起到决定性作用,首先我们来看一下模型空间的坐标,点击一下你的模型,蓝色是z轴,红色是x轴,发现这个模型就是在x轴和z轴上生成的一个面片!!!!! 我们xy轴的模型用的太多,导致可能习惯性的认为横着的就是x轴,竖着的就是y轴.在这里就不是.

我们想要将其变成波浪形,只要将顶点进行上下移动即可,在这个模型上,上下移动说明要移动的是x轴,那上下移动多少才能形成图二这种波浪形呢?这个波浪形不就是sin或者cos函数图像吗?我们用sin函数来实现,首先找到sin函数公式

                                       

                                         图一

                                         图二

                                                图三           

首先找到最基础sin函数公式 y=sinx; 现在我们要弄明白一点,千万不要混淆了,我们要将图一中的顶点上下移动,上下移动在高中时候数学里指的是什么?y坐标,上下移动的叫y坐标,左右的叫x坐标对吧? 所以才有了公式y=sinx.  现在我们遇到了什么?上下移动的是x,左右移动的是z,所以现在的公式是什么?x=sinz;

搞懂了这一点之后,回到我们目标,要求顶点上下移动了多少?

即: offset.x = sin v.vertex.z ;   //将原来的offset.x代码那行注释掉,把这个弄上去,跟我一步一步来

根据当前顶点的z轴的值求出他x该移动到的位置,这样之后,恭喜你成功再次得到了和图一模一样的效果,为什么呢?其实我们并没有错,只是力度不够,直接给他扩大2倍看看:

offset.x = 2*sin v.vertex.z;

 这样我们就得到了图三这样的一个静态图,并且乘的值越大,它位移的越厉害. 我们可以用一个专门的变量来代替数字2,书里面给他定义的是"_Magnitude",它也有一个专业的名词,叫做振幅.

即:offset.x = _Magnitude*sin v.vertex.z

那怎么让这个函数能做出更多的变化呢?之前我们用的是简单的基础公式y=sinx,为了让他实现更多效果,我们需要用他的高级公式见下图

看到这个公式之后,我们根据我们的实际情况给他做一个替换(把y换成x 把x换成z,我们是根据现有的点的z的位置来求x的位置的)

即: x=Asin(wz+o)+k (有个字母不会打用o代替了,嘻嘻~)

振幅的功能刚才说过了,像什么相位啊,初相啊,偏距啊,你懂吗?反正我不懂,我们只需要知道它们各自在函数中起的作用即可,可以这么理解,数学家给我们发明了一种函数,这个函数图像长得像波浪,而振幅啊,相位啊,初相啊等等就是来调整这个波浪的形状的工具即可,你先别管他们什么意思先按照我下面来

好!我们现在根据公式对号入座,根据规范我在这些变量前加了"_"并且首字母也大写,不要忘记命名规范哦,还得记得在前面加声明,都是float类型,算了~我知道有些人没有手^^:

_W("W", Float) = 1

_O("O", Float) = 1

_K("K", Float) = 1

float _W;

float _O;

float _K;

offset.x = _Magnitude*sin (_W*v.vertex.z+_O)+_K;

                                        图四

好,在我们的不屑努力下成功得到了一个emmmm~ "小路吧",图四效果,刚才我们没有去了解那些功能是干啥的,现在我们得去试试了,把鼠标移动到相应的名字上,按住鼠标左键不放,左右匀速移动,可以改变数值,一个一个的去试试,你就知道它是干嘛用的了,下面是我调好的参数,至此对于模型顶点偏移的工具全部制作完成,接下来就是怎么让他自动动起来

当我们测试完参数之后你就会发现,O这个变量,可以让我们这个小河有一种左右移动的感觉,我们在回到之前的那个很专业的解释里去看看对O这个解释,

分号前面看不懂,我只看懂了"坐标系上则为图像的左右移动"哈! 这不就对应上我们的测试了么,所以想让水有种动态流动的感觉,就得对他下手,我直接反手单独给它乘以一个_Time.y ,对方心态爆炸,教程结束

 

现在我们把最基础的模型给实现了,再把它原来的那个公式拿出来对比一下,为了方便对比,我们将位置调整一下

offset.x = sin(  _O * _Time.y + v.vertex.z* _W ) *_Magnitude  + _K;

offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;

按照一 一对应的关系我们得到

_O -> _Frequency

_W ->  _InvWaveLength

我们突然会发现这两个公式就是一摸一样,而且我们还多了一个K的功能,K的功能的确在做这个例子的时候没啥用,所以他省略了,此时我们再拿出那个一脸蒙蔽的公式做对比

offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;

offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;

第一个公式 v.vertex.z * _InvWaveLength 我们已经推导出来了,简单的说就是根据现有的顶点的z坐标,来计算出x坐标的位置,

第二个公式 v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength+  v.vertex.z * _InvWaveLength ,同理,根据现有顶点的x,y,z坐标共同计算x坐标的偏移量

在我们这个例子中,根据之前的公式得出实际上只有v.vertex.z 才能真正影响到x坐标的偏移量,那为什么要把这两个v.vertex.x v.vertex.y没用的东西加上去呢?我认为这只是一个用惯了的通用公式

我们这个模型平面是在x,z轴上建立的,所以z轴影响x轴偏移,那么现在给我们一个x,y轴上建立的平面模型怎么办?此时如果我们shader不写那两个没用的东西,我们就会得到错误的效果,反正为了shader能在更多的不同轴形成的平面上正常运行,加上就行了,反正这又不影响正确结果的运算

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
下列代码出现nan int错误,请解决:float smoothCot() { float err = -1; cogs.clear(); v_end = mesh.vertices_end(); // for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) { cog[0] = cog[1] = cog[2] = valence = 0.0; //cout << valence<<"1" << endl; for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) { double cot_weight = 0.0; MyMesh::HalfedgeHandle heh = mesh.find_halfedge(*v_it, *vv_it); if (!mesh.is_boundary(heh)) { MyMesh::HalfedgeHandle prev_heh = mesh.prev_halfedge_handle(heh); MyMesh::HalfedgeHandle next_heh = mesh.next_halfedge_handle(heh); MyMesh::VertexHandle prev_vh = mesh.to_vertex_handle(prev_heh); MyMesh::VertexHandle next_vh = mesh.to_vertex_handle(next_heh); MyMesh::Point prev_p = mesh.point(prev_vh); MyMesh::Point curr_pi = mesh.point(*v_it); MyMesh::Point curr_pj = mesh.point(*vv_it); MyMesh::Point next_p = mesh.point(next_vh); double cot_alpha = cot(curr_pi - prev_p, curr_pj - prev_p); double cot_beta = cot(curr_pi - next_p, curr_pj - next_p); cot_weight = cot_alpha + cot_beta; //cout << cot_weight<<"2" << endl; } cog += cot_weight *( mesh.point(*vv_it)-mesh.point(*v_it)); valence += cot_weight; //cout << valence<<"3" << endl; } cogs.push_back(cog / valence); } for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end; ++v_it, ++cog_it) { if (!mesh.is_boundary(*v_it)) { MyMesh::Point p = mesh.point(*v_it); //*cog_it += mesh.point(*v_it); err = max(err, (p - *cog_it).norm()); mesh.set_point(*v_it, *cog_it); } } return err; }
06-11
在这段代码中,出现nan和inf通常是由于除以0或开根号时计算负数引起的。你可以在运行代码之前添加一些检查,比如检查cot_weight是否为0,以避免除以0的情况。另外,你还可以在计算cot_alpha和cot_beta之前,添加一个检查,确保两个点之间的距离不为0,以避免计算负数的情况。具体来说,你可以修改代码如下: ``` float smoothCot() { float err = -1; cogs.clear(); v_end = mesh.vertices_end(); for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) { cog[0] = cog[1] = cog[2] = valence = 0.0; for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) { double cot_weight = 0.0; MyMesh::HalfedgeHandle heh = mesh.find_halfedge(*v_it, *vv_it); if (!mesh.is_boundary(heh)) { MyMesh::HalfedgeHandle prev_heh = mesh.prev_halfedge_handle(heh); MyMesh::HalfedgeHandle next_heh = mesh.next_halfedge_handle(heh); MyMesh::VertexHandle prev_vh = mesh.to_vertex_handle(prev_heh); MyMesh::VertexHandle next_vh = mesh.to_vertex_handle(next_heh); MyMesh::Point prev_p = mesh.point(prev_vh); MyMesh::Point curr_pi = mesh.point(*v_it); MyMesh::Point curr_pj = mesh.point(*vv_it); MyMesh::Point next_p = mesh.point(next_vh); double dist1 = (curr_pi - prev_p).norm(); double dist2 = (curr_pj - prev_p).norm(); double dist3 = (curr_pi - next_p).norm(); double dist4 = (curr_pj - next_p).norm(); if (dist1 > 0 && dist2 > 0 && dist3 > 0 && dist4 > 0) { double cot_alpha = cot(curr_pi - prev_p, curr_pj - prev_p); double cot_beta = cot(curr_pi - next_p, curr_pj - next_p); cot_weight = cot_alpha + cot_beta; } } cog += cot_weight * (mesh.point(*vv_it) - mesh.point(*v_it)); valence += cot_weight; } cogs.push_back(cog / valence); } for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end; ++v_it, ++cog_it) { if (!mesh.is_boundary(*v_it)) { MyMesh::Point p = mesh.point(*v_it); err = max(err, (p - *cog_it).norm()); mesh.set_point(*v_it, *cog_it); } } return err; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值