数学可视化(09)——Rayarch光线步进算法

  1. 1. 图形学中的几何对象表现方式
  2. 2. 光线步进算法

 

图形学中的几何对象表现方式

  • 3D几何对象的表示有显式和隐式两种方式
显式 隐式
多边形面片 数学函数公式
点云 距离场函数
NURBS CSG(Constructive Solid Geometry)
体素 分形函数
  • 隐式方式不能提供直观的视觉呈现,但在计算几何体相交、碰撞等方面更方便
  • 通常用显式的方式去构建复杂场景的几何体,用隐式的方式去构建一些加速结构和简化几何体,用来处理优化算法
  • 将用距离场函数及几何体的运算来构建3D几何对象
  • 在二维屏幕上,即使使用隐式3D几何构造方式,提供了几何数据,输入数据也只有uv两个维度
  • 绘制三维几何体,至少需要三个变量来描述,这时需要一个场景深度来辅助处理,也就是知道uv后,再获取该像素点在场景下的深度值
  • 这就需要光线步进算法来协助完成了

光线步进算法

  • 先讨论渲染体、相机、屏幕uv,3个基本的对象
  • 通过屏幕坐标uv和相机的位置,可以得到多条以相机为起点,朝向场景发射的射线
  • 针对每条射线,可以定义该射线的光线步进算法,伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
单根光线的步进:
循环定义最大步进步数MaxStep ;
{
定义光线总共步进的长度depth ;
根据相机位置与方向定义光线步进的起点p;
求的与场景中最近面的距离dist ;
将光线本次步进的距离加到总步进长度上;
如果光线总步进距离大于定义的最大距离
或者本次光线步进距离小于定义的最小距离时;
退出循环;
}
返回光线总步进长度。
  • 我们需要预先定义一些关于光线的预定义数据,包括光线最大的步进步数、总共行进的最大距离长度、光线与物体表面距离的最小长度等

  • 完成后可以得到当前屏幕场景所有深度值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Shader "MathematicalVisualizationArt/MyLearning09"
{
Properties
{
}

SubShader
{
Tags{"RenderType" = "Transparent" "RenderPipeline" = "UniversalRenderPipeline" "IgnoreProjector" = "True"}
LOD 300

Pass
{
Name "DefaultPass"

HLSLPROGRAM

#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
#pragma target 2.0

#pragma vertex vert
#pragma fragment frag

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};

struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
float4 screenPos : TEXCOORD1;
};

Varyings vert (Attributes input)
{
Varyings output = (Varyings)0;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.uv = input.uv;
output.positionCS = vertexInput.positionCS;
output.screenPos = ComputeScreenPos(vertexInput.positionCS);
return output;
}

#define time _Time.y
#define width _ScreenParams.x
#define height _ScreenParams.y

// 一、定义数据
#define MAX_MARCHING_STEPS 100 // 最大步进步数
#define MIN_DIST 0.01 // 最小距离
#define MAX_DIST 100.0 // 最大步进距离总和

// 二、定义三维几何体sdf
// 球体sdf
float sdSphere( float3 p, float r )
{
return length(p)-r;
}
// 平面sdf
float sdPlane( float3 p, float3 n, float h )
{
// n must be normalized
n = normalize(n);
return dot(p,n) + h;
}
// 圆环sdf
float sdTorus( float3 p, float2 t )
{
float2 q = float2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}
// 圆锥sdf
float sdCone( float3 p, float2 c, float h )
{
// c is the sin/cos of the angle, h is height
// Alternatively pass q instead of (c,h),
// which is the point at the base in 2D
float2 q = h*float2(c.x/c.y,-1.0);

float2 w = float2( length(p.xz), p.y );
float2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 );
float2 b = w - q*float2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 );
float k = sign( q.y );
float d = min(dot( a, a ),dot(b, b));
float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) );
return sqrt(d)*sign(s);
}
// 圆角盒sdf
float sdRoundBox( float3 p, float3 b, float r )
{
float3 q = abs(p) - b;
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}

// scene sdf
// 用于计算CSG几何运算后的场景结果
float sdScene(float3 p)
{
//定义球体
float3 spherePos = float3(1.5, 1.0, 0.0);
float sphereRadius = 1.0;
float3 sphereNormal = normalize( p - spherePos );
//定义圆角盒
float3 boxPos = float3(-1.5, 0.5, 0.0);
float3 boxSize = float3(1.0, 0.5, 0.5);
float boxRadius = 0.1;
//定义圆环
float3 torusPos = float3(5.0, 1.0, 0.0);
float2 torusRadius = float2(0.8, 0.3);
//定义圆锥
float3 conePos = float3(-5.0, 2.0, 0.0);
float2 coneRadius = float2(0.1, 0.3);
float coneHeight = 2.0;
//定义平面
float3 planePos = float3(0.0, 0.0, 0.0);
float3 planeNormal = float3(0.0, 1.0, 0.0);
//求交集
float sphereDist = sdSphere(p - spherePos, sphereRadius);
float torusDist = sdTorus(p - torusPos, torusRadius);
float coneDist = sdCone(p - conePos, coneRadius, coneHeight);
float planeDist = sdPlane(p - planePos, planeNormal, 0.0);
float boxDist = sdRoundBox(p - boxPos, boxSize, boxRadius);
return min(min(min(min(sphereDist, boxDist),coneDist),torusDist),planeDist);
}

// RayMarch, 用于计算光线与物体的交点
// 输入分别是摄像机位置与光线方向
float RayMarch(float3 ro, float3 rd)
{
float depth = 0.0;
for(int i = 0; i < MAX_MARCHING_STEPS; i++)
{
float3 p = ro + rd*depth;
float dist = sdScene(p);
depth+=dist;
if(depth > MAX_DIST || dist < MIN_DIST)
break;
}
return depth;
}

half3 PixelColor(float2 uv)
{
half3 c = half3(0, 0, 0);
//
float uvSizeScale = 1;
//四象限转一象限
uv.y = 1.0- uv.y;
//全象限 (-5, 5)
uv = (uv*2.0 -1.0)*uvSizeScale;
//消除屏幕拉伸影响
half co = width/height;
uv = float2(uv.x*co, uv.y);

// 根据摄像机位置与uv坐标计算出光线方向
//定义摄像机
float3 camPos = float3(0.0, 1.0, -5.0);
float3 lightDir = normalize(float3(uv, 1.0));

float dist = RayMarch(camPos, lightDir); // 步进距离总和(深度)
c = half3(dist/(20-time), dist/(20-time), dist/(20-time));

return c;
}

half4 frag(Varyings input) : SV_Target
{
float2 screenUV = GetNormalizedScreenSpaceUV(input.positionCS);
#if UNITY_UV_STARTS_AT_TOP
screenUV = screenUV * float2(1.0, -1.0) + float2(0.0, 1.0);
#endif
half3 col = Gamma22ToLinear(PixelColor(screenUV));
return half4(col, 1);
}
ENDHLSL
}
}
}