[RESOLU]Crayonner pour révéler un dessin

Questions à propos du scripting. Hors Shader, GUI, Audio et Mobile.
EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

[RESOLU]Crayonner pour révéler un dessin

Message par EmileF » 20 Déc 2019 18:04

Bonjour à tous,

C'est vrai que mon titre n'est pas clair, mais je voudrais arriver à créer l'effet quand je gratte avec ma souris sur une feuille de papier, ça révèle petit à petit le dessin gravé. Comme si je crayonnais pour noircir la feuille.

Une image peut-être, pour être plus clair
Image

Est-ce que quelqu'un peut me mettre sur la voie pour obtenir un effet semblable.

Merci à tous
Dernière édition par EmileF le 24 Déc 2019 13:52, édité 1 fois.
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Crayonner pour révéler un dessin

Message par Alesk » 21 Déc 2019 11:25

Salut,

Tu créés une texture qui va contenir ton dessin dans les canaux RGB, et avec l'alpha à zéro.
Puis tu fais une fonction qui va servir à dessiner uniquement dans la couche alpha.

Plus "simple" : tu fais ça avec deux textures, une pour ton dessin et l'autre qui sert de masque (dans lequel du dessine en noir et blanc pour définir les zones opaques), et là il faudra passer par un shader pour gérer le masque d'une texture par l'autre.

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: Crayonner pour révéler un dessin

Message par EmileF » 21 Déc 2019 18:41

Merci Alesk pour ta réponse
Alesk a écrit :Tu créés une texture qui va contenir ton dessin dans les canaux RGB, et avec l'alpha à zéro.
Puis tu fais une fonction qui va servir à dessiner uniquement dans la couche alpha.
En hébreux, pour moi, ça aurai été plus clair. Je ne vois absolument pas comment faire une fonction qui me permettrait de dessiner uniquement dans la couche alpha. Une petite précision ou un petit tuto serait bienvenu.
Alesk a écrit :"Plus "simple" : tu fais ça avec deux textures, une pour ton dessin et l'autre qui sert de masque (dans lequel du dessine en noir et blanc pour définir les zones opaques), et là il faudra passer par un shader pour gérer le masque d'une texture par l'autre.
Là c'est du grec. Mais j'ai trouvé une solution qui s'approche un peu de ce conseil. J'ai 2 textures, un le crayonnage, par dessus mon dessin au travers duquel on voit le crayonnage dans les parties transparentes, et par dessus un cache dont je diminue le scale en grattant avec la souris. Ce n'est pas ce que je voulais mais c'est la seule solution que j'ai trouvé.

Je pensais bien que la solution était dans les shaders, mais je ne les maîtrise vraiment pas et je ne saurais même pas par quel bout commencer.
J'ai essayé sur le web mais je ne suis pas arrivé à trouver un sembant d'explication. Si tu avais une explication ou un tuto, j'aimerai bien changer ma solution.

En tout cas, merci
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Crayonner pour révéler un dessin

Message par Alesk » 21 Déc 2019 20:49


EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: Crayonner pour révéler un dessin

Message par EmileF » 22 Déc 2019 10:55

Merci Alesk.

Ce site à l'air d'être une source énorme de renseignements.
Je vais jeter un coup d’œil à tout ça.
Je me demande comment vous arrivez à trouver des sources pareilles. J'ai beau chercher je tombe toujours sur le même genre de renseignements basiques ou incomplets.

Merci beaucoup Alesk, j'ai du boulot
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Crayonner pour révéler un dessin

Message par Alesk » 22 Déc 2019 11:08

EmileF a écrit :
22 Déc 2019 10:55
Je me demande comment vous arrivez à trouver des sources pareilles. J'ai beau chercher je tombe toujours sur le même genre de renseignements basiques ou incomplets.
Le secret est dans les mots clés qu'on utilise pour faire les recherches ;)
Et pour info, je ne connaissais pas non plus ce site... Je l'ai découvert il y a deux jours !
EmileF a écrit :
22 Déc 2019 10:55
Merci beaucoup Alesk, j'ai du boulot
Bon courage !

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: Crayonner pour révéler un dessin

Message par EmileF » 22 Déc 2019 11:32

Alesk a écrit :Le secret est dans les mots clés qu'on utilise pour faire les recherches ;)
Je pense que c'est dû à ce satané anglais pour lequel j'ai toujours été allergique

Merci Alesk
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: Crayonner pour révéler un dessin

Message par EmileF » 22 Déc 2019 16:04

