高德地圖開發(fā)平臺淘寶seo搜索優(yōu)化
體渲染(Volume Rendering)是NeRF神經場輻射AI模型的基礎,與傳統(tǒng)渲染使用三角形來顯示 3D 圖形不同,體渲染使用其他方法,例如體積光線投射 (Volume Ray Casting)。本文介紹體渲染的原理并提供Three.js實現代碼,源代碼可以從Github下載。
推薦:用 NSDT編輯器 快速搭建可編程3D場景。
1、體渲染基礎
體渲染是基于圖像的方法,通過沿 3D 體積投射光線,將 3D 標量場渲染為 2D 圖像。 我們在屏幕上看到的每個像素都是光線穿過立方體并按一定間隔從體素獲取強度樣本的結果。
但是我們如何投射光線呢?
一個簡單的選擇是使用大小為 (1,1,1) 的 3D 網格立方體,并在兩個不同的渲染通道中渲染正面和背面(啟用和禁用背面剔除)。
對于屏幕中生成的每個立方體片段,我們可以創(chuàng)建一條從立方體正面開始并在背面結束的射線。 有了光線的起點和終點,我們就可以開始對體素進行采樣,以生成最終的片段顏色。
標量場表示為包含每個 (x,y,z) 位置處的強度值的體素
面我們將解釋使用 WebGL 和 ThreeJS 實現體渲染的實現步驟。
2、準備原始文件
原始文件是非常簡單的文件,僅包含體素強度,它們沒有標頭或元數據,并且通常每個體素具有按 X、Y 和 Z 順序排列的 8 位(或 16 位)強度值。
在 OpenGL 或 DirectX 中,我們可以將所有這些數據加載到專門設計的 3D 紋理中,但由于 WebGL 目前不支持存儲或采樣 3D 紋理,因此我們必須以 2D 紋理可以使用的方式存儲它 。 因此,我們可以存儲一個 png 圖像文件,其中所有 Z 切片一個挨著一個,形成 2D 切片的馬賽克。 我開發(fā)了一個非常簡單的轉換器工具,其中包含源代碼。 該工具獲取原始文件并生成一個 png 圖像馬賽克,對 alpha 通道中每個體素的強度進行編碼(盡管理想的是將 png 存儲為 A8 格式只是為了節(jié)省一些空間)。
一旦 png 文件作為 2D 紋理加載到內存中,我們就可以使用我們自己的自定義 SampleAs3DTexture 函數對其進行采樣,就好像它是 3D 紋理一樣。
在本文末尾的參考資料部分中查找更多要測試的原始文件。
3、第一個渲染通道
在第二步中,我們打算生成用作光線終點的片段。 因此,對于第一個渲染通道,我們不是繪制背面顏色,而是將片段的世界空間位置存儲在渲染紋理中,作為 RGB 片段顏色內的 x、y、z 坐標值(此處 RGB 被編碼為浮點值)。
請注意 worldSpaceCoords 如何用于存儲立方體背面位置的世界空間位置。
頂點著色器第一遍:
varying vec3 worldSpaceCoords;void main()
{
//Set the world space coordinates of the back faces vertices as output.
worldSpaceCoords = position + vec3(0.5, 0.5, 0.5); //move it from [-0.5;0.5] to [0,1]
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
片段著色器第一遍:
varying vec3 worldSpaceCoords;void main()
{
//The fragment's world space coordinates as fragment output.
gl_FragColor = vec4( worldSpaceCoords.x , worldSpaceCoords.y, worldSpaceCoords.z, 1 );
}
左:正面顏色坐標 右:背面顏色坐標
4、第2個渲染通道
該渲染通道是實際執(zhí)行體積光線投射的通道,它首先繪制立方體的正面,其中正面的每個點都將是光線起點。
頂點著色器創(chuàng)建兩個輸出:投影坐標(片段的 2D 屏幕坐標)和世界空間坐標。
世界空間坐標將用作光線起點,而投影坐標將用于對存儲立方體背面位置的紋理進行采樣。
頂點著色器第二遍:
varying vec3 worldSpaceCoords;
varying vec4 projectedCoords;void main()
{
worldSpaceCoords = (modelMatrix * vec4(position + vec3(0.5, 0.5,0.5), 1.0 )).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
projectedCoords = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
第二個渲染通道的片段著色器有點復雜,因此我們將分部分進行介紹。
在此示例中,光線 R0 到 R4 從立方體的正面片段位置(f0 到 f4)投射,并在背面位置(I0 到 I4)結束
4.1 獲取光線結束位置
基于上一步的位置,我們對紋理進行采樣,得到背面片段的世界空間位置。
請注意我們如何通過除以 W 將投影坐標轉換為 NDC(標準化設備坐標),然后如何將其轉換為 [0;1] 范圍,以便將其用作 UV 坐標。 當我們對先前渲染通道中生成的 2D 紋理進行采樣時,可以獲得光線的結束位置。
片段著色器第二遍第 1 部分:
//Transform the coordinates it from [-1;1] to [0;1]
vec2 texc = vec2(((projectedCoords.x / projectedCoords.w) + 1.0 ) / 2.0,
((projectedCoords.y / projectedCoords.w) + 1.0 ) / 2.0 );//The back position is the world space position stored in the texture.
vec3 backPos = texture2D(tex, texc).xyz;//The front position is the world space position of the second render pass.
vec3 frontPos = worldSpaceCoords;//The direction from the front position to back position.
vec3 dir = backPos - frontPos;float rayLength = length(dir);
4.2 設置射線
有了前面和后面的位置,我們現在可以創(chuàng)建一條從 frontPos 開始并在 backPos 結束的射線。
片段著色器第二遍第 2 部分
//Calculate how long to increment in each step.
float delta = 1.0 / steps;//The increment in each direction for each step.
vec3 deltaDirection = normalize(dir) * delta;
float deltaDirectionLength = length(deltaDirection);//Start the ray casting from the front position.
vec3 currentPosition = frontPos;//The color accumulator.
vec4 accumulatedColor = vec4(0.0);//The alpha value accumulated so far.
float accumulatedAlpha = 0.0;//How long has the ray travelled so far.
float accumulatedLength = 0.0;vec4 colorSample;
float alphaSample;
4.3 光線行進
一旦設置了射線,我們就開始從起始位置行進射線并將射線當前位置向方向推進。
在每個步驟中,我們都會對紋理進行采樣以搜索體素強度。 值得注意的是,體素僅包含強度值,因此到目前為止它們沒有任何有關顏色的信息。 每個體素的顏色由變換函數給出。 可以查看 sampleAs3DTexture函數代碼來了解變換函數是如何工作的。
在獲得由 sampleAs3DTexture 給出的體素顏色后,將通過 alphaCorrection 參數對其進行校正。 可以在線調整該值并查看不同的結果。
每次迭代的重要部分是實際的顏色組合,其中根據 alpha 值將累積顏色值添加到先前存儲的值之上。 我們還保留了一個 alphaAccumulator,它可以讓我們知道何時停止光線行進。
迭代不斷進行,直到滿足以下三個條件之一:
- 光線傳播的距離達到了假定的光線長度。 請記住,射線從 startPos 到 endPos。
- 累計
alpha
值達到100% - 迭代達到最大常數 MAX_STEPS
最后,片段著色器返回所遍歷的體素值的合成結果。
片段著色器第二遍第 3 部分
//Perform the ray marching iterations
for(int i = 0 ; i < MAX_STEPS ; i++)
{
//Get the voxel intensity value from the 3D texture.
colorSample = sampleAs3DTexture( currentPosition );//Allow the alpha correction customization
alphaSample = colorSample.a * alphaCorrection;//Perform the composition.
accumulatedColor += (1.0 - accumulatedAlpha) * colorSample * alphaSample;//Store the alpha accumulated so far.
accumulatedAlpha += alphaSample;//Advance the ray.
currentPosition += deltaDirection;
accumulatedLength += deltaDirectionLength;//If the length traversed is more than the ray length, or if the alpha accumulated reaches 1.0 then exit.
if(accumulatedLength >= rayLength || accumulatedAlpha >= 1.0 )
break;}gl_FragColor = accumulatedColor;
如果你可以更改每條射線完成的最大迭代次數,則更改控件中的步驟,并且你可能必須相應地調整 alphaCorrection 值。
原文鏈接:體渲染Three.js實現 — BimAnt