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

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

前置基础

渲染管线中的相机和屏幕示意图

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

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

js 复制代码
float clipW = cameraProjectionMatrix[2][3] * viewZ+cameraProjectionMatrix[3][3];
vec3 viewPosition = getViewPosition( vUv, depth, clipW );
二、计算反射位置 d1viewPosition
js 复制代码
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) 不在近平面之前。如果在近平面之前,则将其调整到近平面上。

js 复制代码
if(d1viewPosition.z > -cameraNear){
  //https://tutorial.math.lamar.edu/Classes/CalcIII/EqnsOfLines.aspx
  float t= (-cameraNear - viewPosition.z) / viewReflectDir.z;
  d1viewPosition = viewPosition + viewReflectDir * t;
}
js 复制代码
 ^ -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+t∗viewReflectDir

我们需要找到 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+t∗viewReflectDir.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.z−cameraNear−viewPosition.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+viewReflectDir∗t;

三、计算反射位置在屏幕空间下的位置
js 复制代码
// 屏幕分辨率
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 画直线算法

js 复制代码
// 片段着色器中的当前像素坐标
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的情况

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

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

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

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

只处理钝角的情况

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

点到平面距离

js 复制代码
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;
}
相关推荐
猷咪6 分钟前
C++基础
开发语言·c++
IT·小灰灰7 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧9 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q10 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳010 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾10 分钟前
php 对接deepseek
android·开发语言·php
2601_9498683614 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
摘星编程19 分钟前
React Native + OpenHarmony:UniversalLink通用链接
javascript·react native·react.js
星火开发设计27 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
qq_1777673739 分钟前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos