DX11 shader only renders after previewing/clicking shader material in Editor once per session.

Using a basic DX11 shader to attempt to implement a very basic particle system. Particles seemed to be working in the Editor but when a build is created they don’t appear. Upon further inspection the bug also effects the Editor although there is a strange way to get it to render/work.

Steps to reproduce:

  1. Open project and Default scene.
  2. Play project in Editor - you will see no particles. No matter how many times you play in game mode no particles will render.
  3. Click on the “ParticlesMaterial” in the Editor to force a preview render of the material that is using the shader. Particles start rendering!

After clicking on the material using the particle shader in the Editor (which seems to magically cause the shader to start working) the particles always render ok in the Editor for the rest of the duration of using it. Closing the Editor and reopening you will find the bug is back until you click the material again in the inspector. Also builds never work since there is no way to initiate whatever underlying rendering/pipeline/graphics card state call Unity Editor does when clicking a material to preview in the inspector.

Does anyone have any ideas or has run into this before (this was tested in 4.5.1 pro)?

Create an empty game object and attach Particles.cs. In the behaviour then assign the Computer Shader to ParticleMover.compute and the Particles Material to a new Material using the DX11 material shader (you can find it/assing it under Test/Particle).

The material/shader:

Shader "Test/Particle"
{
    Properties
    {
        _SpriteTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            ZWrite Off Cull Off Fog { Mode Off }
            Blend SrcAlpha One
      
            CGPROGRAM
            #pragma target 5.0
            //#pragma enable_d3d11_debug_symbols

            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #include "UnityCG.cginc"

            // **************************************************************
            // Data structures                                                *
            // **************************************************************
            struct GS_INPUT
            {
                float4    pos        : SV_POSITION;
                float3    normal    : NORMAL;
                float2  tex0    : TEXCOORD0;
                float2    scale    : TEXCOORD1;
                float4    color    : COLOR;
            };

            struct FS_INPUT
            {
                float4    pos        : SV_POSITION;
                float2  tex0    : TEXCOORD0;
                float4    color    : COLOR;
            };

            struct Particle
            {
                float3 position;
                float4 color;
                float size;
            };


            // **************************************************************
            // Vars                                                            *
            // **************************************************************

            Texture2D _SpriteTex;
            SamplerState sampler_SpriteTex;
            StructuredBuffer<Particle> particleBuffer;

            // **************************************************************
            // Shader Programs                                                *
            // **************************************************************

            // Vertex Shader ------------------------------------------------
            GS_INPUT vert(appdata_base v, uint id : SV_VertexID)
            {
                GS_INPUT output = (GS_INPUT)0;

                output.pos =  mul(_Object2World, float4(particleBuffer[id].position, 1));
                output.normal = v.normal;
                output.tex0 = float2(0, 0);
                output.scale.x = particleBuffer[id].size;
                output.scale.y = particleBuffer[id].size;
                output.color = particleBuffer[id].color;
                return output;
            }

            // Geometry Shader -----------------------------------------------------
            [maxvertexcount(4)]
            void geom(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
            {
                float3 up = UNITY_MATRIX_IT_MV[1].xyz;
                float3 look = _WorldSpaceCameraPos - p[0].pos;
                look = normalize(look);
                float3 right = cross(up, look);
                  
                float halfS = 0.5f * p[0].scale.x;
                          
                float4 v[4];
                v[0] = float4(p[0].pos + halfS * right - halfS * up, 1.0f);
                v[1] = float4(p[0].pos + halfS * right + halfS * up, 1.0f);
                v[2] = float4(p[0].pos - halfS * right - halfS * up, 1.0f);
                v[3] = float4(p[0].pos - halfS * right + halfS * up, 1.0f);

                float4x4 vp = mul(UNITY_MATRIX_MVP, _World2Object);

                FS_INPUT pIn;
                pIn.color = p[0].color;

                pIn.pos = mul(vp, v[0]);
                pIn.tex0 = float2(1.0f, 0.0f);
                triStream.Append(pIn);

                pIn.pos =  mul(vp, v[1]);
                pIn.tex0 = float2(1.0f, 1.0f);
                triStream.Append(pIn);

                pIn.pos =  mul(vp, v[2]);
                pIn.tex0 = float2(0.0f, 0.0f);
                triStream.Append(pIn);

                pIn.pos =  mul(vp, v[3]);
                pIn.tex0 = float2(0.0f, 1.0f);
                triStream.Append(pIn);

                triStream.RestartStrip();
            }

            // Fragment Shader -----------------------------------------------
            fixed4 frag(FS_INPUT input) : COLOR0
            {
                fixed4 c = input.color;
                fixed3 col = c * (_SpriteTex.Sample(sampler_SpriteTex, input.tex0));
                fixed4 outcolor = fixed4(col.r, col.g, col.b, c.a * (_SpriteTex.Sample(sampler_SpriteTex, input.tex0)).a);
                return outcolor;
            }

            ENDCG
        }
    }

    Fallback Off
}

