How to make facing camera particles with custum quad mesh?(SOLVED~)

Hi,i want to make like billboard particles with mesh with particlesystem.But not correct. not the same as using billboard particlerendermode,it seem not rotate at correct Z position.How to Fixed this?

Shader "mParticles/Additive" {
Properties {
    _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
    _MainTex ("Particle Texture", 2D) = "white" {}
    _InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
    _BillBoard("UseBillboard",Range(0,1))=1
}

Category {
    Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    Blend SrcAlpha One
    ColorMask RGB
    Cull Off Lighting Off ZWrite Off
   
    SubShader {
        Pass {
       
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_particles
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            fixed4 _TintColor;
            fixed _BillBoard;
           
            struct appdata_t {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                    float2 texcoord1 : TEXCOORD1;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                float2 texcoord1 : TEXCOORD1;
                UNITY_FOG_COORDS(1)
                #ifdef SOFTPARTICLES_ON
                float4 projPos : TEXCOORD2;
                #endif
            };
           
            float4 _MainTex_ST;

            v2f vert (appdata_t v)
            {
                v2f o;
//                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);


float4x4 mv = UNITY_MATRIX_MV;

    // First colunm.
    mv._m00 = 1.0f;
    mv._m10 = 0.0f;
    mv._m20 = 0.0f;
     // Second colunm.
    mv._m01 = 0.0f;
    mv._m11 = 1.0f;
    mv._m21 = 0.0f;

    // Thrid colunm.
    mv._m02 = 0.0f;
    mv._m12 = 0.0f;
    mv._m22 = 1.0f;

           

  o.vertex = mul(UNITY_MATRIX_P, mul(mv, v.vertex));


                #ifdef SOFTPARTICLES_ON
                o.projPos = ComputeScreenPos (o.vertex);
                COMPUTE_EYEDEPTH(o.projPos.z);
                #endif
                o.color = v.color;
   
                o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            sampler2D_float _CameraDepthTexture;
            float _InvFade;
           
            fixed4 frag (v2f i) : SV_Target
            {
                #ifdef SOFTPARTICLES_ON
                float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
                float partZ = i.projPos.z;
                float fade = saturate (_InvFade * (sceneZ-partZ));
                i.color.a *= fade;
                #endif
               
                fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
                UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0)); // fog towards black due to our blend mode
                return col;
            }
            ENDCG
        }
    }   
}
}

any body?

Because unity5.4.0 b17 not support mirror particle system batch,so need use mesh particlerendermode to get it,any help?

math - Calculating a LookAt matrix - Stack Overflow ,make a look at matrix,but not work…

Shader "mParticles/Additive" {
Properties {
    _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
    _MainTex ("Particle Texture", 2D) = "white" {}
    _InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
    _BillBoard("UseBillboard",Range(0,1))=1
}

Category {
    Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    Blend SrcAlpha One
    ColorMask RGB
    Cull Off Lighting Off ZWrite Off
   
    SubShader {
        Pass {
       
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_particles
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            fixed4 _TintColor;
            fixed _BillBoard;
           
            struct appdata_t {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                //    float4 texcoord1 : TEXCOORD1;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
            //    float4 texcoord1 : TEXCOORD1;
                UNITY_FOG_COORDS(1)
                #ifdef SOFTPARTICLES_ON
                float4 projPos : TEXCOORD2;
                #endif
            };
           
            float4 _MainTex_ST;

            float4x4 lookAt(  float3 eye,   float3 center,   float3 up)
{
      float3 zaxis = normalize(center - eye);
      float3 xaxis = normalize(cross(up, zaxis));
      float3 yaxis = cross(zaxis, xaxis);

    float4x4 matrixX;
    //Column Major
    matrixX[0][0] = xaxis.x;
    matrixX[1][0] = yaxis.x;
    matrixX[2][0] = zaxis.x;
    matrixX[3][0] = 0;

    matrixX[0][1] = xaxis.y;
    matrixX[1][1] = yaxis.y;
    matrixX[2][1] = zaxis.y;
    matrixX[3][1] = 0;

    matrixX[0][2] = xaxis.z;
    matrixX[1][2] = yaxis.z;
    matrixX[2][2] = zaxis.z;
    matrixX[3][2] = 0;

    matrixX[0][3] = dot(xaxis, -eye);
    matrixX[1][3] = dot(yaxis, -eye);
    matrixX[2][3] = dot(zaxis, -eye);
    matrixX[3][3] = 1;

    return matrixX;
}

            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);


//float4x4 mv = UNITY_MATRIX_MV;
//
//    // First colunm.
//    mv._m00 = 1.0f;
//    mv._m10 = 0.0f;
//    mv._m20 = 0.0f;
//
//     // Second colunm.
//    mv._m01 = 0.0f;
//    mv._m11 = 1.0f;
//    mv._m21 = 0.0f;
//
//    // Thrid colunm.
//    mv._m02 = 0.0f;
//    mv._m12 = 0.0f;
//    mv._m22 = 1.0f;

           
            float4x4 vp = lookAt(mul(_World2Object,_WorldSpaceCameraPos),0,float3(0,1,0));
            o.vertex =mul(vp,v.vertex);
             o.vertex =mul(UNITY_MATRIX_MVP, o.vertex);
        //      float4 pos=mul(UNITY_MATRIX_MVP,v.vertex);
             // o.vertex .z=pos.z;
// o.vertex = mul(UNITY_MATRIX_P,  o.vertex);
   

                #ifdef SOFTPARTICLES_ON
                o.projPos = ComputeScreenPos (o.vertex);
                COMPUTE_EYEDEPTH(o.projPos.z);
                #endif
                o.color = v.color;
   
                o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            sampler2D_float _CameraDepthTexture;
            float _InvFade;
           
            fixed4 frag (v2f i) : SV_Target
            {
                #ifdef SOFTPARTICLES_ON
                float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
                float partZ = i.projPos.z;
                float fade = saturate (_InvFade * (sceneZ-partZ));
                i.color.a *= fade;
                #endif
               
                fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
                UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0)); // fog towards black due to our blend mode
                return col;
            }
            ENDCG
        }
    }   
}
}

What you’re attempting to do won’t work, not in the way you’re trying to do it at least.

It’s important to understand the difference between applying a billboarding shader onto a single quad mesh and using it on a mesh particle system, and why it works on a single quad.

Basic billboard shaders like this rely on the center of the mesh, position 0,0,0 in local model relative space, being at the center of the quad. If you have a single quad mesh you can rely on that, but if that quad is batched (multiple meshes combined into one) you can no longer rely on 0,0,0 being at the center of all of the quads, you can’t even rely on any of the quads being centered around 0,0,0. Mesh particle systems always batch; all particles in a particle system always get converted into a single mesh object, and the center point isn’t likely to be in the center of any of the “individual” meshes so you’re going to get weird results as it rotates the entire combined mesh and not the individual quads.

Here are the basic ways around this:
Use GetParticles() / SetParticles() and assign the rotation for each mesh in the particle properties. Pros: Works with out crazy shader code! Cons: Can be slow if you have a lot of particles / particle systems, only works for one camera at a time.
Use a geometry shader to find the center of each quad by testing the distance between vertices of a triangle. Pros: Works with multiple cameras and is a lot faster than using SetParticles(). Cons: Only works with quad meshes, requires DX11 or OpenGLES 3.1.

1 Like

thanks~ u are right! batching will get wrong,I want to try save the origin position with uv1,but unity particle system moving run on cpu ,so that is not work,also need get the position in update…so ,only one method is use particle.rotation something lke:

void LookAtCam()
    {
        int count = particleSystem.GetParticles(particles);
        for (int i = 0; i < count; i++)
        {

            //            if (!isBillBoard)
            //            {
            //                particles[i].rotation = rotation;
            //            }
            //            else
            //            {
            //                particles[i].axisOfRotation = Vector3.forward;
            //                particles[i].rotation3D = eulerRotation;
            //            }
            //particles[i].rotation3D=Quaternion.LookRotation(-Camera.current.transform.forward,Camera.current.transform.up).eulerAngles;
            particles[i].rotation3D=eulerRotation;

            particles[i].axisOfRotation =axis;
        }

        particleSystem.SetParticles(particles, count);

    }

but it seem more confused.API is not mention how to use…

I believe the way you’re calculating the rotation with the look at is mostly correct, though you may have to enable 3d rotation on the particle system itself and I don’t remember the orientation of the quad mesh’s “forward” for sure but I don’t think it matches the camera. A simple test is put a quad mesh as a child of the camera with no local rotation and see orientation out has. Most likely you’ll need to do something funny like (camera.transform.up, camera.transform.left) instead of forward and up.

