ニット調画面

環境

Unity2022.2.14f1

概要

画面をニット調にするテストです。

コード

using UnityEngine;
using UnityEngine.Rendering;

public class Knit : MonoBehaviour
{
    [SerializeField] private Material _material = null;
    [SerializeField] private int _tileSize = 16;

    private void Start()
    {
        var camera = GetComponent<Camera>();
        var tileNum = new Vector2Int(camera.pixelWidth / _tileSize, camera.pixelHeight / _tileSize);

        var renderTex = new RenderTexture(tileNum.x, tileNum.y, 24, RenderTextureFormat.Default);
#if true
        renderTex.filterMode = FilterMode.Point;
#endif
        var rts = new RenderBuffer[]
        {
            renderTex.colorBuffer,
        };
        camera.SetTargetBuffers(rts, renderTex.depthBuffer);
        _material.SetVector("_TileNum", new Vector2(tileNum.x / 2, tileNum.y));

        var commandBuffer = new CommandBuffer();
        commandBuffer.name = "BlitRenderTexture";
        commandBuffer.SetRenderTarget(-1);
        commandBuffer.Blit(renderTex, BuiltinRenderTextureType.CurrentActive, _material);
        camera.AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer);
    }
}
Shader "Hidden/Knit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    SubShader
    {
        // 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;
            float4 _MainTex_TexelSize;
            float2 _TileNum;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                float sb = length(uv - float2(0.5, 1.0));
                float sl = length(uv - float2(0.0, 0.5));
                float sr = length(uv - float2(1.0, 0.5));
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 tile = i.uv * _TileNum;
                float2 uvInTile = frac(tile);
                fixed4 col = tex2D(_MainTex, i.uv);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

画像を歪ませる版

Shader "Hidden/Knit2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    SubShader
    {
        // 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;
            float4 _MainTex_TexelSize;
            float2 _TileNum;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                //y = 0.0 ~ 1.0 -> 1.0 ~ 0.5
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                const float scale = 0.85;
                float sb = length(uv - float2(0.5, 1.0)) * scale;
                float sl = length(uv - float2(0.0, 0.5)) * scale;
                float sr = length(uv - float2(1.0, 0.5)) * scale;
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 tile = i.uv * _TileNum;
                float2 uvInTile = frac(tile);

                tile.y += abs(uvInTile.x - 0.5);
                float2 uv = tile / _TileNum;
                fixed4 col = tex2D(_MainTex, uv);

                tile = uv * _TileNum;
                uvInTile = frac(tile);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

RenderTextureを使わず、ImageEffectでやる場合

Shader "Hidden/ImageEffectKnit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TileSize("Tile Size", int) = 16
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    SubShader
    {
        // 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;
            float4 _MainTex_TexelSize;
            uint _TileSize;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                float sb = length(uv - float2(0.5, 1.0));
                float sl = length(uv - float2(0.0, 0.5));
                float sr = length(uv - float2(1.0, 0.5));
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                uint2 tileNum = uint2(_MainTex_TexelSize.z / (_TileSize * 2), _MainTex_TexelSize.w / _TileSize);
                float2 tile = i.uv * tileNum;
                float2 uvInTile = frac(tile);

                float2 uv = (floor(tile) + 0.5) / tileNum;
                fixed4 col = tex2D(_MainTex, uv);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

感想

renderTex.filterMode = FilterMode.Point;

コメントアウトしてバイリニア補間にしたほうが色が漏れてそれっぽく見えました。

参考

Shader - Shadertoy BETA

炎のシミュレーション

環境

Unity2022.2.14f1

概要

炎のシミュレーションのテストです。

参考の動画を見つつ、ナビエストークスを「poisson separable filter」を使用して行ってみました。 正直正しいのかわからないです。

ヤコビ反復32回分の代替になると解説している気がする。

コード

using UnityEngine;
using UnityEngine.UI;

public class TestFluid : MonoBehaviour
{
    public class DoubleRenderTexture
    {
        private RenderTexture[] _textures = new RenderTexture[2];
        public RenderTexture current => _textures[0];
        public RenderTexture back => _textures[1];
        private int _width = 0;
        private int _height = 0;
        public int width => _width;
        public int height => _height;
        public DoubleRenderTexture(int width, int height)
        {
            _width = width;
            _height = height;
            for (int i = 0; i < _textures.Length; i++)
            {
                _textures[i] = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
                _textures[i].enableRandomWrite = true;
                _textures[i].Create();
            }
        }

        public void Flip()
        {
            RenderTexture temp = _textures[0];
            _textures[0] = _textures[1];
            _textures[1] = temp;
        }

        public void Release()
        {
            foreach (var texture in _textures)
                texture.Release();
        }
    }

    [SerializeField] Material _material = null;
    [SerializeField] private ComputeShader _shader = null;
    [SerializeField] private RawImage _densityPreview = null;
    [SerializeField] private RawImage _velocityPreview = null;
    [SerializeField] private float _attenuation = 0.99f;
    [SerializeField] private float _deisityAttenuation = 0.985f;
    [SerializeField] private Vector2Int _vectorNum = new Vector2Int(16, 16);
    [SerializeField] private Vector2Int _densityNum = new Vector2Int(128, 128);

    private RenderTexture _divergenceTexture = null;
    private DoubleRenderTexture _velocityBuffer = null;
    private DoubleRenderTexture _pressureBuffer = null;
    private DoubleRenderTexture _densityBuffer = null;

    private int _knUpdateAdvection;
    private int _knInteractionForce;
    private int _knInteractionDensityForce;
    private int _knUpdateDivergence;
    private int _knUpdatePressurePsfH;
    private int _knUpdatePressurePsfV;
    private int _knUpdateVelocity;
    private int _knUpdateDensity;
    private static int _spTime = Shader.PropertyToID("_Time");
    private static int _spDeltaTime = Shader.PropertyToID("_DeltaTime");
    private static int _spSourceVelocity = Shader.PropertyToID("_SourceVelocity");
    private static int _spResultVelocity = Shader.PropertyToID("_ResultVelocity");
    private static int _spSourcePressure = Shader.PropertyToID("_SourcePressure");
    private static int _spResultPressure = Shader.PropertyToID("_ResultPressure");
    private static int _spResultDivergence = Shader.PropertyToID("_ResultDivergence");
    private static int _spSourceDensity = Shader.PropertyToID("_SourceDensity");
    private static int _spResultDensity = Shader.PropertyToID("_ResultDensity");
    private static int _spSimWidth = Shader.PropertyToID("_SimWidth");
    private static int _spSimHeight = Shader.PropertyToID("_SimHeight");
    private static int _spSimForceStart = Shader.PropertyToID("_SimForceStart");
    private static int _spDensityForceStart = Shader.PropertyToID("_DensityForceStart");
    private static int _spAttenuation = Shader.PropertyToID("_Attenuation");
    private static int _spDeisityAttenuation = Shader.PropertyToID("_DeisityAttenuation");
    private static int _spDensityWidth = Shader.PropertyToID("_DensityWidth");
    private static int _spDensityHeight = Shader.PropertyToID("_DensityHeight");
    private static int _spVectorFieldTex = Shader.PropertyToID("_VectorFieldTex");
    private static int _spDensityTex = Shader.PropertyToID("_DensityTex");

    private void Start()
    {
        Setup();
    }

    private void OnValidate()
    {
        _vectorNum.x = Mathf.Max(_vectorNum.x, 8);
        _vectorNum.y = Mathf.Max(_vectorNum.y, 8);
        _densityNum.x = Mathf.Max(_densityNum.x, 8);
        _densityNum.y = Mathf.Max(_densityNum.y, 8);

        DestroyBuffers();
        Setup();
    }

    private void OnDestroy()
    {
        DestroyBuffers();
    }

    private void Setup()
    {
        CreateBuffers();

        _knUpdateAdvection = _shader.FindKernel("UpdateAdvection");
        _knInteractionForce = _shader.FindKernel("InteractionForce");
        _knInteractionDensityForce = _shader.FindKernel("InteractionDensityForce");
        _knUpdateDivergence = _shader.FindKernel("UpdateDivergence");
        _knUpdatePressurePsfH = _shader.FindKernel("UpdatePressurePsfH");
        _knUpdatePressurePsfV = _shader.FindKernel("UpdatePressurePsfV");
        _knUpdateVelocity = _shader.FindKernel("UpdateVelocity");
        _knUpdateDensity = _shader.FindKernel("UpdateDensity");
    }

    private void CreateBuffers()
    {
        _velocityBuffer = new DoubleRenderTexture(_vectorNum.x, _vectorNum.y);
        _pressureBuffer = new DoubleRenderTexture(_vectorNum.x, _vectorNum.y);
        _densityBuffer = new DoubleRenderTexture(_densityNum.x, _densityNum.y);
        _divergenceTexture = new RenderTexture(_vectorNum.x, _vectorNum.y, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
        _divergenceTexture.enableRandomWrite = true;
        _divergenceTexture.Create();
    }

    private void DestroyBuffers()
    {
        if(_divergenceTexture != null)
            _divergenceTexture.Release();
        if(_velocityBuffer != null)
            _velocityBuffer.Release();
        if(_pressureBuffer != null)
            _pressureBuffer.Release();
        if(_densityBuffer != null)
            _densityBuffer.Release();
    }

    private void Update()
    {
        _shader.SetFloat(_spSimWidth, _vectorNum.x);
        _shader.SetFloat(_spSimHeight, _vectorNum.y);
        _shader.SetFloat(_spDeltaTime, Time.deltaTime);
        _shader.SetFloat(_spAttenuation, _attenuation);
        _shader.SetFloat(_spDeisityAttenuation, _deisityAttenuation);
        _shader.SetFloat(_spTime, Time.time);
        _shader.SetFloat(_spDensityWidth, _densityNum.x);
        _shader.SetFloat(_spDensityHeight, _densityNum.y);

        UpdateAdvection();
        InteractionForce();
        UpdateDivergence();
        UpdatePressure();
        UpdateVelocity();
        UpdateDensity();

        _velocityPreview.texture = _velocityBuffer.current;
        _densityPreview.texture = _densityBuffer.current;
        _material.SetTexture(_spVectorFieldTex, _velocityBuffer.current);
        _material.SetTexture(_spDensityTex, _densityBuffer.current);
    }

    // 移流
    private void UpdateAdvection()
    {
        _shader.SetTexture(_knUpdateAdvection, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateAdvection, _spResultVelocity, _velocityBuffer.back);
        _shader.Dispatch(_knUpdateAdvection, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 外力
    private void InteractionForce()
    {
        var width = _vectorNum.x / 8;
        var halfWidth = width / 2;
        var halfX = _vectorNum.x / 2;
        var start = halfX - halfWidth;
        _shader.SetInt(_spSimForceStart, start);
        _shader.SetTexture(_knInteractionForce, _spResultVelocity, _velocityBuffer.current);
        _shader.Dispatch(_knInteractionForce, width, 1, 1);

        // Density
        width = _densityNum.x / 8;
        halfWidth = width / 2;
        halfX = _densityNum.x / 2;
        start = halfX - halfWidth;
        _shader.SetInt(_spDensityForceStart, start);
        _shader.SetTexture(_knInteractionDensityForce, _spResultDensity, _densityBuffer.current);
        _shader.Dispatch(_knInteractionDensityForce, width, 1, 1);
    }

    // 発散
    private void UpdateDivergence()
    {
        _shader.SetTexture(_knUpdateDivergence, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateDivergence, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdateDivergence, _divergenceTexture.width / 8, _divergenceTexture.height / 8, 1);
    }

    // 圧力
    private void UpdatePressure()
    {
        // Horizontal
        _shader.SetTexture(_knUpdatePressurePsfH, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdatePressurePsfH, _spResultPressure, _pressureBuffer.back);
        _shader.SetTexture(_knUpdatePressurePsfH, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdatePressurePsfH, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);
        // Vertical
        _shader.SetTexture(_knUpdatePressurePsfV, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdatePressurePsfV, _spResultPressure, _pressureBuffer.back);
        _shader.SetTexture(_knUpdatePressurePsfV, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdatePressurePsfV, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);

        _pressureBuffer.Flip();
    }

    // 速度
    private void UpdateVelocity()
    {
        _shader.SetTexture(_knUpdateVelocity, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateVelocity, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdateVelocity, _spResultVelocity, _velocityBuffer.back);
        _shader.Dispatch(_knUpdateVelocity, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 密度
    private void UpdateDensity()
    {
        _shader.SetTexture(_knUpdateDensity, _spSourceDensity, _densityBuffer.current);
        _shader.SetTexture(_knUpdateDensity, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateDensity, _spResultDensity, _densityBuffer.back);
        _shader.Dispatch(_knUpdateDensity, _densityBuffer.width / 8, _densityBuffer.height / 8, 1);
        _densityBuffer.Flip();
    }
}
#pragma kernel UpdateAdvection
#pragma kernel InteractionForce
#pragma kernel InteractionDensityForce
#pragma kernel UpdateDivergence
#pragma kernel UpdatePressurePsfH
#pragma kernel UpdatePressurePsfV
#pragma kernel UpdateVelocity
#pragma kernel UpdateDensity

Texture2D<float4> _SourceVelocity;
Texture2D<float4> _SourcePressure;
Texture2D<float4> _SourceDensity;

RWTexture2D<float4> _ResultVelocity;
RWTexture2D<float4> _ResultPressure;
RWTexture2D<float4> _ResultDivergence;
RWTexture2D<float4> _ResultDensity;

#define SAMPLE _LinearClamp
SamplerState _LinearClamp;
SamplerState _PointClamp;

float _Time;
float _DeltaTime;
float _SimWidth;
float _SimHeight;
uint _SimForceStart;
uint _DensityForceStart;
float _DensityWidth;
float _DensityHeight;
float _Attenuation;
float _DeisityAttenuation;

#define PI 3.1415926538
#define PI2 (PI * 2)

float RandomRange(float2 Seed, float Min, float Max)
{
    float randomno =  frac(sin(dot(Seed, float2(12.9898, 78.233)))*43758.5453);
    return lerp(Min, Max, randomno);
}

// 移流
[numthreads(8,8,1)]
void UpdateAdvection(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0/w, 1.0/h, 0.0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float2 velocity = _SourceVelocity.SampleLevel(SAMPLE, uv, 0).xy;
    float2 result = _SourceVelocity.SampleLevel(SAMPLE, uv - velocity * _DeltaTime, 0).xy;

    _ResultVelocity[id] = float4(result, 0.0, 1.0);
}

// 外力
[numthreads(1,1,1)]
void InteractionForce(uint2 id : SV_DispatchThreadID)
{
    id.x += _SimForceStart;
    const float power = 1.5;
    float3 vec = float3(0.0, power, 0.0);
    _ResultVelocity[id] = float4(vec, 1.0);

    // 適当な風力
    const float speed = 5.0;
    float rad = _Time * speed;
    id.x -= _SimForceStart;
    id.y = _SimHeight / 2;
    vec.xy = float2(cos(rad), sin(rad)) * power * 1.0;
    _ResultVelocity[id] = float4(vec, 1.0);
}

[numthreads(1,1,1)]
void InteractionDensityForce(uint2 id : SV_DispatchThreadID)
{
    id.x += _DensityForceStart;
    _ResultDensity[id] = RandomRange(_Time + id.x, 0.3, 1.0);
}

// 発散
[numthreads(8,8,1)]
void UpdateDivergence(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float x0 = _SourceVelocity.SampleLevel(SAMPLE, uv - px.xz, 0).x;
    float x1 = _SourceVelocity.SampleLevel(SAMPLE, uv + px.xz, 0).x;
    float y0 = _SourceVelocity.SampleLevel(SAMPLE, uv - px.zy, 0).y;
    float y1 = _SourceVelocity.SampleLevel(SAMPLE, uv + px.zy, 0).y;

    float divergence = (x1 - x0 + y1 - y0);

    _ResultDivergence[id] = float4(divergence.xx, 0.0, 1.0);
}

static const float poissonFilter[7] = {
    .57843719174,
    .36519596949,
    .23187988879,
    .14529589353,
    .08816487385,
    .05184872885,
    .02906462467
};

// 圧力 H
[numthreads(8,8,1)]
void UpdatePressurePsfH(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;
    int i;

    float p0 = 0.0;
    [unroll]
    for (i = -6; i <= 6; i++) {
        float x = _SourcePressure.SampleLevel(_LinearClamp, uv + float2(px.x * i, 0), 0).r;
        p0 += poissonFilter[abs(i)] * x;
    }
    _ResultPressure[id] = float4(p0, 0.0, 0.0, 0.0);
}

// 圧力 V
[numthreads(8,8,1)]
void UpdatePressurePsfV(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;
    int i;

    float p1 = 0.0;
    [unroll]
    for (i = -6; i <= 6; i++) {
        float y = _SourcePressure.SampleLevel(_LinearClamp, uv + float2(0, px.y * i), 0).r;
        p1 += poissonFilter[abs(i)] * y;
    }

    float p0 = _ResultPressure[id].x;
    float d = _ResultDivergence[id].r;
    const float scale = 0.5;
    float relaxed = (p1 - d) * 0.25 * scale;

    _ResultPressure[id] = float4(relaxed.xx, 0.0, 1.0);
}

// 速度
[numthreads(8,8,1)]
void UpdateVelocity(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float x0 = _SourcePressure.SampleLevel(SAMPLE, uv - px.xz, 0).r;
    float x1 = _SourcePressure.SampleLevel(SAMPLE, uv + px.xz, 0).r;
    float y0 = _SourcePressure.SampleLevel(SAMPLE, uv - px.zy, 0).r;
    float y1 = _SourcePressure.SampleLevel(SAMPLE, uv + px.zy, 0).r;

    float2 v = _SourceVelocity.SampleLevel(SAMPLE, uv, 0).xy;
    float4 v2 = float4((v - (float2(x1, y1) - float2(x0, y0)) * 0.5), 0.0, 1.0);
    v2 *= _Attenuation;

    _ResultVelocity[id] = v2;
}

// 密度
[numthreads(8,8,1)]
void UpdateDensity(uint2 id : SV_DispatchThreadID)
{
    float dw = _DensityWidth;
    float dh = _DensityHeight;
    float2 baseUv = float2(id.x / dw, id.y / dh);
    float3 dpx = float3(1.0 / dw, 1.0 / dh, 0);
    float2 duv = baseUv + dpx.xy * 0.5;

    float vw = _SimWidth;
    float vh = _SimHeight;
    float3 vpx = float3(1.0 / vw, 1.0 / vh, 0);
    const float scale = 0.075;
    float2 vuv = baseUv + vpx.xy * scale;

    float2 vel = _SourceVelocity.SampleLevel(SAMPLE, vuv, 0).xy;

    float4 col = _SourceDensity.SampleLevel(SAMPLE, duv - vel * _DeltaTime, 0);
    col *= _DeisityAttenuation;
    _ResultDensity[id] = col;
}
Shader "Custom/Fire"
{
    Properties
    {
        _GradientTex ("Gradient Texture", 2D) = "white" {}
    }

    CGINCLUDE
    #include "UnityCG.cginc"

    struct appdata
    {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
    };

    sampler2D _VectorFieldTex;
    float4 _VectorFieldTex_TexelSize;
    sampler2D _DensityTex;
    float4 _DensityTex_TexelSize;
    sampler2D _GradientTex;
 
    static const float PI = 3.1415927;
    static const int   ARROW_V_STYLE = 1;
    static const int   ARROW_LINE_STYLE = 2;
    static const int   ARROW_STYLE = ARROW_LINE_STYLE;
    static const float ARROW_HEAD_ANGLE = 45.0 * PI / 180.0;
    static const float ARROW_SHAFT_THICKNESS = 3.0;

    static const float ARROW_BASE_SIZE = 1024.0;
    #define ARROW_TILE_SIZE (ARROW_BASE_SIZE / (float)_VectorFieldTex_TexelSize.z)
    #define ARROW_HEAD_LENGTH (ARROW_TILE_SIZE / 6.0)

 
    float2 ArrowTileCenterCoord(float2 pos)
    {
        return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
    }
 
    float Arrow(float2 p, float2 v)
    {
        p -= ArrowTileCenterCoord(p);
        float mag_v = length(v), mag_p = length(p);  
        if (mag_v > 0.0)
        {
            float2 dir_p = p / mag_p, dir_v = v / mag_v;
            mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
            v = dir_v * mag_v;
            float dist;
            if (ARROW_STYLE == ARROW_LINE_STYLE)
            {
                dist = max(ARROW_SHAFT_THICKNESS / 4.0 - max(abs(dot(p, float2(dir_v.y, -dir_v.x))),
                    abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0),
                    min(0.0, dot(v - p, dir_v) - cos(ARROW_HEAD_ANGLE / 2.0) * length(v - p)) * 2.0 +
                    min(0.0, dot(p, dir_v) + ARROW_HEAD_LENGTH - mag_v));
            }
            else
            {
                dist = min(0.0, mag_v - mag_p) * 2.0 +
                        min(0.0, dot(normalize(v - p), dir_v) - cos(ARROW_HEAD_ANGLE / 2.0)) * 2.0 * length(v - p) +
                        min(0.0, dot(p, dir_v) + 1.0) +
                        min(0.0, cos(ARROW_HEAD_ANGLE / 2.0) - dot(normalize(v * 0.33 - p), dir_v)) * mag_v * 0.8;
            }
            return clamp(1.0 + dist, 0.0, 1.0);
        }
        else
        {
            return max(0.0, 1.2 - mag_p);
        }
    }
 
    float2 field(float2 pos)
    {
        return tex2D(_VectorFieldTex, pos / ARROW_BASE_SIZE);
    }

    ENDCG

    SubShader
    {
        Tags
        { 
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }

        ZWrite Off
        Lighting Off
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
 
            float4 frag(v2f i) : SV_Target
            {
                float2 fragCoord = i.uv * float2(ARROW_BASE_SIZE,ARROW_BASE_SIZE);

                float4 col;

                float arrow = (1.0 - Arrow(fragCoord.xy, field(ArrowTileCenterCoord(fragCoord.xy)) * ARROW_TILE_SIZE * 0.4));
                float4 densityCol = tex2D(_GradientTex, float2(saturate(tex2D(_DensityTex, i.uv).r), 0.5));
                col.rgb = densityCol.rgb * arrow;
                col.a = max(densityCol.a, 1.0 - arrow);
                return col;
            }
            ENDCG
        }
    }
}

参考

Fast Eulerian Fluid Simulation In Games Using Poisson Filters (SCA 2020 Showcase) - YouTube

Damien Rioux-Lavoie

https://www.shadertoy.com/view/NdjyRh

Unityで流体シミュレーションを実装する 〜 実装編 〜 - e.blog

ShaderToy (GLSL) porting to HLSL - Unity Forum

GradientからTextureを生成

環境

Unity2022.2.14f1

概要

Gradientからテクスチャを生成するエディタ拡張を作成してみました。

コード

using UnityEngine;
using UnityEditor;
using System.IO;

public class GenerateGradiantTextureWindow : EditorWindow
{
    [MenuItem("Tools/GenerateGradiantTexture")]
    public static void Create()
    {
        var window = GetWindow<GenerateGradiantTextureWindow>("GenerateGradiantTexture");
        window.Show();
    }

    private GenerateGradiantTexture _obj = null;

    private void OnEnable()
    {
        var ms = MonoScript.FromScriptableObject(this);
        var path = AssetDatabase.GetAssetPath(ms);
        if(string.IsNullOrEmpty(path) == false)
        {
            path = path.Replace(Path.GetFileName(path), "");
            path = path + "GenerateGradiantTexture.asset";
        }

        _obj = AssetDatabase.LoadAssetAtPath<GenerateGradiantTexture>(path);
        if(_obj == null)
        {
            var assets = AssetDatabase.LoadAllAssetsAtPath(path);
            if(assets.Length > 0)
                _obj = assets[0] as GenerateGradiantTexture;
        }
        if(_obj == null)
        {
            _obj = ScriptableObject.CreateInstance<GenerateGradiantTexture>();
            AssetDatabase.CreateAsset(_obj, path);
            AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
        }
        _obj.Setup();
    }

    private void OnDisable()
    {
        if(_obj == null)
            return;
        EditorUtility.SetDirty(_obj);
        AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(_obj), ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
    }

    private void OnGUI()
    {
        _obj.OnGUI();
        Repaint();
    }
}
using UnityEngine;
using UnityEditor;
using System.IO;
using System;

class GenerateGradiantTexture : ScriptableObject
{
    [SerializeField] [GradientUsage(true)] private Gradient _gradient = null;
    [SerializeField] private Vector2Int _textureSize = new Vector2Int(128, 1);
    [SerializeField] private bool _isHdr = true;

    public void Setup()
    {
    }

    private static string LabeledTextField(string label, string text, float labelFieldWidth = 100, float fieldWidth = 30)
    {
        using (var horizontalScope = new GUILayout.HorizontalScope())
        {
            EditorGUILayout.LabelField(label, GUILayout.Width(labelFieldWidth));
            var str = EditorGUILayout.TextField(text, GUILayout.Width(fieldWidth));
            return str;
        }
    }

    public void OnGUI()
    {
        _gradient = EditorGUILayout.GradientField(_gradient);
        string str;
        str = LabeledTextField("Texture Width", _textureSize.x.ToString(), 100, 30);
        _textureSize.x = Convert.ToInt32(str);
        str = LabeledTextField("Texture Height", _textureSize.y.ToString(), 100, 30);
        _textureSize.y = Convert.ToInt32(str);
        _isHdr = EditorGUILayout.Toggle("Use Hdr", _isHdr);

        if(GUILayout.Button( "Generate" ) == true)
        {
            Generate();
        }
    }

    private void Generate()
    {
        if(_textureSize.x == 0 || _textureSize.y == 0)
            return;

        Texture2D texture = new Texture2D(_textureSize.x, _textureSize.y);
        for(int h = 0; h < texture.height; h++)
        {
            for(int w = 0; w < texture.width; w++)
                texture.SetPixel(w, h, _gradient.Evaluate((float)w / texture.width));
        }
        texture.wrapMode = TextureWrapMode.Clamp;
        texture.Apply();

        byte[] texels = null;
        string fileName = null;
        if(_isHdr == true)
        {
            texels = texture.EncodeToEXR();
            fileName = $"gradiant_{DateTime.Now.ToString("yyyyMMddHHmmss")}.exr";
        }
        else
        {
            texels = texture.EncodeToPNG();
            fileName = $"gradiant_{DateTime.Now.ToString("yyyyMMddHHmmss")}.png";
        }
        File.WriteAllBytes($"{Application.dataPath}/{fileName}", texels);
        AssetDatabase.ImportAsset($"Assets/{fileName}", ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
        TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath($"Assets/{fileName}");
        importer.wrapMode = TextureWrapMode.Clamp;
        AssetDatabase.ImportAsset($"Assets/{fileName}", ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
    }
}

参考

【Unity】Gradientからカラーランプテクスチャを生成する - Qiita

フェイクディスコライト

環境

Unity2022.2.14f1

概要

フェイクポイントライトにCubeMapを使用したものです。

Sphereモデルを影響範囲までスケールしたものにシェーダを適用します。

こちらはフェイクなので遮蔽されなかったりしますが、「PRINCIPLES」のランタンのような使い方は効果高いと思います。

コード

Shader "Custom/SSCubeDecalLight"
{
    Properties
    {
        [HDR] _Color("Color", Color) = (1, 1, 1, 1)
        _MainTex("MainTexe", CUBE) = "white" {}
        _FadeStart("FadeStart", Range(0, 1)) = 0.3
        _FadeStrength("FadeStrength", Range(0, 1)) = 0.6
    }
    SubShader
    {
        Tags
        { 
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }
        Pass
        {
            ZWrite Off
            ZTest Always
            Lighting Off
            Cull Front
            Blend SrcAlpha One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            half4 _Color;
            sampler2D _CameraDepthTexture;
            sampler2D _CameraDepthNormalsTexture;
            UNITY_DECLARE_TEXCUBE(_MainTex);
            float _FadeStart;
            float _FadeStrength;

            struct VSInput
            {
                float4 vertex : POSITION;
                float3 texcoord : TEXCOORD0;
            };
            struct VSOut
            {
                float4 posCS :SV_POSITION;
                float4 posSS : TEXCOORD0;
                float3 rayVS : TEXCOORD1;
            };

            VSOut vert(VSInput v)
            {
                VSOut o;
                o.posCS = UnityObjectToClipPos(v.vertex);
                o.posSS = ComputeScreenPos(o.posCS);
                o.rayVS = UnityObjectToViewPos(v.vertex).xyz;
                o.rayVS.z *= -1;
                return o;
            }

            fixed4 frag(VSOut i) : Color
            {
                const float far = _ProjectionParams.z;
                i.rayVS = i.rayVS * (far / i.rayVS.z);
                float depth;
                float3 depthNormalVS;
                float2 posSS = i.posSS.xy / i.posSS.w;
                float4 depthAndNormal = tex2D(_CameraDepthNormalsTexture, posSS);
                DecodeDepthNormal(depthAndNormal, depth, depthNormalVS);
                depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, posSS);
                depth = Linear01Depth(depth);

                float4 depthPosVS = float4(i.rayVS * depth, 1);
                float3 depthPosWS = mul(unity_CameraToWorld, depthPosVS).xyz;
                float3 depthPosOS = mul(unity_WorldToObject, float4(depthPosWS, 1.0)).xyz;

                float3 depthNormalOS = normalize(mul(depthNormalVS, UNITY_MATRIX_MV));

                float3 lightDirOS = normalize(-depthPosOS);
                float NdotL = saturate(dot(depthNormalOS, lightDirOS));

                float dist = saturate(length(depthPosOS));
                float mask = 1 - dist;

                float fade = 1.0 - saturate((dist - _FadeStart) / _FadeStrength);

                fixed3 diffuse = UNITY_SAMPLE_TEXCUBE(_MainTex, -lightDirOS);
                fixed3 rgb = diffuse * _Color.rgb * mask * NdotL * fade;
                return fixed4(rgb, 1);
            }
            ENDCG
        }
    }
}

参考

環境マッピング - テキトープログラム( ..)φメモ

フェイクポイントライト - テキトープログラム( ..)φメモ

コースティクス - テキトープログラム( ..)φメモ

フェイクスポットライト - テキトープログラム( ..)φメモ

SDFの視覚化(StackedPlane)

環境

Unity2022.2.14f1

概要

板ポリを視線奥方向に重ねることで、RayMarchingをしないで視覚化するという手法のテストです。

参考の記事に記載されているように、角ばったものや、3DTextureの視覚化には向かないようです。

雲や霧状のものの表現には使えそうではあります。

コード

using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using static UnityEngine.GraphicsBuffer;

public unsafe class TestStackedPlane : MonoBehaviour
{
    [SerializeField] private Material _material = null;
    [SerializeField] private int _sliceNum = 64;
    private GraphicsBuffer _gbMatrices = null;
    private Bounds _bounds;
    private static int _spMatrices = Shader.PropertyToID("_Matrices");
    private static int _spSliceNum = Shader.PropertyToID("_SliceNum");

#if UNITY_EDITOR
    private void OnValidate()
    {
        OnEnable();
    }
#endif
    private void OnEnable()
    {
        _gbMatrices = new GraphicsBuffer(Target.Structured, UsageFlags.LockBufferForWrite, 1, sizeof(Matrix4x4));
        _material.SetBuffer(_spMatrices, _gbMatrices);
        _material.SetInt(_spSliceNum, _sliceNum);
        _bounds = new Bounds(Vector3.zero, new Vector3(10000.0f, 10000.0f, 10000.0f));
    }

    private void OnDisable()
    {
        if(_gbMatrices != null)
            _gbMatrices.Dispose();
    }

    private void Update()
    {
        var matrices = _gbMatrices.LockBufferForWrite<Matrix4x4>(0, 1);
        matrices[0] = transform.localToWorldMatrix;
        _gbMatrices.UnlockBufferAfterWrite<Matrix4x4>(1);

        Graphics.DrawProcedural(_material, _bounds, MeshTopology.Quads, _sliceNum * 4, 1);
    }
}
Shader "Unlit/StackedPlane"
{
    Properties
    {
        _Color("Color", Color) = (1, 1, 1, 1)
        _SdfTex ("Sdf Texture", 3D) = "white" {}
    }

    CGINCLUDE
    #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
    #include "UnityCG.cginc"

    #define EPSILON 0.00001
    float _SliceNum;
    fixed4 _Color;
    sampler3D _SdfTex;
    StructuredBuffer<float4x4> _Matrices;

    struct appdata
    {
        uint vertexId : SV_VertexID;
        uint instanceId : SV_InstanceID;
    };

    struct v2f
    {
        float4 posCS : SV_POSITION;
        float3 posOS : TEXCOORD0;
    };
    ENDCG

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
            "IgnoreProjector"="True"
        }
        ZWrite Off
        Blend SrcAlpha One
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            v2f vert (appdata v)
            {
                v2f o;
                unity_ObjectToWorld = _Matrices[v.instanceId];
                const uint vnum = 4;
                uint vertexId = v.vertexId % vnum;
                uint faceId = v.vertexId / vnum;
                const float2 vertices[] =
                {
                    float2(-1, -1),
                    float2(-1,  1),
                    float2( 1,  1),
                    float2( 1, -1)
                };
                const float scale = 1.5;
                o.posOS = float3(vertices[vertexId], ((float)faceId / (float)(_SliceNum - 1)) * 2.0 - 1.0) * scale;
                o.posOS = mul(o.posOS, UNITY_MATRIX_V);
                o.posCS = UnityObjectToClipPos(float4(o.posOS, 1.0));
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float4 color = float4(_Color.rgb, 0.0);
                float alpha = 1.0 / (_SliceNum - 1);
                float dist = tex3D(_SdfTex, i.posOS * 0.5 + 0.5);
                if(dist < EPSILON)
                    color.a += alpha;
                return color;
            }
            ENDCG
        }
    }
}

参考

Raymarchingではない3Dアプローチ - Qiita

1. Realtime 3D Spiral Noise FX — Late's Blender Stuff

SDFの視覚化(NaiveSurfaceNets) - テキトープログラム( ..)φメモ

SDFの視覚化(NaiveSurfaceNets)

環境

Unity2022.2.14f1

概要

NaiveSurfaceNetsを使用して、SDFの3DTextureのポリゴン化を行ってみました。

参考のqiitaのコードをComputeShaderで行ってみた感じです。

マイフレーム実行しても、実用的な速度で処理されているようです。

SDFは参考のGithubにある「SignedDistance.vf」を使用させていただきました。

.vfファイルはVisualEffectGraphにインポータが付属しています。

VectorFieldImporter.csの中のtexture.Apply()の2番目の引数をfalseにしてCPUで読み取れるようにしています。

※ComputeShaderで直接3DTextureを参照するなら必要ない。

また設定でOutput Formatをfloatにしています。

コード

using UnityEngine;

public unsafe class TestGpuNaiveSurfaceNets : MonoBehaviour
{
    [SerializeField] private Texture3D _sdfTexture = null;
    [SerializeField] private ComputeShader _computeShader = null;
    [SerializeField] private Material _material = null;
    private GraphicsBuffer _sdfVoxelBuffer = null;
    private GraphicsBuffer _vertexIdBuffer = null;
    private GraphicsBuffer _vertexBuffer = null;
    private GraphicsBuffer _indexBuffer = null;
    private GraphicsBuffer _neighborBuffer = null;
    private GraphicsBuffer _edgeBuffer = null;
    private GraphicsBuffer _indirectArgBuffer = null;
    private Bounds _bounds;
    private int _klVertices = -1;
    private int _klIndices = -1;
    private int _klIndirectArgs = -1;
    private Vector3Int _tgVertices;
    private Vector3Int _tgIndices;
    private static int _spSdfVoxelSize = Shader.PropertyToID("SdfVoxelSize");
    private static int _spSdfVoxels = Shader.PropertyToID("SdfVoxels");
    private static int _spVertexIds = Shader.PropertyToID("VertexIds");
    private static int _spVertices = Shader.PropertyToID("Vertices");
    private static int _spIndices = Shader.PropertyToID("Indices");
    private static int _spNeighbors = Shader.PropertyToID("Neighbors");
    private static int _spEdges = Shader.PropertyToID("Edges");
    private static int _spIndirectArgs = Shader.PropertyToID("IndirectArgs");
    private void Start()
    {
        if(_sdfTexture.width != _sdfTexture.height || _sdfTexture.width != _sdfTexture.depth)
        {
            Debug.LogError("sdfTexture Size");
            return;
        }
        var size = _sdfTexture.width;
        var texels = _sdfTexture.GetPixelData<float>(0);

        _sdfVoxelBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, size * size * size, sizeof(float));
        _sdfVoxelBuffer.SetData(texels);
        _vertexIdBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, size * size * size, sizeof(int));
        _vertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.Counter, size * size * size, sizeof(Vector3));
        _vertexBuffer.SetCounterValue(0);
        _indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured /*| GraphicsBuffer.Target.Index*/ | GraphicsBuffer.Target.Counter, size * size * size * 18, sizeof(int));
        _indexBuffer.SetCounterValue(0);
        _indirectArgBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 4, sizeof(uint));
        _indirectArgBuffer.SetData(new uint[4]{0, 1, 0, 0});

        // 立方体上の頂点の番号の決め方
        var Neighbors = new Vector3Int[]
        {
            new Vector3Int( 0, 0, 0 ),
            new Vector3Int( 1, 0, 0 ),
            new Vector3Int( 1, 0, 1 ),
            new Vector3Int( 0, 0, 1 ),
            new Vector3Int( 0, 1, 0 ),
            new Vector3Int( 1, 1, 0 ),
            new Vector3Int( 1, 1, 1 ),
            new Vector3Int( 0, 1, 1 ),
        };
        _neighborBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Neighbors.Length, sizeof(Vector3Int));
        _neighborBuffer.SetData(Neighbors);
        // 辺のつながり方
        var Edges = new Vector2Int[]
        {
            new Vector2Int( 0, 1 ),
            new Vector2Int( 1, 2 ),
            new Vector2Int( 2, 3 ),
            new Vector2Int( 3, 0 ),
            new Vector2Int( 4, 5 ),
            new Vector2Int( 5, 6 ),
            new Vector2Int( 6, 7 ),
            new Vector2Int( 7, 4 ),
            new Vector2Int( 0, 4 ),
            new Vector2Int( 1, 5 ),
            new Vector2Int( 2, 6 ),
            new Vector2Int( 3, 7 ),
        };
        _edgeBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Edges.Length, sizeof(Vector2Int));
        _edgeBuffer.SetData(Edges);

        float bsize = size;// * 10.0f;
        _bounds = new Bounds(new Vector3(bsize * 0.5f, bsize * 0.5f, bsize * 0.5f), new Vector3(bsize, bsize, bsize));
        _material.SetBuffer(_spVertices, _vertexBuffer);
        _material.SetBuffer(_spIndices, _indexBuffer);

        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.SetInt(_spSdfVoxelSize, size);

        // Vertices
        _klVertices = _computeShader.FindKernel("GenerateVertices");
        _computeShader.SetBuffer(_klVertices, _spSdfVoxels, _sdfVoxelBuffer);
        _computeShader.SetBuffer(_klVertices, _spVertexIds, _vertexIdBuffer);
        _computeShader.SetBuffer(_klVertices, _spVertices, _vertexBuffer);
        _computeShader.SetBuffer(_klVertices, _spEdges, _edgeBuffer);
        _computeShader.SetBuffer(_klVertices, _spNeighbors, _neighborBuffer);

        _computeShader.GetKernelThreadGroupSizes(_klVertices, out numThreadX, out numThreadY, out numThreadZ);
        _tgVertices.x = ((size - 1) + (int)(numThreadX - 1)) / (int)numThreadX;
        _tgVertices.y = ((size - 1) + (int)(numThreadY - 1)) / (int)numThreadY;
        _tgVertices.z = ((size - 1) + (int)(numThreadZ - 1)) / (int)numThreadZ;

        // Indices
        _klIndices = _computeShader.FindKernel("GenerateIndices");
        _computeShader.SetBuffer(_klIndices, _spSdfVoxels, _sdfVoxelBuffer);
        _computeShader.SetBuffer(_klIndices, _spVertexIds, _vertexIdBuffer);
        _computeShader.SetBuffer(_klIndices, _spVertices, _vertexBuffer);
        _computeShader.SetBuffer(_klIndices, _spIndices, _indexBuffer);
        _computeShader.SetBuffer(_klIndices, _spNeighbors, _neighborBuffer);

        _computeShader.GetKernelThreadGroupSizes(_klIndices, out numThreadX, out numThreadY, out numThreadZ);
        _tgIndices.x = ((size - 2) + (int)(numThreadX - 1)) / (int)numThreadX;
        _tgIndices.y = ((size - 2) + (int)(numThreadY - 1)) / (int)numThreadY;
        _tgIndices.z = ((size - 2) + (int)(numThreadZ - 1)) / (int)numThreadZ;

        // IndirectArgs
        _klIndirectArgs = _computeShader.FindKernel("UpdateIndirectArgs");
        _computeShader.SetBuffer(_klIndirectArgs, _spIndices, _indexBuffer);
        _computeShader.SetBuffer(_klIndirectArgs, _spIndirectArgs, _indirectArgBuffer);
    }

    private void OnDestroy()
    {
        if(_sdfVoxelBuffer != null)
            _sdfVoxelBuffer.Dispose();
        if(_vertexIdBuffer != null)
            _vertexIdBuffer.Dispose();
        if(_vertexBuffer != null)
            _vertexBuffer.Dispose();
        if(_indexBuffer != null)
            _indexBuffer.Dispose();
        if(_neighborBuffer != null)
            _neighborBuffer.Dispose();
        if(_edgeBuffer != null)
            _edgeBuffer.Dispose();
        if(_indirectArgBuffer != null)
            _indirectArgBuffer.Dispose();
    }

    private void LateUpdate()
    {
        // Dispatch
        _vertexBuffer.SetCounterValue(0);
        _indexBuffer.SetCounterValue(0);
        _computeShader.Dispatch(_klVertices, _tgVertices.x, _tgVertices.y, _tgVertices.z);
        _computeShader.Dispatch(_klIndices, _tgIndices.x, _tgIndices.y, _tgIndices.z);
        _computeShader.Dispatch(_klIndirectArgs, 1,1,1);
        GL.Flush();

        if(_indexBuffer != null)
            Graphics.DrawProceduralIndirect(_material, _bounds, MeshTopology.Triangles, _indirectArgBuffer);
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawWireCube(_bounds.center, _bounds.size);
    }
}
uint SdfVoxelSize;
StructuredBuffer<float> SdfVoxels;
StructuredBuffer<uint2> Edges;        // 辺のつながり方
StructuredBuffer<uint3> Neighbors;    // 立方体上の頂点の番号の決め方

RWStructuredBuffer<uint> VertexIds;
RWStructuredBuffer<float3> Vertices;
RWStructuredBuffer<uint> Indices;
RWStructuredBuffer<uint> IndirectArgs;

// v0, v1, v2, v3から構築される面を追加する
void MakeFace(uint v0, uint v1, uint v2, uint v3, bool outside)
{
    uint indexOffset = Indices.IncrementCounter() * 6;

    if (outside)
    {
        Indices[indexOffset    ] = v0;
        Indices[indexOffset + 1] = v3;
        Indices[indexOffset + 2] = v2;
        Indices[indexOffset + 3] = v2;
        Indices[indexOffset + 4] = v1;
        Indices[indexOffset + 5] = v0;
    }
    else
    {
        Indices[indexOffset    ] = v0;
        Indices[indexOffset + 1] = v1;
        Indices[indexOffset + 2] = v2;
        Indices[indexOffset + 3] = v2;
        Indices[indexOffset + 4] = v3;
        Indices[indexOffset + 5] = v0;
    }
}

// 整数座標から配列に入るときの順序を取得
// +X+Y+Z方向に広がる立方体上のi番目の頂点として順序を取得
uint ToIdx(uint x, uint y, uint z, uint i, uint size)
{
    x += Neighbors[i].x;
    y += Neighbors[i].y;
    z += Neighbors[i].z;
    return x + y * size + z * size * size;
}

// 整数座標から配列に入るときの順序を取得
// -X-Y-Z方向に広がる立方体上のi番目の頂点として順序を取得
uint ToIdxNeg(uint x, uint y, uint z, uint i, uint size)
{
    x -= Neighbors[i].x;
    y -= Neighbors[i].y;
    z -= Neighbors[i].z;
    return x + y * size + z * size * size;
}

// 整数座標から実数座標を取得
// +X+Y+Z方向に広がる立方体上のi番目の頂点として実数座標を取得
float3 ToVec(uint i, uint j, uint k, uint neighbor)
{
    i += Neighbors[neighbor].x;
    j += Neighbors[neighbor].y;
    k += Neighbors[neighbor].z;
    return float3(i, j, k);
}

uint GetKind(uint x, uint y, uint z)
{
    // ビットマスクで8つの点の状態を記憶
    // iの位置の点が内側ならばi + 1番目のビットを立てる
    // 頂点の位置と番号の対応は次のように決める        
    //          7----6
    //         /|   /|
    //        4----5 |
    //        | 3--|-2
    //        |/   |/
    // (x,y,z)0----1
    uint kind = 0;
    if (0 > SdfVoxels[ToIdx(x, y, z, 0, SdfVoxelSize)]) kind |= 1 << 0;
    if (0 > SdfVoxels[ToIdx(x, y, z, 1, SdfVoxelSize)]) kind |= 1 << 1;
    if (0 > SdfVoxels[ToIdx(x, y, z, 2, SdfVoxelSize)]) kind |= 1 << 2;
    if (0 > SdfVoxels[ToIdx(x, y, z, 3, SdfVoxelSize)]) kind |= 1 << 3;
    if (0 > SdfVoxels[ToIdx(x, y, z, 4, SdfVoxelSize)]) kind |= 1 << 4;
    if (0 > SdfVoxels[ToIdx(x, y, z, 5, SdfVoxelSize)]) kind |= 1 << 5;
    if (0 > SdfVoxels[ToIdx(x, y, z, 6, SdfVoxelSize)]) kind |= 1 << 6;
    if (0 > SdfVoxels[ToIdx(x, y, z, 7, SdfVoxelSize)]) kind |= 1 << 7;
    return kind;
}

void GenerateVertex(uint x, uint y, uint z)
{
    uint kind = GetKind(x, y, z);
    // 8つの点がすべて内側またはすべて外側の場合はスキップ
    if (kind == 0 || kind == 255)
        return;

    // 頂点の位置を算出
    float3 vertex;
    uint crossCount = 0;

    // 現在焦点を当てている立方体上の辺をすべて列挙
    for (uint i = 0; i < 12; i++) {
        uint2 p = Edges[i];
        uint p0 = p.x;
        uint p1 = p.y;
            
        // 異なる側同士の点でつながってない場合はスキップ
        // ビットマスクからp0 + 1とp1 + 1ビット目(p0とp1の位置の点の状態)を取り出す
        if ((kind >> p0 & 1) == (kind >> p1 & 1))
            continue;

        // 両端の点のボクセルデータ上の値を取り出す
        float val0 = SdfVoxels[ToIdx(x, y, z, p0, SdfVoxelSize)];
        float val1 = SdfVoxels[ToIdx(x, y, z, p1, SdfVoxelSize)];

        // 線形補間によって値が0となる辺上の位置を算出して加算
        vertex += lerp(ToVec(x, y, z, p0), ToVec(x, y, z, p1), (0 - val0) / (val1 - val0));
        crossCount++;
    }
    vertex /= crossCount;

    uint vertexOffset = Vertices.IncrementCounter();
    Vertices[vertexOffset] = vertex;
    VertexIds[ToIdx(x, y, z, 0, SdfVoxelSize)] = vertexOffset;
}

void GenerateIndex(uint x, uint y, uint z)
{
    // 面の追加は0 < x, y, z < size - 1で行う
    //if (x == 0 || y == 0 || z == 0)
    //  return;

    uint kind = GetKind(x, y, z);
    // 8つの点がすべて内側またはすべて外側の場合はスキップ
    if (kind == 0 || kind == 255)
        return;

    // ビットマスクから1ビット目(0の位置の点の状態)を取り出す
    bool outside = (kind & 1) != 0;

    // 面を構築する頂点を取り出す
    // 頂点の位置と番号の対応は次のように決める   
    //    1----0(x, y, z)
    //   /|   /|
    //  2----3 |
    //  | 5--|-4
    //  |/   |/
    //  6----7
    uint v0 = VertexIds[ToIdxNeg(x, y, z, 0, SdfVoxelSize)];
    uint v1 = VertexIds[ToIdxNeg(x, y, z, 1, SdfVoxelSize)];
    uint v2 = VertexIds[ToIdxNeg(x, y, z, 2, SdfVoxelSize)];
    uint v3 = VertexIds[ToIdxNeg(x, y, z, 3, SdfVoxelSize)];
    uint v4 = VertexIds[ToIdxNeg(x, y, z, 4, SdfVoxelSize)];
    uint v5 = VertexIds[ToIdxNeg(x, y, z, 5, SdfVoxelSize)];
    // var v6 = VertexIds[ToIdxNeg(x, y, z, 6, SdfVoxelSize)]; // 使われない
    uint v7 = VertexIds[ToIdxNeg(x, y, z, 7, SdfVoxelSize)];

    // ビットマスクから2ビット目(1の位置の点の状態)を取り出す。異なる側同士の点からなる辺ならば交わるような面を追加
    bool isBit;
    isBit = (kind >> 1 & 1) != 0;
    if ( isBit != outside)
        MakeFace(v0, v3, v7, v4, outside);
    // ビットマスクから4ビット目(3の位置の点の状態)を取り出す
    isBit = (kind >> 3 & 1) != 0;
    if (isBit != outside)
        MakeFace(v0, v4, v5, v1, outside);
    // ビットマスクから5ビット目(4の位置の点の状態)を取り出す
    isBit = (kind >> 4 & 1) != 0;
    if (isBit != outside)
        MakeFace(v0, v1, v2, v3, outside);
}

#pragma kernel GenerateVertices
[numthreads(32, 32, 1)]
void GenerateVertices(uint3 id : SV_DispatchThreadID)
{
    if(id.x >= SdfVoxelSize - 1 || id.y >= SdfVoxelSize - 1 || id.z >= SdfVoxelSize - 1)
        return;

    uint x = id.x;
    uint y = id.y;
    uint z = id.z;
    GenerateVertex(x, y, z);
}

#pragma kernel GenerateIndices
[numthreads(32, 32, 1)]
void GenerateIndices(uint3 id : SV_DispatchThreadID)
{
    if(id.x >= SdfVoxelSize - 2 || id.y >= SdfVoxelSize - 2 || id.z >= SdfVoxelSize - 2)
        return;

    uint x = id.x + 1;
    uint y = id.y + 1;
    uint z = id.z + 1;
    GenerateIndex(x, y, z);
}

#pragma kernel UpdateIndirectArgs
[numthreads(1, 1, 1)]
void UpdateIndirectArgs(uint3 id : SV_DispatchThreadID)
{
    IndirectArgs[0] = Indices.IncrementCounter() * 6;
}
Shader "Unlit/GpuNaiveSurfaceNets"
{
    Properties
    {
    }

    CGINCLUDE
    StructuredBuffer<float3> Vertices;
    StructuredBuffer<uint> Indices;

    ENDCG

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

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                uint vertexId : SV_VertexID;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(float4(Vertices[Indices[v.vertexId]], 1.0));
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(1,0,0,1);
            }
            ENDCG
        }
    }
}

参考

Unityでボクセルデータをもとにメッシュを生成する方法(Naive Surface Nets) - Qiita

GitHub - foreverliu/VectorFieldExamples: Unity VFX Graph examples with vector fields

batファイルでプリプロセッサを実行してglslをhlslに変換

環境

VisualStudio2019Community

概要

VSに付属しているcl.exeを使用してプリプロセッサを実行します。

シェーダのdefineは貧弱だったりするので、先にclを実行して前処理を済ませたりします。

後は独自のスクリプト言語を作った時の前処理に使用するでしょうか。

今回はblenderのshaderのglslをhlslに変換したかったのでバッチファイルを作成してみました。



cl.exeが実行できるように環境変数にパスを通しておく必要があります。 自分の環境だと以下に存在します。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\Hostx64\x64

以下のコードの場合、Testフォルダにglslのコードを入れます。

Outputフォルダにhlslが出力されます。

Intermediateはコンバート時に必要な中間ファイルが出力されるフォルダです。

mat型のオーバーロードに対応していません。vec型と同じようにして追加すればできるようになると思います。

コード

GlslToHlslDefs.h

#if PPMODE==0
#define vec2(...) vec2_overload_tuple((__VA_ARGS__,vec2_2,vec2_1))(__VA_ARGS__)
#define vec2_overload_tuple(tuple) vec2_overload tuple
#define vec2_overload(e1,e2,n,...) n
#define vec2_1(x) float2(x,x)
#define vec2_2(x,y) float2(x,y)

#define vec3(...) vec3_overload_tuple((__VA_ARGS__,vec3_3,vec3_2,vec3_1))(__VA_ARGS__)
#define vec3_overload_tuple(tuple) vec3_overload tuple
#define vec3_overload(e1,e2,e3,n,...) n
#define vec3_1(x) float3(x,x,x)
#define vec3_2(x, y) float3(x,y)
#define vec3_3(x, y, z) float3(x,y,z)

#define vec4(...) vec4_overload_tuple((__VA_ARGS__,vec4_4,vec4_3,vec4_2,vec4_1))(__VA_ARGS__)
#define vec4_overload_tuple(tuple) vec4_overload tuple
#define vec4_overload(e1,e2,e3,e4,n,...) n
#define vec4_1(x) float4(x,x,x,x)
#define vec4_2(x, y) float4(x,y)
#define vec4_3(x, y, z) float4(x,y,z)
#define vec4_4(x, y, z,w) float4(x,y,z,w)

#elif PPMODE==1

#define vec2 float2
#define vec3 float3
#define vec4 float4
#define mat2 float2x2
#define mat3 float3x3
#define mat4 float4x4
#define fract frac

#if 1
#define mod(x,y) ((x) - (y) * floor((x) / (y)))
#else
#define mod fmod
#endif

#define mix lerp
#define atan atan2
#define textureLod(tex, uv, lod) tex2Dlod(tex, float4(uv, 0, lod))
#define iTime _Time.y
#define iChannel0 _MainTex
#define iResolution float2(_MainTex_TexelSize.zw)
#define iMouse float3(0,0,0)
#define MUL_MAT(v0, v1) mul(v1, v0)
#define floatBitsToUint asuint
#define vector vecValue
#endif

GlslToHlsl.bat

@echo off
setlocal enabledelayedexpansion

set curDir=%~dp0
set srcDir=%curDir%\Test
set intermediateDir=%curDir%\Intermediate
set outputDir=%curDir%\Output
set headerPath=%curDir%\GlslToHlslDefs.h
rem echo %headerPath%

cd %srcDir%
for /r %%f in (*.glsl) do (
    set filePath=%%f
    set baseFile=%%~nf
    set extension=%%~xf
    set fileName=!baseFile!!extension!
    set tmpPath0=%intermediateDir%\!baseFile!.i0
    set tmpPath1=%intermediateDir%\!baseFile!.i1
    set tmpPath2=%intermediateDir%\!baseFile!.i2
    set outputPath=%outputDir%\!baseFile!.hlsl
    rem echo !tmpFile0!
    rem 
    copy %headerPath% !tmpPath0!
    type !filePath! >> !tmpPath0!
    call cl.exe /EP /P /C /D PPMODE=0 /Fi!tmpPath1! !tmpPath0!
    rem 
    copy %headerPath% !tmpPath2!
    type !tmpPath1! >> !tmpPath2!
    call cl.exe /EP /P /C /D PPMODE=1 /Fi!outputPath! !tmpPath2!
)
endlocal
pause

参考

[備忘録]マクロの引数の数でオーバーロード - Qiita

余談 C++ Design: g++とvc++の可変引数マクロの挙動の違い

GitHub - Unity-Technologies/HLSLcc: DirectX shader bytecode cross compiler

Shader Playground

NagaにしてOutputFormatをHLSLにする