本案例实现在风力作用下树木植被的弯曲旋转等效果。
一、基本原理
本案例中在上一小节间接绘制的场景基础上实现植被树干会随着风力的大小、方向产生对应的弯曲,下图给出了如何计算某一帧中树干上指定顶点弯曲后位置的策略。
从图中可以看出,为了简化计算,本案例中采用的风向是与 XOZ 平面平行的。设当前风向与 z 轴正方向的夹角为 α,树干原始状态下与 y 轴重合。点 A 为树干模型中的任一顶点,在风的吹动下偏转到 A’点。
顶点着色器需要计算的问题为:已知 A 点坐标(X0,Y0,Z0)、当前风向与 z 轴正方向的夹角 α以及弧 OA’所在圆的半径 OO’,求 A 点偏转到 A’点后的坐标。
- 本案例采用的计算模型中,半径 OO’的大小与风力的大小成反比,风力越大,半径 OO’越小。这样就非常容易地实现了风越大,树干弯曲得越厉害。
下面给出具体的计算步骤。
(1)由于 OA’为半径为 OO’的一段圆弧,那么可以得出 OA’=OA,且 O’O=O’A’。
(2)根据弧长公式,可得出树干弯曲后的弧对应的圆心角θ 的弧度计算公式如下。
θ= OA’/ OO’= OA/ OO’
(3)从图 2-9 以及根据三角函数的知识可以得出如下结论。
A’D= O’A’×sin(θ)= O’O’×sin(OA/ OO’)
OD=OO’- O’A’×cos(θ)= OO’- O’O’×cos(OA/ OO’)
(4)接着可以得出如下结论。
OX’=OD×sin(α)=( OO’- O’O’×cos(OA/ OO’))×sin(α)
OZ’= OD×cos(α)= (OO’- O’O’×cos(OA/ OO’))×cos(α)
(5)设顶点 A 的坐标为(X0,Y0,Z0),偏移 后 A’的坐标为(X1,Y1,Z1)。则可以用 Y0 替换上
面的 OA,那么有如下结论。
OX’=(OO’- OO’×cos(Y0/ OO’))×sin(α)
OZ’= (OO’- OO’×cos(Y0/ OO’))×cos(α)
(6)最后可以得到 A’点的坐标。
X1= X0+ OX’= X0+(OO’- OO’×cos(Y0/ OO’))×sin(α)
Y1= A’D= OO’×sin(Y0/ OO’)
Z1= Z0+ OZ’= Z0+(OO’- OO’×cos(Y0/ OO’))×cos(α)
从上述得出的顶点位置变换公式中可以看出,只需要改变风向角度α ,就可以使椰子树向不同的方向摆动。同时,只需要根据风力大小改变弯曲半径 OO’的大小,就可以改变椰子树树干的弯曲程度。
二、开发步骤
我们首先新定义一个uniform数据用来在顶点着色器中控制风力及风向。
struct {
float bend_R=4;//风力(弯曲半径)
float direction_degree=10;//风向(沿Z轴正方向逆时针旋转)
} conVS;
具体的创建uniform缓冲区及绑定等不再赘述,我们可以通过设置不同数值来实现风力及风向。
接下来我们主要来看一下顶点着色器(基于上一文章的间接绘制):
顶点着色器:
#version 450
// mesh数据
layout (location = 0) in vec4 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inColor;
// 实例位置大小数据
layout (location = 4) in vec3 instancePos;
layout (location = 5) in vec3 instanceRot;
layout (location = 6) in float instanceScale;
layout (location = 7) in int instanceTexIndex;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 modelview;
} ubo;
layout (binding = 3) uniform CON
{
float bend_R;//风力(弯曲半径)
float direction_degree;//风向(沿Z轴正方向逆时针旋转)
} conVS;
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec3 outUV;
layout (location = 3) out vec3 outViewVec;
layout (location = 4) out vec3 outLightVec;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outColor = inColor;
outUV = vec3(inUV, instanceTexIndex);
outUV.t = 1.0 - outUV.t;
//角度变换矩阵
mat4 mx, my, mz;
// 围绕x轴旋转
float s = sin(instanceRot.x);
float c = cos(instanceRot.x);
mx[0] = vec4(c, s, 0.0, 0.0);
mx[1] = vec4(-s, c, 0.0, 0.0);
mx[2] = vec4(0.0, 0.0, 1.0, 0.0);
mx[3] = vec4(0.0, 0.0, 0.0, 1.0);
// 围绕y轴旋转
s = sin(instanceRot.y);
c = cos(instanceRot.y);
my[0] = vec4(c, 0.0, s, 0.0);
my[1] = vec4(0.0, 1.0, 0.0, 0.0);
my[2] = vec4(-s, 0.0, c, 0.0);
my[3] = vec4(0.0, 0.0, 0.0, 1.0);
// 围绕z轴旋转
s = sin(instanceRot.z);
c = cos(instanceRot.z);
mz[0] = vec4(1.0, 0.0, 0.0, 0.0);
mz[1] = vec4(0.0, c, s, 0.0);
mz[2] = vec4(0.0, -s, c, 0.0);
mz[3] = vec4(0.0, 0.0, 0.0, 1.0);
mat4 rotMat = mz * my * mx;
outNormal = inNormal * mat3(rotMat);
vec4 pos = vec4((inPos.xyz * instanceScale) + instancePos, 1.0) * rotMat;
float bend_R = conVS.bend_R+sin(gl_DrawIDARB*10);//这里指的是树的弯曲半径 sin(gl_DrawIDARB*10)差异作用
float direction_degree=conVS.direction_degree;//用角度表示的风向,沿Z轴正方向逆时针旋转
//计算当前的弧度
float curr_radian=pos.y/bend_R;
//计算当前点变换后的Y坐标
float result_height=bend_R*sin(curr_radian);
//计算当前点的增加的长度
float increase=bend_R-bend_R*cos(curr_radian);
//计算当前点最后的x坐标
float result_X=pos.x+increase*sin(radians(direction_degree));
//计算当前点最后的z坐标
float result_Z=pos.z+increase*cos(radians(direction_degree));
//最后结果顶点的坐标
vec4 result_point=vec4(result_X,result_height,result_Z,1.0);
gl_Position = ubo.projection * ubo.modelview * result_point;
vec4 wPos = ubo.modelview * vec4(pos.xyz, 1.0);
vec4 lPos = vec4(0.0, -5.0, 0.0, 1.0);
outLightVec = lPos.xyz - pos.xyz;
outViewVec = -pos.xyz;
}
三、渲染效果
3.1 风向改变
我们可以通过控制conVS.direction_degree从0到360来实现风向不同变动,效果如下:
3.2 风力改变
我们可以通过控制conVS.bend_R来实现风力不同变动,效果如下:
3.3 风力+风向
我们最终可以通过控制conVS.bend_R和conVS.direction_degree来实现不同风向和风力来完成想要的效果,效果文章开头所展示。