Bien, pas mal, le conseil d'Alesk, je suis arrivé à obtenir a peu près ce que je voulais grâce à lui.

Cependant un petit détail m'irrite un peu. Au passage du curseur sur ma feuille, j'ai des taches rondes qui se succèdent et elles grossissent si le curseur ne bouge plus. J'aurais préféré avoir des traits, plus ou moins régulier et qui restent fixe.
Voilà ce que j'obtiens:
Image

Voilà les scripts que j'utilise:
le Painter:

Code : Tout sélectionner

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Runningtap
{
    [RequireComponent(typeof(BoxCollider))]
    public class Painter : MonoBehaviour
    {
        public enum Mode
        {
            auto,
            manual
        }

        public enum Resolution
        {
            _32 = 32, _64 = 64, _128 = 128, _256 = 256, _512 = 512, _1024 = 1024, _2048 = 2048
        }

        [Tooltip("Le mode Auto va itérer à travers tous les objets pour trouver les limites. Le mode manuel utilisera le collisionneur de boîtes attaché comme limites du monde. Utilisez le manuel pour les scènes complexes avec beaucoup d'objets.")]
        public Mode LookupMode = Mode.auto;
        public Resolution SplatResolution = Resolution._256;
        [Tooltip("Splat Rapport de résolution en fonction du rapport largeur/longueur du monde. Le côté le plus long utilisera la résolution sélectionnée. Aide à minimiser l'étirement du projeciton.")]
        public bool UseRelative;
        public bool FadeOverTime;

        public Transform Brush;
        [Range(0, 1)]
        public float BrushSize = 1;
        [Range(0, 1)]
        public float BrushStrength = 1;
        [Range(0, 1)]
        public float FadeSpeed = 1;

        public GameObject World;
        public BoxCollider ManualBounds;

        public Shader RenderTextureShader;

        private Bounds WorldBounds = new Bounds(Vector3.zero, Vector3.zero);
        private Vector4 shaderInfo; //XY = Bounds worldspace offset, ZW = Bounds scale.
        private RenderTexture splatmap;
        private int width;
        private int height;
        private Material drawMaterial;
        private Renderer[] renderers;

        //Shader Strings
        private const string rtxCoord = "_Coordinate";
        private const string rtxStrength = "_Strength";
        private const string rtxSize = "_Size";
        private const string rtxFade = "_Fade";

        private const string revColor = "_Color";
        private const string revRTX = "_Splat";
        private const string revRTXLocaiton = "_SplatLocation";
        private const string revRTXRemap = "_SplatRemap";

        public RenderTexture DebugRenderTexture
        {
            get { return splatmap; }
        }

        void Start()
        {
            ManualBounds = GetComponent<BoxCollider>();
            renderers = World.GetComponentsInChildren<Renderer>();

            drawMaterial = new Material(RenderTextureShader);
            drawMaterial.SetVector(revColor, Color.red);

            CalculateBounds();
            CalculateResolution();

            splatmap = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat);

            SetShaders();
            ManualBounds.enabled = false; // Le collisionneur de boîtes n'est nécessaire que dans l'éditeur pour calculer les limites. Le desactiver pour retirer la physique.
        }

        // Calculer les limites des objets à peindre
        void CalculateBounds()
        {
            if(LookupMode == Mode.auto)
            {
                foreach (var entity in renderers)
                    WorldBounds.Encapsulate(entity.bounds);

                if (ManualBounds != null) // Collisionneur de boîtes de forme pour visualiser les limites en mode automatique.
                {
                    ManualBounds.center = WorldBounds.center;
                    ManualBounds.size = WorldBounds.extents * 2;
                }
            }
            else
                WorldBounds = ManualBounds.bounds;

            shaderInfo = new Vector4
            (
                WorldBounds.center.x - WorldBounds.size.x / 2,
                WorldBounds.center.z - WorldBounds.size.z / 2,
                WorldBounds.size.x,
                WorldBounds.size.z
            );
        }

        // Calculer le rapport de résolution de Splatmap s'il est défini sur UseRelative.
        void CalculateResolution()
        {
            if (UseRelative)
            {
                if (WorldBounds.size.x > WorldBounds.size.z)
                {
                    var ratio = WorldBounds.size.z / WorldBounds.size.x;
                    width = (int)SplatResolution;
                    height = Mathf.RoundToInt(width * ratio);
                }
                else
                {
                    var ratio = WorldBounds.size.x / WorldBounds.size.z;
                    height = (int)SplatResolution;
                    width = Mathf.RoundToInt(height * ratio);
                }
            }
            else
            {
                width = (int)SplatResolution;
                height = (int)SplatResolution;
            }
        }

        // Mise à jour des shaders dessinés.
        void SetShaders()
        {
            foreach (var entity in renderers)
            {
                if (entity.material.HasProperty(revRTX))
                {
                    entity.material.SetTexture(revRTX, splatmap);
                    entity.material.SetVector(revRTXLocaiton, shaderInfo);
                    entity.material.SetVector(revRTXRemap, shaderInfo);
                }
                else
                    Debug.Log("GameObject <color=yellow>" + entity.gameObject.name + "</color> does not have the reveal shader applied, skipped.", entity);
            }
        }

        void Update()
        {
            /* Enveloppez ceci dans toute autre fonctionnalité. (par exemple lorsque l'objet Brosse touche le sol, etc.).
             * Evitez de redessiner la texture de rendu si vous n'avez pas à mettre à jour le splat map. */
            DoPaint();
        }

        // Mise à jour du Splat Map en fonction de la position de l'objet Brush
        public void DoPaint()
        {
            Vector4 worldToRtx = new Vector4(
                map(Brush.transform.position.x, WorldBounds.center.x - WorldBounds.size.x / 2, WorldBounds.center.x + WorldBounds.size.x / 2, 0, 1),
                map(Brush.transform.position.z, WorldBounds.center.z - WorldBounds.size.z / 2, WorldBounds.center.z + WorldBounds.size.z / 2, 0, 1),
                0,
                0
            );
            drawMaterial.SetVector(rtxCoord, worldToRtx);
            drawMaterial.SetFloat(rtxStrength, BrushStrength);
            //drawMaterial.SetFloat(rtxSize, map(BrushSize, 0, 1, 1, 0));
            drawMaterial.SetFloat(rtxSize, map(BrushSize, v1, v2, v3, v4));
            drawMaterial.SetFloat(rtxFade, FadeSpeed);
            RenderTexture temp = RenderTexture.GetTemporary(splatmap.width, splatmap.height, 0, RenderTextureFormat.ARGBFloat);
            Graphics.Blit(splatmap, temp);
            Graphics.Blit(temp, splatmap, drawMaterial);
            RenderTexture.ReleaseTemporary(temp);
        }
        [Range(0,1)]
        public float v1 = 0.49f;
        [Range(0, 1)]
        public float v2 = 0.51f;
        [Range(0, 1)]
        public float v3 = 0.51f;
        [Range(0, 1)]
        public float v4 = 0.49f;

        // Remplacer un nombre dans une plage par une autre plage
        float map(float s, float a1, float a2, float b1, float b2)
        {
            return b1 + (s - a1) * (b2 - b1) / (a2 - a1);
        }

        // Calculer le ratio de la taille du monde pour un Splatmap non carré
        float ratio(float resolution)
        {
            float r;

            if (WorldBounds.size.x > WorldBounds.size.z)
            {
                r = WorldBounds.size.z / WorldBounds.size.x;
            }
            else
                r = WorldBounds.size.x / WorldBounds.size.z;

            return r;
        }
    }
}
C'est moi qui ai ajouté les valeurs v1, v2, v3, v4 pour avoir une possibilité de réglage. Les valeurs d'origine sont dans la ligne qui est commentée.

Voila le shader qui est utilisé:

Code : Tout sélectionner

Shader "Runningtap/Reveal/Displacement" {
	Properties
	{
		_Tess ("Tessellation", Range(1,32)) = 4
		_Displacement ("Displacement", Range(-1.0, 1.0)) = 0.3
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Color ("Color", Color) = (1,1,1,1)
		_Color2 ("Color2", Color) = (1,1,1,1)
		_Splat ("SplatMap", 2D) = "black" {}
		_SplatRemap ("SplatOffset", Vector) = (0,0,0,0)
	}

	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 300

		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows vertex:disp tessellate:tessDistance 
		#pragma target 4.6
		#include "Tessellation.cginc"

		struct appdata
		{
			float4 vertex : POSITION;
			float4 tangent : TANGENT;
			float3 normal : NORMAL;
			float2 texcoord : TEXCOORD0;
			float2 texcoord1 : TEXCOORD1;
			float2 texcoord2 : TEXCOORD2; 
        };

		struct Input
		{
			float2 uv_MainTex;
			float3 worldPos;
		};

		UNITY_INSTANCING_BUFFER_START(Props)
			UNITY_DEFINE_INSTANCED_PROP(sampler2D, _MainTex)
			UNITY_DEFINE_INSTANCED_PROP(sampler2D, _Splat)
			UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
			UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color2)
			UNITY_DEFINE_INSTANCED_PROP(float4, _SplatRemap)
			UNITY_DEFINE_INSTANCED_PROP(half, _Tess)
			UNITY_DEFINE_INSTANCED_PROP(half, _Displacement )
		UNITY_INSTANCING_BUFFER_END(Props) 

		void disp (inout appdata v) 
        {
			float2 WUV = (mul (unity_ObjectToWorld, v.vertex).xz - _SplatRemap.xy)/ _SplatRemap.zw;

            float d = tex2Dlod(_Splat, float4(WUV, 0, 0)).r * _Displacement;
            v.vertex.xyz += v.normal * d;
        }

		float4 tessDistance (appdata v0, appdata v1, appdata v2)
		{
            float minDist = 10.0;
            float maxDist = 50.0;
            return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
        }

		void surf (Input IN, inout SurfaceOutputStandard o)
		{
			float2 WUV = (IN.worldPos.xz - _SplatRemap.xy)/ _SplatRemap.zw;
			 
			half mask = tex2Dlod(_Splat, float4(WUV, 0, 0)).r;
			fixed4 c =   tex2D(_MainTex, IN.uv_MainTex) * _Color;
			fixed4 c2 =  tex2D(_MainTex, IN.uv_MainTex) * _Color2;
			
			float4 mix = lerp(c, c2, mask);
			//o.Alpha = saturate(mix);
			o.Albedo = mix.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}
