Filament引擎(四)——光照渲染Froxelizer實現分析

Froxelizer主要是用于filament光照效果的實現,生成光照渲染時所需的必要信息,幫助渲染過程中明確哪些區域受哪些光源所影響,是Filament中保證光照效果渲染效率的核心所在。這部分的源碼,可以結合filament官方文檔中Light Path部分進行理解。

光照效果渲染

優秀的光照效果渲染一般都是影響引擎渲染性能的重要影響點,引擎也可能會對場景中可以使用的光的數量施加嚴格的限制,以此來保證渲染性能。3D引擎通常有兩種主要的渲染方式:

  • 前向渲染(forward rendering):像拍照一樣,一次性把所有物體的顏色、光照等信息算好再繪制到屏幕上。
  • 延遲渲染(deferred rendering):先記錄每個像素的位置和材質信息,最后再統一計算光照效果,適合復雜場景。

一些現代3D引擎采用延遲渲染的方式,可以輕松支持數十、數百甚至數千個光源,但是往往需要很高的顯存帶寬。按照filament中默認的PBR材質模型,一個像素的渲染,可能就需要160到192 bits的G-Buffer占用,這就意味著非常高的顯存帶寬需求。

前向渲染過去一直難以處理多個光源。一種常見做法是 多次渲染場景(每個可見光源一次),然后將結果疊加。另一種方法是為每個物體分配固定的光源上限。但若場景中存在大片區域(如建筑、道路),這種方法會變得不切實際。

分格著色(Tiled Shading)可應用于前向和延遲渲染方法。這個想法是將屏幕分割成一個圖塊網格,對于每個圖塊,找到影響該圖塊中像素的燈光列表。這樣做的優點是可以減少延遲渲染中的過度繪制和前向渲染中大型對象的著色計算。但是因為深度不連續的問題(如物體邊緣),也可能導致大量無關的計算。

Filament的光照效果渲染以低帶寬、每個像素支持多個動態光源為限定條件,另外期望能夠輕松支持多重采樣抗鋸齒、透明度、多種材料模型等能力。

為了平衡光照渲染效果和渲染性能,Filament引入了一種稱之為“分簇著色(Clustered Shading)”的前向渲染的變體方式。分簇著色擴展了分格著色的概念,但在z軸上添加了分段。“分簇”操作是在視圖空間中完成的,方法是將視錐體分割成3D網格。這個過程,類似把一個2D圖片分割成塊,把3D空間分割成小立方塊,只是這個分割是在是視錐空間進行的。如下圖所示:
Froxel

在Filament中,把分割的結果每塊稱之為Froxel(a voxel in frustum space,造詞小能手結合了frustum和voxel,來表示在視錐空間下的體素)。

在渲染一幀畫面之前,場景中的每個光源會被分配到所有它覆蓋的Froxel中。這樣,每個Froxel最終都會有一個它關聯的光源列表。在實際渲染時,我們只需要找到當前像素片段所屬的Froxel,就能直接獲取影響該像素的所有光源。

Froxel的深度劃分不是等距的,而是指數型增長的。因為人眼對靠近攝像機的物體更敏感(畫面中近處的像素更多),所以我們會把靠近攝像機的位置劃分得更細一些,而遠處劃分得更粗。這種指數型的Froxel深度劃分方式,能讓光源分配在最需要的地方更精確,提升渲染效率和呈現效果。

參考Froxelizer中的注釋,froxel緩沖區的條目數量由最大UBO大小決定(見getFroxelBufferByteCount()函數)。 另外,因為增加froxel數量會加重"記錄緩沖區"的壓力(這個緩沖區負責存儲每個froxel的光索引),filament中還設置記錄緩沖區的容量限制為min(16K[UBO], 64K[uint16])條目。實際上,部分froxel并未被使用,因此我們能存儲更多數據。

渲染實現流程

同通用的圖像渲染流程一樣,Filament中對于光照效果的渲染,從大的使用步驟上來看,也是經過材質的構建、Shader的編譯、GPU Program的啟用、Program參數的傳遞以及最后的DrawCall。前文所提到的Froxelizer,主要的工作是計算出需要傳遞Program的和光照信息相關的參數。

材質的使用

