rive-app / rive-unity

MIT License
104 stars 11 forks source link

Incorrect Orientation #27

Closed martindevans closed 7 months ago

martindevans commented 8 months ago

I've set up a very basic scene, rendering a Rive file (this one) into a texture, and then rendering the texture to a UI RawImage element.

However, as you can see here the image is incorrectly oriented:

image

Is this a setup mistake on my end? I've basically just copy+pasted the example code!

HayesGordon commented 8 months ago

Hey @martindevans, you'll need to do a test to see what renderer is being used. Depending on the renderer you may need to flip the texture. You'll note that that is what the RiveScreen script also does.

A quick hack is just to flip the y scale on the mesh, as shown in the picture:

CleanShot 2024-03-27 at 14 54 23

Alternatively, here is an updated script that shows how to flip the texture in a scriptable way depending on the backend used. Note that it also adjusts the pointer hit positions passed to Rive.

using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
using Rive;

using LoadAction = UnityEngine.Rendering.RenderBufferLoadAction;
using StoreAction = UnityEngine.Rendering.RenderBufferStoreAction;

//[ExecuteInEditMode]
public class RiveTexture : MonoBehaviour
{
    public Rive.Asset asset;
    public RenderTexture renderTexture;
    public Fit fit = Fit.contain;
    public Alignment alignment = Alignment.Center;

    private Rive.RenderQueue m_renderQueue;
    private Rive.Renderer m_riveRenderer;
    private CommandBuffer m_commandBuffer;

    private Rive.File m_file;
    private Artboard m_artboard;
    private StateMachine m_stateMachine;

    private Camera m_camera;

    private void Start()
    {
        var textureDescriptor = TextureHelper.Descriptor(0, 0);
        renderTexture.enableRandomWrite = textureDescriptor.enableRandomWrite;
        m_renderQueue = new Rive.RenderQueue(renderTexture);
        m_riveRenderer = m_renderQueue.Renderer();

        if (asset != null)
        {
            m_file = Rive.File.Load(asset);
            m_artboard = m_file.Artboard(0);
            m_stateMachine = m_artboard?.StateMachine();
        }

        if (m_artboard != null && renderTexture != null)
        {
            m_riveRenderer.Align(fit, alignment, m_artboard);
            m_riveRenderer.Draw(m_artboard);

            m_commandBuffer = m_riveRenderer.ToCommandBuffer();
            m_commandBuffer.SetRenderTarget(renderTexture);
            m_commandBuffer.ClearRenderTarget(true, true, UnityEngine.Color.clear, 0.0f);
            m_riveRenderer.AddToCommandBuffer(m_commandBuffer);
            m_camera = Camera.main;
            if (m_camera != null)
            {
                Camera.main.AddCommandBuffer(CameraEvent.AfterEverything, m_commandBuffer);
            }
        }

        FlipTexture();
    }

    private static bool flipY()
    {
        switch (UnityEngine.SystemInfo.graphicsDeviceType)
        {
            case UnityEngine.Rendering.GraphicsDeviceType.Metal:
            case UnityEngine.Rendering.GraphicsDeviceType.Direct3D11:
                return true;
            default:
                return false;
        }
    }

    void FlipTexture()
    {
        if (!flipY()) return;

        MeshRenderer meshRenderer = GetComponent<MeshRenderer>();

        // Access the material of the MeshRenderer.
        Material material = meshRenderer.material;

        // Check if the material has a main texture.
        if (material.mainTexture != null)
        {
            // Adjust the texture's UV mapping to flip it vertically.
            material.mainTextureScale = new Vector2(1f, -1f); // Flip the texture vertically.
            material.mainTextureOffset = new Vector2(0f, 1f); // Offset the texture to correct its position.
        }
        else
        {
            Debug.LogWarning("Material does not have a main texture set.", this);
        }
    }

    private void Update()
    {

        HitTesting();

        if (m_stateMachine != null)
        {
            m_stateMachine.Advance(Time.deltaTime);
        }
    }

    bool m_wasMouseDown = false;
    private Vector2 m_lastMousePosition;

    void HitTesting()
    {
        Camera camera = Camera.main;

        if (camera == null || renderTexture == null || m_artboard == null) return;

        if (!Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out RaycastHit hit))
            return;

        UnityEngine.Renderer rend = hit.transform.GetComponent<UnityEngine.Renderer>();
        MeshCollider meshCollider = hit.collider as MeshCollider;

        if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
            return;

        Vector2 pixelUV = flipY() ? new Vector2(hit.textureCoord.x, 1 - hit.textureCoord.y) : hit.textureCoord;

        pixelUV.x *= renderTexture.width;
        pixelUV.y *= renderTexture.height;

        Vector3 mousePos = camera.ScreenToViewportPoint(Input.mousePosition);
        Vector2 mouseRiveScreenPos = new(mousePos.x * camera.pixelWidth, (1 - mousePos.y) * camera.pixelHeight);

        var rect = new Rect(0, 0, renderTexture.width, renderTexture.height);

        if (m_lastMousePosition != mouseRiveScreenPos || transform.hasChanged)
        {
            Vector2 local = m_artboard.LocalCoordinate(pixelUV, rect, fit, alignment);
            m_stateMachine?.PointerMove(local);
            m_lastMousePosition = mouseRiveScreenPos;
        }
        if (Input.GetMouseButtonDown(0))
        {
            Vector2 local = m_artboard.LocalCoordinate(pixelUV, rect, fit, alignment);
            m_stateMachine?.PointerDown(local);
            m_wasMouseDown = true;
        }
        else if (m_wasMouseDown)
        {
            m_wasMouseDown = false; Vector2 local = m_artboard.LocalCoordinate(pixelUV, rect, fit, alignment);
            m_stateMachine?.PointerUp(local);
        }
    }

    private void OnDisable()
    {
        if (m_camera != null && m_commandBuffer != null)
        {
            m_camera.RemoveCommandBuffer(CameraEvent.AfterEverything, m_commandBuffer);
        }
    }
}

Long term we should expose a Rive component that takes care of that all for you, but we're still working on other edge cases.

martindevans commented 8 months ago

Thanks for confirming that, I was worried I'd made some stupid setup mistake!

The script doesn't quite work for me since I'm using a canvas image rather than a mesh renderer, but I get the idea.

damzobridge commented 7 months ago

For anyone else using Canvas, you can flip the RawImage this way:

   private void FlipTexture()
    {
        // Check if the texture needs to be flipped based on the graphics API
        if (ShouldFlipTexture())
        {
            rawImage.uvRect = new Rect(0, 1, 1, -1); // Flip the texture by adjusting the UV Rect
        }
    }

    /// <summary>
    /// Determines if the texture should be flipped based on the graphics API.
    /// </summary>
    /// <returns></returns>
    private static bool ShouldFlipTexture()
    {
        switch (SystemInfo.graphicsDeviceType)
        {
            case GraphicsDeviceType.Metal:
            case GraphicsDeviceType.Direct3D11:
                return true;
            default:
                return false;
        }
    }

Note that if you're passing pointer input to Rive, you might also need to account for the image being flipped and invert the pointer values as well.