Shadertoy 教程 Part 14 使用符号距离场函数

摘要:
文章已经获得作者翻译授权,如有转载请务必在取得作者和译者同意之后在文章的重点位置标明原文链接以及说明。欢迎来到Shadertoy的第十四篇教程。你以前有想过Shadertoy上的那些复杂的形状是如何被绘制出来的呢?在本篇文章中,我们将通过InigoQuilez大神,同时也是Shadertoy的联合创始人提供的SDF操作方法,来学习如何绘制复杂的形状。我们下面就开始吧:constintMAX_MARCHING_STEPS=255;constfloatMIN_DIST=0.0;constfloatMAX_DIST=100.0;constfloatPRECISION=0.001;constfloatEPSILON=0.0005;constfloatPI=3.14159265359;constvec3COLOR_BACKGROUND=vec3;constvec3COLOR_AMBIENT=vec3;mat2rotate2d{floats=sin,c=cos;returnmat2;}floatsdSphere{returnlength-r;}floatscene{returnsdSphere;}floatrayMarch{floatdepth=MIN_DIST;floatd;//distancerayhastravelledfor{vec3p=ro+depth*rd;d=scene;depth+=d;ifbreak;}d=depth;returnd;}vec3calcNormal{vec2e=vec2*EPSILON;returnnormalize;}mat3camera{vec3cd=normalize;vec3cr=normalize;vec3cu=normalize;returnmat3;}voidmainImage{vec2uv=/iResolution.y;vec2mouseUV=iMouse.xy/iResolution.xy;ifmouseUV=vec2(0.5);//tricktocentermouseonpageloadvec3col=vec3;vec3lp=vec3;vec3ro=vec3;//rayoriginthatrepresentscamerapositionfloatcameraRadius=2.;ro.yz=ro.yz*cameraRadius*rotate2d;ro.xz=ro.xz*rotate2d+vec2;vec3rd=camera*normalize;//raydirectionfloatd=rayMarch;//signeddistancevaluetoclosestobjectif{col=COLOR_BACKGROUND;//raydidn'thitanything}else{vec3p=ro+rd*d;//pointdiscoveredfromraymarchingvec3normal=calcNormal;//surfacenormalvec3lightPosition=vec3;vec3lightDirection=normalize*.65;//The0.65isusedtodecreasethelightintensityabitfloatdif=clamp*0.5+0.5;//diffusereflectionmappedtovaluesbetween0.5and1.0col=vec3+COLOR_AMBIENT;}fragColor=vec4;}运行以上的代码,你就能在屏幕上看到一个球。

Note: This series blog was translated from Nathan Vaughn'sShaders Language Tutorial and has been authorized by the author. If reprinted or reposted, please be sure to mark the original link and description in the key position of the article after obtaining the author’s consent as well as the translator's. If the article is helpful to you, click this Donation link to buy the author a cup of coffee.
说明:该系列博文翻译自Nathan Vaughn着色器语言教程。文章已经获得作者翻译授权,如有转载请务必在取得作者译者同意之后在文章的重点位置标明原文链接以及说明。如果你觉得文章对你有帮助,点击此打赏链接请作者喝一杯咖啡。

朋友们,你们好!欢迎来到Shadertoy的第十四篇教程。你以前有想过Shadertoy上的那些复杂的形状是如何被绘制出来的呢?我们已经学会了如何绘制球和立方体,但是其他的一些复杂的形状又该如何绘制呢?在本篇文章中,我们将通过Inigo Quilez大神,同时也是Shadertoy的联合创始人提供的SDF操作方法,来学习如何绘制复杂的形状。

初始化

下面我们创建了一份光线步进算法模板,该算法我们之前使用过,如果你是需要开发3D场景,它对你会非常有用的。我们下面就开始吧:

  const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;
const float EPSILON = 0.0005;
const float PI = 3.14159265359;
const vec3 COLOR_BACKGROUND = vec3(.741, .675, .82);
const vec3 COLOR_AMBIENT = vec3(0.42, 0.20, 0.1);

mat2 rotate2d(float theta) {
  float s = sin(theta), c = cos(theta);
  return mat2(c, -s, s, c);
}

float sdSphere(vec3 p, float r, vec3 offset)
{
  return length(p - offset) - r;
}

float scene(vec3 p) {
  return sdSphere(p, 1., vec3(0, 0, 0));
}

float rayMarch(vec3 ro, vec3 rd) {
  float depth = MIN_DIST;
  float d; // distance ray has travelled

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    d = scene(p);
    depth += d;
    if (d < PRECISION || depth > MAX_DIST) break;
  }
  
  d = depth;
  
  return d;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1, -1) * EPSILON;
    return normalize(
      e.xyy * scene(p + e.xyy) +
      e.yyx * scene(p + e.yyx) +
      e.yxy * scene(p + e.yxy) +
      e.xxx * scene(p + e.xxx));
}