有OpenGL、DX、Vulkan等渲染基礎的朋友都知道,需要使用這些圖形API進行圖像的渲染,一般都需要通過編寫頂點著色器和片元著色器,構建出GPU執行的渲染程序,然后再渲染前設置好相關的程序參數(Attribute變量、Uniform變量等),最后材質DrawCall。

而在常見的渲染引擎或者游戲引擎中,如nity、Unreal等,都會通過材質的概念,將渲染程序以及渲染管線中的一些數據、狀態等組織到一起,來進行渲染效果的實現。這樣既方便資源的擴展與復用,又能解耦設計效果和渲染邏輯。

Filament中也存在材質的概念,其實現在MaterialBuilder中,官方有專門的文檔來介紹Filament中的PBR材質,材質系統及自定義材質的方式,可優先參考官方文檔。Filament中的材質構建流程,簡單來說主要包括以下步驟:

  1. 編寫材質文件:filament中的材質使用自定義的文件結構,定義了包括材質參數、頂點著色器、片元著色器等等各種信息。
  2. 材質編譯:Filament提供了材質編譯器(filament/tools/matc),將材質文件編譯為二進制.
  3. 材質構建:使用Material::Builder進行材質構建,package加載編譯后的材質文件,constant設置常量,build方法構建出材質實例。
  4. 材質設置:構建出的材質對象,可以通過setDefaultParameter來設置默認材質實例的參數。如果一個材質對象,需要傳遞不同的參數,應用到不同地方,可通過createInstance來創建不同的實例,然后修改其參數進行應用。
  5. 材質應用:參考Filament引擎(二) ——引擎的調用及接口層核心對象,將材質實例綁定給渲染實體。

Shader的構建

在filament中存在多種光照效果,關于光照的Shader模型如下:

/*** 支持的Shader模型*/
enum class Shading : uint8_t {UNLIT,                  //!< 不應用光照,可采用自發光方式LIT,                    //!< 默認的, 標準光照模型,模擬金屬與非金屬的剛體材質SUBSURFACE,             //!< 次表面散射光照模型,模擬光線穿透材質內部的散射效果CLOTH,                  //!< 布料光照模型,模擬紡織物的纖維堆疊與散射特性SPECULAR_GLOSSINESS,    //!< 鏡面光澤度光照模型,兼容傳統美術流程,通過鏡面反射與光澤度參數替代金屬度
};

以標準光照模型為例,matc中使用MaterialCompiler進行材質編譯時,會先使用MaterialBuilder來進行材質的構建。參考MaterialBuilder::build函數實現,其內部會調用MaterialBuilder::generateShaders函數,進一步調用到ShaderGenerator中的createSurfaceVertexProgramcreateSurfaceFragmentProgram函數。這兩個函數中,在有光源時,都會通過調用generateSurfaceMaterialVariantDefines來在Shader中增加對應的宏,方向光增加宏VARIANT_HAS_DIRECTIONAL_LIGHTING。存在動態光源(有點光源及聚光燈光源),會增加宏VARIANT_HAS_DYNAMIC_LIGHTING

ShaderGenerator::createSurfaceFragmentProgram中,當在材質文件中指定了光照模型shadingModel: lit時,會調用CodeGenerator::generateSurfaceLit來將以下文件也寫入到片元著色器的stream中:

  1. surface_lighting.fs
  2. 啟用了陰影效果渲染時:surface_shadowing.fs
  3. surface_shading_model_standard.fs
  4. surface_brdf.fs
  5. surface_ambient_occlusion.fs
  6. surface_light_indirect.fs
  7. surface_shading_lit.fs
  8. 有方向光時:surface_light_directional.fs
  9. 有動態光時(即存在點光源或者聚光燈光源):surface_light_punctual.fs

后面CodeGenerator::generateSurfaceMain會將surface_main.vssurface_main.fs分別寫入到頂點著色器和片元著色器的stream中,這兩個文件中包含著shader的mian函數。經過各種方式拼接出來的stream,會通過GLSLPostProcessor::process進行優化及轉換,變成指定的Shader語言,GLSL、MSL、WGSL、SPirv等。

