<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by IKEH on Medium]]></title>
        <description><![CDATA[Stories by IKEH on Medium]]></description>
        <link>https://medium.com/@ikeh1024?source=rss-d45c7b007d37------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Ot8QtBv_7GC1WRtuNmKk_Q.png</url>
            <title>Stories by IKEH on Medium</title>
            <link>https://medium.com/@ikeh1024?source=rss-d45c7b007d37------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 09:33:47 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ikeh1024/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[SwiftUI Metal Shader Tutorials + Replacement from GLSL to MSL]]></title>
            <link>https://medium.com/@ikeh1024/swiftui-metal-shader-tutorials-replacement-from-glsl-to-msl-6e97b7307dc2?source=rss-d45c7b007d37------2</link>
            <guid isPermaLink="false">https://medium.com/p/6e97b7307dc2</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[msl]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[shaders]]></category>
            <dc:creator><![CDATA[IKEH]]></dc:creator>
            <pubDate>Thu, 16 Nov 2023 15:35:56 GMT</pubDate>
            <atom:updated>2025-03-25T07:48:24.115Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><ul><li>Up until now, when I wanted to play with Metal, I always talked about low-level things like queues and pipelines, which made it difficult to get started.</li><li>— Example: <a href="https://www.kodeco.com/books/metal-by-tutorials/v2.0/chapters/1-hello-metal">Metal by Tutorials</a></li><li>At that time, a Metal Shader modifier that can be used with SwiftUI was released at WWDC2023.</li><li>This makes it possible to implement it without worrying about low-level layers, and allows you to easily try out Metal Shader.</li><li>However, even at WWDC, it was briefly mentioned in <a href="https://developer.apple.com/videos/play/wwdc2023/10148/">What’s new in SwiftUI , and </a><a href="https://developer.apple.com/documentation/swiftui/shader">the official documentation</a> is only briefly written.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*pKUuTA228B6wWe7Iwctm7g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*VbH0M5zYijVV_Omstz9ckw.png" /></figure><ul><li>Fortunately, there are many technical articles, so in this article I would like to use these as a reference to systematize the content and talk about rewriting an existing shader (ShaderToy’s GLSL) to Metal.</li></ul><h3>References</h3><ul><li><a href="https://alexanderlogan.co.uk/blog/wwdc23/09-metal">Metal for SwiftUI</a></li><li>— Easy to understand and recommended for beginners</li><li><a href="https://goodpatch-tech.hatenablog.com/entry/raindrop_with_ios17">I made a raindrop effect for the iOS 17 weather app</a></li><li><a href="https://qiita.com/fuziki/items/1746278afb7e3a91fcc6">Effect like rare card hologram with Metal Shader on SwiftUI</a></li><li><a href="https://kavsoft.dev/swiftui_5.0_metal_shader_jun23">SwiftUI Metal Shader API in iOS 17</a></li><li><a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-metal-shaders-to-swiftui-views-using-layer-effects">How to add Metal shaders to SwiftUI views using layer effects</a></li><li><a href="https://nerdyak.tech/development/2023/06/16/distortionEffect-with-Metal-shaders-for-better-transitions.html">SwiftUI transitions with distortion effect and Metal Shaders</a></li><li>Screen transition using Shader</li></ul><h3>GitHub</h3><ul><li><a href="https://github.com/pommdau/swiftui-metal-shader-tutorial/tree/main">swiftui-metal-shader-tutorial</a></li><li>— I would be happy if you could star me if you like!</li></ul><h3>Introduction to Shader modifier</h3><h3>Introduction to colorEffect</h3><ul><li>First, let’s try how to actually write a Shader.</li><li>This time, we will create a Shader that will fill in the color passed as an argument.</li><li>Shader modifier can be written as follows.</li><li>— There are several types of modifiers (described later), and here we will use <a href="https://developer.apple.com/documentation/swiftui/view/coloreffect(_:isenabled:)">colorEffect(_:isEnabled:) .</a></li><li>ShaderFunctionSpecify the shader to use.</li><li>— This time fillColorwe have specified a shader called , which will be implemented in the Metal file that we will create later.</li><li>— Also, arguments are the arguments to be passed to the Shader, and are converted to a struct called <a href="https://developer.apple.com/documentation/swiftui/shader/argument">Shader.Argument .</a></li><li>— This time I am converting SwiftUI to ColorSwiftUI .Shader.Argument</li></ul><pre>Rectangle()<br>    .frame(width: 100, height: 100)<br>    .colorEffect(<br>        Shader(function: ShaderFunction(library: .default,name: &quot;fillColor&quot;),<br>               arguments: [.color(Color.blue)])<br>    )</pre><ul><li>It can also be written more concisely as follows.</li><li>— Since ShaderLibrary @dynamicMemberLookupimplements , it can be written like this <a href="https://augmentedcode.io/2023/08/07/applying-metal-shader-to-text-in-swiftui/">(reference)</a></li></ul><pre>Rectangle()<br>    .frame(width: 100, height: 100)<br>    .colorEffect(<br>        ShaderLibrary.default.fillColor(.color(Color.blue))<br>    )</pre><ul><li>fillColorNow let&#39;s implement the Metal Shader that we declared earlier .</li><li>Create a Metal file with an appropriate name as shown below.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VRsFttR0B24MWcN1.jpg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hk192Po0_FyzULvT.jpg" /></figure><ul><li>The Shader function is as follows.</li><li>The language used is Metal Shader Language (MSL).</li><li>stitchablerepresents an MSL function ( <a href="https://developer.apple.com/documentation/metal/mtlfunctionstitchinggraph">for reference</a> )</li><li>half4is the return type of this function, fillColorand is the function name.</li><li>There are three arguments here, and the first two colorEffectare the values ​​passed by default when using , and are the current pixel position and color, respectively.</li><li>— Since this MSL function is called for each pixel, operations can be performed on a pixel-by-pixel basis.</li><li>The third and subsequent arguments argumentscorrespond to the arguments passed in .</li><li>colorEffectIn this case, the return value of the function is reflected in the color of the pixel at the current position, so in this case the original shape will be newColorfilled with.</li></ul><pre>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>[[ stitchable ]] half4 fillColor<br>(<br> float2 position, // Current pixel position (given by default)<br> half4 color, // Color of current pixel (given by default)<br> half4 newColor<br> ) {    <br>    return newColor; // r, g, b, a<br>}</pre><ul><li>When you build with the above steps, a Rectangle filled with the specified color will be displayed as shown below.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/202/0*8rbI2fzRJWF1x5dn.jpg" /></figure><ul><li>For example, if you return the original color if the x coordinate is less than 30px as shown below, it will be as follows.</li></ul><pre>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>[[ stitchable ]] half4 fillColor<br>(<br> float2 position,<br> half4 color,<br> half4 newColor<br> ) {<br>    if (position.x &lt; 30 ) {<br>        return color;<br>    }<br>    return newColor; // r, g, b, a<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/220/0*Ylfg3YQSY5DV60TJ.jpg" /></figure><ul><li>Another colorEffectexample is implementing a Shader that makes an image monochrome.</li><li>Here we are using the original View color and calculating the gray color from the current pixel color.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/176/0*uJZAUqF8R3Pvk3-G.jpg" /></figure><pre>Image(.xcode)<br>    .resizable()<br>    .frame(width: 100, height: 100)<br>    .colorEffect(<br>        ShaderLibrary.default.monochrome()<br>    )</pre><pre>[[ stitchable ]] half4 monochrome<br>(<br> float2 position,<br> half4 color<br> ) {<br>    half v = (color.r + color.g + color.b) / 3.0;<br>    return half4(v, v, v, 1.0);<br>}</pre><ul><li>In this way, <a href="https://developer.apple.com/documentation/swiftui/view/coloreffect(_:isenabled:)">colorEffect(_:isEnabled:)</a> can be said to be a modifier that can be used for the following purposes.</li><li>— If you want to freely draw shapes etc.</li><li>— If you want to manipulate the color of the original View</li></ul><h3>Types of Shader modifiers and their usage</h3><ul><li>Now, as I mentioned earlier that there are several types of modifiers, there are the following three.</li><li><a href="https://developer.apple.com/documentation/swiftui/view/coloreffect(_:isenabled:)">colorEffect(_:isEnabled:)</a></li><li>— If you want to manipulate the color of the original View (e.g. make it monochrome/remove red color)</li><li>— If you want to freely draw shapes etc. (only the drawing area is used as information from the original View)</li><li><a href="https://developer.apple.com/documentation/swiftui/view/distortioneffect(_:maxsampleoffset:isenabled:)">distortionEffect(_:maxSampleOffset:isEnabled:)</a></li><li>— If you want to transform the original image</li><li>— (In my personal opinion, I can’t think of any use for it alone, so layerEffect will likely be used in many cases.)</li><li><a href="https://developer.apple.com/documentation/swiftui/view/layereffect(_:maxsampleoffset:isenabled:)">layerEffect(_:maxSampleOffset:isEnabled:)</a></li><li>— If you want to add effects by manipulating the original View (e.g. pixelization, page turning effect)</li><li>— Has both colorEffect and distortionEffect functions (*Personal impression)</li><li>— I think you can replace it with layerEffect without actually using these two.</li></ul><h3>Introduction to distortionEffect</h3><ul><li>Next, let’s talk about <a href="https://developer.apple.com/documentation/swiftui/view/distortioneffect(_:maxsampleoffset:isenabled:)">distortionEffect(_:maxSampleOffset:isEnabled:) .</a></li><li>As mentioned earlier, this can transform the original image, and this time we will create a Shader that shifts it 10px to the left.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/444/0*PwI3KMLzTZMRnQkj.jpg" /></figure><ul><li>distortionEffectAs with colorEffect, the current pixel position is passed as an argument.</li><li>The return value returns the pixel position, and the color of the pixel returned in the return value becomes the color of the pixel at the current position.</li><li>It’s difficult to express, so I’ll explain it with this Shader…</li><li>— The color referenced by each pixel is the color of the position 10px to the right from the current position.</li><li>— For example, the color of the pixel at (x, y) = (10, 10) is the color of the pixel at (20, 10).</li><li>— As a result, the figure will be drawn 10px to the left.</li></ul><pre>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>[[ stitchable ]] float2 shiftToLeft<br>(<br> float2 position // Current pixel position<br> ) {<br>    return float2(position.x + 10, position.y);<br>}</pre><ul><li>*Some shapes have been added to make it easier to see.</li></ul><pre>ZStack {<br>    Color.yellow.opacity(0.4)<br>    Rectangle()<br>        .stroke(lineWidth: 1)<br>        .frame(width: 100, height: 100)<br>        .zIndex(1)<br>    Rectangle()<br>        .foregroundStyle(.blue)<br>        .frame(width: 100, height: 100)<br>        .distortionEffect(<br>            ShaderLibrary.default.shiftToLeft(),<br>            maxSampleOffset: .zero<br>        )<br>}<br>.frame(width: 150, height: 150)</pre><ul><li>Also, in this case, the shifted portion is not drawn.</li><li>So, if you add the code as below, everything will be drawn.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/0*qr2ivWCOeNK1fUP2.jpg" /></figure><pre>Rectangle()<br>    .foregroundStyle(.blue)<br>    .frame(width: 100, height: 100)<br>+   .padding(.horizontal, 10) // added<br>+   .drawingGroup() // added<br>    .distortionEffect(<br>        ShaderLibrary.default.shiftToLeft(),<br>-       maxSampleOffset: .zero  // The figure is cut.<br>+       maxSampleOffset: .init(width: 10, height: 0) // added<br>    )</pre><ul><li>I haven’t fully digested this topic yet, so I’d like you to refer to the source article.</li><li><a href="https://betterprogramming.pub/swiftui-metal-6ba454dcb6f2">— SwiftUI and Metal<br>Distortion effect and offsets explained</a></li><li>(my vague understanding)</li><li>— paddingExpand the drawing range of View using</li><li>— paddingis not subject to Shader, so drawingGrouptreat it as one View in SwiftUI.</li><li>— maxSampleOffsetto expand the drawing range of Shader modifier?</li><li>(I don’t really understand even after looking at the documentation…)</li></ul><blockquote><a href="https://developer.apple.com/documentation/swiftui/view/distortioneffect(_:maxsampleoffset:isenabled:)">distortionEffect(_:maxSampleOffset:isEnabled:)</a><br>maxSampleOffset</blockquote><blockquote>The maximum distance in each axis between the returned source pixel position and the destination pixel position, for all source pixels.</blockquote><h3>Introduction to layerEffect</h3><ul><li>Finally, try using <a href="https://developer.apple.com/documentation/swiftui/view/layereffect(_:maxsampleoffset:isenabled:)">layerEffect(_:maxSampleOffset:isEnabled:) .</a></li></ul><blockquote><em>Has both colorEffect and distortionEffect functions (*Personal impression)</em></blockquote><ul><li>The Shader implemented colorEffectin this way can be implemented with.distortionEffectlayerEffect</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/478/0*wHgtHdiTRxCm0se8.jpg" /></figure><ul><li>The implementation of Shader is as follows.</li><li>SwiftUI::Layer layerA major feature is that it takes in addition to the current position as an argument .</li><li>— #include &lt;SwiftUI/SwiftUI_Metal.h&gt;You can now use it as , and layer.sample(position)get the color at any position like .</li><li>This function plays a large role, and allows various expressions of Shader.</li><li>The return value also colorEffectreturns the color of the current position.</li></ul><pre>#include &lt;metal_stdlib&gt;<br>#include &lt;SwiftUI/SwiftUI_Metal.h&gt;<br>using namespace metal;<br><br>[[ stitchable ]] half4 monochromeWithLayerEffect<br>(<br> float2 position,<br> SwiftUI::Layer layer<br> ) {<br>    half4 color = layer.sample(position);<br>    half v = (color.r + color.g + color.b) / 3.0;<br>    return half4(v, v, v, 1.0);<br>}<br><br>[[ stitchable ]] half4 shiftToLeftWithLayerEffect<br>(<br> float2 position,<br> SwiftUI::Layer layer<br> ) {<br>    float2 newPosition = float2(position.x + 10, position.y);<br>    half4 color = layer.sample(newPosition);<br>    return color;<br>}</pre><ul><li>The implementation on the SwiftUI side is as follows.</li><li>maxSampleOffsetetc. distortionEffectare the same.</li></ul><pre>Image(.xcode)<br>    .resizable()<br>    .frame(width: 100, height: 100)<br>    .layerEffect(<br>        ShaderLibrary.default.monochromeWithLayerEffect(),<br>        maxSampleOffset: .zero<br>    )</pre><pre>ZStack {<br>    Color.yellow.opacity(0.4)<br>    Rectangle()<br>        .stroke(lineWidth: 1)<br>        .frame(width: 100, height: 100)<br>        .zIndex(1)<br>    Rectangle()<br>        .foregroundStyle(.blue)<br>        .frame(width: 100, height: 100)<br>        .padding(.horizontal, 10)<br>        .drawingGroup()<br>        .layerEffect(<br>            ShaderLibrary.default.shiftToLeftWithLayerEffect(),<br>            maxSampleOffset: .init(width: 10, height: 0)<br>        )<br>}<br>.frame(width: 150, height: 150)</pre><ul><li>This is all about how to use Shader modifiers and how to use them properly.</li></ul><h3>Replacing GLSL-&gt;MSL</h3><ul><li>Rather than actually writing a Shader from scratch, I think you often want to reuse an existing Shader, so let’s look at an example of actually replacing GLSL (ShaderToy) with MSL.</li><li>If you have any questions about the details of GLSL or MSL grammar during the talk, please refer to “MSL and GLSL Grammar” below.</li></ul><h3>Function declarations and types</h3><ul><li>First, let’s replace the Shader written in GLSL below with MSL.</li></ul><blockquote><a href="https://www.shadertoy.com/view/Md23DV"><em>https://www.shadertoy.com/view/Md23DV</em></a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xePQ3JaA6jFhJLLm.jpg" /></figure><ul><li>The values ​​given by the ShaderToy fragColorare fragCoordthe current pixel color and position, respectively.</li></ul><pre>#elif TUTORIAL == 3<br><br>void mainImage( out vec4 fragColor, in vec2 fragCoord )<br>{<br> vec3 color = vec3(0.0, 1.0, 1.0);<br> float alpha = 1.0;<br> <br> vec4 pixel = vec4(color, alpha);<br> fragColor = pixel;<br>}</pre><ul><li>First, replace the function declaration with the MSL one.</li></ul><pre>[[ stitchable ]] half4 aqua<br>(<br> float2 position,<br> SwiftUI::Layer layer<br> ) {<br>    vec3 color = vec3(0.0, 1.0, 1.0);<br>    float alpha = 1.0;<br>    <br>    vec4 pixel = vec4(color, alpha);<br>    fragColor = pixel;<br>}</pre><ul><li>Next, replace the type.</li><li>For example, replace vec3 with float3.</li></ul><pre>[[ stitchable ]] half4 aqua<br>(<br> float2 position,<br> SwiftUI::Layer layer<br> ) {<br>-   vec3 color = vec3(0.0, 1.0, 1.0);<br>+   float3 color = float3(0.0, 1.0, 1.0); # added<br>    float alpha = 1.0;<br>    <br>-   vec4 pixel = vec4(color, alpha);<br>+   float4 pixel = float4(color, alpha); # added<br>    fragColor = pixel;<br>}</pre><ul><li>In GLSL, the value assigned to fragColor is reflected as the pixel’s color.<br>On the other hand, in MSL, the return value of return is the pixel color, as described above, and the type is a variable of type half4, so it is used as such.</li><li>— It is good to recognize that the replacement of vec can be done with a basic float, and that half appears on the MSL side only when it is related to color.</li><li>— There is a difference in precision between float and half, with float being 32 bits and half being 16 bits.</li></ul><pre>[[ stitchable ]] half4 aqua<br>(<br> float2 position,<br> SwiftUI::Layer layer<br> ) {<br>    float3 color = float3(0.0, 1.0, 1.0);<br>    float alpha = 1.0;<br>    <br>    float4 pixel = float4(color, alpha);<br>-   fragColor = pixel;    <br>+   return half4(pixel); # added<br>}</pre><ul><li>With the above, I was able to rewrite it to MSL!</li><li>Basically, it is OK if you replace it mechanically like this.</li></ul><h3>boundingRect and coordinate system</h3><ul><li>Next, try replacing the following.</li></ul><blockquote><a href="https://www.shadertoy.com/view/Md23DV"><em>https://www.shadertoy.com/view/Md23DV</em></a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zsiCUBavnW4r2c-z.jpg" /></figure><ul><li>*Because I want to check the y coordinate, I replaced the original code r.xwith .r.y</li></ul><pre>#elif TUTORIAL == 7<br><br>void mainImage( out vec4 fragColor, in vec2 fragCoord )<br>{<br>   // (0, 0) ~ (1, 1)<br> vec2 r = vec2(fragCoord.x / iResolution.x,<br>      fragCoord.y / iResolution.y);<br> <br> vec3 color1 = vec3(0.841, 0.582, 0.594);<br> vec3 color2 = vec3(0.884, 0.850, 0.648);<br> vec3 color3 = vec3(0.348, 0.555, 0.641);<br> vec3 pixel;<br> <br> if( r.y &lt; 1.0/3.0) {<br>  pixel = color1;<br> } else if( r.y &lt; 2.0/3.0 ) {<br>  pixel = color2;<br> } else {<br>  pixel = color3;<br> }<br> <br> fragColor = vec4(pixel, 1.0);<br>}</pre><ul><li>First, perform the same replacement as before.</li></ul><pre>[[ stitchable ]] half4 tutorial7<br>(<br> float2 position,<br> SwiftUI::Layer layer,<br> float4 bounds // x,y,z,w = (x, y, width, height)<br> ) {<br>    // (0, 0) ~ (1, 1)<br>    float2 r = float2(fragCoord.x / iResolution.x,<br>                      fragCoord.y / iResolution.y);<br>    <br>    float3 color1 = float3(0.841, 0.582, 0.594);<br>    float3 color2 = float3(0.884, 0.850, 0.648);<br>    float3 color3 = float3(0.348, 0.555, 0.641);<br>    float3 pixel;<br>        <br>    if( r.y &lt; 1.0/3.0) {<br>        pixel = color1;<br>    } else if( r.y &lt; 2.0/3.0 ) {<br>        pixel = color2;<br>    } else {<br>        pixel = color3;<br>    }<br>            <br>    return half4(half3(pixel), 1.0);<br>}</pre><ul><li>Also, since we need the size of the original View this time, we pass <a href="https://developer.apple.com/documentation/swiftui/shader/argument/boundingrect?changes=latest_minor&amp;language=_2">the boundingRect</a> defined in <a href="https://developer.apple.com/documentation/swiftui/shader/argument?changes=latest_minor&amp;language=_2">Shader.Argument to arguments.</a></li></ul><pre>Rectangle()<br>    .frame(width: 100, height: 100)<br>    .layerEffect(<br>        ShaderLibrary.default.tutorial7(.boundingRect),<br>        maxSampleOffset: .zero<br>    )</pre><ul><li>iResolutionAs stated in GLSL , this float2is the type and information about the size of the View.</li><li>In MSL, this corresponds to <a href="https://developer.apple.com/documentation/swiftui/shader/argument/boundingrect?changes=latest_minor&amp;language=_2">the boundingRect</a> passed earlier .float4 bounds</li><li>*The width corresponds to w, not , so be careful not to write it incorrectly.z</li></ul><pre>// View width<br>iResolution.x -&gt; bounds.z<br>// View height<br>iResolution.y -&gt; bounds.w</pre><ul><li>fragCoordor positionapplicable.</li><li>Reflecting the above, the code will be as follows.</li></ul><pre>[[ stitchable ]] half4 tutorial7<br>(<br> float2 position,<br> SwiftUI::Layer layer,<br> float4 bounds // x,y,z,w = (x, y, width, height)<br> ) {<br>    // (0, 0) ~ (1, 1)<br>    float2 fragCoord = position;<br>    float2 r = float2(fragCoord.x / bounds.z,<br>                      fragCoord.y / bounds.w);<br>    <br>    float3 color1 = float3(0.841, 0.582, 0.594);<br>    float3 color2 = float3(0.884, 0.850, 0.648);<br>    float3 color3 = float3(0.348, 0.555, 0.641);<br>    float3 pixel;<br>        <br>    if( r.y &lt; 1.0/3.0) {<br>        pixel = color1;<br>    } else if( r.y &lt; 2.0/3.0 ) {<br>        pixel = color2;<br>    } else {<br>        pixel = color3;<br>    }<br>            <br>    return half4(half3(pixel), 1.0);<br>}</pre><ul><li>However, when you build this and check the display, the y coordinate is reversed.</li><li>This is because in GLSL, the origin is at the bottom left, and in MSL, the origin is at the top left, and the y coordinates are reversed.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/292/0*lUm_WvdRhbQ9IIeO.jpg" /></figure><ul><li>Therefore, after normalization, it is necessary to correct the y-coordinate by inverting it.</li></ul><pre>float2 r = float2(fragCoord.x / bounds.z,<br>                  fragCoord.y / bounds.w);<br>r = float2(r.x, 1.0 - r.y);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/288/0*EdkGcmaNAe00WeSD.jpg" /></figure><ul><li>However, I’ve been writing this way for a while, but there’s a problem: There are two ways to normalize the Lower left is the origin(0, 0) ~ (1, 1)coordinates The center is the origin: (-1, -1) ~ (1, 1).</li><li>In the latter case, you can just multiply the y coordinate by -1, but the way you write the normalization differs from person to person, and it was a hassle to judge which coordinate system each time.</li><li>Therefore, I came up with the idea that it would be possible to handle both by inverting the original coordinates from the beginning, and finally decided to make the following corrections.</li></ul><pre>- float2 fragCoord = position;<br>+ float2 fragCoord = float2(position.x, bounds.z - position.y);</pre><ul><li>The final code is as follows.</li></ul><pre>[[ stitchable ]] half4 tutorial7<br>(<br> float2 position,<br> SwiftUI::Layer layer,<br> float4 bounds // x,y,z,w = (x, y, width, height)<br> ) {<br>    float2 fragCoord = float2(position.x, bounds.z - position.y);<br>    // (0, 0) ~ (1, 1)<br>    float2 r = float2(fragCoord.x / bounds.z,<br>                      fragCoord.y / bounds.w);<br>    <br>    float3 color1 = float3(0.841, 0.582, 0.594);<br>    float3 color2 = float3(0.884, 0.850, 0.648);<br>    float3 color3 = float3(0.348, 0.555, 0.641);<br>    float3 pixel;<br>        <br>    if( r.y &lt; 1.0/3.0) {<br>        pixel = color1;<br>    } else if( r.y &lt; 2.0/3.0 ) {<br>        pixel = color2;<br>    } else {<br>        pixel = color3;<br>    }<br>            <br>    return half4(half3(pixel), 1.0);<br>}</pre><h3>Other replacement examples</h3><ul><li>The above was about replacing GLSL with MSL.</li><li>While actually performing the replacement, I came across situations where it was difficult to understand a little bit, such as the reverse of coordinate normalization processing, so it might be a good idea to familiarize yourself with the basics of GLSL by referring to the following. not.</li><li><a href="https://qiita.com/doxas/items/b8221e92a2bfdc6fc211">— [Serial] It’s super easy if you try it! First shader coding starting with WebGL and GLSL (1)</a></li><li><a href="https://www.shadertoy.com/view/Md23DV">— GLSL 2D Tutorials</a></li><li>— — In addition to the coordinate system, it is also useful for how to use functions.</li><li>Finally, I will give an example of what I replaced with my MSL.</li><li>— (Please forgive any inconsistency as the code was developed through trial and error)</li><li>PageCurl effect</li><li><a href="https://www.shadertoy.com/view/ls3cDB">— GLSL(ShaderToy)</a></li><li><a href="https://github.com/pommdau/swiftui-metal-shader-tutorial/blob/main/HelloShader/GLSLtoMSL/PageCurl/PageCurl.metal">— MSL</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*POS219sX0dvCryUj.png" /></figure><ul><li>curtain effect</li><li><a href="https://www.shadertoy.com/view/ssByDV">— GLSL(ShaderToy)</a></li><li><a href="https://github.com/pommdau/swiftui-metal-shader-tutorial/blob/main/HelloShader/GLSLtoMSL/Curtain/CurtainShader.metal">— MSL</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/519/0*33I6WHI1ZOTSN43a.png" /></figure><ul><li>That’s the main topic, and I’ll go into more detail below.</li></ul><h3>MSL and GLSL grammar</h3><ul><li>Below is a summary of the pages I referred to regarding grammar and specifications.</li></ul><h3>GLSL grammar</h3><ul><li><a href="https://qiita.com/doxas/items/b8221e92a2bfdc6fc211">[Serial] It’s super easy if you try it! First shader coding starting with WebGL and GLSL (1)</a></li><li>— A very easy to understand introduction to GLSL</li><li><a href="https://qiita.com/edo_m18/items/71f6064f3355be7e4f45">Notes about GLSL</a></li><li>— List of functions</li><li><a href="https://secondflush2.blog.fc2.com/blog-entry-840.html">GLSL~4.1 Basic types</a></li><li><a href="https://community.khronos.org/t/pi-3-1415926/53090/1">Pi = 3.1415926</a></li><li>— Pi is declared using constants.</li></ul><pre>#define M_PI 3.1415926535897932384626433832795</pre><h3>ShaderToy features</h3><ul><li><a href="https://trap.jp/post/3/">Shader†Introduction†</a></li></ul><blockquote><em>fragCoord is “coordinates of the pixel whose color is to be determined (lower left origin)”<br>fragColor is “a vector whose xyzw components each have the RGBA components of that pixel”.</em></blockquote><ul><li><a href="https://note.com/toyoshimorioka/n/nf908ce35d0ea#qZhLV">Handling of iMouse</a></li></ul><blockquote><em>iMouse is often used with Shadertoy, but is rather troublesome when used with TouchDesigner. Shadertoy’s iMouse holds four values.<br>“Mouse coordinates XY while the left mouse button is pressed” and “Mouse coordinates XY at the moment the left mouse button is pressed”. The latter part is troublesome, as the XY coordinates become 0 the moment the mouse button stops being pressed.</em></blockquote><h3>MSL grammar</h3><ul><li><a href="https://www.main-function.com/entry/2020/05/06/111752">[Metal] Metal Shading Language Specification Version2.2</a></li><li><a href="https://mike-neko.github.io/blog/metal-function/">Summary of functions available in Metal shaders</a></li></ul><h3>constant definition</h3><pre>#define pi float(3.14159265359)<br>#define blue half4(0.0, 0.0, 1.0, 1.0)<br>#define radius float(0.1)</pre><h3>Difference between mod and fmod</h3><ul><li>Since there are differences between GLSL modand MSL fmod, you need to define the mod as shown below.</li><li>This is because the treatment of small numbers is different between each and floor and truncate.</li><li>refs: <a href="https://github.com/gfx-rs/naga/issues/802">Handle differences between the glsl mod function and the metal::fmod function. #802</a></li></ul><blockquote>GLSL</blockquote><blockquote><a href="https://qiita.com/edo_m18/items/71f6064f3355be7e4f45">https://qiita.com/edo_m18/items/71f6064f3355be7e4f45</a><br>x-y*floor(x/y)</blockquote><blockquote>MSL</blockquote><blockquote><a href="https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf">https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf</a></blockquote><blockquote>x-y*trunc(x/y)</blockquote><pre>template&lt;typename Tx, typename Ty&gt;<br>inline Tx mod(Tx x, Ty y)<br>{<br>    return x - y * floor(x / y);<br>}</pre><h3>mix</h3><ul><li>Since mix is ​​not defined in MSL, define a function.</li></ul><blockquote><a href="https://mike-neko.github.io/blog/metal-function/"><em>https://mike-neko.github.io/blog/metal-function/</em></a><em><br>T mix(T x, T y, T a)<br>x + (y — x) * a</em></blockquote><pre>template&lt;typename Txy, typename Ta&gt;<br>inline Txy mix(Txy x, Txy y, Ta a)<br>{<br>    return x + (y - x) * a;<br>}</pre><h3>textureLod</h3><ul><li>This article is very helpful.</li></ul><blockquote><a href="https://goodpatch-tech.hatenablog.com/entry/raindrop_with_ios17"><em>According to someone who tried to create a raindrop effect for an iOS 17 weather app,</em></a><em><br>in the implementation of the sample function in SwiftUI::Layer, the sampler was fixed without the sampler’s address::clamp_to_edge option, so the layerEffect caused the image to be distorted. There seems to be a problem with not being able to get the size. Therefore, you can solve the problem by preparing your own sampler and directly accessing and applying the texture in layer.tex.</em></blockquote><h3>inout</h3><ul><li>GLSL allows you to use inout by reference.</li></ul><pre>void disk(vec2 r, vec2 center, float radius, vec3 color, inout vec3 pixel) {<br> if( length(r-center) &lt; radius) {<br>  pixel = color;<br> }<br>}</pre><ul><li>On the other hand, in MSL, inout seems difficult, so use a function that returns a return value instead.</li><li><a href="https://developer.apple.com/forums/thread/73269">Even when I saw the story about inout Equivalent</a> , it was so difficult that I couldn’t understand it. .</li></ul><pre>half3 disk(float2 r, float2 center, float radius, half3 color, half3 pixel) {<br>    if( length(r-center) &lt; radius) {<br>        return color;<br>    }<br>    return pixel;<br>}</pre><h3>Argument quick reference table</h3><ul><li>We often pass time and drag position to Shader, so here is a quick reference table.</li></ul><h3>Example of passing elapsed time</h3><pre>struct TimeSampleView: View {<br>    <br>    private let startDate = Date()<br>    <br>    var body: some View {<br>        TimelineView(.animation) { context in<br>            let elapsedTime = context.date.timeIntervalSince1970 - self.startDate.timeIntervalSince1970<br>            RoundedRectangle(cornerRadius: 12)<br>                .foregroundStyle(.black)<br>                .frame(width: 100, height: 100)<br>                .layerEffect(<br>                    ShaderLibrary.default.timeSample(<br>                        .float(elapsedTime)<br>                    ),<br>                    maxSampleOffset: .zero<br>                )<br>        }<br>    }<br>}</pre><pre>#include &lt;metal_stdlib&gt;<br>#include &lt;SwiftUI/SwiftUI_Metal.h&gt;<br>using namespace metal;<br><br><br>[[ stitchable ]] half4 timeSample<br>(<br> float2 position,<br> SwiftUI::Layer layer,<br> float secs<br> ) {<br>    float r = abs(sin(secs * 1.0));<br>    float g = abs(cos(secs * 1.3));<br>    float b = abs(tan(secs * 1.5));<br>    return half4(r, g, b, 1.0);<br>}</pre><h3>Example of passing drag position</h3><ul><li>As a side note, withAnimationthere were some Animations that were not usable, for example, the default .springView was not updated.</li></ul><pre>struct DragGestureSample: View {<br>    <br>    @State private var draggingLocation: CGPoint = .init(x: 50, y: 50)<br>    <br>    var body: some View {<br>        RoundedRectangle(cornerRadius: 12)<br>            .foregroundStyle(.black)<br>            .frame(width: 100, height: 100)<br>            .layerEffect(<br>                ShaderLibrary.default.draggingLocationSample(<br>                    .boundingRect,<br>                    .float2(draggingLocation)<br>                ),<br>                maxSampleOffset: .zero<br>            )<br>            .gesture(<br>                DragGesture(coordinateSpace: .local).onChanged { value in<br>                    draggingLocation = value.location<br>                }.onEnded { value in<br>                    withAnimation(.easeInOut(duration: 0.1)) {<br>                        draggingLocation = .init(x: 50, y: 50)<br>                    }<br>                }<br>            )<br>    }<br>}</pre><ul><li>In GLSL (ShaderToy), the drag position iMousecorresponds to .</li><li>You don’t need to worry about the MSL logic, but positionthe point is that normalization is necessary as well.</li></ul><blockquote><a href="https://note.com/toyoshimorioka/n/nf908ce35d0ea"><em>https://note.com/toyoshimorioka/n/nf908ce35d0ea</em></a><em><br>Shadertoy’s iMouse holds 4 values.</em></blockquote><blockquote><em>“Mouse coordinates XY while the left mouse button is pressed” and “Mouse coordinates XY at the moment the left mouse button is pressed”. The latter part is troublesome, as the XY coordinates become 0 the moment the mouse button stops being pressed.</em></blockquote><pre>half3 disk(float2 r, float2 center, float radius, half3 fillColor, half3 backgroundColor) {<br>    if( length(r-center) &lt; radius) {<br>        return fillColor;<br>    }<br>    return backgroundColor;<br>}<br><br>[[ stitchable ]] half4 draggingLocationSample<br>(<br> float2 _position,<br> SwiftUI::Layer layer,<br> float4 bounds, // x,y,z,w = (x, y, width, height)<br> float2 _draggingLocation<br> ) {<br>    // center nomarized<br>    float2 position = float2(_position.x, bounds.w - _position.y);<br>    float2 draggingLocation = float2(_draggingLocation.x, bounds.w - _draggingLocation.y);<br>    float2 uv =  2.0 * float2(position.xy - 0.5 * bounds.zw ) / bounds.w;<br>    float2 uv_draggingLocation =  2.0 * float2(draggingLocation.xy - 0.5 * bounds.zw ) / bounds.w;<br>    <br>    half3 backgroundColor = half3(0.3);<br>    half3 yellow = half3(1.00, 0.329, 0.298);<br>    half3 pixel = disk(uv, uv_draggingLocation, 0.5, yellow, backgroundColor);<br>    <br>    return half4(pixel, 1.0);<br>}</pre><h3>SwiftUI Metal Shader limitations</h3><ul><li>Currently (as of 2023–11–11) SwiftUI Metal Shader has some limitations.</li></ul><h3>Only compatible with SwiftUI View</h3><ul><li>Currently, SwiftUI’s Metal Shader is only compatible with SwiftUI’s View, and cannot be applied to AppKit/UIKit.</li><li>— Reference: <a href="https://zenn.dev/ikeh1024/articles/3baaa63f91f744">How to check that SwiftUI List is internally UIKit</a></li></ul><blockquote><a href="https://developer.apple.com/documentation/swiftui/view/layereffect(_:maxsampleoffset:isenabled:)"><em>https://developer.apple.com/documentation/swiftui/view/layereffect(_:maxsampleoffset:isenabled:)</em></a><em><br>Important<br>Views backed by AppKit or UIKit views may not render into the filtered layer. Instead, they log a warning and display a placeholder image to highlight the error.</em></blockquote><h3>Only one image can be passed to the Shader</h3><ul><li>Currently, only one Image can be passed to the Shader in arguments.</li></ul><blockquote><a href="https://developer.apple.com/documentation/swiftui/shader/argument/image(_:)"><em>image(_:)</em></a><em><br>Currently only one image parameter is supported per Shader instance.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6e97b7307dc2" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>