You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的合并区域,再绘制边缘:

  1. 先把模板缓冲重置为0,然后重新渲染所有Sprite,将模板值设为1(标记合并区域)
  2. 渲染一个比原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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:13:12