在需要進行包含動態光源的光照效果渲染時,以Lit光照模型為例,Shader的主要調用流程為:surface_main.fs: main -> surface_shading_lit.fs: evaluateMaterial -> surface_shading_lit: evaluateLights -> surface_light_punctual.fs: evaluatePunctualLights。在這個渲染過程中,需要用到Froxelizer生成的Froxel信息以及在場景中增加的光源信息,我們還需要確認下這部分信息的傳遞流程。

Froxel信息的傳入

在Filament引擎(三) ——引擎渲染流程的分析中有提到,Filament會在每幀渲染前,view.prepare的過程中,判斷存在點光源或者聚光燈光源時,進行Froxel化,計算出所有分簇被哪些光源所影響,并在Color Pass渲染之前,將這些信息提交到GPU中進行使用。

在filament中,UibGenerator被用來進行GPU緩沖區數據的生成和管理,在ShaderGenerator::createSurfaceFragmentProgram創建片元著色器時,如果存在動態光源,會通過CodeGenerator::generateUniforms在片元著色器中生成綁定點為RECORD_BUFFERFROXEL_BUFFER的Uniform變量,關聯對應的BufferInterfaceBlock對象。這樣片元著色器中,就存在了后續用來接收Froxel數據的Uniform端點。

在FView構造函數中,Froxelizer中用來接收Froxel數據的Uniform Buffer Object對象mRecordsBuffermFroxelsBuffer,通過ColorPassDescriptorSet::init函數,生成分別與RECORD_BUFFERFROXEL_BUFFER綁定點關聯的描述信息,然后在FRenderer::renderJob中,通過 fg.addTrivialSideEffectPass("Prepare View Uniforms", xxx)發送任務到渲染線程,執行view.commitUniformsAndSamplers來真正實現的UBO和綁定點的關聯。

在進行Color Pass的渲染之前,再通過fg.addTrivialSideEffectPass("Prepare Color Passes", xxx)發送任務到渲染線程,等待Froxel化及數據壓縮完成,執行view.commitFroxels來提交壓縮數據到GPU中對應的UBO中。這樣片元著色器在需要進行光照渲染時就可以使用到mRecordsBuffermFroxelsBuffer對應的數據。

光源信息的傳入

光照信息傳入到Shader中的流程,與Froxel信息的傳入類似。在ShaderGenerator::createSurfaceFragmentProgram創建片元著色器時,如果存在動態光源,會通過CodeGenerator::generateUniforms在片元著色器中生成綁定點位LIGHTS的Uniform變量,關聯對應的BufferInterfaceBlock對象,用于后續接收光照信息的傳入。ColorPassDescriptorSet::init函數在生成與Froxel綁定點關聯的描述信息,也會生成與光照信息綁定點關聯的描述信息。

FView.prepare時,會調用FView.prepareLighting,當存在動態光源時,它會調用scene->prepareDynamicLights來將LightSoa數據轉換成LightsUib數組,傳遞到GPU中,在evaluatePunctualLights函數中同Froxel數據一起被使用。

Froxel準備過程

當FView進行prepare時,會進行是否存在動態光源的判斷,即用戶是否設置了點光源或者聚光燈。如果存在動態光源,就會進行Froxel的準備工作。在filament中一個場景下,動態光源的最大個數限制為256,在Froxelizer源碼實現中,緩存相關的信息也是按照最大256個光源來進行實現的。

映射信息

Froxel的準備工作,主要是為了生成緩存信息。在Froxelizer中,有幾個重要的對象:

  1. mFroxelShardedData:對象類型為utils::Slice<FroxelThreadData>,每個FroxelThreadData實際上是數組LightGroupType[8192](LightGroupType實際上是一個uint32_t),切片大小為GROUP_COUNT(也就是8),實際占用256KiB的大小。數據在froxelizePointAndSpotLight函數中被填充,它記錄的是所有Froxel哪些光源所影響。
  2. mFroxelBufferUser: 對象類型為utils::Slice<FroxelEntry>, FroxelEntry結構體中只有一個uint32_t,高16位記錄的是燈光信息的索引(相對mLightRecords),低8位記錄的是燈光的數量。切片Size是mFroxelBufferEntryCount(不出意外就是8192),所以它的內存占用大概為32KiB。這部分數據會傳遞到GPU,用于著色器渲染時查詢受哪些光源的影響。通過FroxelEntry可以在mRecordBufferUser中,找到哪些燈光對當前Froxel生效。
  3. mLightRecords:對象類型為utils::Slice<LightRecord>, LightRecord結構體中只有一個256位的bitset,表示燈光索引,記錄每個Froxel用到了哪些燈光。切片Size為mFroxelBufferEntryCount(8192),和所以它的內存占用大概也是256Kib。
  4. mRecordBufferUser: 對象類型為utils::Slice<RecordBufferType>,RecordBufferType是一個uint8_t,記錄的是“編碼”后的燈光信息。按照filament中的實現,低3位記錄了第幾個分組,高5位記錄了分組中的第幾個。切片Size為RECORD_BUFFER_ENTRY_COUNT(16384),所以它的內存占用大概為16KiB。 數據也是在froxelizeAssignRecordsCompress函數中被填充。

