光线步进——RayMarching入门

暑期实习笔试结束了,IGG笔试题比我想象的要难,听群里说比腾讯笔试难得多,不知道结果如何。。五一假期写写结课论文,看看番,有时间打算研究一下体积云,然后就走到了RayMarching这儿。

关于光线步进

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));
}

首先,把屏幕中心设置为坐标原点(0.0,0.0),射线的z值都是固定的,其中filedOfView可以看成视椎体两条棱的夹角,返回归一化的射线向量。

对射线进行碰撞检测
float sphereSDF(float3 samplePoint){
    return length(samplePoint) - 1.0;
}
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;
}

以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);
存在问题

球体的碰撞检测是比较容易的,如果我们想放一个立方体到“场景”里,怎么搞?

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

光线步进——RayMarching入门
感觉绘制什么样的物体并不是特别容易控制,需要使用一些数学手段,真佩服哪些用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))
              ));
}

可以利用matlab进行检验,绘制一个曲面,计算它的表面法线
光线步进——RayMarching入门

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);
根据法线方向计算光照

设定光源位置,环境光,漫反射颜色,高光颜色,高光系数等信息,计算光照即可

代码部分

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
        }
    }
}