编辑:更新了 JSFiddle 链接,因为它在 Windows 7 上的 Chrome 中无法正确呈现。
上下文
我正在 THREE.JS 中使用粒子,并使用帧缓冲区/渲染目标(双缓冲)将位置写入纹理。这个纹理受其自身的ShaderMaterial影响,然后由PointCloud的ShaderMaterial读取来定位粒子。到目前为止一切都很好;一切都按预期进行。
我现在想做的是使用场景的深度纹理来查看是否有任何粒子与场景的几何体相交。
我做的第一件事是在 PointCloud 的片段着色器中引用我的深度纹理,使用 gl_FragCoord.xy/screenResolution.xy
生成深度纹理的 uv
抬头。
There's a JSFiddle of this here 。它运行良好 - 当粒子位于场景中某物后面时,我告诉粒子渲染为红色,而不是白色。
当我尝试在位置纹理着色器中进行相同的深度比较时,出现了问题。在绘制片段着色器中,我可以使用 gl_FragCoord 的值来获取粒子在屏幕空间中的位置,并将其用于深度 uv 查找,因为在绘制顶点着色器中我使用 modelViewMatrix
和 projectionMatrix
来设置 gl_Position
的值。
我尝试在位置片段着色器中执行此操作,但无济于事。顺便说一句,我的目标是让粒子与 GPU 上的场景发生碰撞。
所以...问题(最后!):
- 给定一个纹理,其中每个像素/纹素都是代表粒子位置的世界空间 3d 向量,我如何将该向量投影到片段着色器中的屏幕空间,最终目标是使用
.xy
这个向量的属性作为深度纹理中的uv
查找?
我尝试过的
在位置纹理着色器中,使用与绘制着色器相同的转换,使用模型 View 和投影矩阵将粒子的位置转换为(我认为是)屏幕空间:
// Position texture's fragment shader: void main() { vec2 uv = gl_FragCoord.xy / textureResolution.xy; vec4 particlePosition = texture2D( tPosition, uv ); vec2 screenspacePosition = modelViewMatrix * projectionMatrix * vec4( particlePosition, 1.0 ); vec2 depthUV = vec2( screenspacePosition.xy / screenResolution.xy ); float depth = texture2D( tDepth, depthUV ).x; if( depth < screenspacePosition.z ) { // Particle is behind something in the scene, // so do something... } gl_FragColor = vec4( particlePosition.xyz, 1.0 ); }
上述主题的变体:
- 通过执行
0.5 - heightUV
来偏移深度的uv
- 使用 tPosition 纹理分辨率而不是屏幕分辨率来缩放深度 UV。
- 另一种深度
uv
变化:执行depthUV = (depthUV - 1.0) * 2.0;
。这有一点帮助,但规模完全偏离了。
- 通过执行
救命啊!提前致谢。
最佳答案
经过大量实验和研究,我将问题范围缩小到 modelViewMatrix
的值和projectionMatrix
当创建 THREE.ShaderMaterial
的实例时,THREE.js 会自动分配.
我想做的是在我的“绘制”着色器中工作得绝对正常,其中 modelViewMatrix
这些着色器被设置(由 THREE.js)为:
new THREE.Matrix4().multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld)
看起来,当创建 ShaderMaterial 将值渲染到纹理(因此不附加到场景/世界中的对象)时,object.matrixWorld
本质上是一个单位矩阵。我需要做的是让我的位置纹理着色器具有相同的 modelViewMatrix
值作为我的绘制着色器(附加到场景/世界中的对象)。
一旦就位,唯一要做的另一件事就是确保我将粒子的位置正确地转换到屏幕空间。我在 GLSL 中编写了一些辅助函数来执行此操作:
// Transform a worldspace coordinate to a clipspace coordinate
// Note that `mvpMatrix` is: `projectionMatrix * modelViewMatrix`
vec4 worldToClip( vec3 v, mat4 mvpMatrix ) {
return ( mvpMatrix * vec4( v, 1.0 ) );
}
// Transform a clipspace coordinate to a screenspace one.
vec3 clipToScreen( vec4 v ) {
return ( vec3( v.xyz ) / ( v.w * 2.0 ) );
}
// Transform a screenspace coordinate to a 2d vector for
// use as a texture UV lookup.
vec2 screenToUV( vec2 v ) {
return 0.5 - vec2( v.xy ) * -1.0;
}
I've made a JSFiddle to show this in action, here 。我已经评论过它(可能太多了),所以希望它能够很好地解释正在发生的事情,让不熟悉此类内容的人能够理解。
关于 fiddle 的快速说明:它看起来并不那么令人印象深刻,因为我所做的只是模仿 depthTest: true
如果在点云上设置该属性,尽管在本例中我将与场景几何体碰撞的粒子的 y 位置设置为 70.0
,这就是渲染屏幕顶部附近的白色带。最终,我将在速度纹理着色器中进行此计算,这样我就可以做出适当的碰撞响应。
希望这对某人有帮助:)
编辑:Here's a version of this通过(可能有缺陷的)碰撞响应来实现。
关于javascript - 将 FBO 值投影到屏幕空间以从深度纹理中读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26576429/