在Froxelizer.h文件頭部有一個圖示,結合后面的分析重繪一下大致如下:
FroxelData

主要實現

由外部設置FView傳入Viewport、投影矩陣等信息,Froxelizer會進行必要數值的計算,決定如何切分視錐。

在filament中Froxelizer::prepare過程中有幾個重要的數值計算的偽代碼如下:

// 記錄Froxel索引的緩沖區大小,決定了一共可以分割多少個froxel出來。
// maxUniformBufferSize為打包多個Uniform變量的結構塊的最大值(字節),OpenGL中通過`glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,  &maxUniformBufferSize)`獲取
mFroxelBufferByteCount = min(8192 * 4, maxUniformBufferSize/16*16);
// Forxel的索引數。FroxelEntry結構體內部存儲的是一個uint32_t,所以也就是4字節
mFroxelBufferEntryCount = getFroxelBufferByteCount()/sizeof(FroxelEntry);
// Z軸的切片固定位16段,上面有提到Froxel的深度劃分不是等距離的,而是按照指數增長的方式切分。切分段數固定位16段。
mFroxelSliceCount = 16;
// 最大索引數固定了,Z方向分段固定,所以XY平面分塊的最大值也就知道
mFroxelMaxXYPlaneCount = mFroxelBufferEntryCount / mFroxelSliceCount;
// XY平面分塊的尺寸計算。分塊的邏輯是按照Viewport的大小劃分出接近mFroxelMaxXYPlaneCount個正方形,且保證分塊的寬高是8的倍數,以優化Shader的性能。
mFroxelXYTiledDimension =  maxRound8(ceil(width / sqrt(mFroxelMaxXYPlaneCount * width / height)), ceil(height / sqrt(mFroxelMaxXYPlaneCount * height / width)));
// 根據尺寸再計算出X和Y方向的切分,保證 mFroxelXCount * mFroxelYCount <= mFroxelMaxXYPlaneCount
mFroxelXCount = ceil(width / mFroxelXYTiledDimension);
mFroxelYCount = ceil(height / mFroxelXYTiledDimension);

根據計算的信息,去更新緩存數據的空間,緩存數據記錄在一個LinearAllocatorArena中。主要記錄的內容有:

  1. 用來表示Z軸切分點的 mFroxelSliceCount + 1(17)個float,記為mDistancesZ.
  2. 用來表示視圖空間下X平面的mFroxelXCount + 1 個float4,通過平面方程x * A + y * B + z * C + w = 0表示一個平面,記為mPlanesX,下面Y平面也類似。
  3. 用來表示視圖空間下Y平面的mFroxelYCount + 1 個float4,記為mPlanesY
  4. 用來表示包圍球的mFroxelXCount * mFroxelYCount * mFroxelSliceCount個float4(其中,xyz表示包圍球中心,w表示球半徑),記為mBoundingSpheres

相機信息中一般包含相機的投影矩陣mProjection,用來將視圖空間坐標轉換成投影空間(或者叫裁切空間)的坐標。在Froxel中,期望存儲的是視圖空間下的6個平面信息,進行Froxel分割時,已經算出x、y、z三個方向上的切分份數,已知viewport大小,filament中通過以下方式,計算出視圖空間下的X平面和Y平面信息。

