光线步进——RayMarching入门

关于光线步进
RayMarching 是一种用于实时场景的快速渲染方法,我的理解是,模拟摄像机位置,根据视椎体的扩张角度,以摄像机位置为原点,进步式发射射线,当射线碰撞到物体之后,返回其深度信息,如果到视椎体的最大距离之前都没有返回,那么可以以此判断该像素点没有对于物体,最后根据返回的信息计算光照。
可以看出,RayMarching是有误差的,如果提高精度,减少步长,循环次数又太多,导致效率很低。
感觉RayMarching目前用来渲染云,雾这些类似的体积渲染比较多。

入门实现

先用RayMarching描绘一个球体,最后在进行光照计算
参考:https://www.shadertoy.com/view/llt3R4

模拟摄像机射线
float3 rayDirection(float filedOfView, float2 size, float2 fragCoord){
float2 xy=fragCoord-size/2;
float z=size.y/tan(radians(filedOfView)/2.0);
return normalize(float3(xy,-z));
}
1
2
3
4
5
首先,把屏幕中心设置为坐标原点(0.0,0.0),射线的z值都是固定的,其中filedOfView可以看成视椎体两条棱的夹角,返回归一化的射线向量。

对射线进行碰撞检测
float sphereSDF(float3 samplePoint){
return length(samplePoint) - 1.0;
}
1
2
3
float shortestDistanceToSurface(float3 eye,float3 marchingDirection,float start,float end){
float depth = start;
for(int i=0;i<maxMarchingSteps;i++){
float dist=sphereSDF(eye+depth*marchingDirection);
if(dist < epsilon){
return depth;
}

depth+=dist;
if(depth>=end){
return end;
}
}
return end;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
以eye坐标为起点,沿着模拟射线的方向进行碰撞检测,返回碰撞点的深度,如果到最大深度仍然没有碰撞,则发挥最大深度

根据深度返回颜色
float dist=shortestDistanceToSurface(eye,dir,minDist,maxDist);
if(dist>=maxDist-epsilon){
return float4(0.0,0.0,0.0,0.0);
}
float value=floor(dist*10.0)*_StepValue;
return float4(1-value,sin(value*10.0),0.0,1.0);
1
2
3
4
5
6
存在问题
球体的碰撞检测是比较容易的,如果我们想放一个立方体到“场景”里,怎么搞?

float cubeSDF(float3 samplePoint){
float3 d=abs(samplePoint)-float3(0.5,0.5,0.5);
return length(max(d,0.0));
}
1
2
3
4

感觉绘制什么样的物体并不是特别容易控制,需要使用一些数学手段,真佩服哪些用RayMarching画画的那些老哥。

计算法线方向
现在,我们知道顶点坐标,通过计算 xyz 三个方向的差值(梯度),归一化后得到一个近似的法线方向

float3 normalCalculate(float3 p){
return normalize(float3(
sphereSDF(float3(p.x + epsilon, p.y, p.z)) - sphereSDF(float3(p.x - epsilon, p.y, p.z)),
sphereSDF(float3(p.x, p.y + epsilon, p.z)) - sphereSDF(float3(p.x, p.y - epsilon, p.z)),
sphereSDF(float3(p.x, p.y, p.z + epsilon)) - sphereSDF(float3(p.x, p.y, p.z - epsilon))
));
}
1
2
3
4
5
6
7
可以利用matlab进行检验,绘制一个曲面,计算它的表面法线


clear;
[X Y]=meshgrid(-0.5:0.05:0.5, -0.5:0.05:0.5);
Z=0.25-X.^2-Y.^2;
vecX=sqrt(((X+0.0001).^2+Y.^2+Z))-sqrt(((X-0.0001).^2+Y.^2+Z));
vecY=sqrt((X.^2+(Y+0.0001).^2+Z))-sqrt((X.^2+(Y-0.0001).^2+Z));
%mesh(X,Y,Z);
quiver(X,Y,vecX,vecY);
1
2
3
4
5
6
7
根据法线方向计算光照
设定光源位置,环境光,漫反射颜色,高光颜色,高光系数等信息,计算光照即可

代码部分
Shader "Unlit/RayMarching_1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_StepValue("StepValue",Range(0.001,0.025))=0.001
_LightPos("LightPos",vector)=(0,0,0,0)
_AmbientCol("AmbientCol",Color)=(1,1,1,1)
_DiffuseCol("DiffuseCol",Color)=(1,1,1,1)
_SpecularCol("SpecularCol",Color)=(1,1,1,1)
_Gloss("Gloss",Range(0.1,255))=10
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

#define maxMarchingSteps 255
#define minDist 0.0
#define maxDist 1000.0
#define epsilon 0.00001
#define sizeX (_ScreenParams.x/_ScreenParams.y)

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
float _StepValue;
float4 _LightPos;
float4 _AmbientCol;
float4 _DiffuseCol;
float4 _SpecularCol;
float _Gloss;

float cubeSDF(float3 samplePoint){
float3 d=abs(samplePoint)-float3(0.5,0.5,0.5);
return length(max(d,0.0));
}

float sphereSDF(float3 samplePoint){
return length(samplePoint) - 0.7;
}

float sceneSDF(float3 samplePoint){
return sphereSDF(samplePoint);
}

float3 rayDirection(float filedOfView, float2 size, float2 fragCoord){
float2 xy=fragCoord-size/2;
float z=size.y/tan(radians(filedOfView)/2.0);
return normalize(float3(xy,-z));
}

float shortestDistanceToSurface(float3 eye,float3 marchingDirection,float start,float end){
float depth = start;
for(int i=0;i<maxMarchingSteps;i++){
float dist=sceneSDF(eye+depth*marchingDirection);
if(dist < epsilon){
return depth;
}

depth+=dist;
if(depth>=end){
return end;
}
}
return end;
}

float3 normalCalculate(float3 p){
return normalize(float3(
sceneSDF(float3(p.x + epsilon, p.y, p.z)) - sceneSDF(float3(p.x - epsilon, p.y, p.z)),
sceneSDF(float3(p.x, p.y + epsilon, p.z)) - sceneSDF(float3(p.x, p.y - epsilon, p.z)),
sceneSDF(float3(p.x, p.y, p.z + epsilon)) - sceneSDF(float3(p.x, p.y, p.z - epsilon))
));
}

float3 LightCalculate(float3 eyePos,float3 pos,float3 lightPos,float3 ambientCol,float3 diffuseColor){
float3 normal = normalCalculate(pos);
float3 col=diffuseColor*max(dot(normal,normalize(lightPos-pos)),0);
col+=ambientCol;
float3 halfDir = normalize(lightPos-pos + eyePos-pos);
float3 specular = _SpecularCol.rgb * pow(max(0, dot(normal, halfDir)), _Gloss);
col+=specular;
return col;
}

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

float4 frag (v2f i) : SV_Target
{
i.uv.x*=_ScreenParams.x/_ScreenParams.y;
float3 dir=rayDirection(45.0,float2(sizeX,1.0),i.uv);
float3 eye= float3(0.0,0.0,5.0);
float dist=shortestDistanceToSurface(eye,dir,minDist,maxDist);
if(dist>=maxDist-epsilon){
return float4(0.0,0.0,0.0,1.0);
}
float3 pos=eye+dist*dir;

float3 col=LightCalculate(eye,pos,_LightPos.xyz,_AmbientCol.xyz,_DiffuseCol.xyz);
return float4(col,1);
//return float4(0.5,0.5,0.5,1);
//float value=floor(dist*10.0)*_StepValue;
//return float4(sin(value*1000),1-value,0.0,1.0);
}

ENDCG
}
}
}

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

--------------------- 

原文地址:https://www.cnblogs.com/ly570/p/10989731.html