The compute shader (optional):

#pragma kernel main

struct Particle
{
    float3 position;
    float4 color;
    float size;
};

RWStructuredBuffer<Particle> particleBuffer;

float deltaTime;                                  

[numthreads(512,1,1)]                               
void main (uint3 id : SV_DispatchThreadID)
{
    particleBuffer[id.x].position.x = particleBuffer[id.x].position.x + deltaTime;
    particleBuffer[id.x].position.y = particleBuffer[id.x].position.y + deltaTime;
    particleBuffer[id.x].position.z = particleBuffer[id.x].position.z + deltaTime;
}

The behaviour:

using UnityEngine;
using System.Collections;

public class Particles : MonoBehaviour
{
    // Material to use to render particles.
    public Material ParticleMaterial;

    // The maximum number of particles available to the particle system.
    [Range(1, 160000)]
    public int MaxParticles = 1000;

    // The compute shader to use.
    public ComputeShader ComputeShader;

    // The local array of particles.
    Particle[] particles;

    // The particle buffer used to send particle data to the GPU.
    ComputeBuffer particleBuffer;

    // GPU particle data structure.
    struct Particle
    {
        public Vector3 position;
        public Color color;
        public float size;
    };

    void Start()
    {
        particles = new Particle[MaxParticles];

        for (int i = 0; i < particles.Length; i++)
        {
            var t = Mathf.Lerp(0, 1, (float)i / (float)(particles.Length - 1));
            var r = Mathf.Lerp(0, 1, t);
            var g = Mathf.Lerp(0, 0, t);

            particles[i].color = new Color(r, g, 0, 1);
            particles[i].size = 1;
            particles[i].position = Random.insideUnitSphere * 25;
        }

        particleBuffer = new ComputeBuffer(MaxParticles, 32); // 32 = sizeof(Particle)
        particleBuffer.SetData(particles);
        ComputeShader.SetBuffer(0, "particleBuffer", particleBuffer);
        ParticleMaterial.SetBuffer("particleBuffer", particleBuffer);
    }

    void Update()
    {
        ComputeShader.SetFloat("deltaTime", Time.deltaTime);
        ComputeShader.Dispatch(0, 512, 1, 1);
    }

    void OnRenderObject()
    {
        ParticleMaterial.SetPass(0);
        Graphics.DrawProcedural(MeshTopology.Points, particles.Length);
    }

    void OnDestroy()
    {
        particleBuffer.Release();
    }
}

I guess this is a in fact a bug and should be fixed in 4.6.

I can replicate the bug on my hardware (AMD HD5770). But I think it’s important to note that importing the material also works around the problem, just like selecting it, so it will only happen after reloading the project and opening the scene.

It’s probably indeed the preview, because selecting the material in debug mode doesn’t trigger the workaround.

clausmeister was having a similar problem a while back. I believe he settled on using a workaround, where he made tiny changes to the destination RenderTexture in Update.

But I think you should definitely report this bug.