// 投影矩陣的轉置矩陣,用來將投影空間轉換成視圖空間
const mat4f trProjection(transpose(mProjection));for (size_t i = 0, n = mFroxelCountX; i <= n; ++i) {float const x = (float(i) * froxelWidthInClipSpace) - 1.0f;// float4{ -1, 0, 0, x } 表示一個平面方程: -1 * x' + 0 * y' + 0 * z' = x,X平面垂直于x軸。這里構建的是投影空間下的平面。// 通過投影矩陣轉置后 與 X平面相乘,并進行歸一化,得到視圖空間下的X'平面方程float4 const p = trProjection * float4{ -1, 0, 0, x };planesX[i] = float4{ normalize(p.xyz), 0 }; 
}// 求視圖空間下的Y'平面方程
for (size_t i = 0, n = mFroxelCountY; i <= n; ++i) {float const y = (float(i) * froxelHeightInClipSpace) - 1.0f;float4 const p = trProjection * float4{ 0, 1, 0, -y };planesY[i] = float4{ normalize(p.xyz), 0 };  // p.w is guaranteed to be 0
}

然后更新mBoundingSpheres,包圍球的計算邏輯為:

  1. 每個Froxel因為是從視錐中截出來的,X、Y是等分的,Z是指數增長的,所以每個Froxel也是一個截錐體。
  2. 每個截錐體有6個面,XYZ三個面一定有一個交點,根據6個面求出截錐體的八個頂點。
  3. 八個頂點相加再除以8(乘0.125)就得到了球的中心。
  4. 計算八個點中離球中心最遠的距離,作為球的半徑。

最后,計算mParamsZ(float)的幾個屬性值,mParamsZ將被傳入Shader中的參數,這部分的內容,在官方文檔中8.4.2.4部分也有介紹,實際上zParamsZ的值大約為:

zParams[0] = 1.0f - Z_FAR / Z_NEAR;
zParams[1] = Z_FAR / Z_NEAR;
zParams[2] = (MAX_DEPTH_SLICES - 1) / log2(Z_SPECIAL_NEAR / Z_FAR);
zParams[3] = MAX_DEPTH_SLICES;      // mFroxelSliceCount

Froxel化過程

參考Froxelizer::froxelizeLights方法。froxel化的過程主要有兩步:先將所有的燈光的處理分組打包成多個任務(CONFIG_MAX_LIGHT_COUNT定義了最大燈光個數,LIGHT_PER_GROUP定義了每組燈光個數),拋到JobSystem中進行執行,將Froxel化的結果寫到mFroxelShardedData中,然后再對Froxel化的結果mFroxelShardedData進行壓縮。

froxelizePointAndSpotLight

froxelizeLoop函數負責將燈光的處理進行分組打包,并拋到JobSystem中執行。froxelizeLoop中構建的process,會以分組數(源碼中固定為8)作為步長,以0-7分別作為offset,分成8個任務,對所有燈光信息進行遍歷,并調用froxelizePointAndSpotLight來進行處理,填充mFroxelShardedData數據。
froxelizePointAndSpotLight的處理過程,主要就是確認指定的燈光對那些Froxel有影響,記錄到mFroxelShardedData中,其實現步驟主要如下:

  1. 根據燈光的位置和半徑,構建出燈光的包圍盒。然后將相機的投影矩陣(裁切矩陣)參與計算,得到燈光包圍盒投影后,在投影平面的八個點。
  2. 計算包圍八個二維點的矩形框上兩個角的點p1(min(x), min(y))和 p2(max(x), max(y))。前面有說到,Froxel化在XY兩個方向上是進行等分的,用這兩個點,就能算出光照影響到的Froxel的XY兩個方向的Index方法。根據光源的中心位置和光源半徑,知道光源影響的近點和遠點,再結合Froxel在Z方向的指數增長的劃分方式,可以算出光照影響在Z方向的兩個Index。這6個Index(記作XMin、XMax、YMin、YMax、ZMin、ZMax),初步限定了光照影響Froxel的范圍。
  3. 在Z方向上,從ZMin到ZMax進行遍歷(記為z1),計算光照球和Z=z1的平面是否相交。相交的話(不包括相切),用相交形成的圓構建一個新的球,記為S1。如果z1和光照球中心所在的Z一致的話,不必計算新球,光照球就是S1。
    1. 計算出這個新球S1的球心,在投影平面上的點C1(x0,y0),和前面一樣,用這個投影點,可以很容易計算出它對應的Froxel在XY兩個方向上的Index。
    2. 在Y方向上,從YMin到YMax進行遍歷(記為y1),計算球S1與當前Y平面是否相交。相交的話,用相交形成的圓可以再構建一個新球,記為S2。
      1. 在X方向上,從XMin到XMax進行遍歷,計算球S2與當前X平面是否相交,找到與S2球相交的X平面區間 bx和ex。
      2. Froxel切分按照先X軸從小到大,再Y軸從小到大,最后Z軸從近到遠的順序進行Index計數。根據(bx, y1, z1) 計算得到X遍歷時的Froxel的最小Index,記為fi。
      3. fifi + ex - bx區間的Froxel,如果該光源是點光源,就都會被該光源影響。如果是聚光燈,就需要計算聚光燈的圓錐體和Froxel的包圍球是否相交,相交的則是被該光源影響。
      4. 被光源影響的Froxel,信息更新方式為:froxelThread[fi++] |= LightGroupType(1) << bit。bit是froxelizePointAndSpotLight的入參,代表光源在分組中的Index。