I also wouldn’t worry about the axis of rotation, that’s only used with the non-3d rotation. However it might be easier to use as you should just be able to set it to one of the camera transform axis. Again try something other than just forward if it doesn’t work, and make sure 3d rotation is off if you’re trying this path.

I try this.and have no idea about that:sweat_smile::sweat_smile:

void LookAtCam()
    {


        int count = particleSystem.GetParticles(particles);
        for (int i = 0; i < count; i++)
        {

            Vector3 campos = Camera.main.transform.position;
            Vector3 zaxis = campos - particles [i].position;
            zaxis.Normalize ();
            Vector3 xaxis = Vector3.Cross (Vector3.up,zaxis);
            xaxis.Normalize ();
            Vector3 yaxis = Vector3.Cross (zaxis, xaxis);
         

            particles[i].rotation3D=new Vector3(Vector3.Angle(Vector3.right,xaxis),Vector3.Angle(Vector3.up,yaxis),Vector3.Angle(Vector3.forward,zaxis));

            particles[i].axisOfRotation =yaxis;
            axis = yaxis;
            eulerRotation =    particles [i].rotation3D;
        }

        particleSystem.SetParticles(particles, count);

    }

2658742--187489--looat.jpg

And try this,still not correct = =

    Vector3 campos = Camera.main.transform.position;
            Vector3 zaxis = campos - particles [i].position;
        //    zaxis.Normalize ();
            Vector3 xaxis = Vector3.Cross (Vector3.up,zaxis);
            xaxis.Normalize ();
            Vector3 yaxis = Vector3.Cross (zaxis, xaxis);
           

        //    particles[i].rotation3D=new Vector3(Vector3.Angle(Vector3.right,xaxis),Vector3.Angle(Vector3.up,yaxis),Vector3.Angle(Vector3.forward,zaxis));

            //particles[i].axisOfRotation =yaxis;

       
            Quaternion roll = new Quaternion ();
            roll.SetLookRotation (zaxis,transform.up);
            float angle;
            Vector3 axis;

            roll.ToAngleAxis(out angle,out axis);
            particles [i].axisOfRotation = axis;
            particles [i].rotation = angle;

            axis = particles[i].axisOfRotation;
            //eulerRotation =    particles [i].rotation3D;
            eulerRotation=Vector3.zero;
            eulerRotation.x=angle;

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(ParticleSystem))]
public class ParticleSystemMeshCameraFacing : MonoBehaviour {

    public enum FacingAxis
    {
        PositiveX,
        NegativeX,
        PositiveY,
        NegativeY,
        PositiveZ,
        NegativeZ
    };
    [TooltipAttribute("The axis of the mesh you want to have face the camera.")]
    public FacingAxis facingAxis = FacingAxis.PositiveZ; // The default facing of Unity's quad

    [TooltipAttribute("Use the world the \"up\" direction or current camera up.")]
    public bool useWorldOrientation = false;

    [TooltipAttribute("Starts particle with a random rotation. Also attemps to respect 2D rotation rate if it is a constant.")]
    public bool useRandomRotation = false;

    private ParticleSystem ps;
    private ParticleSystem.Particle[] particles;

    void Awake()
    {
        ps = GetComponent<ParticleSystem>();
        particles = new ParticleSystem.Particle[ps.maxParticles];
    }

    Vector3 getAxis(Transform t)
    {
        switch (facingAxis)
        {
            case FacingAxis.PositiveX:
                return t.right;
            case FacingAxis.NegativeX:
                return -t.right;
           
            case FacingAxis.PositiveY:
                return t.up;
            case FacingAxis.NegativeY:
                return -t.up;

            case FacingAxis.PositiveZ:
                return t.forward;
            case FacingAxis.NegativeZ:
            default:
                return -t.forward;
        }
    }

    Vector3 getAxis()
    {
        switch (facingAxis)
        {
            case FacingAxis.PositiveX:
                return Vector3.right;
            case FacingAxis.NegativeX:
                return -Vector3.right;
           
            case FacingAxis.PositiveY:
                return Vector3.up;
            case FacingAxis.NegativeY:
                return -Vector3.up;

            case FacingAxis.PositiveZ:
                return Vector3.forward;
            case FacingAxis.NegativeZ:
            default:
                return -Vector3.forward;
        }
    }

