Vulkan_顶点着色器特效1(流动的水面或飘扬的红旗)

顶点着色器妙用系列—流动的水面或飘扬的红旗

本部分主要介绍顶点着色器来实现流动的水面或飘扬的红旗的基本原理。
在这里插入图片描述
基础模型框架主要是参照之前章节的Vulkan_动态地形细分(Tessellation Shader)

一、基本原理

介绍本案例的具体开发步骤之前首先需要了解实现旗帜飘扬的基本原理,如下图所示:
在这里插入图片描述
从上图中可以看出,矩形的网格体由大量的小三角形组成的。这样只要在绘制一帧画面时由顶点着色器根据一定的
规则变换各个顶点的位置,即可得到流动或者迎风飘动的效果。

为了使流动或飘动过程比较平滑,本案例采用的是基于正弦曲线的顶点位置变换规则,具体情况如图下所示:
在这里插入图片描述
从图中可以看出,传入顶点着色器的原始顶点的 z 坐标都是相同的,经过顶点着色器变换后顶点的 z 坐标是根据正弦曲线分布的(注意vulkan是Y向上坐标系,需转换)。具体的计算方法如下:

  1. 首先计算当前处理顶点的 x 坐标与最左侧顶点 x 坐标的差值,即 X 距离。
  2. 然后根据距离与角度的换算率将 x 距离换算为当前顶点与最左侧顶点的角度差(tempAngle)。
  3. 接着将 tempAngle 加上最左侧顶点的对应角度(startAngle)即可得到当前顶点的对应角度(currAngle)。
  4. 最后通过求 currAngle 的正弦值即可得到当前顶点变换后的 z 坐标。

其中所谓距离与角度的换算率指的是由开发人员人为设定的一个值,将距离乘以它之后就可以换算成角度值。例如可以规定, X 方向上距离 4 等于 2π,则换算公式为:X 距离×2π/4。

可以想象出,只要在绘制每帧画面时传入不同的 startAngle 值(例如在 0~2π连续变化),即可得到平滑的基于正弦曲线的动画了。

二、开发步骤

一些基础的步骤不再赘述,本部分主要介绍顶点着色器的不同实现,具体的实现代码根据上部分原理可看注释一一得知:

2.1、基于正弦曲线的 X 方向波浪

顶点着色器如下(基于Vulkan_动态地形细分代码,UBO中添加了三个注释部分的控制参数):

#version 450


layout (set = 0, binding = 0) uniform UBO 
{
	mat4 projection;
	mat4 modelview;
	vec4 lightPos;
	vec4 frustumPlanes[6];
	float displacementFactor;
	float tessellationFactor;
	vec2 viewportDim;
	float tessellatedEdgeSize;
	float uStartAngle; //本帧起始角度(X、Y两个方向都是其值)
	float uWidthSpan; //横向长度总跨度
	float scale;//波动幅度
} ubo; 

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;

layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec2 outUV;

void main(void)
{
	outUV = inUV;
	outNormal = inNormal;
    float uStartAngle=ubo.uStartAngle;//本帧起始角度(X、Y两个方向都是其值)
    float uWidthSpan=ubo.uWidthSpan;//横向长度总跨度
	//计算X向波浪                		
    float angleSpanH=4.0*3.14159265;//横向角度总跨度,用于进行X距离与角度的换算
    float startX=-uWidthSpan/2.0;//起始X坐标(即最左侧顶点的X坐标)
    //根据横向角度总跨度、横向长度总跨度及当前点X坐标折算出当前顶点X坐标对应的角度
    float currAngle=uStartAngle+((inPos.x-startX)/uWidthSpan)*angleSpanH;
    float tz=sin(currAngle)*ubo.scale;//通过正弦函数求出当前点的Y坐标      
	gl_Position = vec4(inPos.x,inPos.y+tz,inPos.z,1); 
}

上述顶点着色器实现了上一小节中所介绍的基于正弦曲线的 X 方向波浪,其中变量 angleSpanH 可以用来控制波浪的密度,其值越大,波浪密度越大。

编译运行可见如下:
在这里插入图片描述

2.2、基于正弦曲线的斜向方向波浪

本质上讲,斜向下方向波浪的顶点着色器与前面的 X 方向波浪的顶点着色器没有本质区别,仅仅是在计算当前顶点的对应角度时增加了 Z 轴方向的计算,不再是仅考虑 X 轴的坐标。因此,形成的波浪方向就是斜向下的。

void main(void)
{
	...
     //计算X向角度          		
     float angleSpanH=4.0*3.14159265;//横向角度总跨度,用于进行X距离与角度的换算
     float startX=-uWidthSpan/2.0;//起始X坐标(即最左侧顶点的X坐标)
     //根据横向角度总跨度、横向长度总跨度及当前点X坐标折算出当前顶点X坐标对应的角度
     float currAngleH=uStartAngle+((inPos.x-startX)/uWidthSpan)*angleSpanH;
    
     //计算出随Y向发展起始角度的扰动值
     float angleSpanZ=4.0*3.14159265;//纵向角度总跨度,用于进行Y距离与角度的换算 
     float uHeightSpan=0.75*uWidthSpan;//纵向长度总跨度
     float startY=-uHeightSpan/2.0;//起始Y坐标(即最上侧顶点的Y坐标)
     //根据纵向角度总跨度、纵向长度总跨度及当前点Y坐标折算出当前顶点Y坐标对应的角度
     float currAngleZ=((inPos.z-startY)/uHeightSpan)*angleSpanZ;
        
     //计算斜向波浪
     float tzH=sin(currAngleH-currAngleZ)*ubo.scale;//通过正弦函数求出当前点的Y坐标 
       
     gl_Position = vec4(inPos.x,inPos.y+tzH,inPos.z,1); 
}

运行可见:
在这里插入图片描述

2.3、基于正弦曲线的X、Z双向波浪

void main(void)
{
	...
   //首先计算当前顶点X方向波浪对应的Y坐标		
   float angleSpanH=4.0*3.14159265;//横向角度总跨度,用于进行X距离与角度的换算
   float startX=-uWidthSpan/2.0;//起始X坐标(即最左侧顶点的X坐标)
   //根据横向角度总跨度、横向长度总跨度及当前点X坐标折算出当前顶点X坐标对应的角度
   float currAngleH=uStartAngle+((inPos.x-startX)/uWidthSpan)*angleSpanH;
   float tzH=sin(currAngleH)*ubo.scale;   //X方向波浪对应的Y坐标
   
   //接着计算当前顶点Z方向波浪对应的Y坐标
   float angleSpanZ=4.0*3.14159265;//纵向角度总跨度,用于进行Y距离与角度的换算
   float uHeightSpan=0.75*uWidthSpan;//纵向长度总跨度
   float startY=-uHeightSpan/2.0;//起始Y坐标(即最上侧顶点的Y坐标)
   //根据纵向角度总跨度、纵向长度总跨度及当前点Y坐标折算出当前顶点Y坐标对应的角度
   float currAngleZ=uStartAngle+3.14159265/3.0+((inPos.z-startY)/uHeightSpan)*angleSpanZ;
   float tzZ=sin(currAngleZ)*ubo.scale; //Y方向波浪对应的Y坐标
   gl_Position = vec4(inPos.x,inPos.y+tzZ+tzH,inPos.z,1); 
}

本质上讲,上述 X、 Z 双向波浪的顶点着色器与前面的 X 方向波浪的顶点着色器没有本质区别,仅仅是首先分别计算了 X 方向和 Z 方向波浪在当前顶点位置的 y坐标,最后将两个 y 坐标叠加,从而实现了波的叠加。因此,运行案例时看到的波浪就是 X、 Z 两个方向的如下:
在这里插入图片描述

2.4、其他

经过上边基于正弦波浪介绍,你也可以将X、Z双向波浪与斜向累加,此时效果更好,可见下图:
在这里插入图片描述
当然上边集中仅是简单的模仿,如果想真正的模拟水面或者布料,我们还需要对其进行贴图采样及实时更新法线光照等来实现逼真效果,后续有时间再优化吧。

除此之外,《GPU Gems》中介绍的采用叠加Gerstner波的方法来渲染水面更值得我们尝试,其主要思路是:
1.通过数学方法产生水面的网格。
2.添加水面的材质。
3.增加光照计算

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页