Froxel化的過程,主要時出于計算效率的考慮,以分組的方式,將所有的光源對Froxel影響的計算,拆分到多個線程進行并行處理。在計算光源是否對Froxel有影響時,為了計算效率和準確性的雙重考慮,按照Z->Y->X三個方向逐步判斷,Z平面與光源球相交后,用相交的圓構建新球來圈定Z值固定時,光源對Y方向Froxel的影響。再以類似的方式,圈定Y值也固定時,光源對X方向Froxel的影響。通過這種方式,高效的確認所有被當前光源影響的Froxel,而不是對所有Froxel遍歷判斷是否被當前光源影響,以此提高Froxel化效率。

froxelizeAssignRecordsCompress

froxelizeAssignRecordsCompress顧名思義,主要用于將froxel的信息進行壓縮。在froxelizeLoop中,為了提高Froxel化效率,將Froxel化過程拆成了8個分組,丟到8個線程中并行處理。最多256個燈光對于每個Froxel的影響,也被分別存儲在8個int32_t中。在froxelizeAssignRecordsCompress中,會對這些數據進行壓縮處理,降低對GPU的帶寬需求,同時也便于后續的計算優化。其主要實現過程如下:

  1. 將Froxel數據(燈光信息, mFroxelShardedData),從N組M位轉換成一個256位的bitset(轉換的結果存儲在mLightRecords中),便于進行相鄰比較和壓縮。每一個froxel就對應著一個LightRecord(256位),記錄哪些燈光針對該Froxel生效。
  2. mLightRecords遍歷進行或運算,得到被使用的所有燈光信息,allLights(LightRecord::bitset類型)。
  3. 遍歷allLights中被置為1的每一位用uint8_t來進行“編碼”:以32個燈光為一組,一共分8組。低3位(0-7),表示屬于哪一組,高5位(0-31),表示一組中的第幾個。編碼信息存儲在mRecordBufferUser中。
  4. 遍歷mLightRecords,對所有Froxel和影響它的光源信息,按照步驟3的方式,進行編碼。編碼信息同樣存儲到mRecordBufferUser

按照以上的方式,如果只有兩個燈光,都只對一半的Froxel生效,則只需要2 + 8192(Froxel總個數) * 0.5(一半的生效) * 2(燈光數)個uint_8,再加上8192個uint32(mFroxelBufferUser)記錄FroxelEntry,即可存儲所有的光源對所有Froxel的影響信息。而原始數據需要8192(Froxel總個數) * 256 (光源數) / 8 個uint_8。在實際的應用中,大量光源對大量Froxel都生效的情況畢竟是少數。

參考文檔:

  1. DOOM (2016) - Graphics Study
  2. Filament官方文檔

