Correct Depth POM & RCSM Cover

从视差映射、浮雕映射中获取正确的深度值

POM和RCSM 在我之前的文章在Unity里实现松散圆锥步进Relaxed Cone Step Mapping就已经介绍过了视差映射和松散圆锥步进浮雕映射的计算方法了,但是之前并没有对计算深度值做相应的研究,同时也限制于篇幅的原因就没有再展开了,这篇文章相当于是之前文章的后续。为了简便,后续将这两种计算方法统称为视差映射。 在视差映射中计算深度值是一个很直接的想法,因为很有可能会有其他物体被放置在视差映射的表面,与之发生穿插,如果不做特殊处理,就会使用模型本身的深度值进行深度比较,导致别的物体不能有正确的被遮挡的效果,削弱了视差映射带来的真实感。网上我找了一圈,并没有找到和计算视差映射的深度值相关的文章,因此我想用这篇文章进行相关的介绍。 Unity的高清管线(HDRP)的Lit Shader支持计算像素深度偏移,提供了Primitive Length,Primitive Width,和Amplitude三个参数。Amplitude可以用来控制视差映射的强度值,虽然其一个单位和世界空间的一米完全不能直接等同起来,但是值越大视差看上去就越深,可以根据视觉实时调整这个参数。另外两个参数就很奇怪了,居然和模型的大小有关,同一个材质球,用在Quad上这里就要填1,用在Plane上就要填10,哪有这种道理?虚幻引擎则是提供了POM的接口,至于输入和输出完全都由用户控制,这里就不太好直接比较了。 回顾POM的计算过程 视差映射一般不会直接在世界空间步进,而是会先将世界空间的视线viewWS转换到切线空间viewTS,在切线空间步进。照常理_ParallaxIntensity是用来控制视差映射的深度的,因此会使用这个参数控制z方向步进的距离,但为了方便和高度图中记载的高度进行对比,会先对viewTS的z分量进行归一化,将_ParallaxIntensity在步进时乘到viewTS的xy分量上,之后就是循环比较深度进入下一个循环了。 但是为什么是切线空间呢?这是因为切线tangent和副切线bitangent代表了贴图UV的xy的正方向,将视线转换到切线空间,其实目的是将视线转到UV空间,或者说是贴图空间(Texture Space,因为其与切线空间的相似性,我们还是用TS来做简写)。这里就出现了最重要的一个问题,Unity中通过GetVertexNormalInputs获得到的世界空间的切线是经过归一化的,丢失了物体自身的缩放,所以我们其实应该先将世界坐标的视线viewWS转换到物体空间viewOS,然后再使用物体空间的tbn矩阵,将viewOS转换到切线空间viewTS。但又如我上面说到的,我们真实的目的是贴图空间,切线空间和贴图空间是存在差异性的。这也就是为什么Unity的HDRP要使用额外的参数Primitive Length和Primitive Width了,这两个参数的目的是通过额外的缩放,将切线空间和贴图空间对应起来。 这两个参数的意义应当是,贴图空间的xy分量每一个单位在物体空间的长度,这里我们记为uvScale。同时我们可以顺理成章地正式引入_ParallaxIntensity这个参数,它的含义应当是,贴图中颜色为0的点对应的物体空间的深度值。贴图空间转换到物体空间,只需要对xyz三个分量分别乘上uvScale.x,uvScale.y,和_ParallaxIntensity即可。_ParallaxIntensity这个参数我们可以作为材质球的一个输入进行控制,uvScale是一个跟模型相关的参数,我们可以在Geometry Shader中计算而得。 uvScale的计算 如上面所属,uvScale指代的是贴图空间的xy分量每一个单位在物体空间的长度。对于两个顶点v0和v1,贴图空间的xy分量其实就是这两个顶点uv值的差,物体空间的长度其实就是两个顶点之间的距离,为了对应到贴图空间上,我们需要计算这段距离在切线和副切线上的投影长度,后者除以前者就是我们需要的uvScale了。由于构成三角形的三个顶点可能会存在某两个顶点之间uv的某个分量的变化率为0,导致我们计算uvScale的时候除以零,我们在检测到这个情况的时候使用第三个顶点即可。 贴图空间变换 在获得了物体空间的切线、副切线和法线之后,为了构成贴图空间的三个基向量,我们需要对这个向量使用uvScale和_ParallaxIntensity进行缩放。这个缩放导致了我们按照以往的float3x3(tangentOS * uvScale.x, bitangentOS * uvScale.y, normalOS * _ParallaxIntensity)构成的矩阵不再是一个正交矩阵,它实际上是贴图空间到物体空间的变换矩阵的转置。因此将物体空间的视线viewOS转换到贴图空间viewTS时,我们要用这个矩阵的转置的逆左乘viewOS,将贴图空间的视线viewTS转换到物体空间viewOS时,我们要用这个矩阵的转置左乘viewTS。 深度的获取 这个就相对来说比较简单了,我们在贴图空间步进的时候,可以知道我们在贴图空间步进的z方向的深度值len。而由于我们的viewTS会做除以z分量的归一化,我们只需要用归一化前的-viewTS乘上len再除以z分量,就能知道我们在贴图空间中总的步进的向量,将其转换到物体空间再转换到世界空间,和当前点的世界空间的坐标相加后再转换到裁剪空间,其z分量除以w分量就是我们需要的深度值了。 具体的代码 这里只做了可行性的研究,应该有个方法能够简化计算矩阵的逆这一步操作。在计算世界空间的切线、副切线和法线的时候,可以不进行归一化,这样我们也就不需要先转换到物体空间再转换到贴图空间了。 POMShader.shader Shader "zznewclear13/POMShader" { Properties { [Toggle(OUTPUT_DEPTH)] _OutputDepth ("Output Depth", Float) = 1 _BaseColor("Base Color", Color) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} _HeightMap("Height Map", 2D) = "white" {} _NormalMap("Normal Map", 2D) = "bump" {} _NormalIntensity("Normal Intensity", Range(0, 2)) = 1 _ParallaxIntensity ("Parallax Intensity", Float) = 1 _ParallaxIteration ("Parallax Iteration", Float) = 15 } HLSLINCLUDE #include "Packages/com....