    void OnWillRenderObject()
    {
        Transform camTrans = Camera.current.transform;
#if UNITY_EDITOR
        ps = GetComponent<ParticleSystem>();
        System.Array.Resize(ref particles, ps.maxParticles);
#endif
        Vector3 particleDir = getAxis(camTrans);
        Vector3 upDir = useWorldOrientation ? Vector3.up : camTrans.up;

        if (ps.simulationSpace == ParticleSystemSimulationSpace.Local) {
            particleDir = ps.transform.InverseTransformDirection(particleDir);
            upDir = ps.transform.InverseTransformDirection(upDir);
        }

        Quaternion particleRotation = Quaternion.LookRotation(particleDir, upDir);

        int num = ps.GetParticles(particles);

        if (useRandomRotation) {
            for (int i = 0; i < num; i++) {
                float rotation2D = (float)(particles[i].randomSeed % 360) + particles[i].angularVelocity * (particles[i].startLifetime - particles[i].lifetime);
                Quaternion initRot = Quaternion.AngleAxis (rotation2D, getAxis());
                particles [i].rotation3D = (particleRotation * initRot).eulerAngles;
            }
        } else {
            Vector3 eulerRotation = particleRotation.eulerAngles;
            for (int i = 0; i < num; i++) {
                particles [i].rotation3D = eulerRotation;
            }
        }
        ps.SetParticles (particles, num);
    }
}
1 Like

Thanks a lot! I try your script,maybe not real understand your comment,and still not work :frowning:2661958--187691--fc.jpg

Odd, I’m confused as to why it doesn’t work for you.

Seems to work if enabled: [×] 3D Start Location

*actually still works if disable it after enabling one time…

2 Likes

ok,mgear give the point~

Got it! thanks~

Ah, yep, missed that was disabled in that screenshot. I didn’t find a way of enabling it from the script side; it seems to be an area of the c# particle interface still lacking.

hi,bglus~i confused with the forward and up direction is the camera,My code is turn shader code into cpu,it should be (partilcles.center-camera.position) ect i think,but your code effect is right,so I confused…

void LookAtCam()
    {
        Vector3 campos = Camera.current.transform.position;
        #if UNITY_EDITOR
        System.Array.Resize(ref particles,_particleSystem.maxParticles);
        #endif
        int count = _particleSystem.GetParticles(particles);
        for (int i = 0; i < count; i++)
        {
          
//            Vector3 zaxis = campos - particles [i].position;
//            zaxis.Normalize ();
//            Vector3 xaxis = Vector3.Cross (Vector3.up,zaxis);
//            xaxis.Normalize ();
//            Vector3 yaxis = Vector3.Cross (zaxis, xaxis);
            //zaxis=_particleSystem.transform.InverseTransformDirection(zaxis);
            //yaxis = _particleSystem.transform.InverseTransformDirection (yaxis);

            //to localspace
            Vector3    zaxis=_particleSystem.transform.InverseTransformDirection(Camera.current.transform.forward);//why
            Vector3    yaxis = _particleSystem.transform.InverseTransformDirection (Camera.current.transform.up);

  
      
            Quaternion roll = new Quaternion ();
            roll.SetLookRotation (zaxis,yaxis);
//            float angle;
//            Vector3 axis;
//
//            roll.ToAngleAxis(out angle,out axis);
//            particles [i].axisOfRotation = axis;
//            particles [i].rotation = angle;

            particles [i].rotation3D = roll.eulerAngles;

            //axis = particles[i].axisOfRotation;
             eulerRotation =    particles [i].rotation3D;
        //    eulerRotation=Vector3.zero;
            //eulerRotation.x=angle;

        }

        _particleSystem.SetParticles(particles, count);

    }

The camera.transform.forward axis is the direction the mesh needs to face to be looking towards the camera, the “up” is just part of the SetLookRotation function for determining the final rotation. The result is the mesh is oriented along the camera’s view direction and not actually towards the camera position, which something like particle.position - camera.transform.position would be trying to get.

The problem with just doing particle.position - camera.transform.position is the particle position might be in world space or local particle system transform space where as the camera position is always in world space.

You’ll want something like this:

Vector3 zaxis = (_particlesystem.transform.InverseTransformPoint(Camera.current.transform.position) - particles*.position).normalized;*
That assumes the particle position is in local space already, and that the mesh is z facing, and that math might be backwards for this case, but you should be able to use that zaxis in the SetLookRotation with out anything else.

1 Like

Very Kind~ Thanks a lot~

Hi there,

I would also like to have billboarding custom particle. Why is this not standard?
I have tried the above script from bgolus but I get compile error with particles*.rotation3D. Is this because I am still using shuriken? Is there a similar method available for particles in shuriken?*