Are Geometry Shaders available?

Hello!
Can I use geometry shaders in Flax to make stuff like infinite amounts of grass and custom things?
I’m thinking like this.
I already checked out Custom Geometry Drawing
but it isn’t quite what I’m looking for, I’m looking for standard HLSL geometry shaders
I know I can use the foliage tool but I prefer geometry shaders for the performance when available :slight_smile:
Thanks!

Okay, made some progress on this, they are totally available just a bit more involved than making a shader and dropping it onto a model.

  1. You need to make a script that reads your base mesh data (vertices, triangles etc) then passes it onto a shader. like here
  2. You can use standard HLSL from there on :smiley:

    (This is totally amazing btw, binding stuff to the GPU from C# like whaaat???)
    Leaving this here in case anyone needs/looks for this in the future.

My mesh to render is just a subdivided plane (might replace it with simple plane and tessellation in the future).
My shader:
#include “./Flax/Common.hlsl”

META_CB_BEGIN(0, Data)

float4x4 WorldMatrix;

float4x4 ViewProjectionMatrix;

META_CB_END

// Geometry data passed to the vertex shader

struct ModelInput

{

    float3 Position : POSITION;

};

// Interpolants passed from the vertex shader

struct VertexOutput

{

    float4 Position : SV_Position;

    float3 WorldPosition : TEXCOORD0;

};

// Interpolants passed to the pixel shader

struct PixelInput

{

    float4 Position : SV_Position;

    float3 WorldPosition : TEXCOORD0;

};

struct geometryOutput

{

    float4 pos : SV_POSITION;

};

float4 ObjectToClipPos(float4 pos){

    float3 wp = mul(float4(pos.xyz, 1), WorldMatrix).xyz;

    return mul(float4(wp, 1), ViewProjectionMatrix);

}

META_GS(true, FEATURE_LEVEL_ES2)

[maxvertexcount(3)]

void geo(triangle float4 IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)

{

    float3 pos = IN[0];

    geometryOutput o;

    o.pos = ObjectToClipPos(float4(pos, 1)+ float4(2, 0, 0, 1));

    triStream.Append(o);

    o.pos =    ObjectToClipPos(float4(pos, 1)+float4(-2, 0, 0, 1));

    triStream.Append(o);

    o.pos =   ObjectToClipPos(float4(pos, 1)+float4(0, 10, 0, 1));

    triStream.Append(o);

}

// Vertex shader function for custom geometry processing

META_VS(true, FEATURE_LEVEL_ES2)

META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true)

VertexOutput VS_Custom(ModelInput input)

{

    VertexOutput output;

    output.WorldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz;

    output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix);

    return output;

}

// Pixel shader fucntion for custom geoemtry drawing on a screen

META_PS(true, FEATURE_LEVEL_ES2)

float4 PS_Custom(PixelInput input) : SV_Target

{

    //return lerp(float4(1, 0.1, 0, 1), float4(0.2, 0.9, 0.3, 1), frac(input.WorldPosition.y / 400));

    return float4(1, 1, 1, 1);

}

My render script:
using System;

using System.Collections.Generic;

using System.Runtime.InteropServices;

using FlaxEngine;

public class GrassDraw : PostProcessEffect

{ /// <summary>

  /// Shader constant buffer data structure that matches the HLSL source.

  /// </summary>

    [StructLayout(LayoutKind.Sequential)]

    private struct Data

    {

        public Matrix WorldMatrix;

        public Matrix ViewProjectionMatrix;

    }

    private static Vector3[] _vertices =

    {

        new Vector3(0, 0, 0),

        new Vector3(100, 0, 0),

        new Vector3(100, 100, 0),

        new Vector3(0, 100, 0),

        new Vector3(0, 100, 100),

        new Vector3(100, 100, 100),

        new Vector3(100, 0, 100),

        new Vector3(0, 0, 100),

    };

    private static uint[] _triangles =

    {

        0, 2, 1, // Face front

        0, 3, 2,

        2, 3, 4, // Face top

        2, 4, 5,

        1, 2, 5, // Face right

        1, 5, 6,

        0, 7, 4, // Face left

        

        0, 4, 3,

        5, 4, 7, // Face back

        5, 7, 6,

        0, 6, 7, // Face bottom

        0, 1, 6

    };

    private GPUBuffer _vertexBuffer;

    private GPUBuffer _indexBuffer;

    private GPUPipelineState _psCustom;

    private Shader _shader;

    public Model grassMesh;

    public Shader Shader

    {

        get => _shader;

        set

        {

            if (_shader != value)

            {

                _shader = value;

                ReleaseShader();

            }

        }

    }

    public override unsafe void OnEnable()

    {

        var vert = grassMesh.LODs[0].Meshes[0].DownloadVertexBuffer();

        var vertList = new List<Vector3>();

        for (int i = 0; i < vert.Length; i++)

        {

            vertList.Add(vert[i].Position);

        }

        _vertices = vertList.ToArray();

        var tri = grassMesh.LODs[0].Meshes[0].DownloadIndexBuffer();

        var triList = new List<uint>();

        for (int i = 0; i < tri.Length; i++)

        {

            triList.Add(Convert.ToUInt32(tri[i]));

        }

        _triangles = triList.ToArray();

        // Create vertex buffer for custom geometry drawing

        _vertexBuffer = new GPUBuffer();

        fixed (Vector3* ptr = _vertices)

        {

            var desc = GPUBufferDescription.Vertex(sizeof(Vector3), _vertices.Length, new IntPtr(ptr));

            _vertexBuffer.Init(ref desc);

        }

        // Create index buffer for custom geometry drawing

        _indexBuffer = new GPUBuffer();

        fixed (uint* ptr = _triangles)

        {

            var desc = GPUBufferDescription.Index(sizeof(uint), _triangles.Length, new IntPtr(ptr));

            _indexBuffer.Init(ref desc);

        }

#if FLAX_EDITOR

        // Register for asset reloading event and dispose resources that use shader

        Content.AssetReloading += OnAssetReloading;

#endif

        // Register postFx to all game views (including editor)

        SceneRenderTask.GlobalCustomPostFx.Add(this);

    }

#if FLAX_EDITOR

    private void OnAssetReloading(Asset asset)

    {

        // Shader will be hot-reloaded

        if (asset == Shader)

            ReleaseShader();

    }

#endif

    public override void OnDisable()

    {

        // Remember to unregister from events and release created resources (it's gamedev, not webdev)

        SceneRenderTask.GlobalCustomPostFx.Remove(this);

#if FLAX_EDITOR

        Content.AssetReloading -= OnAssetReloading;

#endif

        ReleaseShader();

        Destroy(ref _vertexBuffer);

        Destroy(ref _indexBuffer);

    }

    private void ReleaseShader()

    {

        // Release resources using shader

        Destroy(ref _psCustom);

    }

    public override bool CanRender => base.CanRender && Shader && Shader.IsLoaded;

    public override bool UseSingleTarget => true; // This postfx overdraws the input buffer without using output

    public override PostProcessEffectLocation Location => PostProcessEffectLocation.BeforeForwardPass;

    public override unsafe void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)

    {

        // Here we perform custom rendering on top of the in-build drawing

        // Setup missing resources

        if (!_psCustom)

        {

            _psCustom = new GPUPipelineState();

            var desc = GPUPipelineState.Description.Default;

            desc.VS = Shader.GPU.GetVS("VS_Custom");

            desc.PS = Shader.GPU.GetPS("PS_Custom");

            desc.GS = Shader.GPU.GetGS("geo");

            _psCustom.Init(ref desc);

        }

        // Set constant buffer data (memory copy is used under the hood to copy raw data from CPU to GPU memory)

        var cb = Shader.GPU.GetCB(0);

        if (cb != IntPtr.Zero)

        {

            var data = new Data();

            Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection);

            Actor.GetLocalToWorldMatrix(out var world);

            Matrix.Transpose(ref world, out data.WorldMatrix);

            Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix);

            context.UpdateCB(cb, new IntPtr(&data));

        }

        // Draw geometry using custom Pixel Shader and Vertex Shader

        context.BindCB(0, cb);

        context.BindIB(_indexBuffer);

        context.BindVB(new[] { _vertexBuffer });

        context.SetState(_psCustom);

        context.SetRenderTarget(renderContext.Buffers.DepthBuffer.View(), input.View());

        context.DrawIndexed((uint)_triangles.Length);

    }

}
3 Likes