March 16, 2024 · zznewclear13
View Space Normal From Depth Texture Cover

从深度图中获取视空间的法线

为什么要从深度图重建视空间法线 一个很大的应用情景是在后处理的阶段,或是计算一些屏幕空间的效果(如SSR、SSAO等),只能获取到一张深度贴图,而不是每一个几何体的顶点数据,很多的计算中却又需要用到世界空间的法线或者是视空间的法线,这时我们就需要通过深度图来重建视空间的法线。(诶这段话我是不是写过一遍了) 重建视空间法线的方法 bgolus在他的WorldNormalFromDepthTexture.shader里面很全面的介绍了各种重建视空间法线的方法。其中比较值得注意的是来自Janos Turanszki的根据深度差判断当前像素属于哪个平面的方法,和来自吴彧文的横向和纵向多采样一个点来判断当前像素属于哪个平面的方法,其中吴彧文的方法能够在绝大部分情况下获取到最准确的法线(除了尖角的一个像素)。 除了bgolus介绍的方法之外,我在GameTechDev/XeGTAO中还看到了一种方法。这种方法类似于Janos Turanszki的深度差的方法,不过从深度差中获取的是0-1的边缘值(edgesLRTB,edgesLRTB.x越接近0即代表该像素的左侧越是一条边缘),再使用边缘的两两乘积对四个法线进行插值,最终计算出视空间法线。我个人认为当在两个面相接的地方不需要特别准确的法线值时,这是最好的计算法线的方法。用这个方式计算的法线,在两个面相接的地方,法线会有一种从一个面插值到另一个面的效果(且一定程度上抗锯齿),在两个面远近排布的时候,也能获取到准确的法线。 具体的实现方法 根据需要使用的方法,采样深度图。在采样比较集中的情况下,可以使用GatherRed方法来减少采样的次数。GatherRed可以得到双线性采样时的四个像素的R通道的值并封装到一个float4中,当屏幕左下角是(0, 0)时,这个float4的x分量对应采样点左上角的颜色的R通道的值,y对应右上角,z对应右下角,w对应左下角,可以在HLSL的文档中看到Gather的相关介绍。Compute Shader的话可以使用group shared memory进一步减少采样。 使用深度图和当前的uv值计算出像素的视空间的坐标,这一步尤其需要注意视空间坐标Z分量的正负性的问题。Unity的视空间变换矩阵UNITY_MATRIX_V是摄像机位于视空间(0, 0, 0),看向视空间Z轴负方向的,右手系的矩阵。即视空间的坐标Z分量往往是一个负值,其法线的Z分量在往往下是正值(即画面看上去应该多为蓝色)。 从深度图中计算视空间坐标的时候,如果Unity版本比较旧,会没有UNITY_MATRIX_I_P这个矩阵,这时可以使用unity_CameraInvProjection来代替,但需要注意DirectX平台UV上下翻转的问题。 当屏幕左下角是(0, 0)时,使用右侧的视空间坐标减去左侧的视空间坐标,使用上侧的视空间坐标减去下侧的视空间坐标。五个采样点(包括位于中心的当前像素)可以获得四个向量,对于右手系的视空间坐标来说,将这四个向量按照水平向量叉乘竖直向量的顺序,就可以获得四个当前像素的法线了。 最后使用前面介绍的获取法线的方法,从这四个法线中获取最为正确的法线。这些方法往往都会使用深度值来进行判断,这里需要注意的是透视变换带来的深度的非线性的问题。对于屏幕上等距分布的三个点ABC,当他们在世界空间中处于同一条直线时,有 $$ 2 \cdot rawDepthB = rawDepthA + rawDepthC \newline \frac 2 {linearDepthB} = \frac 1 {linearDepthA} + \frac 1 {linearDepthC} $$ ReconstructNormalComputeShader.compute 使用GatherRed的方法,可以减少ReconstructNormalAccurate所需要的的采样,但是在屏幕的边缘会有一些瑕疵,把采样的sampler改成sampler_LinearRepeat在一定程度上能够解决这些瑕疵。这样的话ReconstructNormalFast需要两次采样,ReconstructNormalAccurate则需要五次采样。 要注意使用边缘信息对法线进行插值的方法,需要先对法线进行归一化,不然叉乘导致前后平面计算出的向量长度会远大于同一平面的向量长度,影响最终的法线。 #pragma kernel ReconstructNormalFast #pragma kernel ReconstructNormalAccurate #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #define FEWER_SAMPLES 0 Texture2D<float> _DepthTexture; RWTexture2D<float4> _RW_NormalTexture; SamplerState sampler_LinearClamp; SamplerState sampler_LinearRepeat; float4 _TextureSize; float3 GetViewSpacePosition(float2 uv, float depth) { #if UNITY_UV_STARTS_AT_TOP uv....

