筆刷效果的實現
最終效果:
片元着色器代碼:
Shader "Hidden/Brush"
{
Properties
{
_MainTex("MainTex",2D)="white"{}
_CenterX("CenterX",int) = 0
_CenterY("CenterY",int) = 0
_Radius("Radius",int) = 30
}
SubShader
{
Tags {
"Queue" = "Transparent"
"RenderType" = "Transparent"
}
Blend SrcAlpha OneMinusSrcAlpha
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
half _CenterX;
half _CenterY;
half _Radius;
half4 _MainTex_TexelSize;
fixed4 frag(v2f i) : SV_Target
{
fixed4 col;
half2 pixelPos = half2(i.uv.x*_MainTex_TexelSize.z,i.uv.y*_MainTex_TexelSize.w);
half2 dis = pixelPos - half2(_CenterX,_CenterY);
col = tex2D(_MainTex, i.uv);duzi
clip(col.a - 0.5);
if (sqrt(dis.x*dis.x + dis.y*dis.y) < _Radius)
col.a = 0.49; //unity的renderTexture有個問題,就是用透明的RGBA貼圖設置的時候,
//它會將所有的透明通道信息單獨拿出來全部做個乘法疊加,
//depth only的渲染機制導致,會分開存儲透明信息。
return col;
}
ENDCG
}
}
}
腳本代碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;
public class control : MonoBehaviour
{
private Material _Mat; //要使用的材質
private RectTransform rt; //要被擦除的ui
public Texture _Texture; //ui繪製的貼圖
private RenderTexture _After; //爲材質提供的實時貼圖
private RenderTexture _Before; //過渡貼圖變量
void Start()
{
rt = GetComponent<RectTransform>();
_Mat = new Material(Shader.Find("Hidden/Brush"));
rt.GetComponent<Image>().material = _Mat;
_After = new RenderTexture((int)rt.rect.width, (int)rt.rect.height, 0, RenderTextureFormat.ARGB32);
_Before = new RenderTexture((int)rt.rect.width, (int)rt.rect.height, 0, RenderTextureFormat.ARGB32);
Graphics.Blit(_Texture, _After);
Graphics.Blit(_Texture, _Before);
_Mat.SetTexture("_MainTex", _After); //初始繪製貼圖
}
private void Update()
{
if (Input.GetMouseButton(0))
{
var pos = Vector2.zero;
//獲取相對於該貼圖的像素紋理座標
RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, null, out pos);
pos = pos + new Vector2(rt.rect.width, rt.rect.height) / 2;
_Mat.SetInt("_CenterX", (int)pos.x);
_Mat.SetInt("_CenterY", (int)pos.y);
Graphics.Blit(_Before, _After, _Mat);
Graphics.Blit(_After, _Before);
}
}
//繪製成功可以將_After拷貝到texture2D上,通過遍歷像素點的透明度來判斷貼圖是否擦除完成
//成功後通過Release()方法來釋放_After/_Before空間資源
}
使用着色器來實現刷子效果主要要解決的問題以及解決方法:
問題:
- 如何使用腳本將鼠標位置映射到要被擦除的貼圖的紋理座標 。
- 如何保存下來每一次操作後的貼圖以便於下一次在上一次操作的基礎上繼續操作。
- 如何在Shader其中將筆刷範圍內的像素值的透明度設置爲0,即全透明。
- 因爲傳入Shader中的貼圖是RenderTexture類型的,所以如何將Render Texture的某些像素點設置爲全透明。
方法:
var pos = Vector2.zero;
//獲取相對於該貼圖的像素紋理座標
RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, null, out pos);
pos = pos + new Vector2(rt.rect.width, rt.rect.height) / 2;
rt就是要被擦出的UI,可以通過 RectTransformUtility.ScreenPointToLocalPointInRectangleAPI來將屏幕座標系下的鼠標 位置轉換爲該UI紋理下的像素座標(即左下角爲原點(0,0),右上角爲(width,height))。
我們可以在程序初始化時,將RenderTexture類型的_After綁定到刷子Shader的_MainTex屬性上,在程序運行中,我們只需要改變_After,刷子Shader對應處理的_MainTex也會發生變化。
_Mat.SetTexture("_MainTex", _After); //初始繪製貼圖
需要將每次修改過後的_After保留下來以便下次繼續在上次操作的基礎上繼續進行操作。所以我們需要一個RenderTexture類型的過渡變量_Before;
Graphics.Blit(_Before, _After, _Mat);
Graphics.Blit(_After, _Before);
本來想的時將像素透明度設置爲0(col.a=0),就可以讓像素透明。但是後來遇到了一個坑如果將col.a設置爲0的話,上一次的操作就不會保留下來。也就是說將RenderTexture的像素透明度設置爲0,在RenderTexture中存儲時透明度並不是簡單的存儲爲0。unity的renderTexture有個問題,透明的RGBA貼圖設置的時候,它會將所有的透明通道信息單獨拿出來全部做個乘法疊加,depth only的渲染機制導致,會分開存儲透明信息。所以這裏並不是簡單的將像素點透明度設置爲0就可以了。可以使用像素剔除的方法,也就是說把低於特定透明度的像素直接丟棄掉。
當我設置RenderTexture的透明度時發現,a=0.5是半透明,a=0||a=1都是不透明。所以我的思路是將我要剔除的區域的透明度設置爲0.49,只要把透明度低於0.5的像素點剔除掉就可以實現擦出的效果。
clip(col.a - 0.5);
if (sqrt(dis.x*dis.x + dis.y*dis.y) < _Radius)
col.a = 0.49;
//unity的renderTexture有個問題,就是用透明的RGBA貼圖設置的時候,
//它會將所有的透明通道信息單獨拿出來全部做個乘法疊加,
//depth only的渲染機制導致,會分開存儲透明信息。
return col;
Demo地址:https://github.com/RXBXX/Brush
最後多謝https://gameinstitute.qq.com/community/user/1054734feng,解決了我關於renderTexture透明度存儲的問題。希望可以幫到有類似需求的讀者。希望在交流學習中成長。