HLSL Sprite混合轮廓实现:仅显示外边框并将交集绘为灰色
基于HLSL模板缓冲的Sprite混合外轮廓与交集着色方案
核心思路
通过分Pass渲染结合模板缓冲,精准区分Sprite的非交集填充区、交集灰色区和外轮廓区,完全不需要后期处理就能实现需求。
Step 1:用模板缓冲标记像素覆盖次数
先初始化模板缓冲为0,然后遍历所有Sprite做第一遍渲染:
- 模板操作设为
IncrementSaturation,每绘制一个Sprite,对应像素的模板值+1 - 这一遍只写模板缓冲,不输出可见颜色(透明即可)
HLSL模板配置(以Unity为例,其他引擎逻辑一致):
Stencil { Ref 0 Comp Always Pass IncrementSaturation ZWrite Off } Color = float4(0,0,0,0); // 仅更新模板,不显示内容
Step 2:渲染交集区域(灰色)
针对模板值≥2的像素(即被多个Sprite覆盖的交集部分),渲染你想要的灰色(建议用比基础色深的灰色区分):
Stencil { Ref 2 Comp GreaterEqual Pass Keep ZWrite Off } Color = float4(0.6, 0.6, 0.6, 1.0); // 交集区灰色
Step 3:渲染非交集填充区
针对模板值=1的像素(仅单个Sprite覆盖的区域),渲染原始浅灰色:
Stencil { Ref 1 Comp Equal Pass Keep ZWrite Off } Color = float4(0.8, 0.8, 0.8, 1.0); // 非交集区基础灰色
Step 4:绘制外轮廓(仅整体形状的外边框)
要避免单个Sprite的内部边缘出现轮廓,需要先标记所有Sprite的合并区域,再绘制边缘:
- 先把模板缓冲重置为0,然后重新渲染所有Sprite,将模板值设为1(标记合并区域)
- 渲染一个比原Sprite尺寸稍大的“扩展版”,但只绘制模板值≠1的像素(即合并区域的外部边缘)
标记合并区域的模板配置
Stencil { Ref 1 Comp Always Pass Replace ZWrite Off } Color = float4(0,0,0,0); // 仅更新模板
绘制外轮廓的片元着色逻辑
Stencil { Ref 1 Comp NotEqual Pass Keep ZWrite Off } float4 frag(v2f i) : SV_Target { // 基于UV扩展生成轮廓(也可以用顶点偏移实现) float2 expandedUV = i.uv * 1.05 - 0.025; // 向外扩展5%,居中偏移 // 判断是否在扩展后的边缘范围内 float edgeX = step(0.02, abs(expandedUV.x - 0.5)); float edgeY = step(0.02, abs(expandedUV.y - 0.5)); float isEdge = clamp(edgeX + edgeY, 0, 1); return float4(0,0,0,1.0) * isEdge; // 黑色外轮廓 }
单Pass优化方案(可选)
如果不想分多Pass,可以读取模板缓冲纹理,在片元着色器内一次性判断所有状态:
sampler2D _MainTex; sampler2D _StencilBufferTex; // 引擎导出的模板缓冲纹理 float4 _ScreenParams; float4 frag(v2f i) : SV_Target { float2 uv = i.uv; float stencilVal = tex2D(_StencilBufferTex, uv).r; // 判断交集区 bool isIntersection = stencilVal >= 2; // 判断外边缘:当前在合并区,且至少一个相邻像素不在 float stencilLeft = tex2D(_StencilBufferTex, uv + float2(-1/_ScreenParams.x, 0)).r; float stencilRight = tex2D(_StencilBufferTex, uv + float2(1/_ScreenParams.x, 0)).r; float stencilUp = tex2D(_StencilBufferTex, uv + float2(0, 1/_ScreenParams.y)).r; float stencilDown = tex2D(_StencilBufferTex, uv + float2(0, -1/_ScreenParams.y)).r; bool isOuterEdge = stencilVal == 1 && (stencilLeft == 0 || stencilRight == 0 || stencilUp == 0 || stencilDown == 0); // 输出对应颜色 if(isIntersection) return float4(0.6, 0.6, 0.6, 1.0); else if(isOuterEdge) return float4(0,0,0,1.0); else return float4(0.8, 0.8, 0.8, 1.0); }
注意:部分引擎需要额外配置才能读取模板缓冲到纹理,比如Unity需要设置RenderTexture的模板格式为DepthStencil并启用模板读取。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

