AGSLで遊んでみる

https://qiita.com/keseran/items/cf4529aea92b11ae029d

前回のshader試してみたから今度は画像に処理を行ってみたのでshader触ってみたいけどわからないな〜なんて方の助けになれば幸いです。

画像に処理を加える際の数値などに関して[こちらの記事](https://ics.media/entry/5535/)を参考にさせていただきました。ありがとうございます！

この記事中ではshaderの処理だけを紹介していますが、全体を確認したい場合は[Githubにソースコード](https://github.com/keseran/AGSLSample)を公開していますので実際に動かして確認していただくことができます。


# 開発環境
- MacbookPro macOS Catalina 10.15.7
- AndroidStudio Flamingo Canary9
- Pixel6a AndroidOS 13

# x座標が100px右に進むごとにy座標を+20して階段状に表示してみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/a98e50bb-8c05-2f50-a1c9-8825fcbec2fe.png" width="320">

shader内は条件式を極力書かないほうがいいと思いますが、今回は愚直に・・・😇
```
    half4 main(in float2 fragCoord) {
        if (fragCoord.x > 1000.0) {
            fragCoord.y += 200.0;
        } else if (fragCoord.x > 900.0) {
            fragCoord.y += 180.0;
        } else if (fragCoord.x > 800.0) {
            fragCoord.y += 160.0;
        } else if (fragCoord.x > 700.0) {
            fragCoord.y += 140.0;
        } else if (fragCoord.x > 600.0) {
            fragCoord.y += 120.0;
        } else if (fragCoord.x > 500.0) {
            fragCoord.y += 100.0;
        } else if (fragCoord.x > 400.0) {
            fragCoord.y += 80.0;
        } else if (fragCoord.x > 300.0) {
            fragCoord.y += 60.0;
        } else if (fragCoord.x > 200.0) {
            fragCoord.y += 40.0;
        } else if (fragCoord.x > 100.0) {
            fragCoord.y += 20.0;
        } 
        float4 image = iShader.eval(fragCoord).rgba;
        return half4(image.xyz, 1.0);
    }
```

# 時間経過で画像の切り替えをしてみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/a1dea9ea-ae0d-e192-3cf6-6ee8938887c6.gif" width="320">

やってることは単純で、shaderに割合を渡してimage1とimage2を混ぜ合わせてる感じです。
alphaでimage1とimage2を切り替えるのとほぼ同じですがshaderの勉強に試してみました😇
```
    half4 main(in float2 fragCoord) {
        float4 image1 = iImage1Shader.eval(fragCoord).rgba;
        float4 image2 = iImage2Shader.eval(fragCoord).rgba;
        float3 color = image1.xyz * (1.0 - iRatio) + image2.xyz * iRatio;
        return half4(color.xyz, 1.0);
    }
```

# 上から30pxずつ画像を切り替えてみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/f9195f6e-b37a-cd9f-6a2e-376e1e2406b6.gif" width="320" />

```
    half4 main(in float2 fragCoord) {
        float4 image1 = iImage1Shader.eval(fragCoord).rgba;
        float4 image2 = iImage2Shader.eval(fragCoord).rgba;
        if (iPosition.y > fragCoord.y) {
            image1.xyz = image2.xyz;
        } else if (iPosition.x > fragCoord.x && iPosition.y + 30 > fragCoord.y) {
            image1.xyz = image2.xyz;
        }
        return half4(image1.xyz, 1.0);
    }
```

# Sliderを使って切り替え具合を自分で確認できるようにしてみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/91abbb26-9410-162a-9b9c-7bf4cbb1fc6b.gif" width="320" />

```
    half4 main(in float2 fragCoord) {
        float4 image1 = iImage1Shader.eval(fragCoord).rgba;
        float4 image2 = iImage2Shader.eval(fragCoord).rgba;
        
        if (iResolution.x * iRatio.x <= fragCoord.x && iResolution.y * iRatio.y <= fragCoord.y) {
            return half4(image1);
        } else {
            return half4(image2);
        }
    }
```

# 上から順にモノクロ表示、ネガポジ反転、セピア調を試してみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/ecfa7653-9a6c-9447-b355-cbc52eed157a.png" width="320" />

## モノクロ表示
```
private const val SHADER_UNIFORMS = """
    uniform shader iShader;
    const float R_LUMINANCE = 0.298912;
    const float G_LUMINANCE = 0.586611;
    const float B_LUMINANCE = 0.114478;
    const float3 monochromeScale = float3(R_LUMINANCE, G_LUMINANCE, B_LUMINANCE);
"""
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float4 image = iShader.eval(fragCoord).rgba;
        float grayColor = dot(image.rgb, monochromeScale);
        return half4(float3(grayColor), 1.0);
    }
"""
```

## ネガポジ反転
```
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float4 image = iShader.eval(fragCoord).rgba;
        return half4(1.0 - image.r, 1.0 - image.g, 1.0 - image.b, 1.0);
    }
"""

```

## セピア調
```
private const val SHADER_UNIFORMS = """
    uniform shader iShader;
    const float R_LUMINANCE = 0.298912;
    const float G_LUMINANCE = 0.586611;
    const float B_LUMINANCE = 0.114478;
"""
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float4 image = iShader.eval(fragCoord).rgba;
        float v = image.x * R_LUMINANCE + image.y * G_LUMINANCE + image.z * B_LUMINANCE;
        image.x = v * 0.9;
        image.y = v * 0.7;
        image.z = v * 0.4;
        return half4(image);
    }
"""
```

# モザイク処理を試してみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/f3668845-3f63-cb9a-afcf-bd215e55830d.gif" width="320" />

```
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float2 uv = fragCoord;
        uv.x = floor(uv.x * iResolution.x / iMosaicScale) / (iResolution.x / iMosaicScale) + (iMosaicScale / 2.0) / iResolution.x;
        uv.y = floor(uv.y * iResolution.y / iMosaicScale) / (iResolution.y / iMosaicScale) + (iMosaicScale / 2.0) / iResolution.y;

        float4 image = iShader.eval(uv).rgba;
        return half4(image);
    }
"""
```

# blurを試してみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/9508e83e-7045-8e04-dbf4-430d7d51dd64.gif" width="320" />

```
private const val SHADER_UNIFORMS = """
    uniform shader iShader;
    uniform float2 iResolution;
    uniform float iRadius;
"""
private const val SHADER_SUB = """
    float rand(float2 co) {
        float a = fract(dot(co, float2(2.067390879775102, 12.451168662908249))) - 0.5;
        float s = a * (6.182785114200511 + a * a * (-38.026512460676566 + a * a * 53.392573080032137));
        float t = fract(s * 43758.5453);
        return t;
    }
"""
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float x = (fragCoord.x * iResolution.x) + rand(fragCoord) * iRadius * 2.0 - iRadius;
        float y = (fragCoord.y * iResolution.y) + rand(float2(fragCoord.y, fragCoord.x)) * iRadius * 2.0 - iRadius;
        float4 image = iShader.eval(float2(x, y) / iResolution).rgba;
        return half4(image);
    }
"""
```

# 渦巻表示を試してみました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287537/0f5842c3-b0fb-6318-4f5f-ccd3620955d5.gif" width="320" />

```
private const val SHADER_UNIFORMS = """
    uniform shader iShader;
    uniform float2 iResolution;
    uniform float2 iCenter;
    uniform float iRadius;
    uniform float iSpiralStrength;
"""
private const val SHADER_MAIN = """
    half4 main(in float2 fragCoord) {
        float2 pos = (fragCoord * iResolution) - iCenter;
        float len = length(pos);
        if (len >= iRadius) {
            float4 image = iShader.eval(fragCoord).rgba;
            return half4(image);
        }
        
        float spiral = min(max(1.0 - (len / iRadius), 0.0), 1.0) * iSpiralStrength;
        float x = pos.x * cos(spiral) - pos.y * sin(spiral);
        float y = pos.y * sin(spiral) - pos.y * cos(spiral);
        float2 retPos = (float2(x, y) + iCenter) / iResolution;
        float4 image = iShader.eval(retPos).rgba;
        return half4(image);
    }
"""
```

# あとがき
shaderで画像処理をしていると楽しいのと目を引くことができるので、Android13以上でしかshaderを気軽に使えないのがもったいないな～と思いながら今回色々と作ってみました。