Godot Version
Godot v4.4.stable.mono - Windows 10 (build 19045) - Multi-window, 1 monitor - Vulkan (Forward+) - dedicated AMD Radeon RX 5700 XT (Advanced Micro Devices, Inc.; 32.0.12033.1030) - AMD Ryzen 9 3900X 12-Core Processor (24 threads)
Question
I’m trying to convert the Compute water ripple shader demo written by Bastiaan Olij (@mux213) from GdScript to C#. But I’ve gotten stuck.
Can anyone see the issue?
using Godot;
using System;
//[Tool]
public partial class water_plane : Area3D
{
[Export] public float RainSize = 3.0f;
[Export] public float MouseSize = 5.0f;
[Export] public Vector2I TextureSize = new Vector2I(512, 512);
[Export(PropertyHint.Range, "1.0,10.0,0.1")] public float Damp = 1.0f;
private float t = 0.0f;
private float maxT = 0.1f;
private Texture2Drd texture;
private int nextTexture = 0;
private Vector4 addWavePoint;
private Vector2 mousePos;
private bool mousePressed = false;
public override void _Ready()
{
RenderingServer.CallOnRenderThread(Callable.From(() => InitializeComputeCode(TextureSize)));
var material = GetNode<MeshInstance3D>("MeshInstance3D").MaterialOverride as ShaderMaterial;
if (material != null)
{
material.SetShaderParameter("effect_texture_size", TextureSize);
texture = (Texture2Drd)material.GetShaderParameter("effect_texture");
if(texture == null)
GD.PrintErr("Shader parameter 'effect_texture' is null.");
}
else
GD.PrintErr("Water plane material is null.");
}
public override void _ExitTree()
{
if (texture != null)
texture.TextureRdRid = new Rid();
else
GD.PrintErr("texture is null");
RenderingServer.CallOnRenderThread(Callable.From(FreeComputeResources));
}
public override void _UnhandledInput(InputEvent @event)
{
if (Engine.IsEditorHint()) return;
if (@event is InputEventMouseMotion or InputEventMouseButton)
mousePos = ((InputEventMouse)@event).GlobalPosition;
if (@event is InputEventMouseButton mouseEvent && mouseEvent.ButtonIndex == MouseButton.Left)
mousePressed = mouseEvent.Pressed;
}
private void CheckMousePos()
{
var camera = GetViewport().GetCamera3D();
var parameters = new PhysicsRayQueryParameters3D()
{
From = camera.ProjectRayOrigin(mousePos),
To = camera.ProjectRayOrigin(mousePos) + camera.ProjectRayNormal(mousePos) * 100.0f,
CollisionMask = 1,
CollideWithBodies = false,
CollideWithAreas = true
};
var result = GetWorld3D().DirectSpaceState.IntersectRay(parameters);
if (result.Count > 0)
{
var pos = GlobalTransform.AffineInverse() * ((Vector3)result["position"]);
addWavePoint.X = Mathf.Clamp(pos.X / 5.0f, -0.5f, 0.5f) * TextureSize.X + 0.5f * TextureSize.X;
addWavePoint.Y = Mathf.Clamp(pos.Z / 5.0f, -0.5f, 0.5f) * TextureSize.Y + 0.5f * TextureSize.Y;
addWavePoint.W = 1.0f;
}
else
{
addWavePoint = Vector4.Zero;
}
}
public override void _Process(double delta)
{
if (Engine.IsEditorHint())
{
addWavePoint.W = 0.0f;
}
else
{
CheckMousePos();
}
if (addWavePoint.W == 0.0f)
{
t += (float)delta;
if (t > maxT)
{
t = 0;
addWavePoint.X = GD.RandRange(0, TextureSize.X);
addWavePoint.Y = GD.RandRange(0, TextureSize.Y);
addWavePoint.Z = RainSize;
}
else
{
addWavePoint.Z = 0.0f;
}
}
else
{
addWavePoint.Z = mousePressed ? MouseSize : 0.0f;
}
nextTexture = (nextTexture + 1) % 3;
if (texture != null)
{
texture.TextureRdRid = textureRds[nextTexture];
}
else
{
GD.PrintErr("Texture update failed: Invalid texture or index.");
}
RenderingServer.CallOnRenderThread(Callable.From(() => RenderProcess(nextTexture, addWavePoint, TextureSize, Damp)));
}
private RenderingDevice rd;
private Rid shader;
private Rid pipeline;
private Rid[] textureRds = new Rid[3];
private Rid[] textureSets = new Rid[3];
private Rid CreateUniformSet(Rid textureRd)
{
var uniform = new RDUniform()
{
UniformType = RenderingDevice.UniformType.Image,
Binding = 0
};
uniform.AddId(textureRd);
return rd.UniformSetCreate([uniform], shader, 0);
}
private void InitializeComputeCode(Vector2I initWithTextureSize)
{
rd = RenderingServer.CreateLocalRenderingDevice();
var shaderFile = GD.Load<RDShaderFile>("res://water_plane/water_compute.glsl");
var shaderSpirv = shaderFile.GetSpirV();
shader = rd.ShaderCreateFromSpirV(shaderSpirv);
pipeline = rd.ComputePipelineCreate(shader);
var tf = new RDTextureFormat()
{
Format = RenderingDevice.DataFormat.R32Sfloat,
TextureType = RenderingDevice.TextureType.Type2D,
Width = (uint)initWithTextureSize.X,
Height = (uint)initWithTextureSize.Y,
Depth = 1,
ArrayLayers = 1,
Mipmaps = 1,
UsageBits = RenderingDevice.TextureUsageBits.SamplingBit |
RenderingDevice.TextureUsageBits.ColorAttachmentBit |
RenderingDevice.TextureUsageBits.StorageBit |
RenderingDevice.TextureUsageBits.CanUpdateBit |
RenderingDevice.TextureUsageBits.CanCopyToBit
};
for (int i = 0; i < 3; i++)
{
textureRds[i] = rd.TextureCreate(tf, new RDTextureView(), []);
if (textureRds == null)
{
GD.PrintErr("Failed to create texture!");
}
rd.TextureClear(textureRds[i], new Color(0, 0, 0, 0), 0, 1, 0, 1);
textureSets[i] = CreateUniformSet(textureRds[i]);
}
}
public void RenderProcess(int withNextTexture, Vector4 wavePoint, Vector2I texSize, float pDamp)
{
float[] pushConstant = new float[]
{
wavePoint.X, wavePoint.Y, wavePoint.Z, wavePoint.W,
texSize.X, texSize.Y, pDamp, 0.0f
};
uint xGroups = (uint)(texSize.X - 1) / 8 + 1;
uint yGroups = (uint)(texSize.Y - 1) / 8 + 1;
Rid nextSet = textureSets[withNextTexture];
Rid currentSet = textureSets[(withNextTexture + 2) % 3];
Rid previousSet = textureSets[(withNextTexture + 1) % 3];
// Convert float list to byte array
byte[] pushConstantBytes = new byte[pushConstant.Length * 4];
Buffer.BlockCopy(pushConstant, 0, pushConstantBytes, 0, pushConstantBytes.Length);
long computeList = rd.ComputeListBegin();
rd.ComputeListBindComputePipeline(computeList, pipeline);
rd.ComputeListBindUniformSet(computeList, currentSet, 0);
rd.ComputeListBindUniformSet(computeList, previousSet, 1);
rd.ComputeListBindUniformSet(computeList, nextSet, 2);
rd.ComputeListSetPushConstant(computeList, pushConstantBytes, (uint)pushConstantBytes.Length);
rd.ComputeListDispatch(computeList, xGroups, yGroups, 1);
rd.ComputeListEnd();
rd.Submit();
rd.Sync();
}
private void FreeComputeResources()
{
foreach (var rid in textureRds)
{
if (rid.IsValid)
rd.FreeRid(rid);
}
if (shader.IsValid)
rd.FreeRid(shader);
}
}