January 27, 2022 · zznewclear13
Vertex Animated Plant Cover

使用顶点动画制作随风飘动的植物

动机和想要实现的效果 最直接的动机是看了顽皮狗在Siggraph 2016上的PPT,里面介绍了顽皮狗在神秘海域中是如何让植被随风飘荡的。他们介绍了一种将植被的每一部分的pivot的物体空间坐标写到顶点色里,然后在shader中使用这个坐标进行风的效果的计算的方法。较为震撼在风吹过草原时,植被进行弯曲后,草表面的高光会有一种时空上的起伏感(也就是说神秘海域的植被的法线也会被风影响)。所以我也想要借助写pivot的方法来制作植被受到风吹的效果,通过这个方法计算出正确的风吹之后的植被的法线(同时由于法线贴图的存在,还要计算正确的切线)。 稍微翻了一下网上的资料(也没仔细地去搜索),大部分的就是一个普通的顶点动画,有的是用的sin,有的就直接平移。这就产生了第二个需求,植被在顶点动画中应该保持差不多的长度,不然会发现很明显的拉伸的效果。 当然最好还能投射出正常的影子了,这一步只需要把顶点着色器复制一份到投射影子的pass里就可以了。 这里使用的植被模型是MegaScans上的CORDYLINE模型中的var12这个小模型。 难点和相对应的应对方法 Unity的顶点色限制 稍微测试一下就能发现,Unity的顶点色是UNorm8的格式,也就是说无论你在Maya或是3ds Max里导出的模型的顶点色信息是什么样的,导入到Unity中就会变成只有256精度的UNorm8。顽皮狗使用的是自己的引擎,所以它们能够使用全精度的顶点色,但是由于Unity的引擎限制,我们可以考虑到导出pivot的顶点坐标到模型的UV中。 但是很不幸的是,fbx导入到Unity时,即使UV是float4的类型(也就是16bytes),在Unity中只会识别UV的前两位。所以只能无奈的将pivot的顶点坐标(float3的数据)储存到两个UV的三个通道里,同时将pivot的层级存到剩下的一个通道里。我不知道顽皮狗具体是怎么计算pivot的层级关系的,他在PPT中写的是无需计算,但我在实际操作中只能一层一层的算(而且只能算两层),也希望知道具体怎么操作的人告知一下方法。 所以接下来要做的是在Maya中把pivot的物体空间坐标和pivot的层级写到对应顶点的某两套UV中,本文是写到第二套和第三套UV中(也就是TEXCOORD1和TEXCOORD2)。于是我恶补了一下maya的python脚本的写法,不过在写数值到UV中时,又遇到了一个小问题。Maya的cmds.polyEditUV这个方法,明明能传入uvSetName这个参数,用于操作对应的UV,但我实际使用时只能写数值到当前的UV中,导致最后写的脚本只能僵硬的操作当前UV,每次切换UV时需要重新修改脚本再运行一次。 最终的脚本是这样的: VertexPivotWriteTool.py import maya.cmds as cmds targetVertexStr = "Select any vertex to start." vertexColorStr = "Select any vertex to start." pivotPosition = [0.0, 0.0, 0.0] def ui(): if cmds.window("VertexPivotWriteTool", exists = True): cmds.deleteUI("VertexPivotWriteTool") global targetVertexStr global targetVertexField global vertexColorStr global vertexColorField global pivotLayer vertexPivotWindow = cmds.window("VertexPivotWriteTool", widthHeight = [500, 400]) form = cmds.formLayout(numberOfDivisions = 100) pivotLayerLable = cmds.text("Pivot Layer (0 for root pivot)") pivotLayer = cmds....

