Grass shell Texturing
Demo shell texturing for grass using shader and MultiMeshInstance.main
parent
16778381cb
commit
2b4746b77a
@ -0,0 +1,46 @@
|
|||||||
|
extends Node3D
|
||||||
|
class_name RotateCamera
|
||||||
|
|
||||||
|
@export var camera_rotate_sensitivity: float = 0.005
|
||||||
|
@export var camera_zoom_sensititivy: float = 0.01
|
||||||
|
@export var zoom_max: float = 10.
|
||||||
|
@export var zoom_min: float = 0.1
|
||||||
|
|
||||||
|
@onready var camera: Camera3D = $Camera3D
|
||||||
|
|
||||||
|
var camera_rotate_diff: Vector3 = Vector3()
|
||||||
|
var camera_zoom_weight: float = .15
|
||||||
|
|
||||||
|
var enable_rotate: bool = false
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
self._update_rotation()
|
||||||
|
self._update_zoom()
|
||||||
|
|
||||||
|
func _unhandled_input(event):
|
||||||
|
if self.enable_rotate and event is InputEventMouseMotion:
|
||||||
|
self.camera_rotate_diff.y -= event.relative.x
|
||||||
|
self.camera_rotate_diff.x -= event.relative.y
|
||||||
|
elif event is InputEventMouseButton:
|
||||||
|
if event.is_action_pressed("camera_rotate_button"):
|
||||||
|
self.enable_rotate = true
|
||||||
|
elif event.is_action_released("camera_rotate_button"):
|
||||||
|
self.enable_rotate = false
|
||||||
|
elif event.is_action_pressed("camera_zoom_in"):
|
||||||
|
self.camera_zoom_weight = clampf(self.camera_zoom_weight - camera_zoom_sensititivy, 0., 1.)
|
||||||
|
elif event.is_action_pressed("camera_zoom_out"):
|
||||||
|
self.camera_zoom_weight = clampf(self.camera_zoom_weight + camera_zoom_sensititivy, 0., 1.)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_rotation():
|
||||||
|
if self.enable_rotate:
|
||||||
|
self.rotation += self.camera_rotate_diff * camera_rotate_sensitivity
|
||||||
|
self.camera_rotate_diff = Vector3.ZERO
|
||||||
|
|
||||||
|
func _update_zoom():
|
||||||
|
var from = self.global_position
|
||||||
|
var to = self.global_position + self.camera.get_global_transform().basis.z * zoom_max
|
||||||
|
|
||||||
|
var new_cam_pos = lerp(from, to, camera_zoom_weight)
|
||||||
|
self.camera.global_position = new_cam_pos
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bkavbjcx1vhaf"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://camera.gd" id="1_bmlh2"]
|
||||||
|
|
||||||
|
[node name="camera_root" type="Node3D"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.869029, 0.494761, 0, -0.494761, 0.869029, 0, 0, 0)
|
||||||
|
script = ExtResource("1_bmlh2")
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.306079, 2.53232)
|
@ -0,0 +1,61 @@
|
|||||||
|
shader_type spatial;
|
||||||
|
render_mode cull_disabled;
|
||||||
|
|
||||||
|
//uniform int _ShellIndex; // index of shell being worked on (0 -> _ShellCount)
|
||||||
|
uniform float _NoiseMin; // minimum strand length
|
||||||
|
uniform float _NoiseMax; // maxmimum strand length
|
||||||
|
uniform float _ShellLength; // amount of distance that the shells cover, 1 means shells will span across 1 world space unit
|
||||||
|
uniform int _ShellCount; // total number of shells (for normalizing the shell index)
|
||||||
|
uniform float _ShellDistanceAttenuation; // exponent how far to push the shell outwards
|
||||||
|
uniform float _Thickness; // how thick a strand shall be
|
||||||
|
uniform float _Attenuation; // AO strength factor
|
||||||
|
uniform float _OcclusionBias; // additive bias for AO
|
||||||
|
uniform vec3 _ShellColor;
|
||||||
|
|
||||||
|
uniform float _Density; // amout of strands to generate
|
||||||
|
|
||||||
|
varying flat int _ShellIndex;
|
||||||
|
|
||||||
|
float hash(uint n) {
|
||||||
|
n = (n << 13U) ^ n;
|
||||||
|
n = n * (n * n * 15731U + 1239221U) + 123376312589U;
|
||||||
|
return float(n & uint(0x7fffffffU)) / float(0x7fffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vertex() {
|
||||||
|
_ShellIndex = int(INSTANCE_CUSTOM.x);
|
||||||
|
float shell_height = float(_ShellIndex) / float(_ShellCount);
|
||||||
|
shell_height = pow(shell_height, _ShellDistanceAttenuation);
|
||||||
|
|
||||||
|
VERTEX.xyz += NORMAL.xyz * _ShellLength * shell_height;
|
||||||
|
|
||||||
|
//NORMAL = normalize(NORMAL);
|
||||||
|
UV = UV;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec2 newUV = UV * _Density;
|
||||||
|
vec2 localUV = fract(newUV) * 2. - 1.;
|
||||||
|
|
||||||
|
float localDistanceFromCenter = length(localUV);
|
||||||
|
float h = float(_ShellIndex) / float(_ShellCount); // normalized height
|
||||||
|
|
||||||
|
uint seed = uint(int(newUV.x) + 100 * int(newUV.y) + 100 * 10);
|
||||||
|
|
||||||
|
float rand = mix(_NoiseMin, _NoiseMax, hash(seed)); // getting random value for strand
|
||||||
|
|
||||||
|
if (localDistanceFromCenter > _Thickness * (rand - h) && _ShellIndex > 0) discard; // discarding pixels outside of thickness
|
||||||
|
|
||||||
|
//float ndotl = dot(NORMAL, WORLD) // TODO do light later?
|
||||||
|
float ambientOcclusion = pow(h, _Attenuation); // fake Ambient Occlusion
|
||||||
|
ambientOcclusion += _OcclusionBias;
|
||||||
|
// Called for every pixel the material is visible on.
|
||||||
|
ALBEDO.rgb = _ShellColor * ambientOcclusion;
|
||||||
|
//ALPHA = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void light() {
|
||||||
|
float ndotl = clamp(dot(NORMAL, LIGHT), 0., 1.) * .5 + .5;
|
||||||
|
|
||||||
|
DIFFUSE_LIGHT += ndotl * ndotl;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
[gd_resource type="QuadMesh" load_steps=3 format=3 uid="uid://obd7wi7g27sb"]
|
||||||
|
|
||||||
|
[ext_resource type="Shader" path="res://grass.gdshader" id="1_5707v"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hgeuk"]
|
||||||
|
render_priority = 0
|
||||||
|
shader = ExtResource("1_5707v")
|
||||||
|
shader_parameter/_NoiseMin = 0.4
|
||||||
|
shader_parameter/_NoiseMax = 1.0
|
||||||
|
shader_parameter/_ShellLength = 0.03
|
||||||
|
shader_parameter/_ShellCount = 16
|
||||||
|
shader_parameter/_ShellDistanceAttenuation = 1.0
|
||||||
|
shader_parameter/_Thickness = 3.4
|
||||||
|
shader_parameter/_Attenuation = null
|
||||||
|
shader_parameter/_OcclusionBias = null
|
||||||
|
shader_parameter/_ShellColor = Vector3(0.2, 0.7, 0.2)
|
||||||
|
shader_parameter/_Density = 10.0
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
material = SubResource("ShaderMaterial_hgeuk")
|
||||||
|
subdivide_width = 32
|
||||||
|
subdivide_depth = 32
|
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://yafv1eyajxv0"
|
||||||
|
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.png"
|
||||||
|
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
@ -0,0 +1,23 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://b45p5o8obc4rb"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bkavbjcx1vhaf" path="res://camera.tscn" id="1_fql2y"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://gbmju3x1gp7y" path="res://multi_grass.tscn" id="3_lkk6e"]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sivjy"]
|
||||||
|
albedo_color = Color(0.2, 0.137255, 0.0431373, 1)
|
||||||
|
|
||||||
|
[sub_resource type="PlaneMesh" id="PlaneMesh_vpqsi"]
|
||||||
|
material = SubResource("StandardMaterial3D_sivjy")
|
||||||
|
|
||||||
|
[node name="main" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="camera" parent="." instance=ExtResource("1_fql2y")]
|
||||||
|
|
||||||
|
[node name="light" type="DirectionalLight3D" parent="."]
|
||||||
|
transform = Transform3D(0.758372, 0.524229, 0.38737, -0.457532, 0.00484201, 0.88918, 0.464258, -0.851564, 0.243524, 1.21403, 2.38572, 0.933982)
|
||||||
|
|
||||||
|
[node name="multi_grass" parent="." instance=ExtResource("3_lkk6e")]
|
||||||
|
|
||||||
|
[node name="ground" type="MeshInstance3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.149795, 0)
|
||||||
|
mesh = SubResource("PlaneMesh_vpqsi")
|
@ -0,0 +1,72 @@
|
|||||||
|
extends MultiMeshInstance3D
|
||||||
|
|
||||||
|
@export_range(1, 126) var shell_count = 64
|
||||||
|
@export var noise_min: float = 0.4
|
||||||
|
@export var noise_max: float = 1.0
|
||||||
|
@export var shell_length: float = .1
|
||||||
|
@export var shell_distance_attenuation: float = 1.
|
||||||
|
@export var thickness: float = 3.4
|
||||||
|
@export var attenuation: float = 1.98
|
||||||
|
@export var occlusion_bias: float = .04
|
||||||
|
@export var shell_color: Vector3 = Vector3(0.2, 0.7, 0.2)
|
||||||
|
@export var density: int = 150
|
||||||
|
|
||||||
|
func _generate_mmi(layers: int, mmi: MultiMeshInstance3D, mesh: Mesh, material: Material, cast_shadow: bool):
|
||||||
|
var mdt = MeshDataTool.new()
|
||||||
|
|
||||||
|
if mmi.multimesh == null:
|
||||||
|
mmi.multimesh = MultiMesh.new()
|
||||||
|
mmi.multimesh.transform_format = MultiMesh.TRANSFORM_3D
|
||||||
|
|
||||||
|
var new_mesh: Mesh = mesh.duplicate(true) as Mesh
|
||||||
|
#new_mesh = _normals_to_vertex_color(new_mesh, material) # saves normal data as vertex color to be used by the shader (in the orig code)
|
||||||
|
mmi.multimesh.mesh = new_mesh
|
||||||
|
mmi.multimesh.instance_count = layers
|
||||||
|
mmi.multimesh.visible_instance_count = layers
|
||||||
|
for surface in new_mesh.get_surface_count():
|
||||||
|
material = ShaderMaterial.new()
|
||||||
|
material.set_shader(preload("res://grass.gdshader"))
|
||||||
|
mmi.multimesh.mesh.surface_set_material(surface, material.duplicate(true))
|
||||||
|
|
||||||
|
for i in range(layers):
|
||||||
|
#mmi.multimesh.set_instance_transform(i, Transform3D(Basis(), Vector3(0., .1 * i, 0.)))
|
||||||
|
mmi.multimesh.set_instance_transform(i, Transform3D(Basis(), Vector3()))
|
||||||
|
var grey = float(i) / float(layers)
|
||||||
|
#mmi.multimesh.set_instance_color(i, Color(1., 1., 1., 1.))
|
||||||
|
|
||||||
|
var mat = mmi.multimesh.mesh.surface_get_material(0)
|
||||||
|
if mat is ShaderMaterial:
|
||||||
|
#print("Setting shader params (", i, ")")
|
||||||
|
#mat.set_shader_parameter("_ShellIndex", i)
|
||||||
|
mmi.multimesh.set_instance_custom_data(i, Color(i, 0., 0., 0.)) # passes shell index for each instance
|
||||||
|
mat.set_shader_parameter("_NoiseMin", noise_min)
|
||||||
|
mat.set_shader_parameter("_NoiseMax", noise_max)
|
||||||
|
mat.set_shader_parameter("_ShellLength", shell_length)
|
||||||
|
mat.set_shader_parameter("_ShellCount", layers)
|
||||||
|
mat.set_shader_parameter("_ShellDistanceAttenuation", shell_distance_attenuation)
|
||||||
|
mat.set_shader_parameter("_Thickness", thickness)
|
||||||
|
mat.set_shader_parameter("_Attenuation", attenuation)
|
||||||
|
mat.set_shader_parameter("_OcclusionBias", occlusion_bias)
|
||||||
|
mat.set_shader_parameter("_ShellColor", shell_color)
|
||||||
|
mat.set_shader_parameter("_Density", density)
|
||||||
|
|
||||||
|
mmi.cast_shadow = 1 if cast_shadow else 0
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var mat = ShaderMaterial.new()
|
||||||
|
#var mat2 = StandardMaterial3D.new()
|
||||||
|
mat.set_shader(preload("res://grass.gdshader"))
|
||||||
|
self._generate_mmi(self.shell_count, self, self.get_multimesh().mesh, mat, false)
|
||||||
|
|
||||||
|
print("===== Shell Texturing =====")
|
||||||
|
print("Layers: ", self.shell_count)
|
||||||
|
print("Noise (min): ", self.noise_min)
|
||||||
|
print("Noise (max): ", self.noise_max)
|
||||||
|
print("Shell length: ", self.shell_length)
|
||||||
|
print("Shell count: ", self.shell_count)
|
||||||
|
print("Shell distance attenuation: ", self.shell_distance_attenuation)
|
||||||
|
print("Thickness: ", self.thickness)
|
||||||
|
print("Attenuation: ", self.attenuation)
|
||||||
|
print("Occlusion bias: ", self.occlusion_bias)
|
||||||
|
print("Shell color: ", self.shell_color)
|
||||||
|
print("Density: ", self.density)
|
@ -0,0 +1,15 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://gbmju3x1gp7y"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://multi_grass.gd" id="1_f7cbg"]
|
||||||
|
|
||||||
|
[sub_resource type="QuadMesh" id="QuadMesh_h8afx"]
|
||||||
|
orientation = 1
|
||||||
|
|
||||||
|
[sub_resource type="MultiMesh" id="MultiMesh_j6hjp"]
|
||||||
|
transform_format = 1
|
||||||
|
use_custom_data = true
|
||||||
|
mesh = SubResource("QuadMesh_h8afx")
|
||||||
|
|
||||||
|
[node name="multi_grass" type="MultiMeshInstance3D"]
|
||||||
|
multimesh = SubResource("MultiMesh_j6hjp")
|
||||||
|
script = ExtResource("1_f7cbg")
|
@ -0,0 +1,34 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="Shell Texturing"
|
||||||
|
run/main_scene="res://main.tscn"
|
||||||
|
config/features=PackedStringArray("4.2", "Forward Plus")
|
||||||
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
camera_rotate_button={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
camera_zoom_in={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
camera_zoom_out={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue