浅聊 Three.js 屏幕空间反射SSR-SSRShader

浅聊 Three.js 屏幕空间反射SSR(2)-SSRShader

前置基础
渲染管线中的相机和屏幕示意图

 -Z  (相机朝向的方向)
 |
 |
 |       +--------------+  <- 屏幕/投影平面
 |       |              |
 |       |              |
 |       |     (f)      |  <- 焦距
 |       |              |
 |       |              |
 |       +--------------+
 |              |
 |              |
 |              O  <- 相机原点 (也称为视点)
 |              |
 |
 |
 +---------------------- X (水平轴)
一、计算 viewPosition

根据深度图计算屏幕空间上的 视图位置。

float clipW = cameraProjectionMatrix[2][3] * viewZ+cameraProjectionMatrix[3][3];
vec3 viewPosition = getViewPosition( vUv, depth, clipW );
二、计算反射位置 d1viewPosition
vec3 viewNormal=getViewNormal( vUv );

// 入射光线方向
vec3 viewIncidentDir=normalize(viewPosition);

// 反射光线方向
vec3 viewReflectDir=reflect(viewIncidentDir, viewNormal);

// 反射光线最大长度
float maxReflectRayLen=maxDistance/dot(-viewIncidentDir, viewNormal);

// 反射位置
vec3 d1viewPosition = viewPosition + viewReflectDir * maxReflectRayLen;

请添加图片描述

处理反射位置在近平面(即 -cameraNear)之的情况

目标:
确保反射光线的目标位置 (d1viewPosition) 不在近平面之前。如果在近平面之前,则将其调整到近平面上。

if(d1viewPosition.z > -cameraNear){
  //https://tutorial.math.lamar.edu/Classes/CalcIII/EqnsOfLines.aspx
  float t= (-cameraNear - viewPosition.z) / viewReflectDir.z;
  d1viewPosition = viewPosition + viewReflectDir * t;
}
 ^ -z
 |
 |
 |
 |      * 视点(viewPosition)
 |       \
 |        \
 |------------ (近平面,z = -cameraNear)
 |          \
 |           \
 |            *
 |             \
 |              \
 |               * d1viewPosition (初始位置)
 |
  -------------------------------------> x

解释:
反射光线的参数方程:
P ( t ) = v i e w P o s i t i o n + t ∗ v i e w R e f l e c t D i r P(t) = viewPosition + t * viewReflectDir P(t)=viewPosition+tviewReflectDir

我们需要找到 t t t 使得:
P ( t ) . z = − c a m e r a N e a r P(t).z = -cameraNear P(t).z=cameraNear

因此,我们需要解方程:
v i e w P o s i t i o n . z + t ∗ v i e w R e f l e c t D i r . z = − c a m e r a N e a r viewPosition.z + t * viewReflectDir.z = -cameraNear viewPosition.z+tviewReflectDir.z=cameraNear

解这个方程,得到:
t = − c a m e r a N e a r − v i e w P o s i t i o n . z v i e w R e f l e c t D i r . z t = \frac{-cameraNear - viewPosition.z}{ viewReflectDir.z} t=viewReflectDir.zcameraNearviewPosition.z

最后, 调整反射后目标位置:
d 1 v i e w P o s i t i o n = v i e w P o s i t i o n + v i e w R e f l e c t D i r ∗ t ; d1viewPosition = viewPosition + viewReflectDir * t; d1viewPosition=viewPosition+viewReflectDirt;

三、计算反射位置在屏幕空间下的位置
// 屏幕分辨率
uniform vec2 resolution;

// 视图空间转屏幕空间
vec2 viewPositionToXY(vec3 viewPosition){
  vec2 xy;

  vec4 clip = cameraProjectionMatrix * vec4(viewPosition,1);

  //clip
  xy = clip.xy;

  float clipW = clip.w;

  //NDC
  xy /= clipW;

  //uv
  xy = (xy + 1.) / 2.;

  //screen
  xy *=resolution;

  return xy;
}

vec2 d1 = viewPositionToXY(d1viewPosition);
四、屏幕空间光线步进(Ray Marching)

参考: DDA 画直线算法

// 片段着色器中的当前像素坐标
vec2 d0 = gl_FragCoord.xy;

vec2 d1 = viewPositionToXY(d1viewPosition);

// x 和 y 方向上的距离
float xLen = d1.x-d0.x;
float yLen = d1.y-d0.y;

// 两个点之间的欧几里得距离
float totalLen = length(d1-d0);

// 在 x 和 y 方向上步数的最大值,用于决定采样的步数
float totalStep = max(abs(xLen), abs(yLen));

// 每一步在 x 和 y 方向上的增量
float xSpan = xLen / totalStep;
float ySpan = yLen / totalStep;

for(float i = 0.; i<float(MAX_STEP); i++) {

  if(i >= totalStep) break;

  vec2 xy = vec2(d0.x + i * xSpan, d0.y + i * ySpan);

  if(xy.x < 0. || xy.x > resolution.x || xy.y < 0. || xy.y > resolution.y) break;

  // 比例进度, 0~1
  float s = length(xy - d0) / totalLen;

  vec2 uv = xy / resolution;

  float d = getDepth(uv);

  // 当前像素的视图空间深度值
  float vZ = getViewZ(d);

  if(-vZ >= cameraFar) continue;

  float cW = cameraProjectionMatrix[2][3] * vZ+cameraProjectionMatrix[3][3];
  vec3 vP = getViewPosition( uv, d, cW );

  // https://comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
  float recipVPZ = 1. / viewPosition.z;

  // 基于插值得到的透视矫正后的深度值
  float viewReflectRayZ = 1. / (recipVPZ + s * (1. / d1viewPosition.z - recipVPZ));

  if(viewReflectRayZ <= vZ){
    // 只处理无限厚度的情况
    vec3 vN = getViewNormal(uv);
    if(dot(viewReflectDir,vN) >= 0.) continue;

    float distance = pointPlaneDistance(vP, viewPosition, viewNormal);
    if(distance > maxDistance) break;

    vec4 reflectColor = texture2D(tDiffuse, uv);
    gl_FragColor = reflectColor;
  }
}

只处理 viewReflectRayZ <= vZ的情况

^ -z
 |
 |
 |            * viewPosition (反射的起始位置)
 |             \
 |              \
 |               \
 |                \
 |                 \
 |                  * viewReflectRayZ  (矫正后的深度值)
 |                   \
 |                    \
 |                     * vZ (当前像素深度值)
 |                      \
 |                       \
 |                        \
 |                         \
 |
  -------------------------------------> x
  • 在透视投影下,深度值绝对值越小,表示距离相机越近。在进行光线行进时,我们希望光线从起点出发,经过所有可能的深度值,直到目标位置

  • viewReflectRayZ 是矫正后的深度值,它应该始终小于或等于 vZ,以确保光线距离起点从近到远进行插值和计算。

  • 如果 viewReflectRayZ 大于 vZ, 这种情况可能导致光线跳过当前像素,直接到达更远的像素,产生穿透问题

  • 通过确保 viewReflectRayZ <= vZ,可以保证光线在行进过程中深度值是连续变化的,从而提高插值的精度,避免因不连续的深度值变化而产生的伪影

只处理钝角的情况
点积大于或等于零,表示这两个单位向量的夹角小于或等于 90 度。

点到平面距离

float pointPlaneDistance(vec3 point,vec3 planePoint,vec3 planeNormal){
  // https://mathworld.wolfram.com/Point-PlaneDistance.html
   https://en.wikipedia.org/wiki/Plane_(geometry)
   http://paulbourke.net/geometry/pointlineplane/

  float a = planeNormal.x;
  float b = planeNormal.y;
  float c = planeNormal.z;

  float x0 = point.x;
  float y0 = point.y;
  float z0 = point.z;

  float x = planePoint.x;
  float y = planePoint.y;
  float z = planePoint.z;

  float d = -(a * x + b * y + c * z);

  float distance = (a * x0 + b * y0 + c * z0 + d)/sqrt(a * a + b * b + c * c);
  return distance;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Nuxt.js中使用vue-seamless-scroll,首先需要在plugins目录下创建一个vue-seamless-scroll.js文件,并在文件中导入Vue和SeamlessScroll组件,然后使用Vue.use(SeamlessScroll)进行注册。 接下来,在nuxt.config.js文件中的plugins配置项中添加如下内容: ```javascript plugins: [ '@/plugins/element-ui', '@/plugins/axios', { src: '@/plugins/vue-seamless-scroll', ssr: false }, ] ``` 这样就可以将vue-seamless-scroll插件引入到Nuxt.js项目中了。 然后,在需要使用滚动组件的文件中,使用`<vue-seamless-scroll>`标签,并传入相应的数据和类名等参数。例如: ```html <vue-seamless-scroll :data="runningData" :class-option="scrollOption" class="scroll-container"> <div class="flex-row" v-for="item in runningData" :key="item.id"> <span class="row-1 row-nomal">{{ item.mbShowName }}</span> <span class="row-2 row-nomal">{{ item.mbShowVal }}</span> <span class="row-3 row-nomal">{{ item.updateTime | dateFilter }}</span> </div> </vue-seamless-scroll> ``` 这样就可以在Nuxt.js中使用vue-seamless-scroll组件了。 如果你在公司的基于Nuxt.js的项目中使用滚动组件后刷新页面出现"window is not defined"的错误,可能是因为滚动组件依赖于浏览器环境,而在服务器端渲染时无法访问到window对象。解决这个问题可以将ssr选项设置为false,如前面的配置所示。这样就可以避免在服务器端渲染时出现该错误。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [在 Nuxt中使用滚动组件 vue-seamless-scroll](https://blog.csdn.net/weixin_44769310/article/details/116144924)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值