January 6, 2022 · zznewclear13
Equal Width Outline Cover

在Unity中绘制等宽的描边

对于描边的思考 描边可以说是一个特别关键的效果,不仅仅是二次元卡通渲染需要用到描边,在用户交互的方面,描边也是一个增强用户交互的关键效果。 一般的描边的做法是绘制一个沿物体空间顶点法线(或是记录在顶点色中的描边方向)外扩的模型背面,这种做法在绝大部分情况都看上去不错,但是描边的深度测试会有一些小瑕疵,同时在物体距离摄像机较近的时候,描边会显得较粗,此外这种描边没有抗锯齿的效果,绘制模型的背面也让造成了性能的浪费。另外一种方法是使用Multiple Render Targets,渲染出一个模型的剪影,然后使用类似高斯模糊的办法,对采样进行偏移,这样可以渲染出一个较好的可以有抗锯齿效果的描边,但是仅限于模型向外的描边,缺少模型内部的描边效果。 最好的描边应该是能够支持模型外描边、内描边、材质描边的描边效果,pencil +实现了这些效果,但是效率不是很高,这里有相关的演示(我也是看了这个之后才决定用安吉拉的模型的)。我看到的较好的方案应该还是L-灵刃的使用退化四边形生成描边的办法,github上也分享了源码。 这篇博客中介绍的描边,是基于我上一篇博客中讲的世界空间中绘制等宽线条的方法,使用DrawProcedural绘制的等宽的描边。我认为只有等宽的描边,才是最能表现二次元画面特征的描边。这里的“等宽”,并不是说线条的宽度处处相等,线条当然可以控制每一部分的粗细,但是这个控制的粗细是基于一个固定值的相对粗细(也就是存在顶点色中的描边粗细值),当粗细值相同时,不管是画面的哪个部分的描边的粗细(不管是内描边还是外描边),都应该是相同的。 实现描边时需要注意的点 首先参考退化四边形的案例,需要先对模型文件进行预处理。这里我做了简化,只去寻找两个三角面共用的边,忽略了只属于一个三角面的边的情况(事实上我觉得这样看上去的视觉效果也蛮不错的)。一条共用边对应了这条边的两个顶点,两侧的两个三角形和这两个三角形对应的额外的两个顶点。这里都用序号来表示,需要6个int值(事实上可以忽略两个三角形的编号,就能存在一个int4里了)。判断一条边共同属于两个三角形,就相当于判断两个三角形中的某两个顶点的序号是相同的(实际上顺序是相反的)。但是实际操作中,即使是相同的顶点,在两个三角形中顶点的序号也不一定是相同的,因此需要先把两个相同的顶点(使用距离来判断)合并成一个顶点,这也就是我使用vertRemapping这个数组的目的。剩下的就是循环所有三角形,获取共用边的算法部分了,尽可能的优化一下,不然三角面一多运算的时间要很久。 有了共用边的数据,通过SkinnedMeshRenderer.BakeMesh()可以获取到当前帧每个顶点的物体空间的坐标,就能进行描边的计算了。使用DrawProcedural时顶点的数量可以是公用边数量的两倍,这样需要在Geometry Shader中把顶点数目从2扩充到6,或者是在绘制时将顶点数量设置成共用边数量的六倍,可能后者效率会高一点,不过思考的时候会有点乱,这里就使用Geometry Shader的方法了。 如果是像之前的博客介绍的,以共用边两个顶点为中心,同时向左右两侧外扩的话,会因为深度测试的原因,导致描边部分被模型遮挡,这个问题比较严重,他直接导致了外描边和内描边的粗细不一样,也导致了在一条描边中会露出一部分模型的问题。这里采用的方法是仅向外侧描边,在计算是不是轮廓边的时候同时计算需要描边的方向,使用这个方向向外扩展描边,最后效果还蛮不错的。 要实现风格化描边的话,除了使用顶点色来控制描边的粗细之外,还能使用一张贴图作为描边的笔刷,在绘制描边的时候采样这张贴图,本篇博客就暂不使用这种方法了。 具体的实现描边的操作 对当前模型获取到所有的共用边对应的四个顶点序号,严格保持顺序! 每一帧使用SkinnedMeshRenderer.BakeMesh,获取所有顶点当前的物体空间的坐标。 传入顶点坐标,顶点重映射数组,共用边信息,如果需要的话还要传顶点色到描边的Shader中。 使用DrawProcedural绘制描边,顶点数量为共用边的数量的两倍。 在顶点着色器中,计算共用边四个顶点的裁剪空间(实际上用的是屏幕空间)的坐标,判断这条边是不是轮廓边,同时记录描边外扩的方向,相当于对于每一条边(每两个点)存两个bool变量。 在几何体着色器中,计算共用边两个顶点的屏幕空间的坐标,计算出两个点之间的向量,计算与之相垂直的外扩的方向,根据两个向量,计算出描边的四个顶点的裁剪空间的坐标,并赋予uv的值。 在片元着色器中,根据uv计算出描边的颜色,可以采样贴图,也可以直接返回计算的颜色。 由于整个描边的操作较为复杂,我尽可能多的写了注释。 OutlineObject.cs 定义共用边的结构体,也定义用于保存共用边数据的ScriptableObject。 using UnityEngine; namespace ZZNEWCLEAR13.Outline { [System.Serializable] public class OutlineObject : ScriptableObject { [System.Serializable] public class MeshOutlineInfo { public string meshName; //尽量不要都显示出来,不然很卡。。 //顶点、法线、切线和顶点色 [HideInInspector] public Vector3[] vertices; [HideInInspector] public Vector3[] normals; [HideInInspector] public Vector4[] tangents; [HideInInspector] public Color[] colors; //vertRemapping把相同位置的顶点编号映射到第一个该位置顶点的编号 [HideInInspector] public int[] vertRemapping; //三角形对应的顶点编号 [HideInInspector] public Vector3Int[] triangles; public Line[] commonLines; } public MeshOutlineInfo outlineInfo; } [System....

December 12, 2021 · zznewclear13
Equal Width Line Cover

在Unity中绘制等宽线条

动机 直接动机是想要在unity中制作一个描边效果。对于卡通渲染的描边效果,已经有很多很多的案例了,但是我觉得这些案例不一定能完全满足我的需求,于是想要从画直线开始研究。 从普通绘画的角度来看,很重要的一点就是描边的宽度基本上是一致的:考虑一下远景的物体,画家在绘画的时候使用和近景相同粗细的笔(用一样或者稍弱的力),绘制一个较不精细的物体,而不是使用很细的笔去绘制一个精细的物体,结论就是远处的描边可能稍细一些,但最好是相同粗细,其颜色可能会变浅。 从另一个角度来看,描边往往需要能够控制其宽度,对于较细的线,则会有较明显的锯齿(事实上只要角度不太好的描边,就会有很明显的边缘锯齿),那么控制粗细和进行一定程度的抗锯齿也是一个研究的方向。 从第三个角度,碰巧看到了Freya Holmér制作的Shapes插件,能够绘制高质量的线条画,看上去渲染的效果很好。但是价格过于高昂,于是想要研究一个能够做到差不多效果的工具。 抗锯齿和宽度的思考 在Unity中画直线有蛮多办法,Debug.DrawLine或者Gizmos.DrawLine都能绘制等宽的直线,不过只能固定一个像素宽,而且因为只有一个像素宽,所以会有明显的锯齿。使用LineRenderer可以绘制任意宽度的直线,写特定的shader通过透明度混合能够防止锯齿的出现(不过线段两端不太好做抗锯齿),但是不能保证在不同角度下直线的宽度相等。那么答案就很明显了,通过线段的顶点的数据,生成两个三角面(一个Quad),在GPU中计算出三角面每个顶点的屏幕空间的位置,确保线段的宽度一致。也由于有宽度的存在,给后续的透明度混合抗锯齿留下了操作的空间。 具体的绘制线条的操作 首先看一张图(完了三角形顶点顺序画反了,已经积重难返了,代码表现对就当做对的吧) 我们将使用Graphics.DrawProcedural这个方法来绘制我们的直线,每一段直线由两个三角形组成(这里要注意在unity中三角形顶点顺序是顺时针的),也就是说要绘制N条直线的话,要传入N+1个节点位置数据,而实际绘制时会绘制6N个顶点。这样传入Shader的是一个表示线段节点位置的长度是N+1的Vector3数据_VerticesBuffer,和一个表示顶点序号的从0到6N-1的uint数据,也就是在绘制时图形API自动传输的SV_VERTEXID。 这也就导致了我们的顶点着色器输入数据和普通的Shader有所区别,只有一个uint数据: struct Attributes { uint vertexID : SV_VERTEXID; }; 在顶点着色器获取到SV_VERTEXID之后,我们可以以此来计算出线段的序号lineID和每个顶点在绘制线段时的序号vertexID。在绘制时我们又要知道当前线段的两个节点的位置,在传入_VerticesBuffer之后,我们需要对每个顶点确定其对应的线段节点的序号,即lineID + 0或者lineID + 1,我们可以将这个可以通过vertexID确定的0或者1储存到一个数组vertexIndexes中,方便使用vertexID读取。 为了让线段取得较好的抗锯齿效果,我们把线段整体外扩了_OutlineWidth个像素宽(实际线段宽度是这个的两倍)。此时对于每一个顶点,我们需要知道外扩之后的屏幕空间的顶点位置.由于我们需要使用从LineStart到LineEnd的方向offset(对于1, 2, 4这几个顶点来说,offset是从LineEnd到LineStart的方向)和与其相垂直的方向来做外扩,我们需要把每个顶点的两个方向值储存到数组vertexOffsets中。可以看到顶点永远是往远离较远节点的方向移动的,因此vertexOffset的x值都是-1。而我们选取offset逆时针旋转90°的方向作为相垂直的方向,因此vertexOffset的y值会有正负之分。 最后是uv的数值,我们还需要给每个顶点传入uv的数值,就比较简单了,储存到数组vertexUVs中。 此外还要注意一点,在片元着色器中,我们需要知道LineStart和LineEnd对应的uv位置来做线段的圆形端点,因此我们还需要把这个长方体的高宽比ratio传给片元着色器。 这样,我们片元着色器的输入数据就是positionCS,uv和ratio了: struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float ratio : TEXCOORD1; }; 绘制线条的流程 C#脚本获取要绘制线条的节点数据,传入_VerticesBuffer,调用Graphics.DrawProcedural方法进行绘制。 Shader的顶点着色器根据SV_VERTEXID计算出每个顶点对应的线段序号lineID和顶点序号vertexID。 通过线段序号、顶点序号和vertexIndexes从_VerticesBuffer中找到较近的线段节点的世界坐标,和较远的线段节点的世界坐标。 将两个世界坐标转换到屏幕空间,得到屏幕空间两点对应的向量和与之相垂直的向量,进行归一化。 将每个顶点根据vertexOffsets和描边宽度进行外扩。 将外扩后的裁剪空间的坐标,从vertexUVs中获得的uv和高宽比ratio传给片元着色器。 片元着色器根据ratio和uv,绘制出一个两头圆形的线段。 DrawEqualWidthLine.cs C#脚本比较简单了,没什么特别需要注意的地方。 using UnityEngine; public class DrawEqualWidthLine : MonoBehaviour { public Vector3[] vertices; private int vertexCount; public Material equalWidthMaterial; ComputeBuffer verticesBuffer; private void EnsureBuffer(ref ComputeBuffer buffer, int count, int stride) { if (buffer == null) { buffer = new ComputeBuffer(count, stride, ComputeBufferType....

December 2, 2021 · zznewclear13
zznewclear13 技术美术 图形学 个人博客 technical art computer graphics