<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>风牛菜籽 &#187; Unity</title>
	<atom:link href="http://www.caiyanpei.com/category/network-technology/unity-network-technology/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.caiyanpei.com</link>
	<description>拖延症重症患者</description>
	<lastBuildDate>Tue, 25 Mar 2025 01:51:19 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.6.1</generator>
		<item>
		<title>Unity Shader：从“五彩斑斓的黑”到丝滑渲染的调试探索</title>
		<link>http://www.caiyanpei.com/unity_shader_debug_check/</link>
		<comments>http://www.caiyanpei.com/unity_shader_debug_check/#comments</comments>
		<pubDate>Sun, 23 Apr 2023 12:46:26 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[shader]]></category>
		<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[调优]]></category>
		<category><![CDATA[unity]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=653</guid>
		<description><![CDATA[在Unity中编写Shader时，常常会遇到一些令人抓狂的问题：颜色显示异常、光 &#8230; <a href="http://www.caiyanpei.com/unity_shader_debug_check/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity中编写Shader时，常常会遇到一些令人抓狂的问题：颜色显示异常、光照计算错误、性能突然暴跌，甚至直接导致屏幕“五彩斑斓”或完全黑屏。由于Shader代码在GPU上执行，无法像C#一样逐行调试，这让问题定位变得异常困难。本文探讨几个实用调试技巧，一起尝试下驯服“暴躁”的Shader代码。</p>
<hr />
<h1>​<strong>一、基础调试工具：你的Shader显微镜</strong></h1>
<h2>1. ​<strong>假色输出法（False Color Debugging）​</strong></h2>
<p>当计算结果超出预期范围时，将中间值映射为可视颜色是最直接的调试方式。</p>
<p><strong>示例：​</strong></p>
<pre><code>// 将法线向量可视化（范围[-1,1] → [0,1]）
fixed4 frag (v2f i) : SV_Target {
    float3 normal = i.worldNormal * 0.5 + 0.5; // 映射到颜色范围
    return fixed4(normal, 1.0);
}</code></pre>
<p>通过逐步替换输出值，可以快速定位哪一步计算出现了异常。</p>
<h2>2. ​<strong>分阶段注释法</strong></h2>
<p>将复杂的Shader拆分成多个阶段，逐步启用/禁用代码块：</p>
<ul>
<li>注释掉光照计算，仅输出基础颜色</li>
<li>逐步启用法线贴图、高光、阴影等模块</li>
<li>使用宏定义快速切换（如 <code>#define ENABLE_SPECULAR 0</code>）</li>
</ul>
<h2>3. ​<strong>Unity帧调试器（Frame Debugger）​</strong></h2>
<ul>
<li>​<strong>路径：Window &gt; Analysis &gt; Frame Debugger</strong></li>
<li>逐帧查看Draw Call的执行顺序</li>
<li>检查渲染目标（Render Texture）的中间结果</li>
<li>验证Uniform变量（如矩阵、光照参数）是否正确传递</li>
</ul>
<hr />
<h1>​<strong>二、高级武器库</strong></h1>
<h2>1. ​<strong>RenderDoc：GPU级别的侦探工具</strong></h2>
<ul>
<li>​<strong>适用场景</strong>：深度分析渲染管线、纹理采样、缓冲区内容</li>
<li>​<strong>操作步骤</strong>：
<ol>
<li>在Unity中启动游戏并触发问题</li>
<li>使用RenderDoc捕获一帧</li>
<li>检查Shader输入/输出、纹理坐标、顶点数据</li>
</ol>
</li>
<li>​<strong>关键功能</strong>：
<ul>
<li>查看每个像素的历史记录（Pixel History）</li>
<li>动态修改Shader变量并实时预览</li>
</ul>
</li>
</ul>
<h2>2. ​<strong>Shader Variant剥离（Preprocessor魔法）​</strong></h2>
<p>通过自定义预处理指令，快速定位多平台兼容性问题：</p>
<pre><code>#pragma shader_feature _USE_NORMAL_MAP
// ...
#if _USE_NORMAL_MAP
    // 法线贴图相关代码
#endif</code></pre>
<p>在材质面板中切换<code>_USE_NORMAL_MAP</code>开关，观察表现差异。</p>
<h2>3. ​<strong>GPU Instancing的“陷阱”​</strong></h2>
<p>当使用<code>UNITY_INSTANCING_BUFFER_START</code>时，若出现数据错乱：</p>
<ul>
<li>检查<code>unity_WorldTransformParams</code>是否正确处理</li>
<li>在Vertex Shader中输出实例ID，验证数据索引：
<pre><code>float instanceID = (float)unity_InstanceID;
return fixed4(instanceID, 0, 0, 1);</code></pre>
</li>
</ul>
<hr />
<h1>​<strong>三、常见问题急救手册</strong></h1>
<h2>​<strong>1. 颜色全黑/全白？先检查这些！​</strong></h2>
<ul>
<li>​<strong>UV坐标错误</strong>：输出UV为颜色，观察是否超出[0,1]范围</li>
<li>​<strong>法线方向错误</strong>：在片段着色器中返回<code>fixed4(i.normal * 0.5 + 0.5, 1)</code></li>
<li>​<strong>光照向量计算错误</strong>：手动硬编码光照方向测试</li>
<li>​<strong>Alpha通道问题</strong>：检查混合模式（Blend）和深度写入（ZWrite）</li>
</ul>
<h2>​<strong>2. 性能断崖下跌？GPU在“燃烧”什么？</strong></h2>
<ul>
<li>​<strong>过度分支</strong>：避免在片段着色器中使用<code>if</code>语句，改用<code>step()</code>或<code>lerp()</code></li>
<li>​<strong>采样次数爆炸</strong>：合并纹理采样（如使用RGBA通道存储不同数据）</li>
<li>​<strong>精度问题</strong>：将不必要的<code>float</code>改为<code>half</code>或<code>fixed</code></li>
<li>​<strong>隐藏的循环</strong>：检查<code>for</code>循环是否在片段着色器中意外执行多次</li>
</ul>
<h2>​<strong>3. 平台差异：为什么Android和PC表现不同？</strong></h2>
<ul>
<li>​<strong>精度差异</strong>：移动端GPU的<code>half</code>可能只有10位精度</li>
<li>​<strong>纹理压缩格式</strong>：检查ASTC/RGBA32等格式的兼容性</li>
<li>​<strong>ES3.0限制</strong>：避免使用<code>tex2Dlod</code>等需要Shader Model 3.0以上特性的函数</li>
</ul>
<hr />
<h1>​<strong>四、防御性编码</strong></h1>
<h2>1. ​<strong>数学计算的“安全网”​</strong></h2>
<ul>
<li>使用<code>saturate()</code>函数限制数值范围：
<pre><code>float specular = saturate(dot(N, L)); // 避免负数导致意外结果</code></pre>
</li>
<li>除法保护：<code>(a + 1e-5) / (b + 1e-5)</code></li>
</ul>
<h2>2. ​<strong>Debug宏的妙用</strong></h2>
<p>自定义调试宏，快速切换调试模式：</p>
<pre><code>#define DEBUG_NORMAL 1

fixed4 frag (v2f i) : SV_Target {
    #if DEBUG_NORMAL
        return fixed4(i.normal * 0.5 + 0.5, 1);
    #else
        // 正式代码
    #endif
}</code></pre>
<h2>3. ​<strong>版本兼容性声明</strong></h2>
<p>在Shader开头明确声明目标级别：</p>
<pre><code>#pragma target 3.5
#pragma require tessellation</code></pre>
<hr />
<h1>​<strong>五、调试思维</strong></h1>
<ol>
<li>​<strong>最小化复现</strong>：将Shader简化到仅保留问题的最小代码块</li>
<li>​<strong>对比法</strong>：与官方Standard Shader或已知正常Shader对比输入输出</li>
<li>​<strong>边界值测试</strong>：测试UV(0,0)、(1,1)、法线(0,0,1)等极端情况</li>
<li>​<strong>物理合理性检查</strong>：高光是否能量守恒？光照衰减是否符合预期？</li>
</ol>
<hr />
<p>可能用到的工具清单</p>
<ul>
<li><a title="VSCode Shaderlab插件" href="https://marketplace.visualstudio.com/items?itemName=amlovey.shaderlabvscodefree" target="_blank">VSCode Shaderlab</a>插件：支持语法高亮和自动补全</li>
<li><a title="Unity Graphics Test Framework" href="https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/reference-attribute-unitytest.html" target="_blank">Unity Graphics Test Framework</a>：自动化测试Shader表现</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/unity_shader_debug_check/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Unity Shader优化策略：提升性能与效果的关键</title>
		<link>http://www.caiyanpei.com/unity-shader-optimized/</link>
		<comments>http://www.caiyanpei.com/unity-shader-optimized/#comments</comments>
		<pubDate>Sat, 12 Nov 2022 13:25:40 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[shader]]></category>
		<category><![CDATA[unity]]></category>
		<category><![CDATA[优化]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=647</guid>
		<description><![CDATA[在Unity开发中，Shader是渲染效果的核心，但复杂的Shader往往会带来 &#8230; <a href="http://www.caiyanpei.com/unity-shader-optimized/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity开发中，Shader是渲染效果的核心，但复杂的Shader往往会带来性能问题。为了在保证视觉效果的同时提升性能，Shader优化成为了我们平常必须要理解应用的一环。本文主要探讨Unity中Shader优化的策略。</p>
<hr />
<h1>一、Shader优化的重要性</h1>
<p>Shader是GPU执行的程序，负责计算每个像素的颜色和光照效果。复杂的Shader会导致以下问题：</p>
<ul>
<li>​<strong>GPU负载过高</strong>：帧率下降，游戏卡顿。</li>
<li>​<strong>发热和耗电</strong>：移动设备性能瓶颈。</li>
<li>​<strong>渲染效率低</strong>：影响整体游戏体验。</li>
</ul>
<p>因此，Shader优化不仅是提升性能的关键，也是保证游戏流畅运行的必要手段。</p>
<hr />
<h1>二、Shader优化的核心策略</h1>
<h2>1. ​<strong>减少计算复杂度</strong></h2>
<ul>
<li>​<strong>避免不必要的计算</strong>：在Shader中只计算真正需要的值。例如，如果不需要法线贴图，就不要计算法线相关的值。</li>
<li>​<strong>简化数学运算</strong>：使用低精度的数据类型（如<code>half</code>代替<code>float</code>），减少复杂的数学运算（如<code>pow</code>、<code>sin</code>、<code>cos</code>等）。</li>
<li>​<strong>分支优化</strong>：尽量避免在Shader中使用<code>if</code>语句，因为GPU对分支处理效率较低。可以使用<code>step</code>或<code>lerp</code>等函数替代。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>// 不推荐
if (value &gt; 0.5) {
    color = red;
} else {
    color = blue;
}

// 推荐
color = lerp(blue, red, step(0.5, value));</code></pre>
<h2>2. ​<strong>优化纹理采样</strong></h2>
<ul>
<li>​<strong>减少纹理采样次数</strong>：每次纹理采样都会消耗性能，尽量减少采样次数。例如，将多个纹理合并为一张纹理（纹理图集）。</li>
<li>​<strong>使用Mipmaps</strong>：启用Mipmaps可以减少远处纹理的采样复杂度，提升性能。</li>
<li>​<strong>优化纹理格式</strong>：根据需求选择合适的纹理格式（如ETC2、ASTC），减少显存占用。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>// 不推荐
float4 color1 = tex2D(_MainTex, uv1);
float4 color2 = tex2D(_DetailTex, uv2);

// 推荐
float4 color = tex2D(_CombinedTex, uv);</code></pre>
<h2>3. ​<strong>优化光照计算</strong></h2>
<ul>
<li>​<strong>使用简化光照模型</strong>：在移动设备上，可以使用Lambert或Blinn-Phong等简化光照模型，而不是复杂的PBR模型。</li>
<li>​<strong>烘焙光照</strong>：将静态物体的光照信息烘焙到光照贴图中，减少实时计算。</li>
<li>​<strong>减少实时光源</strong>：尽量减少场景中的实时光源数量，使用点光源或聚光灯替代平行光。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>// 不推荐（复杂PBR）
float3 brdf = CalculateBRDF(normal, viewDir, lightDir);

// 推荐（简化Blinn-Phong）
float3 diffuse = max(dot(normal, lightDir), 0.0) * _LightColor;
float3 specular = pow(max(dot(reflectDir, viewDir), 0.0), _Gloss) * _SpecularColor;</code></pre>
<h2>4. ​<strong>减少顶点着色器计算</strong></h2>
<ul>
<li>​<strong>将计算移到片段着色器</strong>：如果某些计算在顶点着色器和片段着色器中重复，可以将它们移到片段着色器中，减少顶点着色器的负载。</li>
<li>​<strong>优化顶点数据</strong>：减少顶点数据的数量，例如使用压缩的顶点格式。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>// 不推荐（在顶点着色器中计算光照）
v2f vert(appdata v) {
    v2f o;
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.normal = UnityObjectToWorldNormal(v.normal);
    o.lightDir = normalize(_WorldSpaceLightPos0.xyz - o.worldPos);
    return o;
}

// 推荐（在片段着色器中计算光照）
v2f vert(appdata v) {
    v2f o;
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.normal = UnityObjectToWorldNormal(v.normal);
    return o;
}</code></pre>
<h2>5. ​<strong>使用Shader变体</strong></h2>
<ul>
<li>​<strong>减少变体数量</strong>：使用<code>#pragma multi_compile</code>或<code>#pragma shader_feature</code>生成多个Shader变体，避免不必要的功能启用。</li>
<li>​<strong>剔除无用变体</strong>：在Shader中根据平台或功能需求，剔除无用的变体。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>#pragma multi_compile _ _USE_DETAIL
#ifdef _USE_DETAIL
    float4 detailColor = tex2D(_DetailTex, uv);
    color *= detailColor;
#endif</code></pre>
<h2>6. ​<strong>使用GPU Instancing</strong></h2>
<ul>
<li>​<strong>减少Draw Call</strong>：对于大量相同的物体，使用GPU Instancing可以减少Draw Call，提升性能。</li>
<li>​<strong>优化材质设置</strong>：启用<code>Enable GPU Instancing</code>选项，确保材质支持实例化。</li>
</ul>
<p>​<strong>示例：​</strong></p>
<pre><code>#pragma multi_compile_instancing
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)</code></pre>
<hr />
<h1>三、工具与调试</h1>
<h2>1. ​<strong>Frame Debugger</strong></h2>
<ul>
<li>使用Unity的Frame Debugger分析每一帧的渲染过程，找出性能瓶颈。</li>
</ul>
<h2>2. ​<strong>Shader性能分析</strong></h2>
<ul>
<li>使用工具（如RenderDoc、Xcode GPU Frame Capture）分析Shader的性能。</li>
</ul>
<h2>3. ​<strong>Profiler</strong></h2>
<ul>
<li>使用Unity Profiler监控GPU和CPU的负载，定位性能问题。</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/unity-shader-optimized/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用 ETC2、ASTC、PVRTC三种纹理压缩技术的区别</title>
		<link>http://www.caiyanpei.com/%e4%bd%bf%e7%94%a8-etc2%e3%80%81astc%e3%80%81pvrtc%e4%b8%89%e7%a7%8d%e7%ba%b9%e7%90%86%e5%8e%8b%e7%bc%a9%e6%8a%80%e6%9c%af%e7%9a%84%e5%8c%ba%e5%88%ab/</link>
		<comments>http://www.caiyanpei.com/%e4%bd%bf%e7%94%a8-etc2%e3%80%81astc%e3%80%81pvrtc%e4%b8%89%e7%a7%8d%e7%ba%b9%e7%90%86%e5%8e%8b%e7%bc%a9%e6%8a%80%e6%9c%af%e7%9a%84%e5%8c%ba%e5%88%ab/#comments</comments>
		<pubDate>Sat, 16 May 2020 13:51:22 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[日志]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=592</guid>
		<description><![CDATA[ETC2、ASTC 和 PVRTC 三种不同的纹理压缩技术，主要用在移动设备和游 &#8230; <a href="http://www.caiyanpei.com/%e4%bd%bf%e7%94%a8-etc2%e3%80%81astc%e3%80%81pvrtc%e4%b8%89%e7%a7%8d%e7%ba%b9%e7%90%86%e5%8e%8b%e7%bc%a9%e6%8a%80%e6%9c%af%e7%9a%84%e5%8c%ba%e5%88%ab/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>ETC2、ASTC 和 PVRTC 三种不同的纹理压缩技术，主要用在移动设备和游戏平台上减少纹理的内存占用和提高性能。</p>
<p>1. ETC2（Ericsson Texture Compression 2）：<br />
ETC2 是一种由 Ericsson 开发的纹理压缩格式，适用于大多数 Android 设备。<br />
它支持不同的压缩质量，包括 ETC2_RGBA8、ETC2_RGB8、ETC2_RGBA1 以及 ETC2_RGB8A1 等。<br />
ETC2 压缩质量较高，可以提供相对较好的画质。</p>
<p>2. ASTC（Adaptive Scalable Texture Compression）：<br />
ASTC 是一种由 ARM 开发的高级纹理压缩格式，支持广泛的质量和压缩率。<br />
ASTC 适用于支持 OpenGL ES 3.0 及更高版本的设备，以及支持 Metal API 的 iOS 设备。<br />
ASTC 提供了更高的灵活性，可以在不同纹理上获得更好的压缩比例和画质。</p>
<p>3. PVRTC（PowerVR Texture Compression）：<br />
PVRTC 是由 Imagination Technologies 开发的纹理压缩格式，主要用于设备采用 PowerVR GPU 的 iOS 设备。<br />
PVRTC 分为 PVRTC1 和 PVRTC2，PVRTC2 在画质和压缩率上都有提升。<br />
PVRTC 对于图像的细节较少的情况下，可以实现较好的压缩效果。但在某些情况下可能会出现一些失真。</p>
<p>区别总结：<br />
ETC2 适用于大多数 Android 设备，提供较高的画质和压缩率。<br />
ASTC 支持广泛的质量和压缩率，适用于 OpenGL ES 3.0 及更高版本的设备，以及支持 Metal API 的 iOS 设备。<br />
PVRTC 主要用于 PowerVR GPU 的 iOS 设备，可以在细节较少的情况下获得较好的压缩效果。</p>
<p>在选择纹理压缩技术时，需要考虑目标平台的支持和性能要求，以及画质需求。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/%e4%bd%bf%e7%94%a8-etc2%e3%80%81astc%e3%80%81pvrtc%e4%b8%89%e7%a7%8d%e7%ba%b9%e7%90%86%e5%8e%8b%e7%bc%a9%e6%8a%80%e6%9c%af%e7%9a%84%e5%8c%ba%e5%88%ab/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Unity中的C#委托与事件</title>
		<link>http://www.caiyanpei.com/unity_delegate_event/</link>
		<comments>http://www.caiyanpei.com/unity_delegate_event/#comments</comments>
		<pubDate>Tue, 21 Apr 2020 12:55:28 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[日志]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=633</guid>
		<description><![CDATA[在Unity开发中，C#的委托（Delegate）​和事件（Event）​就像一 &#8230; <a href="http://www.caiyanpei.com/unity_delegate_event/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity开发中，C#的<strong>委托（Delegate）​</strong>和<strong>事件（Event）​</strong>就像一对双胞胎兄弟，曾困惑于它们的本质区别。本文将通过实战案例揭开这对&#8221;孪生兄弟&#8221;的神秘面纱，了解它们的正确使用姿势。</p>
<h1>一、委托：灵活的函数指针</h1>
<h2>1.1 委托的本质</h2>
<p>委托本质上是一个类型安全的函数指针容器，可以存储多个方法引用。我们可以通过+=运算符实现多播委托：</p>
<pre class="brush: cpp; title: ; notranslate">
public delegate void HealthHandler(float health);

public class Player {
    public HealthHandler OnHealthChanged;

    public void TakeDamage(float damage) {
        currentHealth -= damage;
        OnHealthChanged?.Invoke(currentHealth);
    }
}
</pre>
<h2>1.2 委托的妙用场景</h2>
<ul>
<li>实现回调系统（如成就解锁）</li>
<li>跨脚本通信（非MonoBehaviour类间通信）</li>
<li>异步操作处理（如资源加载完成回调）</li>
</ul>
<h1>二、事件：安全的委托封装器</h1>
<h2>2.1 事件的核心特征</h2>
<p>事件在委托基础上添加了访问控制层，使用<code>event</code>关键字声明：</p>
<pre class="brush: cpp; title: ; notranslate">
public class Achievements {
    public event Action OnAchievementUnlocked;

    private void Unlock(string achievementName) {
        OnAchievementUnlocked?.Invoke(achievementName);
    }
}
</pre>
<h2>2.2 事件的安全机制</h2>
<ul>
<li>封装性：外部类只能订阅/取消订阅（+=/-=）</li>
<li>访问控制：仅声明类可触发事件</li>
<li>线程安全：编译器自动生成线程同步代码</li>
</ul>
<h1>三、关键差异对比表</h1>
<div>
<table>
<thead>
<tr>
<th>特性</th>
<th>委托</th>
<th>事件</th>
</tr>
</thead>
<tbody>
<tr>
<td>访问权限</td>
<td>公共可调用</td>
<td>仅声明类可触发</td>
</tr>
<tr>
<td>赋值操作</td>
<td>支持=赋值</td>
<td>仅支持+=/-=</td>
</tr>
<tr>
<td>多播能力</td>
<td>原生支持</td>
<td>通过委托机制支持</td>
</tr>
<tr>
<td>空引用检查</td>
<td>需手动检查</td>
<td>自动生成空检查代码</td>
</tr>
<tr>
<td>设计目的</td>
<td>通用回调机制</td>
<td>观察者模式实现</td>
</tr>
<tr>
<td>典型应用场景</td>
<td>函数参数、回调列表</td>
<td>UI交互、游戏事件通知</td>
</tr>
</tbody>
</table>
</div>
<h1>四、Unity实战案例解析</h1>
<h2>4.1 委托实现技能冷却系统</h2>
<pre class="brush: cpp; title: ; notranslate">
public class SkillSystem {
    public delegate void CooldownCallback(int skillID);
    public CooldownCallback OnCooldownStart;

    public void CastSkill(int skillID) {
        // 释放技能逻辑
        OnCooldownStart?.Invoke(skillID);
    }
}

// 使用端
skillSystem.OnCooldownStart += id =&gt; Debug.Log($&quot;技能{id}开始冷却&quot;);
</pre>
<h2>4.2 事件实现成就系统</h2>
<pre class="brush: cpp; title: ; notranslate">
public class AchievementSystem {
    public event Action&lt;string&gt; OnAchievementCompleted;

    public void CompleteAchievement(string name) {
        OnAchievementCompleted?.Invoke(name);
    }
}

// 订阅端
achievementSystem.OnAchievementCompleted += name =&gt; {
    PlayEffect(name);
    UpdateUI(name);
};
</pre>
<h1>五、设计选择指南</h1>
<p>✅ ​<strong>使用委托当：​</strong></p>
<ul>
<li>需要函数作为参数传递时</li>
<li>实现回调函数列表时</li>
<li>需要直接控制调用时</li>
</ul>
<p>✅ ​<strong>使用事件当：​</strong></p>
<ul>
<li>构建发布-订阅系统时</li>
<li>需要保护触发权限时</li>
<li>处理UI交互事件时</li>
<li>实现系统解耦时</li>
</ul>
<h1>六、性能优化贴士</h1>
<ol>
<li>避免每帧触发高频事件（如Update中的物理检测）</li>
<li>及时取消订阅防止内存泄漏</li>
<li>对高频事件使用EventManager集中管理</li>
<li>值类型参数优先使用ref传递</li>
<li>使用对象池重用事件参数</li>
</ol>
<h1>结语</h1>
<p>理解委托和事件的区别就像掌握魔法世界中的两种咒语：委托是灵活的基础咒语，而事件则是施加了保护咒的强化版本。在Unity开发中，建议80%的场景使用事件，仅在需要完全控制调用时使用原始委托。记住：​<strong>事件不是委托的替代品，而是其安全封装形态</strong>。合理运用这对黄金组合，可以让游戏代码既优雅又高效！</p>
<div data-conv-index="1" data-speech-index="0"></div>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/unity_delegate_event/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Unity中的Shader魔法</title>
		<link>http://www.caiyanpei.com/unity_shader_magic/</link>
		<comments>http://www.caiyanpei.com/unity_shader_magic/#comments</comments>
		<pubDate>Thu, 21 Nov 2019 12:52:13 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[shader]]></category>
		<category><![CDATA[unity]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=644</guid>
		<description><![CDATA[在Unity的世界里，Shader就像是给游戏对象施法的魔杖，它能让平凡的3D模 &#8230; <a href="http://www.caiyanpei.com/unity_shader_magic/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity的世界里，Shader就像是给游戏对象施法的魔杖，它能让平凡的3D模型焕发出惊人的视觉效果。本文探讨Unity中Shader的奥秘，从基础概念到应用，了解下这门强大的图形编程艺术。</p>
<h1>一、揭开神秘面纱</h1>
<h2>1.1 什么是Shader？</h2>
<p>Shader是运行在GPU上的小程序，专门处理图形渲染的各个阶段。它决定了物体如何被绘制到屏幕上，控制着颜色、光照、纹理等视觉效果。</p>
<h2>1.2 Shader的类型</h2>
<ul>
<li>​<strong>表面着色器（Surface Shader）​</strong>：Unity的高层抽象，适合初学者</li>
<li>​<strong>顶点/片段着色器（Vertex/Fragment Shader）​</strong>：更底层的控制</li>
<li>​<strong>固定函数着色器（Fixed Function Shader）​</strong>：旧式硬件兼容</li>
<li>​<strong>计算着色器（Compute Shader）​</strong>：用于通用计算任务</li>
</ul>
<h1>二、创建第一个Shader</h1>
<h2>2.1 使用Shader Graph</h2>
<p>Unity的Shader Graph是可视化Shader编程工具，适合美术和程序员协作：</p>
<ol>
<li>创建Shader Graph资源</li>
<li>添加主纹理节点</li>
<li>连接颜色输出</li>
<li>调整光照模型</li>
<li>生成最终Shader</li>
</ol>
<h2>2.2 手撸Shader代码</h2>
<p>对于更高级的控制，可以直接编写Shader代码：</p>
<pre><code>Shader "Custom/SimpleDiffuse" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}</code></pre>
<h1>三、Shader进一步</h1>
<h2>3.1 实现卡通渲染（Toon Shading）</h2>
<ul>
<li>使用法线贴图增强细节</li>
<li>实现边缘检测（Edge Detection）</li>
<li>创建颜色分级（Color Ramp）</li>
</ul>
<pre><code>Shader "Toon/Basic" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _Ramp ("Ramp Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Toon

        // 实现Toon光照模型
        // 使用Ramp纹理进行颜色分级
        ENDCG
    }
    FallBack "Diffuse"
}</code></pre>
<h2>3.2 创建水效果Shader</h2>
<ul>
<li>使用法线贴图模拟水面波纹</li>
<li>实现折射和反射效果</li>
<li>添加深度效果（Depth Fade）</li>
</ul>
<pre><code>Shader "Water/Basic" {
    Properties {
        _MainTex ("Wave Texture", 2D) = "white" {}
        _BumpMap ("Normal Map", 2D) = "bump" {}
        _ReflectionTex ("Reflection", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Water

        // 实现水特效
        // 处理折射、反射
        // 添加动画效果
        ENDCG
    }
    FallBack "Transparent/Diffuse"
}</code></pre>
<h1>四、Shader优化策略</h1>
<ol>
<li>​<strong>减少纹理采样</strong>：合并纹理或使用纹理图集</li>
<li>​<strong>优化数学运算</strong>：使用低精度数据类型</li>
<li>​<strong>减少分支语句</strong>：尽量避免if-else语句</li>
<li>​<strong>利用GPU并行性</strong>：设计适合并行计算的结构</li>
<li>​<strong>使用Shader变体</strong>：针对不同情况生成优化版本</li>
</ol>
<h1>五、Shader调试</h1>
<ol>
<li>​<strong>使用Frame Debugger</strong>：逐步分析渲染过程</li>
<li>​<strong>添加调试输出</strong>：返回特定颜色值检查中间结果</li>
<li>​<strong>使用Profiler</strong>：分析Shader性能瓶颈</li>
<li>​<strong>Shader错误日志</strong>：检查编译错误和警告</li>
<li>​<strong>可视化工具</strong>：如AMD GPU PerfStudio</li>
</ol>
<h1>六、现代渲染管线</h1>
<h2>6.1 通用渲染管线（URP）</h2>
<ul>
<li>轻量级、高性能</li>
<li>适合移动平台和VR</li>
<li>支持Shader Graph</li>
</ul>
<h2>6.2 高清渲染管线（HDRP）</h2>
<ul>
<li>高质量、高保真</li>
<li>适合PC和主机平台</li>
<li>先进的物理效果</li>
</ul>
<h1>七、实现昼夜循环Shader</h1>
<ol>
<li>创建天空盒材质</li>
<li>编写Shader控制太阳位置</li>
<li>实现光照渐变</li>
<li>添加星星和月亮效果</li>
<li>控制云层运动</li>
</ol>
<pre><code>Shader "Skybox/Procedural" {
    Properties {
        _SunColor ("Sun Color", Color) = (1,1,1,1)
        _HorizonColor ("Horizon Color", Color) = (0.5,0.5,0.5,1)
        // 其他属性...
    }
    SubShader {
        Tags { "Queue"="Background" "RenderType"="Background" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // 实现昼夜循环逻辑
            ENDCG
        }
    }
}</code></pre>
<h1>结语</h1>
<p>Shader编程是Unity开发中很有创造性和挑战性的领域之一。通过掌握Shader技术，以为游戏带来令人惊叹的视觉效果。优秀的Shader不仅需要技术功底，更需要艺术眼光和创造力。从简单的漫反射到复杂的水面效果，每一步都是对图形编程艺术的探索。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/unity_shader_magic/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>unity帧率优化</title>
		<link>http://www.caiyanpei.com/unity%e5%b8%a7%e7%8e%87%e4%bc%98%e5%8c%96/</link>
		<comments>http://www.caiyanpei.com/unity%e5%b8%a7%e7%8e%87%e4%bc%98%e5%8c%96/#comments</comments>
		<pubDate>Tue, 06 Jun 2017 12:41:13 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[日志]]></category>
		<category><![CDATA[unity]]></category>
		<category><![CDATA[优化]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=586</guid>
		<description><![CDATA[在Unity中，帧率（Frame Rate）是指每秒钟渲染的帧数，常用单位为FP &#8230; <a href="http://www.caiyanpei.com/unity%e5%b8%a7%e7%8e%87%e4%bc%98%e5%8c%96/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity中，帧率（Frame Rate）是指每秒钟渲染的帧数，常用单位为FPS（Frames Per Second）。游戏开发过程中，如果帧率低了，可能就是受到以下因素的影响：</p>
<p>1. 渲染时间：每帧的渲染时间取决于场景中的物体数量、复杂度以及使用的特效和着色器等。渲染时间过长会导致帧率下降。</p>
<p>2. CPU性能：CPU负责执行游戏逻辑、物理模拟、AI计算等任务。如果CPU性能不足，可能无法在每帧的时间限制内完成必要的计算，导致帧率下降。</p>
<p>3. GPU性能：GPU负责处理图形渲染任务。如果GPU性能不足，无法及时处理渲染指令，导致帧率下降。</p>
<p>4. 游戏逻辑复杂度：复杂的游戏逻辑、AI计算、碰撞检测等任务会消耗CPU的计算资源，影响帧率。</p>
<p>帧率对游戏的运行流畅度有重要影响。较高的帧率能够提供更流畅的动画和交互体验，而较低的帧率可能导致卡顿、延迟响应和不流畅的动画效果。</p>
<p>几个可考优化方案：</p>
<p>1. 减少渲染开销：通过减少复杂的特效、减少细节或使用合理的LOD系统来减少渲染负载，从而提高帧率。</p>
<p>2. 优化代码性能：通过优化游戏逻辑、减少资源加载和销毁、使用对象池等技术来减少CPU计算开销。</p>
<p>3. 使用批处理技术：通过合并渲染操作，减少Draw Call的数量，从而提高GPU性能。</p>
<p>4. 适当使用线程：将耗时的计算任务放在单独的线程中执行，避免阻塞主线程，提高CPU利用率。</p>
<p>5. 使用合理的资源管理：合理使用纹理压缩、资源压缩和内存优化技术，减少内存占用和加载时间。</p>
<p>6. 避免过度渲染：根据场景需要，避免无意义的渲染操作，如遮挡物体、避免渲染在屏幕外的物体等。</p>
<p>7. 使用性能分析工具：利用Unity提供的性能分析工具，如Profiler，可以定位性能瓶颈并进行针对性的优化。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/unity%e5%b8%a7%e7%8e%87%e4%bc%98%e5%8c%96/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>u3d的drawcall几个注意的地方</title>
		<link>http://www.caiyanpei.com/u3d%e7%9a%84drawcall%e5%87%a0%e4%b8%aa%e6%b3%a8%e6%84%8f%e7%9a%84%e5%9c%b0%e6%96%b9/</link>
		<comments>http://www.caiyanpei.com/u3d%e7%9a%84drawcall%e5%87%a0%e4%b8%aa%e6%b3%a8%e6%84%8f%e7%9a%84%e5%9c%b0%e6%96%b9/#comments</comments>
		<pubDate>Fri, 19 May 2017 12:33:50 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[日志]]></category>
		<category><![CDATA[drawcall]]></category>
		<category><![CDATA[unity]]></category>

		<guid isPermaLink="false">http://www.xiuchezu.com/?p=570</guid>
		<description><![CDATA[在Unity中，Draw Call是指渲染器在绘制场景中的每个独立物体或图元时发 &#8230; <a href="http://www.caiyanpei.com/u3d%e7%9a%84drawcall%e5%87%a0%e4%b8%aa%e6%b3%a8%e6%84%8f%e7%9a%84%e5%9c%b0%e6%96%b9/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在Unity中，Draw Call是指渲染器在绘制场景中的每个独立物体或图元时发出的绘制命令。Draw Call的生成大致由以下因素决定的：</p>
<p>1. 独立物体：每个独立的物体（GameObject）通常需要一个单独的Draw Call来进行渲染。例如，如果场景中有100个不同的物体，则会生成100个Draw Call。</p>
<p>2. 材质（Material）：每个材质都会产生一个Draw Call。如果多个物体使用相同的材质，则它们可以合并为一个Draw Call。</p>
<p>3. 纹理（Texture）：每个使用不同纹理的物体也会产生额外的Draw Call。如果多个物体使用相同纹理，则它们可以合并为一个Draw Call。</p>
<p>Draw Call数量的增加会对游戏的运行流畅度产生影响，因为每个Draw Call都需要CPU和GPU的处理，过多的Draw Call会增加渲染的开销。较高的Draw Call数量可能导致以下问题：</p>
<p>1. CPU开销：每个Draw Call都需要一定的CPU开销，包括物体和材质的设置、状态切换等。当Draw Call数量过多时，CPU将花费更多的时间来处理渲染命令，可能降低游戏的帧率。</p>
<p>2. GPU开销：在GPU端，每个Draw Call都需要进行渲染资源切换和状态设置。大量的Draw Call会增加GPU的负载，可能导致性能瓶颈，如卡顿和低帧率。</p>
<p>为了优化Draw Call，常见的有以下几个优化方案：</p>
<p>1. 合批（Batching）：尽量减少独立物体和材质的数量，以减少Draw Call的生成。使用相同材质的物体可以合并为一个Draw Call，可以使用静态批处理（Static Batching）或动态批处理（Dynamic Batching）来实现。</p>
<p>2. 材质合并（Material Merging）：如果多个物体使用相似的材质，可以将它们合并为一个材质，以减少Draw Call的数量。</p>
<p>3. 纹理图集（Texture Atlas）：将多个小纹理合并为一个大纹理图集，可以减少纹理切换和Draw Call的数量。</p>
<p>4. GPU Instancing：对于使用相同网格和材质的大量物体，可以使用GPU Instancing来减少Draw Call的数量。GPU Instancing允许多个物体同时渲染，只产生一个Draw Call。</p>
<p>5. 动态LOD（Level of Detail）：根据物体的距离和屏幕尺寸，使用不同级别的细节模型（LOD）来减少多余的绘制。通过动态LOD，可以减少不必要的Draw Call。</p>
<p>6. 静态合并（Static Mesh Combining）：将多个静态网格合并为一个网格，以减少Draw Call的数量。适用于静态不可变的场景元素。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/u3d%e7%9a%84drawcall%e5%87%a0%e4%b8%aa%e6%b3%a8%e6%84%8f%e7%9a%84%e5%9c%b0%e6%96%b9/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lua 实现多层继承</title>
		<link>http://www.caiyanpei.com/lua-%e5%ae%9e%e7%8e%b0%e5%a4%9a%e5%b1%82%e7%bb%a7%e6%89%bf/</link>
		<comments>http://www.caiyanpei.com/lua-%e5%ae%9e%e7%8e%b0%e5%a4%9a%e5%b1%82%e7%bb%a7%e6%89%bf/#comments</comments>
		<pubDate>Fri, 05 Jun 2015 13:32:21 +0000</pubDate>
		<dc:creator>tsai</dc:creator>
				<category><![CDATA[lua]]></category>
		<category><![CDATA[Unity]]></category>

		<guid isPermaLink="false">http://www.caiyanpei.com/?p=624</guid>
		<description><![CDATA[在 Lua 中实现多层继承的方法与实现多重继承的方法类似。可以通过使用基类和派生 &#8230; <a href="http://www.caiyanpei.com/lua-%e5%ae%9e%e7%8e%b0%e5%a4%9a%e5%b1%82%e7%bb%a7%e6%89%bf/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>在 Lua 中实现多层继承的方法与实现多重继承的方法类似。可以通过使用基类和派生类的方式来实现多层继承。以下是一个示例：</p>
<pre class="brush: jscript; title: ; notranslate">
-- 基类
local BaseClass = {}

function BaseClass:new()
local newObj = {}
setmetatable(newObj, self)
self.__index = self
return newObj
end

function BaseClass:baseMethod()
print(&quot;BaseClass baseMethod&quot;)
end

-- 派生类1
local DerivedClass1 = BaseClass:new()

function DerivedClass1:derivedMethod1()
print(&quot;DerivedClass1 derivedMethod1&quot;)
end

-- 派生类2
local DerivedClass2 = DerivedClass1:new()

function DerivedClass2:derivedMethod2()
print(&quot;DerivedClass2 derivedMethod2&quot;)
end
</pre>
<p>在上面的示例中，`BaseClass` 是基类，`DerivedClass1` 是从 `BaseClass` 派生的第一层派生类，`DerivedClass2` 是从 `DerivedClass1` 派生的第二层派生类。</p>
<p>现在，你可以创建对象并调用相应的方法：</p>
<pre class="brush: jscript; title: ; notranslate">
local obj = DerivedClass2:new()
obj:baseMethod() -- 调用基类方法
obj:derivedMethod1() -- 调用第一层派生类方法
obj:derivedMethod2() -- 调用第二层派生类方法
</pre>
<p>输出结果：</p>
<pre class="brush: jscript; title: ; notranslate">
BaseClass baseMethod
DerivedClass1 derivedMethod1
DerivedClass2 derivedMethod2
</pre>
<p>通过创建派生类并在每一层中调用 `new()` 方法来实现多层继承。每一层的派生类都可以访问其上一层的方法和属性，从而形成了多层继承的效果。</p>
<p>需要注意的是，多层继承可能增加代码的复杂性，并且容易出现命名冲突。因此，在设计和使用多层继承时，要仔细考虑和管理类之间的关系和命名空间，以避免潜在的问题。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.caiyanpei.com/lua-%e5%ae%9e%e7%8e%b0%e5%a4%9a%e5%b1%82%e7%bb%a7%e6%89%bf/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