歡迎轉載,轉載請保留文章出處。求閑的博客[https://blog.csdn.net/junzia/article/details/149668651]


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93083.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93083.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93083.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2025 環法對決,VELO Angel Glide 坐墊輕裝上陣

2025環法第16賽段的風禿山之巔&#xff0c;當最后一縷夕陽沉入云層&#xff0c;山風裹挾著礫石的氣息掠過賽道&#xff0c;一場足以載入史冊的激戰正酣。帕雷-潘特的肌肉在汗水里賁張&#xff0c;鏈條與齒輪的咬合聲混著粗重喘息&#xff0c;在171.5公里賽程的最后3公里陡坡上&…

Linux程序->進度條

進度條最終效果&#xff1a; 目錄 進度條最終效果&#xff1a; 一&#xff1a;兩個須知 1&#xff1a;緩沖區 ①&#xff1a;C語言自帶緩沖區 ②&#xff1a;緩沖區的刷新策略 2&#xff1a;回車和換行的區別 二&#xff1a;倒計時程序 三&#xff1a;入門板進度條的實…

Python爬蟲實戰:研究tldextract庫相關技術構建新聞網站域名分析爬蟲系統

1. 引言 網絡爬蟲作為一種自動獲取互聯網信息的技術,在數據挖掘、信息檢索、輿情分析等領域有著廣泛的應用。Python 因其豐富的庫和簡潔的語法,成為了開發爬蟲的首選語言。tldextract 是 Python 中一個強大的域名解析庫,能夠準確地從 URL 中提取頂級域名、二級域名等關鍵信…

【算法-華為機試-火星基地改造】

基地改造題目描述目標輸入輸出代碼實現題目描述 在2XXX年&#xff0c;人們發現了一塊火星地區&#xff0c;這里看起來很適合建設新家園。但問題是&#xff0c;我們不能一次性將這片地區的空氣變得適合人類居住&#xff0c;得分步驟來。 把這片火星地區想象成一個巨大的棋盤。棋…

C++入門自學Day1-- C語言的宏函數和C++內聯函數

一、函數調用開銷函數調用會涉及&#xff1a;參數壓棧&#xff08;或寄存器傳參&#xff09;跳轉到函數體返回值處理棧幀銷毀這個過程對小函數來說可能非常浪費&#xff0c;因此&#xff0c;宏函數和內聯函數的目的就是避免“函數調用的開銷”&#xff0c;通過代碼展開&#xf…

Pytorch混合精度訓練最佳實踐

混合精度訓練&#xff08;Mixed Precision Training&#xff09;是一種通過結合單精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;計算來加速訓練、減少顯存占用的技術。它在保持模型精度的同時&#xff0c;通常能帶來 2-3 倍的訓練速度提升&#x…

Qt C++動態庫SDK在Visual Studio 2022使用(C++/C#版本)

01 將C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平臺下 SDK 的集成。2.1 Visual Studio 平臺下 C/C環境配置及集成到 IDE 中xxx.lib 和 xxx.dll 適合在 Windows 操作系統平臺使用&#xff0c;這里以 VS2022 環境為例。2.1.1 C/C 工程環境配置與集成1、C# SDK 接口…

大語言模型 LLM 通過 Excel 知識庫 增強日志分析,根因分析能力的技術方案(2):LangChain + LlamaIndex 實現

文章大綱 1 技術原理總覽 2 詳細實現步驟(含代碼) 2.1 環境準備 2.2 Excel → LlamaIndex 節點 2.3 構建向量索引(FAISS 本地) 2.4 Google Cloud 向量檢索(可選替換 FAISS) 2.5 LangChain 問答鏈 A. RAG 模式(向量檢索 + LLM 生成) B. SQL 模式(無 RAG,直接查表) 2.…

提升ARM Cortex-M系統性能的關鍵技術:TCM技術解析與實戰指南

文章目錄引言一、TCM基礎架構與工作原理1.1 TCM的物理特性1.2 與緩存機制的對比1.3 ARM Cortex-M系列對TCM的支持二、TCM的典型應用場景2.1 實時中斷處理2.2 低功耗模式下的待機代碼2.3 高性能算法執行2.4 系統初始化階段的關鍵代碼三、實戰指南&#xff1a;在STM32H7上配置和優…

大數據之路:阿里巴巴大數據實踐——大數據領域建模綜述

為什么需要數據建模 核心痛點 數據冗余&#xff1a;不同業務重復存儲相同數據&#xff08;如用戶基礎信息&#xff09;&#xff0c;導致存儲成本激增。計算資源浪費&#xff1a;未經聚合的明細數據直接參與計算&#xff08;如全表掃描&#xff09;&#xff0c;消耗大量CPU/內存…

實戰演練1:實戰演練之命名實體識別

實戰演練1:實戰演練之命名實體識別 命名實體識別簡介 代碼 命名實體識別簡介 什么是命名實體識別任務 命名實體識別(Named Entity Recognition,簡稱NER)是指識別文本中具有特定意義的實體,主要包括人名、地名、機構名、專有名詞等。通常包括兩部分: (1)實體邊界識別。(2)確定…

數據結構基礎內容(第七篇:堆、哈夫曼樹)

# 堆 Heap 優先隊列(Priority Queue) 結構性:用 *數組* 表示的完全二叉樹; 有序性:任一結點的關鍵字是其子樹所有結點的最大值(或最小值) * “最大堆(MaxHeap)”,也稱“大頂堆”:最大值 * “最小堆(MinHeap)”,也稱“小頂堆” :最小值 主要操作有: ? MaxHeap Create( i…

CS231n-2017 Lecture7訓練神經網絡(二)筆記

本節主要是神經網絡的動態部分&#xff0c;也就是神經網絡學習參數和搜索最優超參數的過程梯度檢查&#xff1a;進行梯度檢查&#xff0c;就是簡單地把解析梯度與數值計算梯度進行比較&#xff0c;防止反向傳播的邏輯出錯&#xff0c;僅在調試過程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未顯示項目根目錄問題

問題&#xff1a; 在IDEA中編寫代碼時&#xff0c;發現左上方只顯示項目的子模塊&#xff0c;未顯示根項目名稱。 如圖所示&#xff0c;未顯示子模塊的根項目&#xff1a;問題分析 頂層根目錄未被識別為項目根目錄&#xff0c;需要手動添加識別。 問題解決 進入File – Project…

OpenCV 圖像變換全解析:從鏡像翻轉到仿射變換的實踐指南

前言處理圖像時&#xff0c;翻轉、旋轉、平移等操作很常用。OpenCV 提供了簡單的方法實現這些變換&#xff0c;本文帶你快速學會用它做圖像翻轉和仿射變換。1 圖像翻轉(圖像鏡像旋轉)在OpenCV中&#xff0c;圖片的鏡像旋轉是以圖像的中心為原點進行鏡像翻轉的。cv2.flip(img,fl…

【運維】Linux運維命令記錄

重置root密碼使用命令重新設置一下root賬戶的密碼 passwd root根據提示設置一下密碼&#xff0c;然后使用sudo -i 時輸入密碼就可以切換到root賬戶了ssh登陸以后&#xff0c;要用sudo -i命令給用戶提權&#xff0c;提到超級管理員&#xff0c;然后輸入密碼才有用

PandasAI連接LLM進行智能數據分析

1. 引言 Pandas是一個數據分析開源組件庫&#xff0c;提供了高性能、易用的數據結構和數據分析工具。它的核心的功能是其DataFrame對象&#xff0c;這是一個帶有行和列標簽的二維表格數據結構&#xff0c;支持缺失數據處理、時間序列功能、靈活的數據輸入輸出方法、數據對齊和…

Spring之【Bean的生命周期】

目錄 1、生成BeanDefinition BeanDefinitionRegistry接口 DefaultListableBeanFactory實現類 2、合并BeanDefnition AbstractBeanFactory類 3、BeanFactoryPostProcessor的方法回調 AbstractApplicationContext類 PostProcessorRegistrationDelegate類 4、BeanPostPro…

搜狐新聞直播間適配HarmonyOs實現點贊動畫

01背景介紹隨著新聞客戶端鴻蒙單框架系統適配工作的推進&#xff0c;從原來的基礎功能到現在已經適配全功能的85%以上。與此同時&#xff0c;我們也在持續深入挖掘鴻蒙系統的特性&#xff0c;以提升整體應用的質量與用戶體驗。在這一過程中&#xff0c;動畫作為增強交互與視覺體…

83、設置有人DTU設備USR-M100采集傳感器數據,然后上傳阿里云服務

基本思想:設置M100 采集傳感器數據 一、首先將DTU設備USR-M100連接路由器上,然后使用python代碼搜索同一局域網設備, import platform import sys import os import time import threadinglive_ip = 0def get_os():os = platform.system()if os == "Windows":re…