mat3 camera(vec3 cameraPos, vec3 lookAtPoint) {
	vec3 cd = normalize(lookAtPoint - cameraPos);
	vec3 cr = normalize(cross(vec3(0, 1, 0), cd));
	vec3 cu = normalize(cross(cd, cr));
	
	return mat3(-cr, cu, -cd);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec2 mouseUV = iMouse.xy/iResolution.xy;
  
  if (mouseUV == vec2(0.0)) mouseUV = vec2(0.5); // trick to center mouse on page load

  vec3 col = vec3(0);
  vec3 lp = vec3(0);
  vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position
  
  float cameraRadius = 2.;
  ro.yz = ro.yz * cameraRadius * rotate2d(mix(-PI/2., PI/2., mouseUV.y));
  ro.xz = ro.xz * rotate2d(mix(-PI, PI, mouseUV.x)) + vec2(lp.x, lp.z);

  vec3 rd = camera(ro, lp) * normalize(vec3(uv, -1)); // ray direction

  float d = rayMarch(ro, rd); // signed distance value to closest object

  if (d > MAX_DIST) {
    col = COLOR_BACKGROUND; // ray didn't hit anything
  } else {
    vec3 p = ro + rd * d; // point discovered from ray marching
    vec3 normal = calcNormal(p); // surface normal

    vec3 lightPosition = vec3(0, 2, 2);
    vec3 lightDirection = normalize(lightPosition - p) * .65; // The 0.65 is used to decrease the light intensity a bit

    float dif = clamp(dot(normal, lightDirection), 0., 1.) * 0.5 + 0.5; // diffuse reflection mapped to values between 0.5 and 1.0

    col = vec3(dif) + COLOR_AMBIENT;    
  }

  fragColor = vec4(col, 1.0);
}

运行以上的代码,你就能在屏幕上看到一个球。
Shadertoy 教程 Part 14 使用符号距离场函数第1张

我们来分析一下这段代码,弄明白光线步进算法是如何工作的。在代码的顶部位置,我们定义了一些常量,这些常量我们在第六篇教程中看到过了。

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;
const float EPSILON = 0.0005;
const float PI = 3.14159265359;
const vec3 COLOR_BACKGROUND = vec3(.741, .675, .82);
const vec3 COLOR_AMBIENT = vec3(0.42, 0.20, 0.1);

我们通过定义变量的方式存储了背景颜色以及环境光照的颜色,这样我们就能快速地更改3D物体的颜色。下一步,我们将会定义一个rotate2d函数,该函数是用来在2D平面上对物体进行旋转的,我们在第10篇教程中讨论过这点。我们将通过这个函数,使用鼠标移动3D模型。

  mat2 rotate2D(float theta) {
    float s = sin(theta), c = cos(theta);
    return mat2(c, -s, s, c);
  }

接下来使用的函数是创建3D场景的基本工具函数。我们在第六篇教程中首次学过它。sdSphere函数是一个用来创建球的符号距离场函数(SDF)。scene函数则是用来渲染场景中所有的物体。如果你读过Shadertoy上的代码,scene函数也被命名为map函数。

  float sdSphere(vec3 p, float r, vec3 offset)
{
  return length(p - offset) - r;
}

float scene(vec3 p) {
  return sdSphere(p, 1., vec3(0));
}

float rayMarch(vec3 ro, vec3 rd) {
  float depth = MIN_DIST;
  float d; // distance ray has travelled

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    d = scene(p);
    depth += d;
    if (d < PRECISION || depth > MAX_DIST) break;
  }
  
  d = depth;
  
  return d;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1, -1) * EPSILON;
    return normalize(
      e.xyy * scene(p + e.xyy) +
      e.yyx * scene(p + e.yyx) +
      e.yxy * scene(p + e.yxy) +
      e.xxx * scene(p + e.xxx));
}

