<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Majcher</title>
    <description>The latest articles on DEV Community by Chris Majcher (@chris_majcher_de2b3fa94ff).</description>
    <link>https://dev.to/chris_majcher_de2b3fa94ff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3474615%2Fdc242711-2cc3-4608-adea-6846b54a89ae.jpg</url>
      <title>DEV Community: Chris Majcher</title>
      <link>https://dev.to/chris_majcher_de2b3fa94ff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris_majcher_de2b3fa94ff"/>
    <language>en</language>
    <item>
      <title>Series: Unity GPU Instancing - Learning Out Loud (Part 1 of 4)</title>
      <dc:creator>Chris Majcher</dc:creator>
      <pubDate>Tue, 02 Sep 2025 21:16:12 +0000</pubDate>
      <link>https://dev.to/chris_majcher_de2b3fa94ff/series-unity-gpu-instancing-learning-out-loud-part-1-of-4-1dgf</link>
      <guid>https://dev.to/chris_majcher_de2b3fa94ff/series-unity-gpu-instancing-learning-out-loud-part-1-of-4-1dgf</guid>
      <description>&lt;p&gt;&lt;em&gt;Series: Unity GPU Instancing - Learning Out Loud -&amp;gt; Part 1 of 4&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 - Why Instancing Exists: A Hands-On Baseline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;
We're going to spawn one hundred thousand cubes, profile the pain, flip on Enable GPU Instancing, then add per-renderer colors with MaterialPropertyBlock without breaking instancing. By the end, we'll have screenshots and numbers that explain where the time goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity 6000.2.2f1 Universal 3D project.&lt;/li&gt;
&lt;li&gt;Create a URP Unlit material named &lt;code&gt;Mat_Instanced&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set VSync Count to &lt;code&gt;Don't Sync&lt;/code&gt; (Default)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;Application.targetFrameRate = -1&lt;/code&gt; in a bootstrap script.&lt;/li&gt;
&lt;li&gt;Disable Dynamic Batching for consistent measures.&lt;/li&gt;
&lt;li&gt;Keep Game view Stats, Profiler, and Frame Debugger handy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Project Setup&lt;/strong&gt;&lt;br&gt;
We're going to create an empty scene named &lt;code&gt;Part1_Baseline&lt;/code&gt;. Create a GameObject named &lt;code&gt;InstancingPlayground&lt;/code&gt; and attach the script below. Assign &lt;code&gt;Mat_Instanced&lt;/code&gt; to the &lt;code&gt;baseMaterial&lt;/code&gt; field. For the first test, we want to make sure the material's Enable GPU Instancing checkbox is &lt;em&gt;off&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw0y485dfzxse4xey866.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuw0y485dfzxse4xey866.png" alt="Unity Inspector: Mat_Instanced with Enable GPU Instancing Off" width="396" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experiment A: Baseline with Instancing Off&lt;/strong&gt;&lt;br&gt;
We're going to use the scene as it is now. Ensure that the material's Enable GPU Instancing is &lt;em&gt;off&lt;/em&gt;. Enter Play Mode and record Game view Stats, Profiler Main Thread time, and a Frame Debugger capture. Expecting thousands and thousands of draws.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qz0q9mz2in48nqc8ahs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qz0q9mz2in48nqc8ahs.png" alt="Baseline: Game Scene" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft34furhbevazy7jrj8sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft34furhbevazy7jrj8sp.png" alt="Baseline: Profiler main thread" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5dnnuk766orr9fnxyi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5dnnuk766orr9fnxyi8.png" alt="Baseline: Game view Stats" width="299" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experiment B: Flip on Enable GPU Instancing&lt;/strong&gt;&lt;br&gt;
For this we're going to tick the checkbox on &lt;code&gt;Mat_Instanced&lt;/code&gt;. Repeat the same measurements. Expect lower main thread time, possibly similar draw count though because each renderer still issues a draw.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqe1r0qmrdox7e3dazuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqe1r0qmrdox7e3dazuq.png" alt="Instancing On: Game Scene" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dw5kql8h8oj6chcq9qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dw5kql8h8oj6chcq9qq.png" alt="Instancing On: Profiler main thread" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50yurcnhn887e0l26fne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50yurcnhn887e0l26fne.png" alt="Instancing On: Game view Stats" width="301" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experiment C: Per-Instance Color via MPB&lt;/strong&gt;&lt;br&gt;
Use Shader Graph or the HLSL shader that I've got below and set a per-renderer color with &lt;code&gt;MaterialPropertyBlock&lt;/code&gt; so instancing remains intact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp59g38ht5yp8lizsoi28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp59g38ht5yp8lizsoi28.png" alt="Instancing + MPB: Game Scene" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwwto2qiptgb6pauxxwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwwto2qiptgb6pauxxwy.png" alt="Instancing + MPB: Profiler main thread" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmviebzkyzw6298grgr54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmviebzkyzw6298grgr54.png" alt="Instancing + MPB: Game view Stats" width="301" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1 Results&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;SRP&lt;/th&gt;
&lt;th&gt;Instancing&lt;/th&gt;
&lt;th&gt;Per-Instance Color&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Batches&lt;/th&gt;
&lt;th&gt;SetPass&lt;/th&gt;
&lt;th&gt;Main Thread (ms)&lt;/th&gt;
&lt;th&gt;FPS (avg)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline (GameObjects)&lt;/td&gt;
&lt;td&gt;100000&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;GO&lt;/td&gt;
&lt;td&gt;103324&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;52.2&lt;/td&gt;
&lt;td&gt;19.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GameObjects + Instancing&lt;/td&gt;
&lt;td&gt;100000&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;On&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;GO&lt;/td&gt;
&lt;td&gt;70764&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;46.6&lt;/td&gt;
&lt;td&gt;21.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GameObjects + Instancing + MPB&lt;/td&gt;
&lt;td&gt;100000&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;On&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;GO&lt;/td&gt;
&lt;td&gt;157&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;30.9&lt;/td&gt;
&lt;td&gt;32.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checkbox alone is not magic! Turning on GPU instancing, but keeping everything as separate GameObjects, helped only a little. SetPass stayed the same so the CPU state setup didn't improve.&lt;/li&gt;
&lt;li&gt;Real win came from using instanced properties correctly. When we changed to per-instance color via MaterialPropertyBlock (and the shader marked it as an instanced property), Unity actually combined draws.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What does this mean so far?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1. Enabling GPU Instancing is necessary but not sufficient.&lt;/strong&gt;&lt;br&gt;
Keeping materials/keywords identical and feed variation through instanced properties (MPB or instanced arrays). This allows Unity to automatically instance draws, but, the 157 batches is still getting close to the per pass limit, not including other passes like shadows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. CPU is a big bottleneck when dealing with GameObjects.&lt;/strong&gt;&lt;br&gt;
Even after the huge batch drop, 30.9ms main thread isn't 60FPS. It's overhead from the transform, culling, and renderers from 100k GameObjects. To scale further, we'll need to move to &lt;code&gt;Graphics.DrawMeshInstanced&lt;/code&gt; (Part 2!) and then &lt;em&gt;Indirect&lt;/em&gt; (Part 3)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. SetPass tells the story.&lt;/strong&gt;&lt;br&gt;
No change with the checkbox alone (47 to 47!) means we aren't reducing state binds; the big drop to 24 with MPB shows we're finally reusing the same pass across big instance groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Post - Part 2!&lt;/strong&gt;&lt;br&gt;
We will compare SRP Batcher with and without instancing, then switch to &lt;code&gt;Graphics.DrawMeshInstanced&lt;/code&gt; to achieve real batching.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>unity3d</category>
      <category>performance</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Why I'm Starting this Unity Development Blog (and What We'll Learn Here)</title>
      <dc:creator>Chris Majcher</dc:creator>
      <pubDate>Tue, 02 Sep 2025 04:09:42 +0000</pubDate>
      <link>https://dev.to/chris_majcher_de2b3fa94ff/why-im-starting-this-unity-development-blog-and-what-well-learn-here-51fa</link>
      <guid>https://dev.to/chris_majcher_de2b3fa94ff/why-im-starting-this-unity-development-blog-and-what-well-learn-here-51fa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I have been working as a software developer for over thirteen years and Unity has been one of the foundations for creating both games and interactive applications. From multiplayer Discord activities to AR ghost detectors to full-on VR headset racing in a real car, Unity lets me bring ideas to life quickly. This blog is where I will share what I have learned and am still learning along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Blog Exists
&lt;/h2&gt;

&lt;p&gt;As developers we all run into challenges such as performance bottlenecks, confusing sync bugs, or figuring out how to make user interfaces scale across different devices. I have worked through many of these and I want to document the solutions, both to sharpen my own skills and to save others time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This blog will provide tutorials on performance optimization, Jobs and Burst, rendering techniques, user interface layouts, and general best practices.
&lt;/li&gt;
&lt;li&gt;It will also feature devlogs with behind-the-scenes progress on my Unity projects.
&lt;/li&gt;
&lt;li&gt;Finally it will include insights that I have gained from debugging issues and solving production challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Walkthrough and Tutorial
&lt;/h2&gt;

&lt;p&gt;In upcoming posts I will break down practical steps such as reducing draw calls, building multiplayer systems, integrating AR and VR features, and more. Each article will include code, screenshots, and profiling examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Writing about development inevitably makes you a better developer.
&lt;/li&gt;
&lt;li&gt;Sharing progress builds both community and portfolio.
&lt;/li&gt;
&lt;li&gt;Staying sharp with Unity requires using it consistently, even during quieter periods.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This blog is both a personal devlog and a public resource. My goal is to help Unity developers build smarter and faster, myself included. Over the past six months I have realized that staying hands-on is the only way to avoid rusting over. Expect new posts frequently and hopefully something I share will help you as well. And perhaps I will even improve as a writer along the way.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>programming</category>
      <category>csharp</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
