unity3D 塗塗樂使用shader實現上色效果
之前我博文里面發過一個簡單的通過截圖方式來實現的模型上色方法,但是那個方法不合適商用,因為你需要對的很准確才可以把貼圖完美截取下來,只要你手抖了一下就會發現貼歪了。那么有沒有更好的方法來實現這個效果呢,這就需要使用Shader的方式來實現這個效果。
剛好看到了一篇有關於塗塗樂原理的實現方法,EsayAR的官方案例里面就是使用的這個方法,EsayAR的官方案例其實基本完成這個塗塗樂的效果在看過塗塗樂原理,結合案例的是用Vuforia實現的一個效果,發現利用Shader的方式來實現塗塗樂UV圖片的修正這個方式比利用對准框的方法要好,而且也避免了UV在位置偏移的問題。(PS:點擊打開鏈接 Vuforia的案例,注意PC上是沒有問題的,但是移動端UV會出現破面的問題)
這里要注意PC上實現的效果並不代表手機上能實現塗塗了得效果,就如Vuforia的案例一樣PC上是沒有任何問題,但是在移動端就會出現一些問題。(PS:這里我使用的EsayAR的來實現Vuforia的方法)
注意這里提供的源碼是使用EsayAR來寫的(PS:移動端是有問題)
using System; using UnityEngine; using System.Collections; using EasyAR; public class Coloring3D : MonoBehaviour { public MeshRenderer realityPlane; public Camera rendercamera; private Vector3[] marks; private Material cubeMat; private Vector2 sizeVector2; private Vector2 webcamTex2ScreenScale; private ImageTargetBehaviour imageTargetBehaviour; private Texture2D scanTexture2D; private bool isSetupScale = false; // Use this for initialization void Start () { Init(); } public void Init() { marks = new Vector3[4]; sizeVector2 = new Vector2(); imageTargetBehaviour = transform.GetComponentInParent<ImageTargetBehaviour>(); sizeVector2 = imageTargetBehaviour.Size; //計算識別Plane的4個角的位置 for (int i = 0; i < marks.Length; i++) { float x = -1, y = -1; if ((i + 1) % 2 == 0) x = 1; else x = -1; if (i >= 2) y = 1; marks[i] = new Vector3(sizeVector2.x / 2 * x, 0, sizeVector2.y / 2 * y); } } void SetupScale() { //if (!imageTargetBehaviour.GetIsShow())//判斷模型是不是顯示出來,可以注釋掉 //return; if (!isSetupScale) { cubeMat = this.transform.GetComponent<MeshRenderer>().material; var texture = realityPlane.material.mainTexture;//獲取攝像頭照到的紋理圖片 cubeMat.mainTexture = texture;//將紋理賦值到材質球上 //計算屏幕和整張圖片的比例 var maxScale = Mathf.Max((float)Screen.width / texture.width, (float)Screen.height / texture.height); var t2sWidth = texture.width * maxScale; var t2sHeight = texture.height * maxScale; webcamTex2ScreenScale.x = Screen.width / t2sWidth; webcamTex2ScreenScale.y = Screen.height / t2sHeight; isSetupScale = true; } for (int i = 0; i < marks.Length; i++) { marks[i] = marks[i] + this.transform.parent.position; var pos = AdjustUV(rendercamera.WorldToViewportPoint(marks[i])); string posName = String.Format("p{0}", i); cubeMat.SetVector(posName, pos);//傳入到shader進行紋理的拉伸 } } Vector4 AdjustUV(Vector3 v) { //識別圖在屏幕的位置 var webcamX = FixWebcamTextureToScreenUV(v.x, webcamTex2ScreenScale.x); var webcamY = FixWebcamTextureToScreenUV(v.y, webcamTex2ScreenScale.y); return new Vector4(webcamX*v.z, (1 - webcamY)*v.z, v.z, 1.0f); } float FixWebcamTextureToScreenUV(float value, float scale) { return (1.0f - scale) * 0.5f + value * scale; } // Update is called once per frame void OnWillRenderObject() { SetupScale(); } }
接下來是shader:
Shader "Unlit/PaintingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 p0; float4 p1; float4 p2; float4 p3; v2f vert (appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv.x = v.uv.x; o.uv.y = v.uv.y; return o; } fixed4 frag (v2f i) : SV_Target { float2 mid; float3 newUV; //計算紋理的位置 if (i.uv.x + i.uv.y >= 1) { mid = i.uv; newUV = p0 * (1 - mid.x - mid.y) + p1 * mid.x + p2 * mid.y; } else { mid = float2(1 - i.uv.y, i.uv.x + i.uv.y - 1); newUV = p2 * (1 - mid.x - mid.y) + p1 * mid.x + p3 * mid.y; } //返回計算好的紋理位置 return tex2D(_MainTex, newUV.xy / newUV.zz); } ENDCG } } }
這里看看效果PC的效果圖片吧!
(PS:這里要注意移動端是會出現問題的,但是PC端是沒有問題的)
最后附上一張移動端的塗塗樂效果圖出來
功能有實時渲染,點擊屏幕后就取消實時渲染然后上色到模型上。(PS:移動端的和PC端的代碼編寫差異挺大的,但是還是離不上面用到的源碼)
Vuforia的移動端問題有待解決,如果解決這個問題我會第一時間寫博客告訴大家。
(如果想交流一下塗塗樂的問題,可以加我QQ:245076259)