接下来,我们又创建了camera函数,通过一个观察目标点来定义相机模型,关于这点,我们也在第10篇教程中提到过。使用目标观察点相机聚焦到一个目标。

  mat3 camera(vec3 cameraPos, vec3 lookAtPoint) {
	vec3 cd = normalize(lookAtPoint - cameraPos);
	vec3 cr = normalize(cross(vec3(0, 1, 0), cd));
	vec3 cu = normalize(cross(cd, cr));
	
	return mat3(-cr, cu, -cd);
}

现在,我们分析一下mainImage函数。重新设置一下UV坐标,这样就能将像素坐标控制在-0.5到0.5之间。我们也需要计算方位比例,这样x轴的值将会是一些处在整数和负数之间的值。

  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;

由于我们使用鼠标旋转3D物体,我们就需要设置mouseUV坐标。当鼠标点击屏幕的时候,我们将其坐标设置在0到1之间。

  vec2 mouseUV = iMouse.xy/iResolution.xy;

这里还有一个问题,当我们在Shadertoy上发布着色器代码,用户首次加载我们的代码时,坐标会以初始值(0,0)作为mouseUV的坐标。我们可以使用一个小技巧,通过给它分配一个新的值来修复这个小缺陷。

  if (mouseUV == vec2(0.0)) mouseUV = vec2(0.5); //  trick to center mouse on page load

接下来,声明了一个颜色变量,col,这个值可以任意设定。然后就需要设置目标观察点,lp,以及射线源头,ro,这些我们在第10篇教程中讨论过。我们的球目前在场中没有偏移,它的位置在vec2(0,0)。我们应该使目标观察点也保持在这个位置上,当然也可以随意调整它。

vec3 col = vec3(0);
vec3 lp = vec3(0); // lookat point
vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position

使用鼠标旋转相机,但是要注意相机与3D物体之间的距离,我们在第10篇教程中学过,使用ratate2d函数让相机在距离物体cameraRadius的间距上旋转。

float cameraRadius = 2.;
ro.yz = ro.yz * cameraRadius * rotate2d(mix(-PI/2., PI/2., mouseUV.y));
ro.xz = ro.xz * rotate2d(mix(-PI, PI, mouseUV.x)) + vec2(lp.x, lp.z);

vec3 rd = camera(ro, lp) * normalize(vec3(uv, -1)); // ray direction

看起来差不多了!除此之外还有许多方式移动相机。不同的人使用的方式有所差别。你只需要选择自己中意的方式即可。

3D物体的合并操作

我们已经理解了上面提供的代码的含义了,现在开始做一些3D合并操作吧。我之前已经在第五篇教程中提到过关于2d的一些操作,3D操作其实与它们还是有一些相似的。我们会使用一些工具函数将物体结合在一起或者对它们进行裁剪。这些函数都可以 Inigo Quilez的3D 网页上找到。让我们在scene函数之上定义一些工具函数。

(结合)Union:把多个图形合并在一起,或者在一块屏幕上同时绘制多个图形。我们应该对这个函数已经很熟悉了,我们之前就是用它在屏幕上绘制多个物体的。

