Help us understand the problem. What is going on with this article?

[Unity] CustomRenderTextureに移動跡を書き込む

はじめに

積もった雪の中を動いて通った跡を残すような処理を作ってみます。
こんなかんじ
crt.gif

今回のプロジェクトは下記にあります。
https://github.com/jnhtt/SnowTest

環境

  • MacBook Pro(15-inch, 2017)
  • Unity 2018.4.3f1

概要

積もった雪の中を動いて通った跡を残すような処理を目指します。
ちゃんとした処理が欲しい場合は↓を使った方が良いでしょう。
https://github.com/EsProgram/InkPainter

おまけでtessellationも使います。

  1. CustomRenderTextureの作成
  2. CustomRenderTextureにデータを書き込むシェーダーとマテリアル作成
  3. PlaneとCustomRenderTextureを使って描画するシェーダーとマテリアル作成
  4. 適当に移動操作を作る
  5. CustomRenderTextureの更新処理を作成

 CustomRenderTextureの作成

右クリック > Create > Custom Render Textureを選ぶ
スクリプトから生成してもOK

スクリーンショット 2019-07-28 23.10.57.png

 CustomRenderTextureにデータを書き込むシェーダーとマテリアル作成

下記のShaderを設定したMaterialを作成して、CustomRenderTextureのMaterialに設定します。

CustomRenderTextureに設定するShaderの下記の箇所に注意してください。

#pragma vertex CustomRenderTextureVertexShader
#include "UnityCustomRenderTexture.cginc"
v2f_customrendertexture i

ワールド座標を渡してuvにマッピングします。

Shader "Custom/CRTShader"
{
    Properties
    {
        _EffectiveDist ("Effective Distance", Range(0, 64)) = 32
        _X ("X", Range(0, 1024)) = 512
        _Y ("Y", Range(0, 1024)) = 512
    }
    SubShader
    {
        Cull Off
        ZWrite Off
        ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex CustomRenderTextureVertexShader
            #pragma fragment frag
            #include "UnityCustomRenderTexture.cginc"

            float2 _WorldPos;
            float _EffectiveDist;
            float _X;
            float _Y;

            float4 frag(v2f_customrendertexture i) : SV_Target
            {
                float2 uv = i.globalTexcoord;
                float du = 1.0 / _CustomRenderTextureWidth;
                float dv = 1.0 / _CustomRenderTextureHeight;

                //インスペクター上で書き込みをテストしたい場合はここを使う
                //float2 pos = float2(_X * du, _Y * dv);
                float2 pos = float2(_WorldPos.x * du, _WorldPos.y * dv);
                float dist = distance(pos, uv);
                float scale = 0;
                float ed = _EffectiveDist * du;
                if (dist < ed)
                {
                    scale = 1 - dist / ed;
                } 
                float2 c = tex2D(_SelfTexture2D, uv);
                float p = scale + c.g;
                return float4(clamp(p, 0, 1), c.r, 0, 0);
            }
            ENDCG
        }
    }
}

 PlaneとCustomRenderTextureを使って描画するシェーダーとマテリアル作成

下記のShaderを設定したMaterialを作成して、CustomRenderTextureをVTF2Dlodに設定します。

CustomRenderTextureを頂点Shaderで使用します。
頂点Shaderでテクスチャをフェッチするにはtex2Dlodを使用します。

Tessellationを使うための設定として、Shaderの下記の箇所に注意してください。

