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:
- Open project and Default scene.
- Play project in Editor - you will see no particles. No matter how many times you play in game mode no particles will render.
- 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();
}
}