I’m in the process of converting my game from the Built-In Renderer to the 2D Renderer/URP. My game includes some generated (flat/2D) meshes rendered with the MeshRenderer component.
The 2D Renderer fails to render these meshes (when using sprite shaders) seemingly because the special internal shader properties unity_SpriteColor and unity_SpriteProps are unset. I assume these are normally set by the renderer component, and MeshRenderer doesn’t set them because it wasn’t designed to render sprites.
I can work around the issue by setting these internal properties in a MaterialPropertyBlock like so:
MaterialPropertyBlock properties = new MaterialPropertyBlock();
properties.SetColor("unity_SpriteColor", Color.white);
properties.SetVector("unity_SpriteProps", new Vector4(1, 1, -1, 0));
GetComponent<MeshRenderer>().SetPropertyBlock(properties);
However, the documentation seems to discourage the use of MaterialPropertyBlocks in URP.
Interestingly, my MeshRenderers do work when I have the SRP Batcher enabled. Presumably this is because the draw call is combined with others that already have the proper properties set? But if so, that feels pretty fragile. I noticed this issue when I disabled the SRP Batcher to investigate some unrelated batching issues.
So my questions are:
Is there a more appropriate way to set these shader properties?
Is there some other way to allow sprite shaders (i.e. Sprite-Lit-Default, or Shader Graph shaders using Sprite Lit/Sprite Custom Lit materials) to work with MeshRenderer? I.e. can I make a sprite shader that ignores these properties?
Is there a better way to render a flat mesh besides MeshRenderer?
Using CustomProperties with MaterialPopertyBlock will disable SRP-Batcher.
Sprite-Lit-Default , or Shader Graph shaders using Sprite Lit /Sprite Custom Lit materials are only meant to be used with 2D Renderers in URP-2D (Sprite, SpriteShape etc…) and these properties are internally set when doing so (unity_SpriteColor and unity_SpriteProps represent Renderer Color, Flip, Animation index etc… ). They may also require Light2D component(s) to be present in the scene which may not be desired when using MeshRenderer.
If you still want to use MeshRenderer, I would suggest creating a custom Shader / ShaderGraph that does not use unity_SpriteColor / unity_SpriteProps as these are specific to 2D Renderers anyway.
It’s possible to create a sprite-asset with custom importers that has a flat geometry, which can then be used with SpriteRenderer.
Please do let us know if you need more info. Submitting a bug report with a repro project with details will also help analyzing the issue better. Thanks.
Hi @Venkify! I really appreciate the detailed response, thank you!
Another dev on Bluesky suggested setting these properties on the Material instance directly (at runtime, since Unity seems to refuse to save these properties on the actual asset). Does that have the same caveats as using a MaterialPropertyBlock?
This would be the ideal solution for me but I haven’t been able to figure out how to do it. Is it possible to make a Shader Graph shader that works with URP-2D, can receive the 2D Light Texture for custom lighting, but doesn’t use these sprite properties?
To be clear, I don’t need the sprite properties in this situation so losing them is not an issue.
This sounds interesting too. Are there any examples I could look at?
I was thinking I could use Sprite.OverrideGeometry but it doesn’t allow me to create a sprite that is larger than the base texture, so that’s not going to work in this case. It’s close though.
I also wanted to mention that the LineRenderer, which also isn’t a sprite renderer, does seem to set unity_SpriteColor / unity_SpriteProps. The inconsistency is a bit confusing!
I’m fairly certain there’s no way to make this work with the existing options in Shader Graph. The URP-2D shaders/includes are hard-coded to expect unity_SpriteColor and unity_SpriteProps to be set, and I couldn’t find any way to set these from within a Shader Graph shader. However, after digging into the internals of Shader Graph I figured out a sort-of-reasonable workaround.
I added a new UniversalSubTarget based on the existing “Sprite Custom Lit” SubTarget that hard-codes the values of unity_SpriteColor and unity_SpriteProps to reasonable defaults. This still disables the SRP Batcher but it’s a cleaner solution than setting these properties manually, at runtime, on each individual material.
I’ve shared my solution in the GitHub Gist below:
I’m not fully confident that I implemented this correctly but it does seem to work.
If I’m still missing a better way to handle this, please let me know! In particular, I’d love to figure out how to make this compatible with the SRP Batcher.
May I know why you would want to use Sprite-Lit-Default especially given it requires URP-2D and 2D Lights ? That would help us analyze the issue better.
Note:
Sorting in 3D is based on Z (Depth)
and for 2D is based on Sorting Layer.
My game (Pinball Spire) uses a mix of SpriteRenderers, SpriteShapeRenderers, LineRenderers, MeshRenderers, and ParticleSystems, all with flat 2D geometry (well… I do have a couple of special cases I’ll need to deal with, but I’ll worry about those later).
This is an existing game developed on the Built-In Render Pipeline. I’m currently exploring the possibility of converting the entire game to URP-2D to improve performance. This includes redoing all my shaders and replacing all existing Lights with Light2Ds.
Yeah, this is something I ran into even with the Built-In Render Pipeline. I’m already using Z-depth extensively for layering.
This is awesome, thank you! I was able to use these functions to display my meshes with the SpriteRenderer. I definitely prefer this solution over messing with the MeshRenderer.
Still, it would be cool if the MeshRenderer was supported in URP-2D, even if it came with certain caveats. It already works when the SRP Batcher is enabled (though maybe not always?). It just doesn’t feel intuitive or consistent as it is currently.
But for the specific case I was looking at, the SpriteRenderer is a great solution, thanks!
I have a follow-up question about modifying a sprite with the SpriteDataAccessExtensions functions. Although these functions allow me to modify the underlying mesh, they don’t update the sprite’s “bounds” property, which leads to inaccurate render culling.
I did see that the bounds can be updated on the SpriteRenderer (through .localBounds and .bounds) but this property doesn’t seem to persist when saved in the scene.
I believe you may be modifying sprite geometry at runtime. A better option would be to do it as a AssetPostProcessor. Here is a simple demonstration script:
using Unity.Collections;
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;
using UnityEngine.U2D;
// This is just a simple demonstration for Sprite Post Processor.
public class SpritePostProcessorDemonstration : AssetPostprocessor
{
// OnPostprocessSprites can be used to modify imported sprites.
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
foreach (var sprite in sprites)
{
var posArray = sprite.GetVertexAttribute<Vector3>(VertexAttribute.Position);
var posArrayNew = new NativeArray<Vector3>(posArray.Length, Allocator.Temp);
for (int i = 0; i < posArray.Length; ++i)
{
posArrayNew[i] = posArray[i] * 0.2f;
}
sprite.SetVertexAttribute(VertexAttribute.Position, posArrayNew);
posArrayNew.Dispose();
// Set Rect to adjust Bounds.
SerializedObject so = new SerializedObject(sprite);
so.FindProperty("m_Rect").rectValue = new Rect(sprite.rect.position, sprite.rect.size * 0.2f);
so.ApplyModifiedProperties();
}
}
// Ensure this is called last when there are multiple postprocessors.
public override int GetPostprocessOrder()
{
return int.MaxValue;
}
}
Thanks for the tip! I’m modifying the sprites in the editor (because they’re generated as part of the scene editing process) and savings them into the scene. I’m not saving them as individual assets currently. Would it make a difference if I making my modification from within an AssetPostprocessor?
I tried adjusting the bounds through SerializedObject as you demonstrated and that almost works. It updates the sprite size but doesn’t pick up the center position. I tried setting this through other properties but SpriteRenderer didn’t seem to respect my changes no matter what I tried.
I’ve resorted to using SpriteRenderer.localBounds for now and storing the bounds in a new component so they can be reapplied on awake.
using UnityEngine;
[ExecuteAlways]
public class SpriteRendererBounds : MonoBehaviour
{
public Bounds Bounds;
private void Awake()
{
if (TryGetComponent<SpriteRenderer>(out SpriteRenderer spriteRenderer))
{
spriteRenderer.localBounds = Bounds;
}
}
}