Appearance
SinCos
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="sinCos" class="shader-toy-stage bg-black"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue'
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
onStart()
} else {
isRunning.value = false
}
}
const onStart = () => {
import('glslCanvas').then(module => {
const canvas = document.getElementById('sinCos')
const glslCanvas: any = new module.default(canvas)
glslCanvas.load(`
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
const int AA = 8; // 抗锯齿级别,可以根据需要调整
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = (fragCoord - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
uv *= 1.0;
float amplitude = 1.0;
float frequency = 1.0;
// 没有 AA 采样
// float y = amplitude * sin(u_time) * cos(uv.x * frequency + u_time);
// if(uv.y < y) {
// color = vec3(1.0);
// }
// 累积采样结果
float totalSampled = 0.0;
// 对每个像素进行多次采样
for (int i = 0; i < AA; ++i) {
for (int j = 0; j < AA; ++j) {
// 计算当前采样点的偏移量,使用随机偏移量或更均匀的分布方式
vec2 offset = (vec2(float(i), float(j)) + vec2(0.5)) / float(AA) - 0.5;
// 根据偏移量和原始 UV 坐标计算采样点的 UV 坐标。这里再次考虑了分辨率的纵横比,以确保采样均匀
vec2 sampleUV = uv + offset / min(u_resolution.y, u_resolution.x);
// 根据采样点的 UV 坐标和时间计算波形的高度
float y = amplitude * sin(u_time * 0.4) * cos(sampleUV.x * frequency + u_time * 0.4);
// 使用 step 函数判断采样点的 y 坐标是否小于波形的 y 坐标。如果是,则返回 1;否则返回 0
// float sample = step(sampleUV.y, y); // step 函数返回 0 或 1
float sample = smoothstep(sampleUV.y - 0.01, sampleUV.y + 0.01, y);
// 将判断结果累加到 totalSampled 中
totalSampled += sample;
}
}
// 计算平均采样结果,并根据结果决定颜色
float alpha = totalSampled / float(AA * AA); // 平均后的透明度值
vec3 color = vec3(1.0) * alpha; // 根据透明度值决定颜色
gl_FragColor = vec4(color, 1.0);
}`)
})
}
</script>
星星
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="stars" class="shader-toy-stage bg-black"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue'
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
onStart()
} else {
isRunning.value = false
}
}
const onStart = () => {
import('glslCanvas').then(module => {
const canvas = document.getElementById('stars')
const glslCanvas: any = new module.default(canvas)
glslCanvas.load(`
#extension GL_OES_standard_derivatives: enable
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
// 旋转
mat2 rot(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
// uv , 星辉的亮度
float star(vec2 uv, float flare) {
// 画圆形
float d = length(uv);
float m = 0.05 / d;
// 大的十字
float r = max(0.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += r * flare;
// 小的十字
uv *= rot(3.1415926 / 4.0);
float r2 = max(0.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += r2 * flare * 0.25;
// d > 0.8 为 0,d < 0.2 为 1
return m * smoothstep(0.8, 0.2, d);
}
// 将星星绘制的更加错落有致,利用一个 Hash 函数来让每个格子里的星星发生一点点偏移
float hash21(vec2 p) {
p = fract(p * vec2(123.45, 456.21));
p += dot(p, p + 45.32);
return fract(p.x + p.y);
}
vec3 layer(vec2 uv) {
vec3 col = vec3(0.0);
float splitNumb = 3.0;
vec2 st = fract(uv * splitNumb) - 0.5;
// 由于想要的是每个格子产生的随机数需要保持一致,所以不能够使用 st 坐标来产生随机数
// 而是要获取每个格子的 index 或者说是 id 来产生随机数
// 即每次遍历都是该 id 为参数的随机函数产生的结果
// 使用 floor 函数来获取每个格子的 id。
vec2 id = floor(uv * splitNumb);
// 此时会有断层出现
// float offset = hash21(id) * 0.3;
// col += star(st - offset, 1.0);
// 解决断层
// y -> -1.0,0.0,1.0
// x -> -1.0,0.0,1.0
// 在一个 3x3 的邻域内,对于每个邻域点,都计算一个基于哈希函数的内部偏移
// 并使用这个偏移和邻域偏移来调用一个 Star 函数来计算某种效果(可能是星星或亮点)
// 然后将这个效果累加到 col 上。这种技术常用于生成复杂的纹理、噪声或其他视觉效果
// ------------------------------------------------------------------------
// 这两个循环,正在遍历一个 3x3 的网格(以当前点为中心),这通常用于某种邻域操作或采样
// 代码遍历了当前像素 uv 周围的一些格子(即邻近的格子),并对每个格子应用星星效果
// 循环遍历了 y 值从 -1 到 1 的整数范围,代表垂直方向上的三个点(通常是中心点以及上下两个相邻的点)
for (float y = -1.0; y <= 1.0; y++) {
// 循环遍历了 x 值从 -1 到 1 的整数范围,代表水平方向上的三个点(通常是中心点以及左右两个相邻的点)
for (float x = -1.0; x <= 1.0; x++) {
// 创建了一个二维向量 neighborOffset,它表示从当前点到邻域中某个点的偏移
// 用于定义当前正在处理的邻居(或称为“邻近格子”)的偏移量
// 代码的目的是遍历当前像素所在位置(由 uv 表示)附近的格子或“细胞”(cells),并为每个这样的格子或细胞计算星星的效果
vec2 neighborOffset = vec2(x, y);
// 使用哈希函数 Hash21,结合一个标识符 id 和邻域偏移来计算一个浮点数值 n
// 这是一个伪随机值,用于生成某种噪声或纹理
float n = hash21(id + neighborOffset);
// 计算了一个内部偏移 innerOffset,它基于之前计算的哈希值 n
// 可以把下面三个 innerOffset 分别查看效果
// fract 函数返回其参数的小数部分,所以 fract(n * 52.) 将 n 乘以 52 并取其小数部分
// 这会为 innerOffset 引入某种随机或周期性的变化
// vec2 innerOffset = vec2(n - 0.5, n - 0.3);
// vec2 innerOffset = vec2(x - 0.5, y - 0.3);
vec2 innerOffset = vec2(n - 0.5, fract(n * 25.) - 0.5);
// 通过 size 来改变星辉的亮度
float size = fract(n * 345.34) * 0.8 + 0.2;
float flare = smoothstep(0.4, 0.8, size);
// 星星颜色
vec3 color = sin(vec3(0.2, 0.5, 0.9) * fract(n * 2345.2) * 6.28) * 0.5 + 0.5;
color *= vec3(0.3 + size * 0.2, 0.5, 0.5 + size * 1.5);
// st 是一个二维向量,表示当前像素或片段的屏幕空间位置(通常是一个 (x, y) 坐标对)
// neighborOffset 是一个二维向量,表示从当前位置到某个邻接位置的偏移量(例如,上下左右或对角线方向的偏移)
// innerOffset 同样是一个二维向量,是基于某种哈希函数或其他随机机制生成的,用于为邻接位置引入额外的随机或周期性偏移
// st - neighborOffset - innerOffset 的结果就是计算出一个新的二维位置,相对于当前位置的一个偏移点,并且这个偏移点被 innerOffset 进一步随机化或扰动了
// 把 innerOffset 换成 0.1 之类的值,则是所有的星星都偏移固定的值
float s = star(st - neighborOffset - innerOffset, flare);
col += s * size * color;
}
}
vec2 nuv = fract(uv * splitNumb);
// 网格
// if (abs(nuv.x) < fwidth(nuv.x) || abs(nuv.y) < fwidth(nuv.y)) {
// col = vec3(1.0);
// }
// if (abs(uv.x) < fwidth(uv.x)) {
// col = vec3(0.0, 1.0, 0.0);
// }
// if (abs(uv.y) < fwidth(uv.y)) {
// col = vec3(1.0, 0.0, 0.0);
// }
return col;
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = (fragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
// vec3 col = layer(uv);
vec3 col = vec3(0.0);
float t = u_time * 0.1;
// 把 uv 旋转
uv *= rot(t);
for (float i = 0.0; i < 1.0; i += 1. / 2.0) {
// t 的作用是模拟深度,z 轴的方向
float depth = fract(i + t);
float fade = smoothstep(0.0, 0.3, depth) * smoothstep(1.0, 0.9, depth);
float scale = mix(20.0, .05, depth);
col += layer(uv * scale + i * 435.32) * fade;
}
gl_FragColor = vec4(col, 1.0);
}`)
})
}
</script>
合并两个物体、光照、阴影
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="lightShadow" class="shader-toy-stage bg-black"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue'
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
onStart()
} else {
isRunning.value = false
}
}
const onStart = () => {
import('glslCanvas').then(module => {
const canvas = document.getElementById('lightShadow')
const glslCanvas: any = new module.default(canvas)
glslCanvas.load(`
#extension GL_OES_standard_derivatives: enable
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
// minVec2 比较两个 vec2 类型的向量 a 和 b,返回其中第一个分量(x)较小的那个向量
// 假设一个函数
// vec2 tempFun(vec3 p) {
// vec2 a = vec2(sdSphere(p - vec3(0.0, 1.0, 0.0), 1.0, 2.0);
// vec2 b = vec2(sdPlane(p), 1.0);
// return minVec2(a, b);
// }
// 光线的当前位置 p 为 (0.5, 0.5, 0.5)
// 球体的距离:sdSphere(p - vec3(0.0, 1.0, 0.0), 1.0) = length(vec3(0.5, -0.5, 0.5)) - 1.0 ≈ 0.3536
// 平面的距离:sdPlane(p) = 0.5
// 比较两个距离:min(vec2(0.3536, 2.0), vec2(0.5, 1.0)),返回 vec2(0.3536, 2.0),表示最近的物体是球体
// 通过这种方式,tempFun 函数可以有效地组合多个物体,并在光线行进算法中正确地处理它们
vec2 minVec2(vec2 a, vec2 b) {
return a.x < b.x ? a : b;
}
// 计算一个点 p 到一个无限大水平平面(地面)的距离
// 这个平面通常被定义为 y=0 的平面,即地面位于 y 轴的零点
float getGroundDist(vec3 p) {
float groundDist = p.y;
return groundDist;
}
// 计算一个点 p 到一个球体的距离
// 球体的中心由 sphere 表示,半径由 radius 表示
float getSphereDist(vec3 p, vec3 spherePos, float radius) {
// 从当前点的位置 p 到球体中心的距离中减去球体的半径,得到点 p 到球体表面的最短距离
// 如果点 p 在球体内部,sphereDist 为负值
// 如果点 p 在球体表面,sphereDist 为零
// 如果点 p 在球体外部,sphereDist 为正值
float sphereDist = length(p - spherePos) - radius;
return sphereDist;
}
// 计算一个点 p 到一个立方体的(最近的)距离,要考虑 p 在内部和外部的情况,二者都要计算得出结果,外部返回正值,内部返回负值
// 假设立方体的半尺寸为 cubeSize = vec3(1.0, 1.0, 1.0),表示立方体在 x、y、z 轴上的半长度都为 1.0,立方体的中心位于原点 (0, 0, 0)
// 如果点 p = vec3(1.5, 1.5, 1.5),则 p 在立方体外部
// 如果点 p = vec3(0.5, 0.5, 0.5),则 p 在立方体内部
// 如果点 p = vec3(1.0, 1.0, 1.0),则 p 在立方体边界上
// 通过 getCubeDist 函数,可以计算出点 p 到立方体的最短距离
float getCubeDist(vec3 p, vec3 cubePos, vec3 cubeSize) {
// abs(p - cubePos) - cubeSize 计算点 p 相对于立方体中心 cubePos 的距离
// 首先计算点 p 与立方体中心 c 之间的差值 p − cubePos
// 然后取绝对值 abs(p − cubePos),表示点 p 相对于立方体中心的水平、垂直和深度方向的距离
// 最后减去立方体的半尺寸 cubeSize,得到 tempDist
// tempDist 的每个分量表示点在对应方向上超出立方体边界的距离
// 点在立方体内部,所有值是负值
// 点在立方体边界,至少一个是 0
// 点在立方体外,至少一个是正值
vec3 tempDist = abs(p - cubePos) - cubeSize;
// max(tempDist, 0.0) 将 tempDist 的所有负分量设置为 0,只保留正分量,表示点到立方体外部的距离(假如其中有一个是负值,则 p 是在立方体内)
// length(max(tempDist, 0.0)) 计算这个向量的长度,即点到立方体外部的最短距离
// -----------------------------------------------------------------------
// min(max(tempDist.x, max(tempDist.y, tempDist.z)), 0.0) 这部分的作用是处理点在立方体内部的情况
// max(tempDist.x, max(tempDist.y, tempDist.z)) 找出 tempDist 中最大的分量,表示点在立方体内部最深的轴向距离
// min(..., 0.0) 确保这个值不会超过 0,因为点在立方体内部时,tempDist 的所有分量都是负值
// min(max(tempDist.x, tempDist.y, tempDist.z), 0.0) 将这个最大值与 0 比较,取较小值,表示点距离立方体在某个轴上最近的面的距离为(x)个单位
// -----------------------------------------------------------------------
// 最后合并距离,将点到立方体外部的距离和点到立方体内部的距离相加,得到点到立方体的最短距离
// -----------------------------------------------------------------------
// 假如点在外部,则会使用 length(max(tempDist, 0.0))
// 假如点在内部,则会使用 min(max(tempDist.x, max(tempDist.y, tempDist.z)), 0.0)
float cubeDist = length(max(tempDist, 0.0)) + min(max(tempDist.x, max(tempDist.y, tempDist.z)), 0.0);
return cubeDist;
}
// 多个物体的合并的 dist 取 min,即物体距离相机最近的 dist
float getDist(vec3 pos) {
vec3 SPHERE_POS = vec3(0.5, 1.0, 6.0);
float SPHERE_RADIUS = 1.0;
vec3 CUBE_POS = vec3(0.0, 0.0, 6.0);
vec3 CUBE_SIZE = vec3(1.0, 1.0, 1.0);
float groundDist = getGroundDist(pos);
float cubeDist = getCubeDist(pos, CUBE_POS, CUBE_SIZE);
float sphereDist = getSphereDist(pos, SPHERE_POS, SPHERE_RADIUS);
return min(groundDist, min(cubeDist, sphereDist));
}
// 联合形状
// 两个形状距离值的最小值,表示两个形状的并集
float unionShape(float shapeA, float shapeB) {
return min(shapeA, shapeB);
}
// 左手坐标系
// ro:相机的位置(Ray Origin),即相机在三维空间中的坐标。这个点是所有视线(或光线)的起点
// target:相机的目标点,即相机“看”向的点。这个点决定了相机的前进方向(Forward 向量)
// up:相机的向上方向(Up 向量),通常与相机的前进方向垂直,这个向量用于确定相机的右侧方向(Right 向量)和确保相机的坐标系是正交的
// 相机源点、目标、向上方向
// R、U、F 分别是 Right、Up 和 Forward 向量
mat3 getCameraMat(vec3 ro, vec3 target, vec3 up) {
vec3 f = normalize(target - ro); // 计算 Forward 向量(F)
// 叉积 cross(a, b) 的结果是一个垂直于向量 a 和 b 的向量
// 注意:由于使用的是左手坐标系,所以是使用 up 叉乘 f,而不是反过来进行叉乘,进行叉乘运算时一定要注意其方向性!
vec3 r = cross(up, f); // Right 向量(R)是 Forward 向量和 Up 向量的叉积,表示相机的右侧方向
vec3 u = normalize(cross(f, r)); // 为了确保 Up 向量垂直于 Forward 向量,需要重新计算 Up 向量为 Right 向量和 Forward 向量的叉积
return mat3(r, u, f);
}
// rayOrigin 代表光线的起点
// rayDirection 代表光线的方向
float rayMarching(vec3 rayOrigin, vec3 rayDirection) {
// d0 是光线从 rayOrigin 出发后行进的总距离
float d0 = 0.0;
const float MAX_STEPS = 256.0;
float SURFACE_DIST = 0.001;
float MAX_DIST = 100.0;
for (float i = 0.0; i < MAX_STEPS; i += 1.0) {
// 光线的当前位置 pos
// rayDirection * d0 计算光线在方向 rayDirection 上行进距离 d0 后的向量,然后将这个向量加到源点 rayOrigin 上,得到新的位置 pos。
vec3 pos = rayOrigin + d0 * rayDirection;
// dS 表示从当前光线位置 p 到最近的场景物体表面的距离
float dS = getDist(pos);
d0 += dS;
// 如果从当前位置到球面的距离 dS 小于某个阈值 SURFACE_DIST,则可能表示光线已经“击中”了表面,因此退出循环
// 因为点 p 可能在物体内,float dS = getDist(pos) 中的 dS 会返回负值
// 如果光线行进的距离 d0 大于某个最大距离 MAX_DIST,则退出循环,可能是因为光线已经行进得太远而没有“击中”任何物体
if (dS < SURFACE_DIST || d0 > MAX_DIST) {
break;
}
}
return d0;
}
// 黑白网格
vec3 groundGrid(vec3 p) {
vec3 c1 = vec3(1.0);
vec3 c2 = vec3(0.0);
// floor(p.x) 和 floor(p.z) 分别取 p.x 和 p.z 的整数部分
// mod(..., 2.0) 取上述和对 2 的模,结果为 0 或 1
float s = mod(floor(p.x) + floor(p.z), 2.0);
// mix(c1, c2, s) 根据 s 的值在 c1 和 c2 之间进行插值
// 当 s 为 0 时,返回 c1(白色)
// 当 s 为 1 时,返回 c2(黑色)
return mix(c1, c2, s);
}
// 在基于距离场(Distance Field)的渲染中,法线(Normal)的计算是一个关键步骤
// 距离场是一种标量场,其中每个点的值表示该点到最近表面的距离
// 由于距离场本身是一个标量函数,而法线是一个向量,因此需要通过某种方式从标量场中提取出向量信息
// 有限差分法(Finite Difference Method)是一种自然且有效的方法,用于从标量场中近似计算梯度,进而得到法线
// 对于 x 方向
// grad_x ≈ [f(p + eps_x) - f(p - eps_x)] / (2 * eps) * ex
// 具体查看(自定义效果 - 两个物体的合并 -1.png)、(自定义效果 - 两个物体的合并 -2.png)、(自定义效果 - 两个物体的合并 -3.png)
vec3 getNormal(vec3 p) {
float e = 0.001;
// vec3(..., 0.0, 0.0):构造一个三维向量,只有 x 分量有效
// 通过对点 p 在 x 方向上加上和减去 e,然后计算距离差,这个差值反映了距离场在 x 方向上的变化率,可以得到 x 方向上的梯度
// 结果是一个三维向量,但只有 x 分量是非零的,表示梯度在 x 方向上的分量
vec3 grad_x = vec3((getDist(p + vec3(e, 0.0, 0.0)) - getDist(p - vec3(e, 0.0, 0.0))) / (2.0 * e), 0.0, 0.0);
// vec3(0.0, ..., 0.0):构造一个三维向量,只有 y 分量有效
// 通过对点 p 在 y 方向上加上和减去 e,然后计算距离差,这个差值反映了距离场在 y 方向上的变化率,可以得到 y 方向上的梯度
// 结果是一个三维向量,但只有 y 分量是非零的,表示梯度在 y 方向上的分量
vec3 grad_y = vec3(0.0, (getDist(p + vec3(0.0, e, 0.0)) - getDist(p - vec3(0.0, e, 0.0))) / (2.0 * e), 0.0);
// vec3(0.0, 0.0, ...):构造一个三维向量,只有 z 分量有效
// 通过对点 p 在 z 方向上加上和减去 e,然后计算距离差,这个差值反映了距离场在 z 方向上的变化率,可以得到 z 方向上的梯度
// 结果是一个三维向量,但只有 z 分量是非零的,表示梯度在 z 方向上的分量
vec3 grad_z = vec3(0.0, 0.0, (getDist(p + vec3(0.0, 0.0, e)) - getDist(p - vec3(0.0, 0.0, e))) / (2.0 * e));
// 将三个方向上的梯度向量相加,得到该点的总梯度向量
// 然后使用 normalize 函数将其标准化(即长度变为 1),得到法向量
// 因为梯度向量指向函数值增长最快的方向,而对于表示表面的函数,其法向量与梯度向量方向相反(或相同,取决于表面函数的定义,但通常通过取梯度来近似法向量,并可能需要根据具体情况调整方向)
// 标准化确保了返回的法向量是一个单位向量
return normalize(grad_x + grad_y + grad_z);
}
// 计算一个 3D 点 p 与光源之间的漫反射光照强度
vec3 getLight(vec3 lightPos, vec3 p) {
float SHADOW = 0.1;
// 计算从点 p 到光源的方向,方向的箭头指向 lightPos
// 首先计算从点 p 到光源 lightPos 的向量
// 然后,使用 normalize 函数将这个向量标准化(或归一化),使其长度为 1
// 标准化后的向量 l 表示,从点 p 指向光源的方向
vec3 l = normalize(lightPos - p);
vec3 n = getNormal(p);
// 计算漫反射光照强度
// 使用点积(dot 函数)来计算法线向量 n 和光源方向向量 l 之间的角度的余弦值
// 这个余弦值表示了光源方向和表面法线之间的“对齐”程度,从而决定了光照的强度
// 由于余弦值可能是负的(当光源在表面的背面时),使用 max 函数确保结果始终是非负的
// 因此,dif 变量存储了漫反射光照的强度
float dif = max(dot(n, l), 0.0);
// 如果不需要阴影
// return dif;
// 阴影检测(偏移起点防止自相交)
float dis = rayMarching(p + 0.01 * n, l);
if (dis < length(lightPos - p)) {
// 存在遮挡则减弱光照
dif *= SHADOW;
}
// 地面的黑白格子
// 由于 getDist 中有 planeDist,所以 p 点包含了地面
// 修改不同的 groundY,会看到最远处的变化
float groundY = 0.001;
if(p.y < groundY) {
return groundGrid(p) * dif;
}
return vec3(dif);
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = (fragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
vec3 color = vec3(0.0, 0.0, 0.0);
// SuperSampling(超级采样)。对每个像素进行多次采样并取平均值:
const int samples = 2;
for (int i = 0; i < samples; ++i) {
for (int j = 0; j < samples; ++j) {
vec2 offset = vec2(float(i), float(j)) / float(samples) - 0.5;
vec2 uvSample = uv + offset / min(u_resolution.y, u_resolution.x);
// 计算每个子样本的颜色
vec3 sampleColor = vec3(0.0);
vec3 lightPos = vec3(2.0, 5.0, 2.0);
lightPos.xz += vec2(sin(u_time) + 2.0, cos(u_time)) * 2.0;
// 相机信息
vec3 cameraPos = vec3(0.0, 3.0, 0.0);
vec3 cameraUp = vec3(0.0, 1.0, 0.0);
vec3 cameraTarget = vec3(0.0, 1.0, 6.0);
// rayDirection 是视线(或光线)的方向向量,表示视线(或光线)沿着哪个方向行进
// 之所以 * vec3(uv, 1.0),uv.x 表示水平方向的偏移,uv.y 表示垂直方向的偏移,1.0 表示沿着相机的前进方向(即深度方向)的偏移
// 1.0 表示沿着相机的前进方向(即深度方向)的偏移
// vec3(uv, 1.0) 将二维的 uv 坐标扩展为一个三维向量,其中 z 分量为 1.0
vec3 rayDirection = getCameraMat(cameraPos, cameraTarget, cameraUp) * vec3(uv, 1.0);
// 视线(或光线)步进行进了多少距离
float rayDist = rayMarching(cameraPos, rayDirection);
// 视线(或光线)当前位置 p(从相机发出一条射线,一直延伸到接触了物体表面,这之间的距离)
// rayDirection * rayDist 计算视线(或光线在方向 rayDirection 上行进距离 rayDist 后的向量,然后将这个向量加到源点 cameraTarget 上,得到新的位置 p。
vec3 pointOfCameraTouchObject = cameraPos + rayDirection * rayDist;
vec3 sampleLightColor = getLight(lightPos, pointOfCameraTouchObject);
sampleColor = sampleLightColor;
// 累加颜色
color += sampleColor;
}
}
// 计算平均颜色
color /= float(samples * samples);
gl_FragColor = vec4(color, 1.0);
}`)
})
}
</script>