1.游戏引擎随笔 0x36:UE5.x Nanite 源码解析之可编程光栅化(下)
2.Rç¬è«å¿
å¤åºç¡ââCSS+SelectorGadget
游戏引擎随笔 0x36:UE5.x Nanite 源码解析之可编程光栅化(下)
书接上回。
在展开正题之前,先做必要的铺垫,解释纳尼特(Nanite)技术方案中的Vertex Reuse Batch。纳尼特在软光栅路径实现机制中,将每个Cluster对应一组线程执行软光栅,源码编音乐每ThreadGroup有个线程。在光栅化三角形时访问三角形顶点数据,但顶点索引范围可能覆盖整个Cluster的个顶点,因此需要在光栅化前完成Cluster顶点变换。纳尼特将变换后的顶点存储于Local Shared Memory(LDS)中,进行组内线程同步,确保所有顶点变换完成,光栅化计算时直接访问LDS,实现软光栅高性能。
然而,在使用PDO(Masked)等像素可编程光栅化时,纳尼特遇到了性能问题。启用PDO或Mask时,可能需要读取Texture,根据读取的Texel决定像素光栅化深度或是否被Discard。读取纹理需计算uv坐标,而uv又需同时计算重心坐标,点读网站源码增加指令数量,降低寄存器使用效率,影响Active Warps数量,降低延迟隐藏能力,导致整体性能下降。复杂材质指令进一步加剧问题。
此外,当Cluster包含多种材质时,同一Cluster中的三角形被重复光栅化多次,尤其是材质仅覆盖少数三角形时,大量线程闲置,浪费GPU计算资源。
为解决这些问题,纳尼特引入基于GPU SIMT/SIMD的Vertex Reuse Batch技术。技术思路如下:将每个Material对应的三角形再次分为每个为一组的Batch,每Batch对应一组线程,每个ThreadGroup有个线程,正好对应一个GPU Warp。利用Wave指令共享所有线程中的变换后的顶点数据,无需LDS,减少寄存器数量,增加Warp占用率,分红复利盘源码提升整体性能。
Vertex Reuse Batch技术的启用条件由Shader中的NANITE_VERT_REUSE_BATCH宏控制。
预处理阶段,纳尼特在离线时构建Vertex Reuse Batch,核心逻辑在NaniteEncode.cpp中的BuildVertReuseBatches函数。通过遍历Material Range,统计唯一顶点数和三角形数,达到顶点去重和优化性能的目标。
最终,数据被写入FPackedCluster,根据材质数量选择直接或通过ClusterPageData存储Batch信息。Batch数据的Pack策略确保数据对齐和高效存储。
理解Vertex Reuse Batch后,再来回顾Rasterizer Binning的数据:RasterizerBinData和RasterizerBinHeaders。在启用Vertex Reuse Batch时,这两者包含的是Batch相关数据,Visible Index实际指的是Batch Index,而Triangle Range则对应Batch的三角形数量。
当Cluster不超过3个材质时,直接从FPackedCluster中的VertReuseBatchInfo成员读取每个材质对应的BatchCount。有了BatchCount,即可遍历所有Batch获取对应的棋牌游戏源码买卖三角形数量。在Binning阶段的ExportRasterizerBin函数中,根据启用Vertex Reuse Batch的条件调整BatchCount,表示一个Cluster对应一个Batch。
接下来,遍历所有Batch并将其对应的Cluster Index、Triangle Range依次写入到RasterizerBinData Buffer中。启用Vertex Reuse Batch时,通过DecodeVertReuseBatchInfo函数获取Batch对应的三角形数量。对于不超过3个材质的Cluster,DecodeVertReuseBatchInfo直接从Cluster的VertReuseBatchInfo中Unpack出Batch数据,否则从ClusterPageData中根据Batch Offset读取数据。
在Binning阶段的AllocateRasterizerBinCluster中,还会填充Indirect Argument Buffer,将当前Cluster的Batch Count累加,用于硬件光栅化Indirect Draw的Instance参数以及软件光栅化Indirect Dispatch的ThreadGroup参数。这标志着接下来的光栅化Pass中,每个Instance和ThreadGroup对应一个Batch,以Batch为光栅化基本单位。
终于来到了正题:光栅化。本文主要解析启用Vertex Reuse Batch时的软光栅源码,硬件光栅化与之差异不大,此处略过。源码打包deb教程此外,本文重点解析启用Vertex Reuse Batch时的光栅化源码,对于未启用部分,除可编程光栅化外,与原有固定光栅化版本差异不大,不再详细解释。
CPU端针对硬/软光栅路径的Pass,分别遍历所有Raster Bin进行Indirect Draw/Dispatch。由于Binning阶段GPU中已准备好Draw/Dispatch参数,因此在Indirect Draw/Dispatch时只需设置每个Raster Bin对应的Argument Offset即可。
由于可编程光栅化与材质耦合,导致每个Raster Bin对应的Shader不同,因此每个Raster Bin都需要设置各自的PSO。对于不使用可编程光栅化的Nanite Cluster,即固定光栅化,为不降低原有性能,在Shader中通过两个宏隔绝可编程和固定光栅化的执行路径。
此外,Shader中还包括NANITE_VERT_REUSE_BATCH宏,实现软/硬光栅路径、Compute Pipeline、Graphics Pipeline、Mesh Shader、Primitive Shader与材质结合生成对应的Permutation。这部分代码冗长繁琐,不再详细列出讲解,建议自行阅读源码。
GPU端软光栅入口函数依旧是MicropolyRasterize,线程组数量则根据是否启用Vertex Reuse Batch决定。
首先判断是否使用Rasterizer Binning渲染标记,启用时根据VisibleIndex从Binning阶段生成的RasterizerBinHeaders和RasterizerBinData Buffer中获取对应的Cluster Index和光栅化三角形的起始范围。当启用Vertex Reuse Batch,这个范围是Batch而非Cluster对应的范围。
在软光栅中,每线程计算任务分为三步。第一步利用Wave指令共享所有线程中的Vertex Attribute,线程数设置为Warp的Size,目前为,每个Lane变换一个顶点,最多变换个顶点。由于三角形往往共用顶点,直接根据LaneID访问顶点可能重复,为确保每个Warp中的每个Lane处理唯一的顶点,需要去重并返回当前Lane需要处理的唯一顶点索引,通过DeduplicateVertIndexes函数实现。同时返回当前Lane对应的三角形顶点索引,用于三角形设置和光栅化步骤。
获得唯一顶点索引后,进行三角形设置。这里代码与之前基本一致,只是写成模板函数,将Sub Pixel放大倍数SubpixelSamples和是否背面剔除bBackFaceCull作为模板参数,通过使用HLSL 语法实现。
最后是光栅化三角形写入像素。在Virtual Shadow Map等支持Nanite的场景下,定义模板结构TNaniteWritePixel来实现不同应用环境下Nanite光栅化Pipeline的细微差异。
在ENABLE_EARLY_Z_TEST宏定义时,调用EarlyDepthTest函数提前剔除像素,减少后续重心坐标计算开销。当启用NANITE_PIXEL_PROGRAMMABLE宏时,可以使用此机制提前剔除像素。
最后重点解析前面提到的DeduplicateVertIndexes函数。
DeduplicateVertIndexes函数给每个Lane返回唯一的顶点索引,同时给当前Lane分配三角形顶点索引以及去重后的顶点数量。
首先通过DecodeTriangleIndices获取Cluster Local的三角形顶点索引,启用Cluster约束时获取所有Lane中最小的顶点索引,即顶点基索引。将当前三角形顶点索引(Cluster Local)减去顶点基索引,得到相对顶点基索引的局部顶点索引。
接下来生成顶点标志位集合。遍历三角形三个顶点,将局部顶点索引按顺序设置到对应位,表示哪些顶点已被使用。每个标志位是顶点的索引,并在已使用的顶点位置处设置为1。使用uint2数据类型,最多表示个顶点位。
考虑Cluster最多有个顶点,为何使用位uint2来保存Vertex Mask而非位?这是由于Nanite在Build时启用了约束机制(宏NANITE_USE_CONSTRAINED_CLUSTERS),该机制保证了Cluster中的三角形顶点索引与当前最大值之差必然小于(宏CONSTRAINED_CLUSTER_CACHE_SIZE),因此,生成的Triangle Batch第一个索引与当前最大值之差将不小于,并且每个Batch最多有个唯一顶点,顶点索引差的最大值为,仅需2个位数据即可。约束机制确保使用更少数据和计算。
将所有Lane所标记三个顶点的Vertex Mask进行位合并,得到当前Wave所有顶点位掩码。通过FindNthSetBit函数找出当前Lane对应的Mask索引,加上顶点基索引得到当前Lane对应的Cluster Local顶点索引。
接下来获取当前Lane对应的三角形的Wave Local的三个顶点索引,用于后续通过Wave指令访问其他Lane中已经计算完成的顶点属性。通过MaskedBitCount函数根据Vertex Mask以及前面局部顶点索引通过前缀求和得到当前Lane对应的Vertex Wave Local Index。
最后统计Vertex Mask所有位,返回总计有效的顶点数量。
注意FindNthSetBit函数,实现Lane与顶点局部索引(减去顶点基索引)的映射,返回当前Lane对应的Vertex Mask中被设置为1的位索引。如果某位为0,则返回下一个位为1的索引。如果Mask中全部位都设置为1,则实际返回为Lane索引。通过二分法逐渐缩小寻找索引范围,不断更新所在位置,最后返回找到的位置索引。
最后,出于验证目的进行了Vertex Reuse Batch的性能测试。在材质包含WPO、PDO或Mask时关闭Vertex Reuse Batch功能,与开启功能做对比。测试场景为由每颗万个三角形的树木组成的森林,使用Nsight Graphics进行Profiling,得到GPU统计数据如下:
启用Vertex Reuse Batch后,软光栅总计耗时减少了1.毫秒。SM Warp总占用率有一定提升。SM内部工作量分布更加均匀,SM Launch的总Warp数量提升了一倍。长短板Stall略有增加,但由于完全消除了由于LDS同步导致的Barrier Stall,总体性能还是有很大幅度的提升。
至此,Nanite可编程光栅化源码解析讲解完毕。回顾整个解析过程,可以发现UE5团队并未使用什么高深的黑科技,而是依靠引擎开发者强悍的工程实现能力完成的,尤其是在充分利用GPU SIMT/SIMD机制榨干机能的同时,保证了功能与极限性能的实现。这种能力和精神,都很值得我们学习。
Rç¬è«å¿ å¤åºç¡ââCSS+SelectorGadget
CSSï¼å ¨ç§°å«ä½Cascading Style Sheetsï¼å³å±å æ ·å¼è¡¨ãâå±å âæ¯æå½å¨HTMLä¸å¼ç¨äºæ°ä¸ªæ ·å¼æ件ï¼å¹¶ä¸æ ·å¼åçå²çªæ¶ï¼æµè§å¨è½ä¾æ®å±å 顺åºå¤çãâæ ·å¼âæç½é¡µä¸æå大å°ãé¢è²ãå ç´ é´è·ãæåçæ ¼å¼ãHTMLå®ä¹äºç½é¡µçç»æï¼ä½æ¯åªæHTML页é¢çå¸å±å¹¶ä¸ç¾è§ï¼å¯è½åªæ¯ç®åçèç¹å ç´ çæåï¼ä¸ºäºè®©ç½é¡µçèµ·æ¥æ´å¥½çä¸äºï¼è¿éåå©äºCSSãCSSæ¯ç®åå¯ä¸çç½é¡µé¡µé¢æçæ ·å¼æ åï¼æäºå®ç帮å©ï¼é¡µé¢æä¼åå¾æ´ä¸ºç¾è§ãå¦ä¸å¾çå³ä¾§ï¼å³ä¸ºCSSãå°±å±é¨æ¾å¤§æ¥çï¼ä¸å¾æ示就æ¯ä¸ä¸ªCSSæ ·å¼ã大æ¬å·åé¢æ¯ä¸ä¸ªCSSéæ©å¨ï¼æ¤éæ©å¨çæææ¯é¦å éä¸id为head_wrapperä¸class为s-ps-isliteçèç¹ï¼ç¶ååéä¸å ¶å é¨çclass为s-p-topçèç¹ã大æ¬å·å é¨åçå°±æ¯ä¸æ¡æ¡æ ·å¼è§åï¼ä¾å¦positionæå®äºè¿ä¸ªå ç´ çå¸å±æ¹å¼ä¸ºç»å¯¹å¸å±ï¼bottomæå®å ç´ çä¸è¾¹è·ä¸ºåç´ ï¼widthæå®äºå®½åº¦ä¸º%å 满ç¶å ç´ ï¼heightåæå®äºå ç´ çé«åº¦ãä¹å°±æ¯è¯´ï¼æ们å°ä½ç½®ã宽度ãé«åº¦çæ ·å¼é ç½®ç»ä¸åæè¿æ ·çå½¢å¼ï¼ç¶åç¨å¤§æ¬å·æ¬èµ·æ¥ï¼æ¥çå¨å¼å¤´åå ä¸CSSéæ©å¨ï¼è¿å°±ä»£è¡¨è¿ä¸ªæ ·å¼å¯¹CSSéæ©å¨éä¸çå ç´ çæï¼å ç´ å°±ä¼æ ¹æ®æ¤æ ·å¼æ¥å±ç¤ºäºãå¨ç½é¡µä¸ï¼ä¸è¬ä¼ç»ä¸å®ä¹æ´ä¸ªç½é¡µçæ ·å¼è§åï¼å¹¶åå ¥CSSæ件ä¸ï¼å ¶åç¼ä¸ºcssï¼ãå¨HTMLä¸ï¼åªéè¦ç¨linkæ ç¾å³å¯å¼å ¥å好çCSSæ件ï¼è¿æ ·æ´ä¸ªé¡µé¢å°±ä¼åå¾ç¾è§ãä¼é ã
å¨ç¬è«è¿ç¨ä¸é½éè¦ç¬åç®æ çèç¹ï¼æ们ç¥éç½é¡µç±ä¸ä¸ªä¸ªèç¹ç»æï¼CSSéæ©å¨ä¼æ ¹æ®ä¸åçèç¹è®¾ç½®ä¸åçæ ·å¼è§åï¼é£ä»ä¹æ¯èç¹ï¼åææ ·æ¥å®ä½èç¹å¢ï¼ä¸é¢å´ç»è¿ä¸¤ä¸ªé®é¢è¿è¡ä»ç»ã
å¨HTMLä¸ï¼æææ ç¾å®ä¹çå 容é½æ¯èç¹ï¼å®ä»¬ææäºä¸ä¸ªHTML DOMæ ãæ们å çä¸ä»ä¹æ¯DOMï¼DOMæ¯W3Cï¼ä¸ç»´ç½èçï¼çæ åï¼å ¶è±æå ¨ç§°Document Object Modelï¼å³æ档对象模åãå®å®ä¹äºè®¿é®HTMLåXMLææ¡£çæ åï¼W3Cæ档对象模åï¼DOMï¼æ¯ä¸ç«äºå¹³å°åè¯è¨çæ¥å£ï¼å®å 许ç¨åºåèæ¬å¨æå°è®¿é®åæ´æ°ææ¡£çå 容ãç»æåæ ·å¼ãW3C DOMæ å被å为å¦ä¸3个ä¸åçé¨åï¼
æ ¸å¿DOMï¼é对任ä½ç»æåææ¡£çæ å模åã
XML DOMï¼é对XMLææ¡£çæ å模åã
HTML DOMï¼é对HTMLææ¡£çæ å模åã
æ ¹æ®W3CçHTML DOMæ åï¼HTMLææ¡£ä¸çææå 容é½æ¯èç¹ãæ´ä¸ªææ¡£æ¯ä¸ä¸ªææ¡£èç¹ï¼æ¯ä¸ªHTMLå ç´ æ¯å ç´ èç¹ï¼HTMLå ç´ å çææ¬æ¯ææ¬èç¹ï¼æ¯ä¸ªHTMLå±æ§æ¯å±æ§èç¹ï¼æ³¨éæ¯æ³¨éèç¹ãHTML DOMå°HTMLææ¡£è§ä½æ ç»æï¼è¿ç§ç»æ被称为èç¹æ ï¼å¦ä¸å¾æ示ã
èç¹æ ä¸çèç¹å½¼æ¤æ¥æå±çº§å ³ç³»ãæ们常ç¨ç¶ï¼parentï¼ãåï¼childï¼åå å¼ï¼siblingï¼çæ¯è¯æè¿°è¿äºå ³ç³»ãç¶èç¹æ¥æåèç¹ï¼å级çåèç¹è¢«ç§°ä¸ºå å¼èç¹ãå¨èç¹æ ä¸ï¼é¡¶ç«¯èç¹ç§°ä¸ºæ ¹ï¼rootï¼ãé¤äºæ ¹èç¹ä¹å¤ï¼æ¯ä¸ªèç¹é½æç¶èç¹ï¼åæ¶å¯æ¥æä»»ææ°éçåèç¹æå å¼èç¹ãä¸å¾å±ç¤ºäºèç¹æ 以åèç¹ä¹é´çå ³ç³»ã
å¨CSSä¸ï¼æ们使ç¨CSSéæ©å¨æ¥å®ä½èç¹ãä¾å¦ï¼ä¸å¾ä¸divèç¹çid为containerï¼é£ä¹å°±å¯ä»¥è¡¨ç¤ºä¸º#containerï¼å ¶ä¸#å¼å¤´ä»£è¡¨éæ©idï¼å ¶åç´§è·idçå称ãå¦å¤ï¼å¦ææ们æ³éæ©class为wrapperçèç¹ï¼ä¾¿å¯ä»¥ä½¿ç¨.wrapperï¼è¿é以ç¹ï¼.ï¼å¼å¤´ä»£è¡¨éæ©classï¼å ¶åç´§è·classçå称ãå¦å¤ï¼è¿æä¸ç§éæ©æ¹å¼ï¼é£å°±æ¯æ ¹æ®æ ç¾åçéï¼ä¾å¦æ³éæ©äºçº§æ é¢ï¼ç´æ¥ç¨h2å³å¯ãè¿æ¯æ常ç¨ç3ç§è¡¨ç¤ºï¼åå«æ¯æ ¹æ®idãclassãæ ç¾åçéï¼è¯·ç¢è®°å®ä»¬çåæ³ã
å¦å¤ï¼CSSéæ©å¨è¿æ¯æåµå¥éæ©ï¼å个éæ©å¨ä¹é´å ä¸ç©ºæ ¼åéå¼ä¾¿å¯ä»¥ä»£è¡¨åµå¥å ³ç³»ï¼å¦#container .wrapper på代表å éæ©id为containerçèç¹ï¼ç¶åéä¸å ¶å é¨çclass为wrapperçèç¹ï¼ç¶ååè¿ä¸æ¥éä¸å ¶å é¨çpèç¹ãå¦å¤ï¼å¦æä¸å ç©ºæ ¼ï¼å代表并åå ³ç³»ï¼å¦div#container .wrapper p.text代表å éæ©id为containerçdivèç¹ï¼ç¶åéä¸å ¶å é¨çclass为wrapperçèç¹ï¼åè¿ä¸æ¥éä¸å ¶å é¨çclass为textçpèç¹ãè¿å°±æ¯CSSéæ©å¨ï¼å ¶çéåè½è¿æ¯é常强大çãå¦å¤ï¼CSSéæ©å¨è¿æä¸äºå ¶ä»è¯æ³è§åï¼å ·ä½å¦ä¸è¡¨æ示ã
ä½æ¯ï¼è¿æ ·æ¯æ¬¡é½è¦æµªè´¹é¨åæ¶é´å»å¯»æ¾å®ä½ç¹ï¼è¿æ ·æ¢ä¸å¾æ¹ä¾¿ï¼ä¹ä¸é«æï¼é£ä¹å¦ä½æé«ç¬è«ä¸è¿é¨åå·¥ä½çæçå¢ï¼ä»å¤©æç»å¤§å®¶å享ä¸ä¸ªç¬è«çå©å¨ï¼å®å°±æ¯ï¼SelectorGadget æ件ã
point and click CSS selectorsï¼å¼ºå¤§çè°·ææ件CSSçæå¨ï¼æå©äºæ们快éæ¾å°htmlçèç¹ä¿¡æ¯ï¼å®ä¹æ¯æXpath表达å¼ãSelector Gadgetæ¯ä¸ä¸ªå¼æºçChromeæ©å±ç¨åºï¼å¯ä»¥è½»æ¾å°å¨å¤æçç½ç«ä¸çæåéæ©CSSéæ©å¨ãå®è£ æ©å±ç¨åºåï¼è½¬å°ä»»æ页é¢å¹¶å¯å¨å®ãç½ç«å³ä¸æ¹ä¼æå¼ä¸ä¸ªæ¹æ¡ãåå»æ¨å¸æéæ©å¨å¹é ç页é¢å ç´ ï¼å®å°å为绿è²ï¼ãç¶åSelectorGadgetå°ä¸ºè¯¥å ç´ çæä¸ä¸ªæå°çCSSéæ©å¨ï¼å¹¶çªåºæ¾ç¤ºï¼é»è²ï¼éæ©å¨å¹é çææå 容ãç°å¨åå»çªåºæ¾ç¤ºçå ç´ å°å ¶ä»éæ©å¨ä¸å é¤ï¼çº¢è²ï¼ï¼æåå»æªçªåºæ¾ç¤ºçå ç´ å°å ¶æ·»å å°éæ©å¨ãéè¿è¿ä¸ªéæ©åæç»è¿ç¨ï¼SelectorGadgetå¯ä»¥å¸®å©æ¨æ¾å°æ»¡è¶³æ¨éæ±çå®ç¾CSSéæ©å¨ã
é¦å éè¦å®è£ ä¸ä¸è¿ä¸ªç¥å¨ãå¨è°·ææµè§å¨ä¸çåºç¨ååºéï¼æç´¢å°SelectorGadgetæ件ï¼ç¹å»âæ·»å è³Chromeâå³å¯ãå¦æä¸è½æå¼Chromeåºç¨ååºï¼å¯ä»¥éè¿ç½ä¸çå ¶ä»éå¾è·å该æ件ï¼ä¹ååæå¨æ·»å è³è°·ææµè§å¨å³å¯ãæå¨æ·»å æ¹æ³æ¯ï¼æå¼è°·ææµè§å¨æ©å±ç¨åºï¼å¹¶å¼å¯å¼åè 模å¼ï¼å°è¯¥æ件ææ½å°æµè§å¨éï¼å¦æä¸æåï¼å¯ä»¥éæ©âå 载已解åçæ©å±ç¨åºâï¼å°è¯¥æ件夹å å缩å解åæ·»å è¿å»ã
åæ¶å¨é¡µé¢æ ä¸è½çå°çº¢æ¡å¤çSelectorGadgetæ å¿ã
å±ä»¬ä»¥ / ç½é¡µä¸ºä¾ï¼é¦å ç¹å»ç½é¡µä¸æ¹çSelectorGadgetï¼ç¶åå¨ç½é¡µä¸æ¹å¼¹åºSelectorGadgetæ¡ãæ¥ä¸æ¥ï¼å±ä»¬å°è¯ä¸ä¸å¦ä½ä½¿ç¨ï¼æ¯å¦æ们æ³å®ä½âæå¼¹ä¸å®¶2âçèç¹ï¼ç´æ¥ç¹å»å®ï¼åä¼å¨æ¡å æ¾ç¤ºå ¶èç¹ââ.item-titleï¼å¦ä¸å¾ï¼ã绿è²ï¼åå»å¸æéæ©å¨å¹é ç页é¢å ç´ ãé»è²ï¼çæçè¿ä¸ªCSSéæ©å¨ã.item-titleãè½å¹é çææå 容ï¼ä»ä¸å¾å¯ä»¥åç°è¯¥CSSéæ©å¨å¯ä»¥æååªäºä¿¡æ¯ãå½ä½ æé¼ æ æ¾å°è¿äºé«äº®çå ç´ ä¸æ¶ï¼ä¼æ¾ç¤ºçº¢è²ï¼ä»£è¡¨è¦å°å ¶ä»éæ©å¨ä¸å é¤ï¼èåå»æªçªåºæ¾ç¤ºçå ç´ å°å ¶æ·»å å°éæ©å¨ã
å¨å®é æ建CSS表达å¼è¿ç¨ä¸ï¼éè¦æé ç½é¡µå¼åå·¥å ·éçcopy selectoråè½ä½¿ç¨ï¼é常æ¹ä¾¿ãæ¯å¦ï¼å®ä½æå以ä¸ç½é¡µï¼ /allmovies ï¼çº¢æ¡å çææçµå½±å称信æ¯ã
å ·ä½æä½æµç¨å¦ä¸ï¼å¨æä¸çµå½±å称ä¸å³å»ââæ£æ¥ï¼å®ä½å°çµå½±å称çç½é¡µæºç å¤ï¼ç¶åå³å»âcopyâcopy selectorï¼åæå¼SelectorGadgetï¼å¨æ¡å ç²è´´CSS表达å¼ï¼æEnteré®ï¼åç°å¨ç½é¡µä¸åç¡®å®ä½åºè¯¥çµå½±å称ãä½æ们éè¦å®ä½ææççµå½±å称ï¼å¨å·²æçCSS表达å¼ãï¼body > div:nth-child(5) > div.inner-wrapper > div.inner-2col-main > div > ul > li:nth-child(1) > a > span.item-titleï¼ãä¸è¿è¡ä¿®æ¹ï¼è¿éå¯ä»¥åç°ãli:nth-child(1)ãåªéåäºç¬¬ä¸ä¸ªï¼èæ们æééåææï¼å æ¤å»æåé¢çã(1)ãå³å¯ãå¨å ·ä½å®è·µä¸ï¼å¦ä½ä¿®æ¹è¿ææèµäºå°ä¼ä¼´å¯¹ç½é¡µç»æåCSSè¯æ³çç解ãæç»ï¼è·å以ä¸çº¢æ¡å çµå½±å称çCSSéæ©å¨è¡¨è¾¾å¼ä¸ºï¼ãbody > div:nth-child(5) > div.inner-wrapper > div.inner-2col-main > div > ul > li:nth-child > a > span.item-titleããå¨å®é åºç¨ä¸ï¼ä¸åèç¹æ ç¾ä¹é´ç¨ç©ºæ ¼åéï¼ãbody div:nth-child(5) div.inner-wrapper div.inner-2col-main div ul li:nth-child a span.item-titleãã
ä½å¨SelectorGadget使ç¨ä¸ï¼åç°å ¶æ建çCSS表达å¼å¾å¾å¾å¤æï¼å¤§é¨åæ åµä¸å ¶å®ä¹æ¯å¯ä»¥èªå·±æ建CSS表达å¼çãæ建CSSéæ©è¡¨è¾¾å¼çå ³é®å¨äºæ¸ æ¥æ´ä¸ªç½é¡µç»æï¼æ¾å°æ ç¾ä¹é´çå±äºå ³ç³»ãå±æ§å¼å±äºåªä¸ªæ ç¾çï¼å°±å¯ä»¥æ¯è¾å¿«éçæ建ãèä¸æµè§å¨å¾æºè½ï¼é¼ æ å¨æºç ä¸çä½ç½®ï¼å¯ä»¥å¨ç½é¡µæ¾ç¤ºä¸åç°é´å½±ï¼è¿ä¸ªåè½ç®ç´å¤ªé¦äºã以ä¸çº¢æ¡å ææçµå½±ååçCSS表达å¼ä¹å¯ä»¥è¡¨ç¤ºä¸ºï¼ãdiv.movlist ul li a span.item-titleã
ç¶åç¨SelectorGadgetéªè¯ï¼å¦ä¸å¾ï¼åç°é«äº®éä¸çé¨åå°±æ¯ææ³è¦å®ä½çä¿¡æ¯ï¼è¯´æè¿ä¸ªCSS表达å¼æ£ç¡®ã
以ä¸ç®åä»ç»äºå¦ä½æ建CSS表达å¼ï¼å¨ç¬è«è¿ç¨ä¸å®ä½æåç¹å®èç¹æ°æ®æ¯é常éè¦çä¸æ¥ï¼æäºè¿æ¥ææä¸æ¸¸çç²¾åæ°æ®æåä¸æ¸ æ´ãå¸ææ¬æ¬¡æç¨è½ç»æéè¦çå°ä¼ä¼´ä¸ç¹å°å°å¸®å©ï¼
æ´å¤å 容å¯å ³æ³¨å ¬å ±å·âYJYæè½ä¿®ç¼â~~~
å¾æå顾
Rç¬è«å¨å·¥ä½ä¸çä¸ç¹å¦ç¨
Rç¬è«å¿ å¤åºç¡ââHTMLåCSSåè¯
Rç¬è«å¿ å¤åºç¡ââéæç½é¡µ+å¨æç½é¡µ
Rç¬è«å¿ å¤âârvestå ç使ç¨
2024-11-29 22:302414人浏览
2024-11-29 22:171096人浏览
2024-11-29 22:13902人浏览
2024-11-29 21:53496人浏览
2024-11-29 21:421904人浏览
2024-11-29 20:421536人浏览
1.小说阅读app源码_小说网站cms源码uniapp+手机+小程序三端)小说阅读app源码_小说网站cms源码uniapp+手机+小程序三端) 随着互联网技术的飞速发展,小说网站逐渐成为了人们日
1.什么是电脑程序源代码什么是电脑程序源代码 源码就是指编写的最原始程序的代码。运行的软件是要经过编写的,程序员编写程序的过程中需要他们的“语言”。音乐家用五线谱和音符,建筑师用图纸和笔,那程序员