#pragma surface surf BlinnPhong addshadow vertex:disp tessellate:tessEdge
#include "Tessellation.cginc"
Shader "Custom/Displace"
{
    Properties
    {
        _MainTex ("Main Tex", 2D) = "white" {}
        _MinColor ("Min Color", Color) = (0.4, 0.4, 0.4, 1)
        _MaxColor ("Max Color", Color) = (1, 1, 1, 1)
        _MinHeight ("Min Height", Float) = -0.1
        _MaxHeight ("Max Height", Float) = 2
        _VTF2Dlod ("VTF", 2D) = "gray" {}
        _EdgeLength ("Edge length", Range(3, 40)) = 10
        _Parallax ("Height", Range(0.0, 1.0)) = 0.5
    }

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

        CGPROGRAM
        #pragma target 5.0
        #pragma surface surf BlinnPhong addshadow vertex:disp tessellate:tessEdge
        #include "Tessellation.cginc"

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

        float _EdgeLength;
        float _Parallax;
        sampler2D _MainTex;
        sampler2D _VTF2Dlod;
        float4 _MinColor;
        float4 _MaxColor;
        float _MinHeight;
        float _MaxHeight;

        float4 tessEdge(appdata v0, appdata v1, appdata v2)
        {
            return UnityEdgeLengthBasedTessCull(v0.vertex, v1.vertex, v2.vertex, _EdgeLength, _Parallax * 1.5f);
        }

        void disp(inout appdata v)
        {
            float2 uv = 1 - v.texcoord.xy;
            float d = tex2Dlod(_VTF2Dlod, float4(uv, 0, 0)).r;
            v.vertex.xyz += v.normal * (1 - d) * _Parallax;
        }

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            float3 c = tex2D(_MainTex, IN.uv_MainTex).rgb;
            float h = (_MaxHeight - IN.worldPos.y) / (_MaxHeight - _MinHeight);
            o.Albedo = c * ((1 - h) * _MaxColor + h * _MinColor);
        }

        ENDCG
    }
}

 適当に移動操作を作る

重要ではないので説明はありません。

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

public class Cube : MonoBehaviour
{
    private const float SPEED = 2f;
    private void Update()
    {
        if (Input.GetKey(KeyCode.W))
        {
            transform.position += Time.deltaTime * SPEED * Vector3.forward;
        }
        else if (Input.GetKey(KeyCode.S))
        {
            transform.position -= Time.deltaTime * SPEED * Vector3.forward;
        }
        else if (Input.GetKey(KeyCode.A))
        {
            transform.position -= Time.deltaTime * SPEED * Vector3.right;
        }
        else if (Input.GetKey(KeyCode.D))
        {
            transform.position += Time.deltaTime * SPEED * Vector3.right;
        }
    }
}

CustomRenderTextureの更新処理を作成

位置をShaderに渡してCustomRenderTextureを更新します。

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

public class Test : MonoBehaviour
{
    [SerializeField]
    private CustomRenderTexture texture;
    [SerializeField]
    private Transform target;
    [SerializeField]
    private Material material;

    private Vector2 p;
    private Vector3 center;

#if true
    void Start()
    {
        center = new Vector3(5f, 0f, 5f);
        texture.initializationColor = Color.clear;
        texture.Initialize();
        texture.ClearUpdateZones();
        p.x = target.position.x * 0.1f * texture.width;
        p.y = target.position.z * 0.1f * texture.height;
        material.SetVector("_WorldPos", p);

        var defaultZone = new CustomRenderTextureUpdateZone();
        defaultZone.needSwap = true;
        defaultZone.passIndex = 0;
        defaultZone.rotation = 0f;
        defaultZone.updateZoneCenter = new Vector2(0.5f, 0.5f);
        defaultZone.updateZoneSize = new Vector2(1f, 1f);

        texture.SetUpdateZones(new CustomRenderTextureUpdateZone[] { defaultZone });
    }

    private void FixedUpdate()
    {
        // PlaneサイズとCustomRenderTextureのサイズに合わせて調整してください。
        p.x = (target.position.x + center.x) * 0.1f * texture.width;
        p.y = (target.position.z + center.z) * 0.1f * texture.height;
        material.SetVector("_WorldPos", p);
        texture.Update(1);
    }
#endif
}

さいごに

雪を掻き分けて移動する表現っぽいのができました。
Planeを分けてuvの調整と部分更新を使えば広いMapにも使えるかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした