回顧上一篇 ShaderToy入門教程(1) - SDF 和 Raymarching 算法
繼續上篇,這篇涵蓋以下黑體所示內容
- 符號距離函數
- Ray-marching算法
- 曲面法線和光照
- 相機變換
- 構造實體形狀(CSG)
- 模型變換
- 平移和旋轉
- 比例縮放
- 非均勻縮放
- 結論
- 參考
曲面法線和光照
計算機圖形中的大多數光照模型使用曲面法線的概念來計算材料在表面上的給定點處應該是什麼顏色。 當曲面由顯式幾何體(如多邊形)定義時,通常會爲每個頂點指定法線,在表面上的給定點的法線通過它周圍的頂點法線通過插值計算獲得。
那麼我們如何找到由有符號距離函數定義的場景曲面法線? 利用梯度! 從概念上講,函數在處的梯度告訴你從向哪個方向移動將最快增加的值。
這是直覺:對於表面上的一個點,(我們的SDF),評估爲零。在該表面的內側,變爲負,而在外側,它變爲正。 因此,表面上最快速地從負到正的方向將與表面正交。
梯度公式:
但是沒有必要使用微積分。 我們不是採用函數的實數導數,而是通過對錶面上的點周圍的採樣點進行近似,就像在學習如何做之前學習如何計算函數中的斜率作爲過度一樣。
vec3 estimateNormal(vec3 p) {
return normalize(vec3(
sceneSDF(vec3(p.x + EPSILON, p.y, p.z)) - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)),
sceneSDF(vec3(p.x, p.y + EPSILON, p.z)) - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)),
sceneSDF(vec3(p.x, p.y, p.z + EPSILON)) - sceneSDF(vec3(p.x, p.y, p.z - EPSILON))
));
}
有了這些知識,我們可以計算表面上任何一點的法線,在Phong反射模型中使用兩個光源,我們得到這個結果:
完整代碼如下:
// 這一部分略去, 拷貝上面part1的代碼即可
/**
* 對於那些SDF求出來在曲面上的點求標準化的法線向量
*/
vec3 estimateNormal(vec3 p) {
return normalize(vec3(
sceneSDF(vec3(p.x + EPSILON, p.y, p.z)) - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)),
sceneSDF(vec3(p.x, p.y + EPSILON, p.z)) - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)),
sceneSDF(vec3(p.x, p.y, p.z + EPSILON)) - sceneSDF(vec3(p.x, p.y, p.z - EPSILON))
));
}
/**
* Lighting contribution of a single point light source via Phong illumination.
*
* The vec3 returned is the RGB color of the light's contribution.
*
* k_a: 環境色
* k_d: 漫反射顏色
* k_s: 鏡面顏色
* alpha: 光澤係數
* p: position of point being lit
* eye: 相機的位置
* lightPos: 光的位置
* lightIntensity: 光的顏色/強度
*
* See https://en.wikipedia.org/wiki/Phong_reflection_model#Description
*/
vec3 phongContribForLight(vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye, vec3 lightPos, vec3 lightIntensity) {
vec3 N = estimateNormal(p);
vec3 L = normalize(lightPos - p);
vec3 V = normalize(eye - p);
vec3 R = normalize(reflect(-L, N));
float dotLN = dot(L, N);
float dotRV = dot(R, V);
if (dotLN < 0.0) {
// Light not visible from this point on the surface
return vec3(0.0, 0.0, 0.0);
}
if (dotRV < 0.0) {
// Light reflection in opposite direction as viewer, apply only diffuse
// component
return lightIntensity * (k_d * dotLN);
}
return lightIntensity * (k_d * dotLN + k_s * pow(dotRV, alpha));
}
/**
* Lighting via Phong illumination.
*
* The vec3 returned is the RGB color of that point after lighting is applied.
* k_a: 環境色
* k_d: 漫反射顏色
* k_s: 鏡面顏色
* alpha: 光澤係數
* p: position of point being lit
* eye: 相機的位置
*
* See https://en.wikipedia.org/wiki/Phong_reflection_model#Description
*/
vec3 phongIllumination(vec3 k_a, vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye) {
const vec3 ambientLight = 0.5 * vec3(1.0, 1.0, 1.0);
vec3 color = ambientLight * k_a;
vec3 light1Pos = vec3(4.0 * sin(iTime),2.0, 4.0 * cos(iTime));
vec3 light1Intensity = vec3(0.4, 0.4, 0.4);
color += phongContribForLight(k_d, k_s, alpha, p, eye, light1Pos, light1Intensity);
vec3 light2Pos = vec3(2.0 * sin(0.37 * iTime),
2.0 * cos(0.37 * iTime),
2.0);
vec3 light2Intensity = vec3(0.4, 0.4, 0.4);
color += phongContribForLight(k_d, k_s, alpha, p, eye,
light2Pos,
light2Intensity);
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec3 dir = rayDirection(45.0, iResolution.xy, fragCoord);
vec3 eye = vec3(0.0, 0.0, 5.0);
float dist = shortestDistanceToSurface(eye, dir, MIN_DIST, MAX_DIST);
if (dist > MAX_DIST - EPSILON) {
// Didn't hit anything
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
// The closest point on the surface to the eyepoint along the view ray
vec3 p = eye + dist * dir;
vec3 K_a = vec3(0.2, 0.2, 0.2);
vec3 K_d = vec3(0.7, 0.2, 0.2);
vec3 K_s = vec3(1.0, 1.0, 1.0);
float shininess = 10.0;
vec3 color = phongIllumination(K_a, K_d, K_s, shininess, p, eye);
fragColor = vec4(color, 1.0);
}
相機變換
我不會花太多時間,因爲這種解決方案並不是Ray marching行進所獨有的。 正如在光線跟蹤(ray tracing)中一樣,要在相機上進行變換,您可以通過變換矩陣來改變視線以定位和旋轉相機。
但是,根據一系列的平移和旋轉來確定如何定位相機不是那麼直觀,不是那麼實用。 一種更好的描述方法是“我希望相機在這一點上,看相另一點。這正是gluLookAt在OpenGL中的用途。
在着色器內部,我們無法使用該功能,但是我們可以通過gluLookAt使用頁的描述,看看它如何計算自己的轉換矩陣,然後在GLSL中創建自己的矩陣。
mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) {
vec3 f = normalize(center - eye);
vec3 s = normalize(cross(f, up));
vec3 u = cross(s, f);
return mat4(
vec4(s, 0.0),
vec4(u, 0.0),
vec4(-f, 0.0),
vec4(0.0, 0.0, 0.0, 1)
);
}
由於球的各個角度看起來都一樣,因此我在這裏把它換成了一個立方體。 將相機放置在(8,5,7)處,並使用新的viewMatrix函數將其指向原點,現在我們有了:
代碼在這裏
繼續閱讀下一篇