wind shader
程序化纸张飞行着色器
通过风相互作用、轨道旋转和动态颤振模拟逼真的纸质物理学。非常适合在 3D 场景中制作神奇效果、记录动画或环境故事。
核心功能
-
风模拟:
使用基于纹理的顶点置换生成自然的纸张颤动
-
轨道运动:
使对象围绕可自定义的中心点旋转
-
动态旋转:
在自然纸张方向和面向目标的方向之间混合
-
材料体系:
支持 PBR 纹理(反照率、粗糙度、法线贴图)
主要特点
-
🌀 可调节的风速和风向
-
⭕ 可定制的圆形飞行路径
-
🎚️ 风/方向混合控制
-
📜 逼真的纸张般的运动
-
✅ 风效果的开/关切换
-
🖼️ 视觉细节的纹理支持
适合:
-
神奇的文档动画
-
环保碎纸
-
法术效果视觉对象
-
讲故事的场景元素
- 通过 GDScript,您可以访问着色器的参数以创建大量特殊效果和动画。这些可以引导玩家、增强角色能力,或者简单地模拟风的物理特性——就像角色走过时纸张爆炸一样。存在多种可能性。
Shader code
shader_type spatial;
render_mode cull_disabled;
uniform sampler2D normalmap_tex:repeat_enable,filter_linear;
uniform sampler2D albedo_tex:repeat_enable,filter_linear;
uniform sampler2D rough_tex:repeat_enable,filter_linear;
uniform vec2 dir_2;
uniform float speed2:hint_range(0.0, 2.0)=0.1;
uniform sampler2D normalmap_tex_2:repeat_enable,filter_linear;
uniform vec3 center;
uniform float radius;
uniform float rotation_speed;
uniform vec3 axis;
uniform float wind_up:hint_range(0.0, 1.0, 0.1)=0.5;
uniform float wind_target:hint_range(-1.0, 1.0, 1.0)=-1.0;
uniform bool Wind=false;
void vertex() {
if (Wind){
float wind_seed=0.0;
vec2 new_uv=vec2(UV.y,UV.x);
vec4 normal_color2 = texture(normalmap_tex_2,new_uv*0.5+TIME*dir_2*speed2);
VERTEX.y+=normal_color2.r*0.2;
// 2. 计算模型在轨道上的位置(绕轴旋转)
float angle = TIME * rotation_speed;
vec3 orbit_pos = center + radius * vec3(
sin(angle), // X 坐标(绕Y轴旋转)
0.0, // Y 坐标(保持不变)
cos(angle) // Z 坐标
);
// 3. 计算指向中心的方向(新Z轴)
vec3 new_z = normalize(mix(wind_target*MODEL_MATRIX[1].xyz,center - orbit_pos,wind_up));
// 4. 构建正交坐标系(根据旋转轴调整基准方向)
vec3 up = axis; // 使用旋转轴作为基准上方向
vec3 new_x = normalize(cross(up, new_z));
vec3 new_y = normalize(cross(new_z, new_x));
// 5. 构造变换矩阵(旋转 + 位置)
mat4 transform = mat4(
vec4(new_x, 0.0),
vec4(new_y, 0.0),
vec4(new_z, 0.0),
vec4(orbit_pos, 1.0)
);
// 6. 应用变换
MODELVIEW_MATRIX = VIEW_MATRIX * transform;
POSITION = PROJECTION_MATRIX * MODELVIEW_MATRIX * vec4(VERTEX, 1.0);
}
}
void fragment(){
ALBEDO=texture(albedo_tex,UV).rgb;
ROUGHNESS=texture(rough_tex,UV).r;
NORMAL_MAP=texture(normalmap_tex,UV).rgb;
}




hey! thanks for the shader. do you mind sharing an example? I’m having troubles setting it up.
sure,maybe 0.586
This shader requires the use of GDScript in conjunction, apologies for forgetting to mention this earlier.
extends Node3D
@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@export var wind_start:bool
@onready var mesh_instance_3d_2: MeshInstance3D = $MeshInstance3D2
var x:=0
var qi:=false
var now_position:Vector3
var mubiao:Vector3
@export var ti_paper:MeshInstance3D##替身
@export var min_radius:=1.0
@export var max_radius:=2.1
@export var wind_height:=2.9
@export var up_time:=1.0
@export var fly_time:=0.5
@onready var audio_stream_player_3d: AudioStreamPlayer3D = $AudioStreamPlayer3D
func _ready() -> void:
now_position=global_position
if ti_paper:
mubiao=ti_paper.global_position
func _process(delta: float) -> void:
if wind_start and x==0:
audio_stream_player_3d.play()
mesh_instance_3d_2.hide()
mesh_instance_3d.material_override.set_shader_parameter(“Wind”,true)
mesh_instance_3d.material_override.set_shader_parameter(“center”,global_position)
mesh_instance_3d.material_override.set_shader_parameter(“radius”,min_radius)
animation_player.play(“wind_up”)
x+=1
qi=!qi
if qi and x==1:
up_wind()
x+=1
set_process(false)
func up_wind() -> void:
#time_now+=delta
#if now_position.y < global_position.y+wind_height and !forward:
#var wind_position= Vector3(global_position.x,global_position.y+sqrt(time_now)*wind_up_speed,global_position.z)
#wind_position.y=clamp(wind_position.y,global_position.y,global_position.y+3.0)
#now_position=wind_position
#mesh_instance_3d.material_override.set_shader_parameter(“center”,wind_position)
#var wind_radius=0.2+sqrt(time_now)
#wind_radius=clamp(wind_radius,min_radius,max_radius)
#now_radius=wind_radius
#mesh_instance_3d.material_override.set_shader_parameter(“radius”,wind_radius)
#elif now_position.y >= global_position.y+wind_height or forward:
#if x==1:
#forward=true
#x+=1
#if x==2 and only_forward:
#var tween=create_tween().set_ease (Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SPRING)
#tween.tween_property(mesh_instance_3d.material_override,”shader_parameter/center”,mubiao+Vector3(0.1,0.2,0.1),7.0)
#x+=1
#await tween.finished
#mesh_instance_3d.material_override.set_shader_parameter(“Wind”,false)
#ti_paper.show()
#queue_free()
#elif x==2 and !only_forward:
#forward_wind(now_position)
var tweenup=create_tween().set_ease (Tween.EASE_OUT_IN).set_trans(Tween.TRANS_SPRING)
now_position.y+=wind_height
tweenup.tween_property(mesh_instance_3d.material_override,”shader_parameter/center”,now_position,up_time)
var tween01=create_tween().set_ease (Tween.EASE_IN).set_trans(Tween.TRANS_SPRING)
tween01.tween_property(mesh_instance_3d.material_override,”shader_parameter/radius”,max_radius,up_time/2.0)
await tweenup.finished
forward_wind(now_position)
func forward_wind(wind_position:Vector3) -> void:
#time_for+=delta
#var for_position=wind_position
#var now_2_position=Vector2(now_position.x,now_position.z)
#var mubiao_2=Vector2(mubiao.x,mubiao.z)
#if now_2_position.distance_to(mubiao_2)>0.1:
#for_position= Vector3(wind_position.x+sqrt(time_for)*0.2,wind_position.y+sqrt(time_for)*0.1,wind_position.z+sqrt(time_for)*0.2)
#for_position.y=clamp(for_position.y,global_position.y+0.5,mubiao.y+1.2)
#for_position.z=clamp(for_position.z,global_position.z+0.5,mubiao.z)
#now_position=for_position
#time_for=0.0
#else:
#for_position= Vector3(wind_position.x,wind_position.y-sqrt(time_for)*0.05,wind_position.z)
#for_position.y=clamp(for_position.y,mubiao.y-0.1,wind_position.y)
#now_position=for_position
#if now_position.y < mubiao.y:
#mesh_instance_3d.material_override.set_shader_parameter(“Wind”,false)
#ti_paper.show()
#queue_free()
#mesh_instance_3d.material_override.set_shader_parameter(“center”,for_position)
#var for_radius=now_radius-sqrt(time_for)*0.2
#for_radius=clamp(for_radius,min_radius,max_radius)
#mesh_instance_3d.material_override.set_shader_parameter(“radius”,for_radius)
var tween=create_tween().set_ease (Tween.EASE_IN).set_trans(Tween.TRANS_SPRING)
tween.tween_property(mesh_instance_3d.material_override,”shader_parameter/center”,mubiao+Vector3(0.1,0.5,0.1),fly_time)
var tween02=create_tween().set_ease (Tween.EASE_IN).set_trans(Tween.TRANS_SPRING)
tween02.tween_property(mesh_instance_3d.material_override,”shader_parameter/radius”,min_radius+0.1,fly_time+0.2)
await tween.finished
mesh_instance_3d.material_override.set_shader_parameter(“Wind”,false)
ti_paper.show()
queue_free()