关于outline的一点测试和总结

本文深入探讨Oracle数据库中的Outlines特性,包括其工作原理、如何创建和使用Outlines,以及与之相关的参数设置。通过实际测试案例,展示了不同场景下Outlines的应用效果,同时提供了注意事项,帮助读者更好地理解和运用这一特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 原文链接  个人博客 http://www.killdb.com/?p=182

我们知道outlines 特性在oracle 8i就引入了,不过我用的很少,今天同事问到了,
我也就再回顾温习一下,如下是做的简单测试。
首先先来看2个跟outline相关的参数:
create_stored_outlines ---控制是否自动创建outline
use_stored_outlines    ---控制是否启用outline

使用outline的方式有很多种,列出如下几种方式:
1. 针对sql语句或sqlid
SQL> conn roger/roger
Connected.
SQL> alter session set create_stored_outlines = true;
Session altered.
SQL> select * from v$version where rownum <2;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Prod
SQL>
SQL> create outline test_id for CATEGORY test_outlines on 
  2  select owner,object_id from test_ht where object_id=:p;
Outline created.
SQL>  select name,category,sql_text from user_outlines where category=upper('test_outlines');
NAME      CATEGORY          SQL_TEXT                                              
--------- ----------------- -------------------------------------------------------
TEST_ID   TEST_OUTLINES     select owner,object_id from test_ht where object_id=:p
SQL> select * from user_outline_hints where name=upper('test_id');
NAME    NODE  STAGE JOIN_POS HINT                                                           
------- ---- ------ -------- ----------------------------------------------------------------
TEST_ID    1      1        1 INDEX_RS_ASC(@"SEL$1" "TEST_HT"@"SEL$1" ("TEST_HT"."OBJECT_ID"))
TEST_ID    1      1        0 OUTLINE_LEAF(@"SEL$1")
TEST_ID    1      1        0 ALL_ROWS
TEST_ID    1      1        0 OPTIMIZER_FEATURES_ENABLE('10.2.0.4')
TEST_ID    1      1        0 IGNORE_OPTIM_EMBEDDED_HINTS
SQL> select name,category,used from user_outlines where category=upper('test_outlines');
NAME            CATEGORY                       USED
--------------- ------------------------------ ------
TEST_ID         TEST_OUTLINES                  UNUSED
SQL>  --该outline未使用
SQL> var p number;
SQL> exec :p :=1000;
PL/SQL procedure successfully completed.
SQL> select name,category,used from user_outlines where category=upper('test_outlines');
NAME            CATEGORY                       USED
--------------- ------------------------------ ------
TEST_ID         TEST_OUTLINES                  UNUSED
SQL>
---设置参数use_stored_outlines
SQL> var p number;
SQL> exec :p :=10000;
PL/SQL procedure successfully completed.
SQL> select owner,object_id from test_ht where object_id=:p;
OWNER                           OBJECT_ID
------------------------------ ----------
WMSYS                               10000
SQL>  select name,category,used from user_outlines where category=upper('test_outlines');
NAME            CATEGORY                       USED
--------------- ------------------------------ ------
TEST_ID         TEST_OUTLINES                  UNUSED
SQL> alter session set use_stored_outlines=TEST_OUTLINES
  2  ;
Session altered.
SQL>  var p number;
SQL> exec :p :=20000;
PL/SQL procedure successfully completed.
SQL> select owner,object_id from test_ht where object_id=:p;
OWNER                           OBJECT_ID
------------------------------ ----------
SYS                                 20000
SQL>
SQL> select name,category,used from user_outlines where category=upper('test_outlines');
NAME            CATEGORY                       USED
--------------- ------------------------------ ------
TEST_ID         TEST_OUTLINES                  USED
SQL>
SQL> select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,object_id%';
SQL_ID        SQL_TEXT
------------- ---------------------------------------------------------------------------
3v6z2kwca0ttm select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,ob
              ject_id%'
45s1gxyr1y5k3 select owner,object_id from test_ht where object_id=:p
6cc34dzmkg686 create table test_ht as select owner,object_id,object_name from dba_objects
SQL> select * from table(dbms_xplan.DISPLAY_CURSOR('&sql_id',null));
Enter value for sql_id: 45s1gxyr1y5k3
old   1: select * from table(dbms_xplan.DISPLAY_CURSOR('&sql_id',null))
new   1: select * from table(dbms_xplan.DISPLAY_CURSOR('45s1gxyr1y5k3',null))
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
SQL_ID  45s1gxyr1y5k3, child number 0
-------------------------------------
select owner,object_id from test_ht where object_id=:p
Plan hash value: 793292976
---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_HT |     1 |     9 |     2   (0)| 00:00:01 |
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
|*  2 |   INDEX RANGE SCAN          | IDX_ID  |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID"=:P)
SQL_ID  45s1gxyr1y5k3, child number 1
-------------------------------------
select owner,object_id from test_ht where object_id=:p
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Plan hash value: 793292976
---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_HT |     1 |     9 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_ID  |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID"=:P)
Note
-----
   - outline "TEST_ID" used for this statement
42 rows selected.
SQL>
----对于存在的cursor创建outline
SQL> select owner,object_id,object_name from test_ht where object_id=3000;
OWNER                           OBJECT_ID
------------------------------ ----------
OBJECT_NAME
------------------------------------------------------------------
SYS                                  3000
EXU8SYNU
SQL> select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,object_id%';
SQL_ID        SQL_TEXT
------------- ---------------------------------------------------------------------------
3v6z2kwca0ttm select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,ob
              ject_id%'
8gz444rhg594f select owner,object_id,object_name from test_ht where object_id=3000
SQL> select hash_value, child_number, sql_text from v$sql where sql_text like '%select owner,object_id%';
HASH_VALUE CHILD_NUMBER SQL_TEXT
---------- ------------ ---------------------------------------------------------------------------
 413165363            0 select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,ob
                        ject_id%'
  47485093            0 select hash_value, child_number, sql_text from v$sql where sql_text like '%
                        select owner,object_id%'
3773998222            0 select owner,object_id,object_name from test_ht where object_id=3000
SQL> exec dbms_outln.create_outline(3773998222,0);
PL/SQL procedure successfully completed.
SQL>
SQL> select owner,object_id,object_name from test_ht where object_id=3000;
OWNER                           OBJECT_ID OBJECT_NAME
------------------------------ ---------- --------------------
SYS                                  3000 EXU8SYNU
SQL> select hash_value, child_number, sql_text from v$sql where sql_text like '%select owner,object_id%';
HASH_VALUE CHILD_NUMBER SQL_TEXT
---------- ------------ ---------------------------------------------------------------------------
 413165363            0 select sql_id,sql_text from v$sqlarea where sql_text like '%select owner,ob
                        ject_id%'
  47485093            0 select hash_value, child_number, sql_text from v$sql where sql_text like '%
                        select owner,object_id%'
3773998222            0 select owner,object_id,object_name from test_ht where object_id=3000
SQL> select * from table(dbms_xplan.display_cursor('8gz444rhg594f'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------
SQL_ID  8gz444rhg594f, child number 0
-------------------------------------
select owner,object_id,object_name from test_ht where object_id=3000
Plan hash value: 793292976
---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_HT |     1 |    33 |     2   (0)| 00:00:01 |
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------
|*  2 |   INDEX RANGE SCAN          | IDX_ID  |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID"=3000)
Note
-----
   - outline "SYS_OUTLINE_11071210544304523" used for this statement
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------
23 rows selected.
SQL> select name,CATEGORY,USED,SQL_TEXT from user_outlines
  2  where name='SYS_OUTLINE_11071210544304523';
NAME                            CATEGORY        USED   SQL_TEXT
------------------------------- --------------- ------ ---------------------------------------------------------------------------
SYS_OUTLINE_11071210544304523   DEFAULT         USED   select owner,object_id,object_name from test_ht where object_id=3000
SQL>
这里需要说明一下的是alter session set create_stored_outlines = true;这是由于bug5454975的缘故(10204已经修复)
虽然我这里是10204,不过我还是设置了一下,列出来说明,以提醒大家。详见metalink ID 445126.1
最后再补充一下,如果不用outline,那么可以将其删除,可以通过如下的几种方式:
execute DBMS_OUTLN.drop_by_cat('TEST_OUTLINES');
execute DBMS_OUTLN.CLEAR_USED('TEST_OUTLINES');
execute DBMS_OUTLN.drop_unused; ---这是删除所有状态为unused的outline,要慎重。
SQL> show parameter outline
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
_outline_bitmap_tree                 boolean     TRUE
_plan_outline_data                   boolean     TRUE
create_stored_outlines               string
SQL>
关于outlines的使用,有几个需要注意的地方:
1.  参数cursor_sharing设置为force时,outlines将无效;
2.  literial sql的共享程度不高的情况下,使用outline会生产很多个执行计划,可能会有一些问题;
3.  一般情况我们在使用outline的时候,也是发现某个sql的执行计划不稳定的时候,由于执行计划是基于
统计信息的,那么由于生产系统中统计信息可能是在不断的变化,那么使用outline固定的执行计划不见得
一定就是最好的,这一点需要考虑。
4.  由于outlines信息的存放在用户outln下,那么该用户就显得尤为重要,维护的时候需要注意,不能随便
给drop了。
5.  outline创建以后,不是说就一层不变了,可以进行编辑,至于什么时候编辑,怎么编辑,大家可以参考
metalink 文档730062.1 How to Edit a Stored Outline to Use the Plan from Another Stored Outline。

转载于:https://www.cnblogs.com/lovewifelovelife/archive/2011/08/28/3295137.html

<think>我们被要求使用Godot Shader实现圆角描边效果。根据引用[2],我们知道Godot可以通过Next Pass来实现描边效果,即两次渲染:一次渲染主物体,一次渲染描边。但是,用户要求的是圆角描边,这意味着描边应该是圆滑的,而不是直角的。 思路: 1. 我们可以使用两个Pass:第一个Pass渲染主体,第二个Pass渲染描边。 2. 描边的圆角效果可以通过在片元着色器中对距离进行平滑处理来实现。 具体步骤: 首先,我们需要在材质中设置两个Pass: - 第一个Pass(主材质)正常渲染物体。 - 第二个Pass(描边材质)用于渲染描边,并且我们会在这个Pass中实现圆角效果。 在描边Pass中,我们通常会扩大物体(例如通过顶点外扩)来形成轮廓,然后在片元着色器中,我们根据像素的位置与原始边界的距离来调整颜色,以实现圆角效果。 圆角效果可以通过计算每个片段到原始形状(比如矩形)边界的距离,然后使用平滑函数(如smoothstep)来实现圆角。 但是,请注意,我们的描边Pass需要知道原始形状的边界信息。对于矩形,我们可以使用UV坐标来帮助计算。 假设我们要为一个矩形节点(如ColorRect)实现圆角描边效果。我们可以使用CanvasItem着色器。 步骤: 1. 创建一个主材质(用于物体本身)。 2. 创建一个描边材质,并设置为主材质的Next Pass。 在描边材质的着色器中,我们将: - 在顶点着色器中,将顶点向外扩展一定距离(描边宽度)。 - 在片元着色器中,计算当前片元在原始矩形(未扩展)中的位置,然后计算到矩形边界的距离,并使用这个距离来绘制圆角描边。 注意:圆角描边效果要求描边的边缘是圆滑的,所以我们在描边的着色器中,除了实现描边外,还要在描边的边缘部分进行圆角化处理。 具体实现: 在描边Pass的着色器中: - 顶点着色器:将顶点沿着法线方向(或者简单地向外扩展)移动,以形成描边。 - 片元着色器:计算当前片元在原始矩形内部的距离(即到矩形四条边的距离),然后取最小值。然后我们使用这个距离来绘制一个圆角矩形,并且只保留描边宽度的部分。 但是,我们还需要在描边中实现圆角,因此我们需要计算到矩形四个角的距离,并利用圆角半径来调整。 然而,为了简化,我们可以先计算到矩形四条边的距离,然后取最小值,然后使用这个最小距离圆角半径来绘制圆角矩形。 另一种思路:在片元着色器中,我们使用SDF(有符号距离场)来绘制一个圆角矩形,然后只绘制在描边宽度范围内的部分。 参考步骤(描边Pass的片元着色器): 1. 计算当前片元在原始矩形中的相对位置(即相对于矩形中心,或者使用UV坐标)。 2. 计算到矩形四条边的距离,然后取最小值(即到矩形边界的距离)。 3. 然后,我们使用这个距离减去圆角半径,再与描边宽度进行比较,以实现圆角描边。 但是,由于我们要的是描边(即一个环状),所以我们需要绘制的是原始矩形向外扩展一定宽度形成的边框,并且这个边框的角是圆滑的。 我们可以这样: 定义: - 矩形的实际大小(在UV空间中,原始矩形为(0,0)到(1,1)) - 圆角半径(r) - 描边宽度(w) 在扩展后的矩形(即原始矩形向外扩展w)中,我们计算每个片元到原始矩形的距离(d)。如果d在0到w之间,则绘制描边颜色,并且为了圆角,我们在距离原始矩形角点一定范围内进行圆滑处理。 然而,更常用的方法是使用SDF函数来绘制一个带圆角的矩形边框。 这里有一个SDF函数用于圆角矩形(有符号距离场): // 参数:点p,矩形大小b(从中心到边的距离),圆角半径r float roundedBoxSDF(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; } 注意:这个函数返回的是有符号距离。正值表示在矩形外部,负值表示在内部。 在描边Pass中,我们将顶点扩展了描边宽度w(即向外扩展w),所以整个矩形的尺寸变成了原始尺寸+2w(在UV空间中,原始矩形为(0,0)到(1,1))。 但是,在着色器中,我们通常使用归一化的坐标。我们可以将UV坐标映射到以矩形中心为原点的坐标系,并且范围是(-0.5, 0.5)或者(-1,1)?这取决于我们如何设置。 为了方便,我们假设UV坐标是(0,0)到(1,1)。那么我们可以将坐标转换到中心: vec2 center = UV - vec2(0.5); vec2 size = vec2(0.5); // 因为从中心到边界的距离是0.5 原始矩形的大小(从中心到边界)是vec2(0.5, 0.5)。现在,因为我们扩展了w,所以整个矩形的大小变成了vec2(0.5+w, 0.5+w)?但是注意,顶点扩展是在顶点着色器中进行的,所以我们在片元着色器中处理的已经是扩展后的矩形。 我们需要计算当前片元在扩展后的矩形中的位置相对于原始矩形(未扩展)的SDF。但是我们希望描边是原始矩形外w宽度的边框,并且圆角半径为r。 因此,我们可以这样: - 定义原始矩形的半尺寸(half_size)为vec2(0.5) - 定义扩展后的矩形的半尺寸为half_size + w(注意:w需要转换为UV空间的单位,即w_pixels / texture_size,但通常我们使用相对单位) 然而,为了简单,我们可以将描边宽度w定义为一个相对值(0到1之间),这样在UV空间中,扩展w个单位。 步骤: 1. 在顶点着色器中,我们将顶点向外扩展w(在屏幕空间或UV空间?)。这里我们可以在顶点着色器中将顶点位置沿法线方向扩展,但如果是矩形,我们可以直接扩展顶点位置(因为矩形顶点有四个方向)。 2. 在片元着色器中,我们计算当前点(在扩展后的矩形中)到原始矩形的有符号距离。然后,我们利用这个距离描边宽度以及圆角半径来绘制圆角描边。 具体着色器代码: 主材质的着色器(简单示例): shader_type canvas_item; void fragment() { COLOR = texture(TEXTURE, UV); // 如果是ColorRect,可能没有纹理,可以直接设置颜色 // 或者使用uniform颜色 } 描边材质的着色器: shader_type canvas_item; // 定义描边颜色 uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); // 红色描边 // 描边宽度(以UV单位,相对值) uniform float outline_width : hint_range(0, 0.5) = 0.05; // 圆角半径(以UV单位,相对值) uniform float corner_radius : hint_range(0, 0.5) = 0.1; // 圆角矩形的SDF函数 float roundedBoxSDF(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; } void fragment() { // 将UV坐标转换到中心为原点的坐标系,范围:(-0.5,0.5)到(0.5,0.5) vec2 center = UV - vec2(0.5); // 原始矩形的半尺寸(从中心到边的距离) vec2 half_size = vec2(0.5) - outline_width; // 注意:这里我们减去outline_width是因为描边在外部,原始矩形内部区域需要减去描边宽度?不对。 // 实际上,原始矩形(主矩形)的半尺寸是0.5(在UV空间中,从中心到边界的距离是0.5)。但是,在描边Pass中,整个矩形已经被扩展了outline_width,所以当前片元可能落在原始矩形外部。 // 我们想要绘制的是原始矩形外部到扩展宽度outline_width之间的区域,并且这个区域是圆角的。 // 因此,我们计算当前点center到原始矩形(半尺寸为0.5)的SDF,但注意,原始矩形的半尺寸应该是0.5,而圆角半径我们使用corner_radius。 // 但是,在描边Pass中,我们扩展了outline_width,所以整个矩形的半尺寸变成了0.5+outline_width?不对,顶点扩展时,我们向外扩展了outline_width,所以半尺寸应该是0.5+outline_width。 // 然而,SDF函数中,我们需要传入原始矩形的半尺寸(即0.5)圆角半径(corner_radius),然后我们计算当前点相对于原始矩形的距离。 // 注意:SDF函数返回的是到原始矩形(半尺寸0.5)的距离,正值表示在外部,负值表示在内部。 float d = roundedBoxSDF(center, vec2(0.5), corner_radius); // 现在,我们想要的是在原始矩形外部,并且距离在0到outline_width之间的区域。 // 但是,这样画出来的描边在角上会是直角,因为SDF函数已经考虑了圆角,所以这个距离d在角上会自然形成圆角。 // 使用smoothstep来平滑边缘(可选,也可以直接使用step) // 我们取距离d在0到outline_width之间的部分,注意d>0表示在外部,d<0表示在内部(我们不需要内部)。 // 我们只绘制d在0到outline_width之间的部分,并且d>outline_width的部分不绘制(透明)。 // 但是,为了圆角描边的平滑,我们可以使用smoothstep来抗锯齿: float t = smoothstep(0.0, outline_width, d); // 这样t在0到outline_width之间是从0到1的渐变,但是这样画出来的是从原始边界到扩展边界渐变的描边。如果我们想要实心描边,可以: // float t = step(0.0, d) * step(d, outline_width); // 但是这样没有抗锯齿 // 但是注意:d是到原始矩形的距离,当d在0到outline_width之间时,我们绘制描边颜色。 // 然而,我们想要的是描边颜色,并且内部透明(因为主材质已经渲染了内部)。所以,我们设置颜色为outline_color,并根据t来混合。 // 但是,我们这里需要的是:当d在0到outline_width之间时,颜色为outline_color,并且当d接近0时,我们希望有一定的过渡(抗锯齿),所以使用smoothstep。 // 但是注意:smoothstep(0, outline_width, d)会在d<0时返回0,d>outline_width时返回1,而我们想要的是d>0且d<outline_width时返回0到1的渐变,但这里我们实际需要的是反过来的:在d=0处为1,d=outline_width处为0?不对,因为d=0是原始边界,d=outline_width是描边外边界。 // 因此,我们调整: float alpha = 1.0 - smoothstep(0.0, outline_width, d); // 这样,在d=0时,alpha=1,d=outline_width时,alpha=0,中间渐变。 // 但是,这样画出来的描边,在原始矩形内部(d<0)也会被绘制(因为d<0时,smoothstep(0,outline_width,d)返回0,所以alpha=1),但我们不希望绘制内部,所以需要将内部剔除。 // 因此,我们只绘制d>0的部分: if (d < 0.0) { discard; // 或者设置alpha=0 // 或者我们可以用max(d,0.0)来调整,但这里我们直接: } // 或者我们可以这样计算: // float alpha = smoothstep(-outline_width, 0.0, d) * (1.0 - smoothstep(0.0, outline_width, d)); // 这样会得到一个从-d到0再到d的双向渐变?但我们只需要外部。 // 更简单的方法:我们只需要d>0的部分,并且d<outline_width的部分,然后从1(在d=0)渐变到0(在d=outline_width): // 因为d>0,所以我们可以: // alpha = 1.0 - (d / outline_width); // 但是这样没有抗锯齿。 // 我们使用: alpha = smoothstep(outline_width, 0.0, d); // 当d从0增加到outline_width时,alpha从1降到0 // 但是,这个函数在d<0时,会返回大于1的值(因为0.0>outline_width?不对,smoothstep(a,b,x)要求a<b,所以这里a=outline_width, b=0.0,这样写是错误的) // 正确的做法:使用clamp // 或者我们换一种思路:我们想要的是在d=0处为1,在d=outline_width处为0,中间平滑过渡。所以: alpha = 1.0 - smoothstep(0.0, outline_width, d); // 但是,当d<0时,smoothstep(0,outline_width,d)返回0,所以alpha=1,但我们不要内部,所以: alpha *= step(0.0, d); // 这样d<0时alpha=0 // 但是这样在d=0处会有突变?我们可以用smoothstep来平滑d<0的部分: // 或者我们允许在原始边界附近有一点过渡(包括内部一点),这样抗锯齿效果更好: // 我们可以将d的范围从(-aa, outline_width+aa)进行平滑,其中aa是抗锯齿的宽度。 // 更健壮的方法: float aa = 0.01; // 抗锯齿宽度,可以根据需要调整 alpha = smoothstep(-aa, aa, d) * (1.0 - smoothstep(outline_width-aa, outline_width+aa, d)); // 这样,在d=-aa到aa之间,smoothstep(-aa,aa,d)从0到1,在d=outline_width-aa到outline_width+aa之间,1-smoothstep从1到0。 // 但是这样会绘制到内部一点,但通常抗锯齿需要这样。 // 我们选择一种方法,这里使用第一种简单方法(只绘制外部,并渐变)加上抗锯齿: // 计算d在0到outline_width之间的比例,然后进行平滑 // alpha = smoothstep(0.0, outline_width, d); // 这个不对,它得到的是0到1,但是0在d=0,1在d=outline_width,我们想要反的 // 所以: alpha = 1.0 - smoothstep(0.0, outline_width, d); // 然后加上抗锯齿,我们可以在边界处用fwidth(d)来动态计算抗锯齿,但为了简单,我们使用固定值: alpha = smoothstep(0.0, 0.01, d) * alpha; // 这样在d=0附近,smoothstep(0,0.01,d)会从0到1,乘以原来的alpha(在d=0时为1,d=outline_width时为0) // 或者更简单:直接使用clamp线性渐变,然后加上抗锯齿函数smoothstep(边缘处)。 // 我们这里采用: // 只绘制d>0的部分,并且d<outline_width的部分,然后使用抗锯齿: if (d < 0.0) { alpha = 0.0; } else if (d > outline_width) { alpha = 0.0; } else { // 线性渐变 alpha = 1.0 - (d / outline_width); // 或者使用平滑 // alpha = smoothstep(outline_width, 0.0, d); // 但是这样写参数顺序不对,因为smoothstep要求edge0<edge1 // 所以写成:alpha = 1.0 - smoothstep(0.0, outline_width, d); } // 但是这样没有抗锯齿,边缘会有锯齿。 // 因此,我们使用smoothstep来抗锯齿: float in_aa = 0.0; float out_aa = outline_width; alpha = smoothstep(in_aa, in_aa+0.01, d) * (1.0 - smoothstep(out_aa-0.01, out_aa, d)); // 最后,设置颜色 COLOR = outline_color; COLOR.a *= alpha; } 但是,上面的SDF函数计算的是到原始矩形的距离,而原始矩形是圆角的(corner_radius>0)。所以,我们只需要在SDF函数中使用我们传入的圆角半径即可。 注意:圆角半径corner_radius不能大于矩形最小边的一半(即0.5,因为半尺寸为0.5)。 另外,顶点着色器部分:我们如何扩展顶点? 在CanvasItem着色器中,顶点着色器默认有VERTEX变量,代表顶点位置(在屏幕空间)。我们可以通过修改VERTEX来扩展。 但是,对于矩形,我们可以根据顶点的位置(在四个角)分别向外扩展。但是,我们也可以使用法线,然而Godot的矩形顶点没有法线信息。我们可以使用UV来推断方向。 一个简单的方法:在顶点着色器中,将顶点从中心向外扩展。我们可以计算每个顶点相对于中心的方向向量,然后向外移动。 顶点着色器(描边Pass): void vertex() { // 计算中心(在屏幕空间) vec2 center = (vec2(0.5,0.5) - UV) * TEXTURE_PIXEL_SIZE; // 注意:这里可能需要调整,因为我们不知道TEXTURE_PIXEL_SIZE?对于ColorRect,可能没有纹理。 // 另一种方法:我们直接使用模型空间(即VERTEX)的中心。假设矩形中心在(0,0)?不一定,因为Godot的矩形节点中心默认在左上角?或者中心?这取决于节点的锚点。 // 为了简单,我们假设矩形节点的中心在0.5*SIZE(即锚点在0.5),所以中心位置是position + 0.5*size。 // 但是,在顶点着色器中,我们不知道整个矩形的大小。所以,我们可以使用UV来推断方向。 // 每个顶点的UV坐标是(0,0),(0,1),(1,0),(1,1)。我们可以计算从中心(UV=vec2(0.5))到当前顶点的方向向量。 vec2 dir = UV - vec2(0.5); // 归一化,但注意在四个角上,方向向量长度不为1,所以我们需要归一化吗?不需要,因为我们只需要方向,扩展是固定距离。 // 但是,非归一化的dir在角上会大于1,所以我们需要归一化?或者我们直接扩展固定距离(在屏幕空间)? // 由于我们扩展的单位是outline_width(UV单位),所以我们可以直接: dir = normalize(dir); // 归一化,这样每个方向扩展的距离相同(但这样在角上会扩展成圆形,而不是矩形扩展) // 但是,我们想要的是矩形扩展(即四个方向分别扩展),而不是圆形扩展。 // 对于矩形,我们应该分别沿xy方向扩展。所以,我们可以根据UV计算每个顶点在xy方向上需要扩展的方向。 // 一个更好的方法:我们分别计算xy方向上的扩展量。因为矩形扩展应该是水平垂直的,而不是径向的。 // 因此,我们根据UV的xy分量分别判断: // 在左边界,UV.x=0,我们向左扩展;右边界向右扩展;上边界向上;下边界向下。 // 但是,一个顶点可能同时处于两个边界(角点),所以我们需要同时扩展两个方向。 // 具体:扩展向量 = vec2( (UV.x<0.5 ? -1 : 1) * outline_width_screen, (UV.y<0.5 ? -1 : 1) * outline_width_screen ) // 但是,在UV空间中,outline_width是相对值,我们需要转换为屏幕空间的偏移量。 // 在CanvasItem顶点着色器中,我们可以使用PROJECTION_MATRIXMODELVIEW_MATRIX将UV空间的偏移转换为屏幕空间?或者,我们可以直接用像素单位。 // 我们可以将outline_width定义为像素值,然后转换为屏幕空间坐标。 // 为了简单,我们假设outline_width是UV空间的相对值(0~1),那么扩展的偏移量就是dir * outline_width,但是注意,矩形扩展应该是轴向的(axis-aligned),所以我们应该分别计算xy的偏移。 // 然而,圆角扩展并不严格要求轴向,但通常描边是均匀的。所以,我们可以使用径向扩展,这样在角上自然形成圆角。 // 因此,我们使用归一化的dir,然后乘以outline_width(在UV空间)来扩展。 // 但是,UV空间的1个单位对应屏幕空间是多少?我们需要将UV空间转换为屏幕空间。 // 在顶点着色器中,我们可以使用TEXTURE_PIXEL_SIZE来转换,但是ColorRect可能没有纹理。所以,我们可以使用屏幕像素大小。 // 另一种方法:我们使用uniform定义描边宽度的像素值,然后在顶点着色器中,将顶点沿着法线方向(或者我们这里用dir方向)扩展。 // 由于我们使用UV空间,所以我们可以这样: // 扩展向量 = dir * outline_width * TEXTURE_SIZE; // 但是,我们不知道TEXTURE_SIZE,因为ColorRect没有纹理。 // 因此,我们可能需要使用屏幕像素单位来定义描边宽度。 // 修改:我们使用像素单位定义描边宽度,然后在顶点着色器中将顶点沿法线方向扩展。 // 但是,矩形节点的顶点没有法线。我们可以自己计算:对于矩形,每个顶点的法线可以指向外部(即从中心指向顶点)。 // 在顶点着色器中: // 计算中心到当前顶点的向量,然后归一化,作为法线方向。 vec2 normal = normalize((UV - vec2(0.5)) * vec2(1.0, SCREEN_PIXEL_SIZE.x/SCREEN_PIXEL_SIZE.y)); // 考虑宽高比?因为UV是归一化的,但屏幕像素可能不是正方形。 // 但是,我们不需要归一化?因为扩展应该是均匀的(在屏幕空间中)。 // 我们使用像素单位: uniform float outline_width_px = 10.0; // 描边宽度,单位:像素 // 扩展方向:我们使用顶点到中心的方向,并乘以outline_width_px(转换为屏幕空间坐标) // 屏幕空间坐标:我们需要将像素值转换为实际坐标。在Godot中,可以使用`vec2(outline_width_px) / SCREEN_PIXEL_SIZE`来转换为屏幕空间偏移量。 // 扩展向量 = normal * outline_width_px / SCREEN_PIXEL_SIZE; // 但是,SCREEN_PIXEL_SIZE是vec2(1.0/屏幕宽度, 1.0/屏幕高度),所以: vec2 expand = normal * outline_width_px * SCREEN_PIXEL_SIZE; // 但是,这样扩展后,矩形会变成圆形扩展(因为法线是径向的),但矩形扩展应该是轴向的。所以,我们这里使用径向扩展,这样在角上会自然形成圆角,符合我们的需求。 VERTEX += expand; } 但是,上面的顶点扩展方法在矩形上使用径向扩展,会导致矩形被扩展成一个类似圆角矩形(扩展后的形状是原始矩形的等距偏移,即圆角矩形)。这正好符合我们的圆角描边需求。 因此,我们结合: 顶点着色器:将顶点沿中心到顶点的方向(法线)扩展outline_width_px像素。 片元着色器:使用圆角矩形的SDF函数,并计算当前片元到原始矩形(圆角)的距离,然后根据距离绘制描边。 但是,注意:在片元着色器中,我们计算SDF时使用的坐标center是UV空间,而顶点扩展是在屏幕空间,这会导致不一致。因为UV坐标没有改变,所以center仍然是相对于原始矩形的位置。 所以,我们需要在片元着色器中考虑顶点扩展后的UV?不,UV坐标是纹理坐标,它不会随着顶点扩展而改变。因此,我们仍然可以使用原始的UV坐标来计算SDF。 也就是说,在片元着色器中,我们使用原始的UV(0,0)到(1,1)来计算,而顶点扩展只是让这个矩形在屏幕上变大了,但UV坐标不变。这符合我们的需求,因为我们计算SDF时,原始矩形的UV范围还是(0,0)到(1,1)。 因此,我们不需要在片元着色器中做额外调整。 但是,顶点扩展后,整个矩形变大了,所以UV坐标覆盖的区域还是0~1,但实际屏幕上的矩形变大了。这并不影响我们计算SDF,因为SDF计算是基于UV坐标的(即相对位置)。 所以,最终代码: 主材质(简单示例): shader_type canvas_item; uniform vec4 main_color : source_color = vec4(0.0, 0.0, 1.0, 1.0); // 蓝色 void fragment() { // 如果是ColorRect,可能没有纹理,我们直接使用颜色 COLOR = main_color; } 描边材质(作为Next Pass): shader_type canvas_item; render_mode unshaded; // 避免受光照影响 uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); // 红色描边 uniform float outline_width_px : hint_range(0, 50) = 10.0; // 描边宽度,像素 uniform float corner_radius : hint_range(0, 0.5) = 0.1; // 圆角半径,UV单位 // 圆角矩形的SDF函数 float roundedBoxSDF(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; } void vertex() { // 计算中心到当前顶点的向量(在UV空间) vec2 dir = UV - vec2(0.5); // 归一化,注意:当dir为0时归一化会出问题,但UV为0.5的情况只会在中心,而矩形顶点不会在中心。 // 考虑宽高比:我们希望在屏幕空间中扩展是均匀的(圆形),所以需要将dir转换为屏幕空间的方向。 // 因为UV空间是正方形的,但屏幕可能不是,所以我们需要调整y方向: // 获取屏幕宽高比 float aspect = SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y; // 注意:SCREEN_PIXEL_SIZE = vec2(1.0/screen_width, 1.0/screen_height) // 所以,aspect = (1.0/screen_width) / (1.0/screen_height) = screen_height/screen_width // 因此,在y方向上,我们需要乘以aspect来补偿? // 不对,因为UV空间的y方向0~1对应屏幕高度,x对应宽度。所以,在屏幕空间中,UV的y方向单位长度对应的像素比x方向多(当屏幕宽>高时,aspect<1)。 // 我们希望在屏幕空间中,扩展是圆形,所以我们需要将UV空间的dir转换为屏幕空间: // 屏幕空间的方向 = vec2(dir.x, dir.y * aspect); // 然后归一化: vec2 screen_dir = normalize(vec2(dir.x, dir.y * aspect)); // 然后,扩展向量 = screen_dir * outline_width_px * SCREEN_PIXEL_SIZE; vec2 expand = screen_dir * outline_width_px * SCREEN_PIXEL_SIZE; VERTEX += expand; } void fragment() { vec2 center = UV - vec2(0.5); // 原始矩形的半尺寸(UV空间) vec2 half_size = vec2(0.5); float d = roundedBoxSDF(center, half_size, corner_radius); // 我们只绘制d>0的部分(在原始矩形外部),并且d<描边宽度(在UV空间)?注意,我们在顶点着色器中扩展了outline_width_px像素,但d是UV空间的距离,所以我们需要将描边宽度转换为UV空间的距离。 // 但是,d是UV空间的距离,而我们的扩展是屏幕空间的。所以,我们需要将屏幕空间的扩展宽度转换为UV空间的距离。 // 转换:屏幕空间的扩展宽度(outline_width_px)对应UV空间的距离 = outline_width_px * SCREEN_PIXEL_SIZE.x / (1.0) ?因为UV空间的1.0对应屏幕上的纹理宽度(即节点的宽度,单位像素)的像素数。 // 节点的宽度像素 = 1.0 / SCREEN_PIXEL_SIZE.x ? 不对,SCREEN_PIXEL_SIZE.x是1.0/屏幕宽度,而节点的宽度是节点在屏幕上的像素宽度。 // 在Godot中,我们可以使用TEXTURE_PIXEL_SIZE,但ColorRect可能没有纹理。所以,我们使用节点的尺寸:在着色器中,我们可以使用`vec2 node_size = 1.0 / SCREEN_PIXEL_SIZE;` 但是,这是整个屏幕的尺寸,不是节点的。 // 在CanvasItem中,我们可以使用`texture_size`,但ColorRect没有纹理,所以不可用。 // 因此,我们只能将描边宽度在UV空间的值作为uniform传递,或者我们换一种思路:在片元着色器中,我们使用屏幕空间来计算SDF。 // 但是,为了简单,我们假设节点覆盖整个屏幕?不,显然不是。 // 另一种方法:我们不在片元着色器中使用UV空间,而是使用屏幕空间的坐标。但是,这样就需要知道原始矩形在屏幕空间的位置尺寸。 // 这变得复杂了。因此,我们放弃使用SDF,而是采用另一种方法:在片元着色器中,我们计算到原始矩形(在屏幕空间)的距离,然后绘制圆角描边。 // 或者,我们接受在UV空间中计算,并且将描边宽度在UV空间中的值作为uniform传递,由用户设置(或者通过GDScript根据描边宽度像素值计算后设置)。 // 我们添加一个uniform:outline_width_uv,表示在UV空间中,描边的宽度(根据节点大小描边宽度像素值计算)。 // 在GDScript中,我们可以这样设置: // material.set_shader_parameter("outline_width_uv", outline_width_px / rect_size.x) # 假设宽度方向,取x方向计算 // 因此,我们在着色器中添加: uniform float outline_width_uv : hint_range(0, 0.5) = 0.05; // 然后,在片元着色器中,d就是UV空间的有符号距离,而描边宽度在UV空间就是outline_width_uv。 float alpha = 0.0; if (d > 0.0 && d < outline_width_uv) { // 线性渐变 alpha = 1.0 - (d / outline_width_uv); } // 加上抗锯齿 float aa = 0.01; // 抗锯齿宽度(UV空间) alpha = smoothstep(-aa, aa, d) * (1.0 - smoothstep(outline_width_uv-aa, outline_width_uv+aa, d)); COLOR = outline_color; COLOR.a = alpha; } 但是,这样就需要用户在GDScript中设置outline_width_uv,比较麻烦。 另一种思路:在顶点着色器中,我们扩展了顶点,那么在片元着色器中,我们只需要绘制扩展后的矩形中不在原始矩形内的部分,并且这部分中,我们根据圆角半径来圆滑角。 我们可以使用UV坐标直接计算: - 在原始矩形内部(UV在0.5±(0.5 - corner_radius)的矩形区域内)的部分,我们直接 discard。 - 在角上,我们计算到角点的距离,如果大于圆角半径,则 discard,否则保留。 但是,这种方法比较繁琐。 考虑到时间,我们采用SDF方法,并通过GDScript设置outline_width_uv。 因此,最终方案: 主材质:正常渲染矩形。 描边材质(Next Pass): - 顶点着色器:径向扩展顶点(屏幕空间)。 - 片元着色器:使用UV空间的SDF计算距离,然后绘制描边,其中描边宽度为outline_width_uv(由外部设置)。 注意:圆角半径corner_radius也是UV空间的。 我们编写完整的描边Pass着色器代码: shader_type canvas_item; render_mode unshaded; // 避免受光照影响 uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); uniform float outline_width_uv : hint_range(0, 0.5) = 0.05; uniform float corner_radius : hint_range(0, 0.5) = 0.1; float roundedBoxSDF(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; } void vertex() { // 径向扩展顶点(屏幕空间) vec2 dir = UV - vec2(0.5); float aspect = SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y; vec2 screen_dir = normalize(vec2(dir.x, dir.y * aspect)); // 注意:我们这里扩展的像素量需要在GDScript中通过uniform设置一个对应的值,但顶点扩展量我们之前是用的outline_width_px,这里为了匹配,我们还是用这个,但注意这个变量在片元中没用,所以我们可以删除outline_width_px,而用outline_width_uv来推导出屏幕扩展量?不,我们这里保留顶点扩展的像素量,由GDScript设置。 // 改为:我们使用一个uniform for像素 uniform float outline_width_px = 10.0; vec2 expand = screen_dir * outline_width_px * SCREEN_PIXEL_SIZE; VERTEX += expand; } void fragment() { vec2 center = UV - vec2(0.5); vec2 half_size = vec2(0.5); float d = roundedBoxSDF(center, half_size, corner_radius); // 抗锯齿参数 float aa = fwidth(d); // 或者固定值0.01 // 计算alpha:在d=0到outline_width_uv之间,alpha=1,并且平滑过渡 float alpha = smoothstep(-aa, aa, d) * (1.0 - smoothstep(outline_width_uv-aa, outline_width_uv+aa, d)); COLOR = outline_color; COLOR.a = alpha; } 但是,我们用了两个uniform:outline_width_px(用于顶点扩展)outline_width_uv(用于片元着色器计算描边宽度)。这两者应该同步,所以我们在GDScript中设置时,需要根据节点宽度计算outline_width_uv = outline_width_px / rect_size.x,其中rect_size.x是节点的宽度(像素)。 注意:圆角半径corner_radius是UV空间的,所以如果用户想用像素单位,也需要转换。 因此,我们可以提供两个版本的uniform:像素单位的宽度圆角半径,然后在GDScript中转换为UV空间。 或者,我们也可以只在着色器中使用像素单位,但这就需要将整个SDF计算放在屏幕空间,这比较复杂。 鉴于时间,我们就用UV空间,并提供给用户两个uniform(outline_width_pxoutline_width_uv),并告知用户需要在GDScript中同步它们。 为了简化,我们可以只保留outline_width_px,然后在GDScript中自动计算outline_width_uv,并传递给着色器。 所以,在Godot中,我们可以这样: - 创建一个材质,设置Next Pass为描边材质。 - 在脚本中,获取描边材质的shader_material,然后设置参数。 示例脚本(挂在ColorRect上): extends ColorRect var outline_width_px = 10.0 func _ready(): # 获取主材质 var main_material = self.material # 获取描边材质(next_pass) var outline_material = main_material.next_pass # 设置描边材质的参数 outline_material.set_shader_parameter("outline_width_px", outline_width_px) # 计算outline_width_uv = outline_width_px / self.size.x outline_material.set_shader_parameter("outline_width_uv", outline_width_px / self.size.x) # 注意:当节点大小改变时,需要重新计算outline_width_uv,所以我们可以连接resized信号 self.connect("resized", self._on_resized) func _on_resized(): if material and material.next_pass: material.next_pass.set_shader_parameter("outline_width_uv", outline_width_px / self.size.x) 同理,corner_radius_px也可以通过类似方式转换为corner_radius(UV空间)。 最后,我们给出完整的Godot着色器代码(描边Pass)以及设置方法。 由于时间限制,我们可能不能完全测试,但这是实现圆角描边效果的一种可行方法。 注意:这种方法在矩形旋转时可能有问题,因为我们的扩展是屏幕空间的,而SDF计算是基于UV的(旋转后UV不会旋转)。如果需要支持旋转,我们需要在SDF计算中考虑旋转矩阵,或者将坐标转换到局部空间。 但矩形节点(ColorRect)通常不会旋转,所以暂时不考虑。 因此,我们提供如下代码: 主材质(shader_material): shader_type canvas_item; uniform vec4 main_color : source_color = vec4(0.0,0.0,1.0,1.0); void fragment() { COLOR = main_color; } 描边材质(next_pass): shader_type canvas_item; render_mode unshaded; uniform vec4 outline_color : source_color = vec4(1.0,0.0,0.0,1.0); uniform float outline_width_px : hint_range(0,50) = 10.0; uniform float outline_width_uv : hint_range(0,0.5) = 0.05; uniform float corner_radius : hint_range(0,0.5) = 0.1; float roundedBoxSDF(vec2 p, vec2 b, float r) { vec2 q = abs(p) - b + r; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; } void vertex() { vec2 dir = UV - vec2(0.5); float aspect = SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y; vec2 screen_dir = normalize(vec2(dir.x, dir.y * aspect)); vec2 expand = screen_dir * outline_width_px * SCREEN_PIXEL_SIZE; VERTEX += expand; } void fragment() { vec2 center = UV - vec2(0.5); vec2 half_size = vec2(0.5); float d = roundedBoxSDF(center, half_size, corner_radius); float aa = 0.01; // 抗锯齿宽度,可根据需要调整 float alpha = smoothstep(-aa, aa, d) * (1.0 - smoothstep(outline_width_uv-aa, outline_width_uv+aa, d)); COLOR = outline_color; COLOR.a = alpha; } 在项目中,将主材质赋值给ColorRect的material,然后设置主材质的next_pass为描边材质。然后在脚本中,设置outline_width_pxoutline_width_uv(根据节点大小动态计算)。 注意:corner_radius是UV空间的,所以如果用户想用像素单位,需要自行转换:corner_radius = corner_radius_px / size.x 我们也可以提供corner_radius_px,然后用户用脚本转换。 由于时间,我们只提供UV空间的圆角半径。 总结:我们通过两个Pass(主Pass描边Pass)来实现圆角描边效果,其中描边Pass使用顶点扩展SDF函数计算圆角描边。 注意:这种方法只适用于矩形(ColorRect)。如果是任意形状,则需要不同的方法。 以上代码仅供参考,可能需要调试。</think>在Godot引擎中实现圆角描边效果,可以通过结合**顶点扩展****片段着色器中的距离计算**来实现。以下是完整实现方案: ### 解决方案 分为两个核心部分:主材质渲染物体本身,Next Pass材质实现圆角描边效果。 --- ### 1. 主材质(渲染物体本身) ```glsl // 主材质 (shader_type canvas_item) shader_type canvas_item; uniform vec4 albedo : source_color = vec4(1.0); // 物体基础颜色 void fragment() { COLOR = albedo * texture(TEXTURE, UV); } ``` --- ### 2. 描边材质(Next Pass) ```glsl // 圆角描边材质 (shader_type canvas_item) shader_type canvas_item; render_mode unshaded; // 避免受光照影响 uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); // 描边颜色 uniform float outline_width : hint_range(0, 0.5) = 0.05; // 描边宽度 (UV空间) uniform float corner_radius : hint_range(0, 0.5) = 0.1; // 圆角半径 (UV空间) // 圆角矩形SDF函数 (有符号距离场) float rounded_rect_sdf(vec2 p, vec2 size, float radius) { vec2 d = abs(p) - size + radius; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius; } void vertex() { // 顶点外扩 (屏幕空间扩展) vec2 dir = normalize(UV - 0.5); float aspect = SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y; vec2 screen_dir = normalize(vec2(dir.x, dir.y * aspect)); VERTEX += screen_dir * outline_width * 100.0 * SCREEN_PIXEL_SIZE; } void fragment() { // 坐标转换到中心点 vec2 center_coord = UV - vec2(0.5); vec2 half_size = vec2(0.5) - outline_width * 0.5; // 计算SDF距离 float d = rounded_rect_sdf(center_coord, half_size, corner_radius); // 圆角描边计算 float alpha = smoothstep(-0.01, 0.01, d); // 抗锯齿处理 alpha *= 1.0 - smoothstep(outline_width - 0.01, outline_width + 0.01, d); // 输出描边 COLOR = outline_color; COLOR.a *= alpha; } ``` --- ### 关键参数说明 1. **`outline_width`** 描边宽度(UV空间),值越大描边越粗,建议范围 `0.02~0.1` 2. **`corner_radius`** 圆角半径(UV空间),值越大圆角越明显,最大不超过 `0.5` 3. **`outline_color`** 使用RGBA格式设置描边颜色,如 `vec4(1.0, 0.5, 0.0, 1.0)` 表示橙色 --- ### 使用步骤 1. **创建主材质** - 新建 `ShaderMaterial` - 复制主材质代码到着色器 2. **创建描边材质** - 新建另一个 `ShaderMaterial` - 复制描边材质代码到着色器 - 调整 `outline_color`/`outline_width`/`corner_radius` 参数 3. **绑定Next Pass** ```gdscript # 在GDScript中连接材质 var main_material = $Sprite.material var outline_material = preload("res://outline_material.tres") main_material.next_pass = outline_material ``` 4. **动态调整参数** ```gdscript # 运行时修改描边属性 func update_outline(width: float, radius: float, color: Color) { outline_material.set_shader_parameter("outline_width", width) outline_material.set_shader_parameter("corner_radius", radius) outline_material.set_shader_parameter("outline_color", color) } ``` --- ### 技术原理 1. **顶点扩展** 在 `vertex()` 阶段沿法线方向外扩顶点,形成描边基础形状[^2] 2. **SDF距离场** 使用 `rounded_rect_sdf` 函数计算每个片段到几何边的距离,实现精准的圆角控制 3. **边缘抗锯齿** 通过 `smoothstep()` 函数平滑过渡边缘像素,消除锯齿[^4] 4. **Next Pass渲染** 利用Godot的次通道特性,使物体被渲染两次(主体+描边)[^2] --- ### 效果调整建议 - **锐利描边**:减小 `smoothstep()` 的第三个参数值(如 `0.005`) - **渐变描边**:用 `mix()` 函数混合多个颜色值 - **动态效果**:在 `_process()` 中修改 `corner_radius` 实现圆角动画 > 此方案适用于2D节点(Sprite, ColorRect等),3D物体需改用 `spatial` 着色器并调整顶点扩展方式[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值