float opUnion(float d1, float d2) { 
  return min(d1, d2);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opUnion(d1, d2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第2张

Smooth Union: 使两个物体平滑地结合在一起,然后通过参数k,来处理合并边缘平滑程度。k表示等于0表示顺滑度为0,即正常的结合。

  float opSmoothUnion(float d1, float d2, float k) {
  float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
  return mix( d2, d1, h ) - k*h*(1.0-h);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSmoothUnion(d1, d2, 0.2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第3张

Interscetion: 取两个图形的相交部分

  float opIntersection(float d1, float d2) {
  return max(d1,d2);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opIntersection(d1, d2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第4张

Smooth Intersection:结合两个物体,并且使用k值来决定边缘的融合程度。0表示不融合。

  float opSmoothIntersection(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
  return mix( d2, d1, h ) + k*h*(1.0-h);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSmoothIntersection(d1, d2, 0.2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第5张

裁剪(Subtraction): 用d1裁剪d2

  float opSubtraction(float d1, float d2 ) {
  return max(-d1, d2);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSubtraction(d1, d2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第6张

平滑裁剪(Smooth Subtraction): 用d1裁剪d2,使用平滑的边缘参数k

  float opSmoothSubtraction(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
  return mix( d2, -d1, h ) + k*h*(1.0-h);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSmoothSubtraction(d1, d2, 0.2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第7张

反向裁剪2: 用d2裁剪d1.

  float opSubtraction2(float d1, float d2 ) {
  return max(d1, -d2);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSubtraction2(d1, d2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第8张

Smooth Subtraction 2:从d2裁剪d1,使用平滑值k

float opSmoothSubtraction2(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
  return mix( d1, -d2, h ) + k*h*(1.0-h);
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  return opSmoothSubtraction2(d1, d2, 0.2);
}

Shadertoy 教程 Part 14 使用符号距离场函数第9张

3D定位

Inigo Quilez's 3D SDFs 的网页上描述了一系列的3D SDF操作,能够帮助我们在绘制3D物体时省下不少时间。有些操作还能提高性能,因为我们不需要重复地运用光线步进函数。

我们之前学习过如果使用变换矩阵来旋转一个图形,同时将一个3D物体移动到一定距离。如果你需要缩放一个图形,你可以简单地修改SDF的维度即可。

如果你需要绘制对称的场景,那么你就需要使用opSymx操作。这个方法将会沿着X轴创建一个对称的3D物体。如果你绘制的球在vec3(1,0,0)的位置,那么在vec3(-1, 0, 0)的位置,我们会得到另外一个球;

  float opSymX(vec3 p, float r, vec3 o)
{
  p.x = abs(p.x);
  return sdSphere(p, r, o);
}

float scene(vec3 p) {
  return opSymX(p, 1., vec3(1, 0, 0));
}

Shadertoy 教程 Part 14 使用符号距离场函数第10张

如果想要沿着y轴或者z轴做对称效果,那么只需要用p.y或者p.z替换p.x即可。同时要记得同时调整你的偏移值。

如果你要沿着两个轴而不是一个轴绘制球体,那么你可以使用opSymXZ函数,它分别会在XZ平面上创建一个对象,结果就是出现了四个球。如果我们在vec3(1, 0, 1)的位置上绘制一个球,那么在vec3(1,0,1), vec3(-1,0,1),vec3(1,0,-1)和vec3(-1, 0, -1)位置上都会出现一个球。

  float opSymXZ(vec3 p, float r, vec3 o)
{
  p.xz = abs(p.xz);
  return sdSphere(p, r, o);
}

float scene(vec3 p) {
  return opSymXZ(p, 1., vec3(1, 0, 1));
}

Shadertoy 教程 Part 14 使用符号距离场函数第11张

如果想要沿着多个轴创建一个无限数量的3D物体效果,可以使用opRep函数来实现这种效果。参数,c,用来控制在每条轴上物体在3D空间中的间距。

  float opRep(vec3 p, float r, vec3 o, vec3 c)
{
  vec3 q = mod(p+0.5*c,c)-0.5*c;
  return sdSphere(q, r, o);
}

float scene(vec3 p) {
  return opRep(p, 1., vec3(0), vec3(8));
}

Shadertoy 教程 Part 14 使用符号距离场函数第12张

如果想要在轴上创建出有限数量的3D物体,使用opRepLim函数。参数 c,仍然表示间距,参数 l,表示所在轴上的物体的数量。例如vec3(1,0,1)可以沿着x轴和z轴的正负方向绘制一个球体。

  float opRepLim(vec3 p, float r, vec3 o, float c, vec3 l)
{
  vec3 q = p-c*clamp(round(p/c),-l,l);
  return sdSphere(q, r, o);
}

float scene(vec3 p) {
  return opRepLim(p, 0.5, vec3(0), 2., vec3(1, 0, 1));
}

Shadertoy 教程 Part 14 使用符号距离场函数第13张

给SDF的计算结果添加p,并且任意修改p,就可以让物体产生形变以及扭曲的效果。在opDisplace函数中,你可以任意的修改这个值来创建各种数学效果。

  float opDisplace(vec3 p, float r, vec3 o)
{
  float d1 = sdSphere(p, r, o);
  float d2 = sin(p.x)*sin(p.y)*sin(p.z) * cos(iTime);
  return d1 + d2;
}

float scene(vec3 p) {
  return opDisplace(p, 1., vec3(0));
}

Shadertoy 教程 Part 14 使用符号距离场函数第14张

下面是所示的完整代码:

  const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;
const float EPSILON = 0.0005;
const float PI = 3.14159265359;
const vec3 COLOR_BACKGROUND = vec3(.741, .675, .82);
const vec3 COLOR_AMBIENT = vec3(0.42, 0.20, 0.1);

mat2 rotate2d(float theta) {
  float s = sin(theta), c = cos(theta);
  return mat2(c, -s, s, c);
}

float sdSphere(vec3 p, float r, vec3 offset)
{
  return length(p - offset) - r;
}

float opUnion(float d1, float d2) { 
  return min(d1, d2);
}

float opSmoothUnion(float d1, float d2, float k) {
  float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
  return mix( d2, d1, h ) - k*h*(1.0-h);
}

float opIntersection(float d1, float d2) {
  return max(d1, d2);
}

float opSmoothIntersection(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
  return mix( d2, d1, h ) + k*h*(1.0-h);
}

float opSubtraction(float d1, float d2) {
  return max(-d1, d2);
}

float opSmoothSubtraction(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
  return mix( d2, -d1, h ) + k*h*(1.0-h);
}

float opSubtraction2(float d1, float d2) {
  return max(d1, -d2);
}

float opSmoothSubtraction2(float d1, float d2, float k) {
  float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
  return mix( d1, -d2, h ) + k*h*(1.0-h);
}

float opSymX(vec3 p, float r, vec3 o)
{
  p.x = abs(p.x);
  return sdSphere(p, r, o);
}

float opSymXZ(vec3 p, float r, vec3 o)
{
  p.xz = abs(p.xz);
  return sdSphere(p, r, o);
}

float opRep(vec3 p, float r, vec3 o, vec3 c)
{
  vec3 q = mod(p+0.5*c,c)-0.5*c;
  return sdSphere(q, r, o);
}

float opRepLim(vec3 p, float r, vec3 o, float c, vec3 l)
{
  vec3 q = p-c*clamp(round(p/c),-l,l);
  return sdSphere(q, r, o);
}

float opDisplace(vec3 p, float r, vec3 o)
{
  float d1 = sdSphere(p, r, o);
  float d2 = sin(p.x)*sin(p.y)*sin(p.z) * cos(iTime);
  return d1 + d2;
}

float scene(vec3 p) {
  float d1 = sdSphere(p, 1., vec3(0, -1, 0));
  float d2 = sdSphere(p, 0.75, vec3(0, 0.5, 0));
  //return d1;
  //return d2;
  //return opUnion(d1, d2);
  //return opSmoothUnion(d1, d2, 0.2);
  //return opIntersection(d1, d2);
  //return opSmoothIntersection(d1, d2, 0.2);
  //return opSubtraction(d1, d2);
  //return opSmoothSubtraction(d1, d2, 0.2);
  //return opSubtraction2(d1, d2);
  //return opSmoothSubtraction2(d1, d2, 0.2);
  //return opSymX(p, 1., vec3(1, 0, 0));
  //return opSymXZ(p, 1., vec3(1, 0, 1));
  //return opRep(p, 1., vec3(0), vec3(8));
  //return opRepLim(p, 0.5, vec3(0), 2., vec3(1, 0, 1));
  return opDisplace(p, 1., vec3(0));
}

float rayMarch(vec3 ro, vec3 rd) {
  float depth = MIN_DIST;
  float d; // distance ray has travelled

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    d = scene(p);
    depth += d;
    if (d < PRECISION || depth > MAX_DIST) break;
  }
  
  d = depth;
  
  return d;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1, -1) * EPSILON;
    return normalize(
      e.xyy * scene(p + e.xyy) +
      e.yyx * scene(p + e.yyx) +
      e.yxy * scene(p + e.yxy) +
      e.xxx * scene(p + e.xxx));
}

mat3 camera(vec3 cameraPos, vec3 lookAtPoint) {
	vec3 cd = normalize(lookAtPoint - cameraPos);
	vec3 cr = normalize(cross(vec3(0, 1, 0), cd));
	vec3 cu = normalize(cross(cd, cr));
	
	return mat3(-cr, cu, -cd);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec2 mouseUV = iMouse.xy/iResolution.xy;
  
  if (mouseUV == vec2(0.0)) mouseUV = vec2(0.5); // trick to center mouse on page load

  vec3 col = vec3(0);
  vec3 lp = vec3(0);
  vec3 ro = vec3(0, 0, 3); // ray origin that represents camera position
  
  float cameraRadius = 2.;
  ro.yz = ro.yz * cameraRadius * rotate2d(mix(-PI/2., PI/2., mouseUV.y));
  ro.xz = ro.xz * rotate2d(mix(-PI, PI, mouseUV.x)) + vec2(lp.x, lp.z);

  vec3 rd = camera(ro, lp) * normalize(vec3(uv, -1)); // ray direction

  float d = rayMarch(ro, rd); // signed distance value to closest object

  if (d > MAX_DIST) {
    col = COLOR_BACKGROUND; // ray didn't hit anything
  } else {
    vec3 p = ro + rd * d; // point discovered from ray marching
    vec3 normal = calcNormal(p); // surface normal

    vec3 lightPosition = vec3(0, 2, 2);
    vec3 lightDirection = normalize(lightPosition - p) * .65; // The 0.65 is used to decrease the light intensity a bit

    float dif = clamp(dot(normal, lightDirection), 0., 1.) * 0.5 + 0.5; // diffuse reflection mapped to values between 0.5 and 1.0

    col = vec3(dif) + COLOR_AMBIENT;    
  }

  fragColor = vec4(col, 1.0);
}

总结

通过本篇教程,我们学习了各种3D物体的形变,例如unionsintersections,以及subtractions等操作。同时学会了使用“positional”方法来在不同的轴上绘制相同的图形。下面的一些资源中,包含了我创建的一个光线步进的模板代码,以及上文中提到的一些3D SDF函数操作。这里讨论的还只是一小部分SDF操作,还有其他的很多操作,你需要访问Inigo Quilez的网站来学习。

资源

Ray Marching Template
3D SDF Operations
Combination
Elongation
Rounding
Onion
Metric
Repetition
Extrusion2D
Revolution2D
Ray Marching Primitives
Ray Marching Primitives Commented

免责声明:文章转载自《Shadertoy 教程 Part 14 使用符号距离场函数》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇window.requestAnimationFrame() ,做逐帧动画,你值得拥有Sqlserver实现故障转移 — 域控(1)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

gzip压缩算法

gzip,zlib,以及图形格式png,使用的是同一个压缩算法deflate。我们通过对gzip源码的分析来对deflate压缩算法做一个详细的说明: 第一,gzip压缩算法基本原理的说明。 第二,gzip压缩算法实现方法的说明。 第三,gzip实现源码级的说明。 1. Gzip压缩算法的原理          gzip 对于要压缩的文件,首先使用LZ7...

PHP常用符号和函数

(转)最近在写PHP程序的时候发现了一些特殊的PHP符号,例如连续小于符号,三个小于符号,eot,eod,echo示例,print示例等,突然间 发现用这么久的PHP了,竟然连PHP的基本符号都没有认全,看到@号还查了半天才知道什么意思.把基本符号和一些外面常见的PHP符号整理成了列表,在我的博客上帖一下吧,需要的朋友们可以参考下PHP相关的特殊符号~注解...

Crash日志解析

当应用程序崩溃时,会创建一个崩溃报告,这对于了解导致崩溃的原因非常有用。本文档包含有关如何表示,理解和解释崩溃报告的基本信息。 1、介绍 2、获取崩溃和低内存报告 3、象征性的奔溃报告 1、位码(bitCode) 2、确定奔溃报告是否符号化 3、用Xcode标记iOS奔溃报告 4、用atos表示崩溃报告 5、符号故障排除 4、崩溃报告分析 1、...

BUAA_2019_MATLAB基础与应用_期末复习纲要

Matlab复习提纲 一、概述 1. Matlab(Matrix Laboratory)概述 1980年,由美国的 Clever Moler 博士开发; 是一款 科学与工程计算软件; 第四代智能计算机语言。 2. 功能与特点 开放性强、可扩展性强,兼容性强,直观灵活; MATLAB提供了丰富的矩阵运算处理功能,是基于矩阵运算的处理工具; 矩阵运...

Cscope how to support java and c++

Cscope 首先在文件夹下建立cscope索引文件 find -name '*.c' > cscope.file cscope -Rbkq 这个命令会生成三个文件:cscope.out, cscope.in.out, cscope.po.out。 当中cscope.out是主要的符号索引,后两个文件是使用"-q"选项生成的。能够加快cscope...

error LNK2019: 解析的外部符号 __imp__DispatchMessageW@4,在函数的符号 _WinMain@16 据引述

错误: 1>WinMain.obj : error LNK2019: 解析的外部符号 __imp__DispatchMessageW@4,在函数的符号 _WinMain@16 据引述 1>WinMain.obj : error LNK2019: 无法解析的外部符号 __imp__TranslateMessage@4,该符号在函数 _WinMa...