J'ai réglé la propriété Tesselation = 1 et la propriété Displacement à -1;

Est-ce que quelqu'un peut me dire si c'est réalisable d'avoir un trait plus ou régulier et continu au lieu de tache, et quand le curseur reste fixe, la marque reste fixe.
Et si c'est possible, un petit tuyau pour y arriver.

Merci
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Crayonner pour révéler un dessin

Message par Alesk » 22 Déc 2019 22:04

Il faut que tu conditionne l'appel à la fonction DoPaint() à une action sur le bouton de ta souris pour que ça ne dessine que lorsque tu le désires.

Et pour tracer des lignes, il faut mémoriser la position de la souris d'une frame à l'autre, et faire une boucle qui va répéter le tracé de la texture de brosse de dessin entre cette dernière position mémorisée et la position actuelle.

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: Crayonner pour révéler un dessin

Message par EmileF » 23 Déc 2019 01:15

Alesk a écrit :Il faut que tu conditionne l'appel à la fonction DoPaint() à une action sur le bouton de ta souris pour que ça ne dessine que lorsque tu le désires.
Oui, oui, c'est ce que j'avais fait. J'ai oublié de mettre les scripts
Alesk a écrit :Et pour tracer des lignes, il faut mémoriser la position de la souris d'une frame à l'autre, et faire une boucle qui va répéter le tracé de la texture de brosse de dessin entre cette dernière position mémorisée et la position actuelle.
C'est ce que je crois faire, voilà mes scripts

Code : Tout sélectionner

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Crayonnage : MonoBehaviour
{
    Camera Camera;
    Transform World;
    Transform Brush;

    private void Start()
    {
        Camera = GameObject.Find("Camera").GetComponent<Camera>();
        Brush = GameObject.Find("Brush").transform;
        World = GameObject.Find("ExampleRevealRGB").transform;
    }

    private void Update()
    {
        if (Input.GetMouseButton(0))
        {
            //Extention permettant de voir si le curseur est sur le transform World
            if (Camera.RayCastObjet(World))
            {
                //Extension permettant de repérer la position de la souris sur le transform
                Brush.position = Camera.RaycastPos(World);
            }
        }
    }

}

Les extensions;

Code : Tout sélectionner

    public static bool RayCastObjet(this Camera camera, Transform transform)
    {
        Ray ray = camera.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out RaycastHit hit))
        {
            if (hit.transform == transform)
            {
                return true;
            }
        }
        return false;
    }
    
       public static Vector3 RaycastPos(this Camera camera, Transform transform)
    {
        //on envoie le rayon vers la position de la souris
        Ray ray = camera.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out RaycastHit hit))
        {
            //s'il rencontre le transform
            if (hit.transform == transform)
            {
                //retourne la position du clic;
                return hit.point;
            }
        }
        return Vector3.zero;
    }


La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Répondre

Revenir vers « Scripting »