Unity Shader - Ray Marching - T2 - SimpleSphereAndPlane

141 篇文章 35 订阅
9 篇文章 2 订阅

自学Raymarching汇总:Unity Shader - Ray Marching Study Summary - 学习汇总

这次Raymarching任务:球体、平板地表的测试

CSharp

SphereHolder.cs和上一文一样:https://editor.csdn.net/md/?articleId=105734156

using System.Collections;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
// jave.lin 2020.04.24 - 面板挂载器
public class PlaneHolder : MonoBehaviour
{
    public Vector3 pos => transform.position;
    public Vector4 n 
    {
        get
        {
            Vector3 n = transform.up;
            Vector4 result = n;
            result.w = dist;
            return result;
        }
    }
    [Range(0, 200)]
    public float dist = 0;

#if UNITY_EDITOR
    [Range(0, 10)]
    public float drawSize = 5;
    private void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;

        Vector3 right = transform.right * drawSize;
        Vector3 forward = transform.forward * drawSize;
        Vector3 up = transform.up * drawSize;
        Vector3 p = transform.position;
        Vector3 p0 = p + forward - right;
        Vector3 p1 = p + forward + right;
        Vector3 p2 = p - forward + right;
        Vector3 p3 = p - forward - right;
        // plane
        Gizmos.DrawLine(p0, p1);
        Gizmos.DrawLine(p1, p2);
        Gizmos.DrawLine(p2, p3);
        Gizmos.DrawLine(p3, p0);
        // normal
        Gizmos.color = Color.blue;
        Gizmos.DrawLine(p, p + up);
    }
#endif
}

using UnityEngine;
// jave.lin 2020.04.24 - 球体与平板
public class T2_SimpleSphereAndPlane : MonoBehaviour
{
    private int _Ray_hash = Shader.PropertyToID("_Ray"); 
    private int _Sphere_hash = Shader.PropertyToID("_Sphere"); 
    private int _PlanePos_hash = Shader.PropertyToID("_PlanePos"); 
    private int _PlaneNormal_hash = Shader.PropertyToID("_PlaneNormal"); 

    public bool raymarching = true; 
    public Camera cam;
    public Material mat;

    public SphereHolder sh;
    public PlaneHolder ph;
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        var aspect = cam.aspect;                // 宽高比
        var near = cam.nearClipPlane;           // 近截面距离长度
        var rightDir = transform.right;         // 相机的右边方向(单位向量)
        var upDir = transform.up;               // 相机的顶部方向(单位向量)
        var forwardDir = transform.forward;     // 相机的正前方(单位向量)
        // fov = field of view,就是相机的顶面与底面的连接相机作为点的夹角,
        // 我们取一半就好,与相机正前方方向的线段 * far就是到达远截面的位置(这条边当做下面的tan公式的邻边使用)
        // tan(a) = 对 比 邻 = 对/邻
        // 邻边的长度是知道的,就是far值,加上fov * 0.5的角度,就可以求出高度(对边)
        // tan(a)=对/邻
        // 对=tan(a)*邻
        var halfOfHeight = Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad) * near;
        // 剩下要求宽度
        // aspect = 宽高比 = 宽/高
        // 宽 = aspect * 高
        var halfOfWidth = aspect * halfOfHeight;
        // 前,上,右的角落偏移向量
        var forwardVec = forwardDir * near;
        var upVec = upDir * halfOfHeight;
        var rightVec = rightDir * halfOfWidth;
        // 左下角 bottom left
        var bl = forwardVec - upVec - rightVec;
        // 左上角 top left
        var tl = forwardVec + upVec - rightVec;
        // 右上角 top right
        var tr = forwardVec + upVec + rightVec;
        // 右下角 bottom right
        var br = forwardVec - upVec + rightVec;

        // 视锥体近截面角落点的射线
        var frustumFarCornersRay = Matrix4x4.identity;
        // 经shader中顶点颜色赋值后出入到屏幕,可以确定,第0是:左下角,1:左上角,2:右上角,3:右下角
        frustumFarCornersRay.SetRow(0, bl);
        frustumFarCornersRay.SetRow(1, tl);
        frustumFarCornersRay.SetRow(2, tr);
        frustumFarCornersRay.SetRow(3, br);
        mat.SetMatrix(_Ray_hash, frustumFarCornersRay);
        // sphere informations
        mat.SetVector(_Sphere_hash, sh.spInfo);
        // plane informations
        mat.SetVector(_PlanePos_hash, ph.pos);
        mat.SetVector(_PlaneNormal_hash, ph.n);
        // blit
        if (raymarching) Graphics.Blit(source, destination, mat);
        else Graphics.Blit(source, destination);
    }
}

Shader

主要核心逻辑

/* T2_SimpleSphereAndPlane.cginc
  jave.lin 2020.04.24
 */

#include "UnityCG.cginc"
#include "../SDFs.cginc"
struct appdata {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    uint vid : SV_VertexID;
};
struct v2f {
    float4 vertex : SV_POSITION;
    float2 uv : TEXCOORD0;
    float3 ray : TEXCOORD1;
};

float4x4 _Ray;                  // 视锥体角落射线
float4 _Sphere;                 // 球体信息:.xyz = pos, .w = radius
float3 _PlanePos;               // 平板的质心点
float4 _PlaneNormal;            // 平板的法线

#define EPSILON 0.01            // 最小接触距离
#define MAX_STEP_TIMES 100      // 最大步进次数

float sceneDF(float3 pos) {  	// 获取场景中所有几何体当中最近距离的
    // 使用自己的 DIY 版
    // sphere d
    // float sphereDist = length(pos - _Sphere.xyz) - _Sphere.w;
    // plane d
    // references : Unity Shader - 模型消融/淡入淡出效果
    // https://blog.csdn.net/linjf520/article/details/98878168
    // float planeDist = dot(pos - _PlanePos, _PlaneNormal.xyz);

    // 使用 IQ 大神SDFs版
    float sphereDist = sdSphere(pos - _Sphere.xyz, _Sphere.w);
    float planeDist = sdPlane(pos - _PlanePos, _PlaneNormal);
    return min(sphereDist, planeDist);
}

fixed4 getColor(v2f i) {
    float3 ori = i.ray;                 // 射线起点
    float3 dir = normalize(i.ray);      // 射线方向
    float3 pos;                         // 当前步进到的位置
    float dist;                         // 当前步进到的最近距离
    float d;                            // 当前最近距离
    float far = _ProjectionParams.z;    // far

    ori += _WorldSpaceCameraPos.xyz;    // 偏移,加上相机位置
    pos = ori;                          // 从起点出发
    UNITY_LOOP
    for (int it = 0; it < MAX_STEP_TIMES; it++) {
        d = sceneDF(pos);    	// 获取当前几何体集合中最近的距离
        dist += d;              // 调整当前步进到的最近距离
        pos = ori + dir * dist; // 调整当前的步进位置
        if (d < EPSILON) {      // 有碰撞
            return (float) it / MAX_STEP_TIMES * fixed4(1,1,0,1);
        }
        if (dist > far) {
            return (float) it / MAX_STEP_TIMES * fixed4(1,0,0,1);
            return 0;
        }
    }
    return fixed4(1,0,0,1);
}

v2f vert (appdata v) {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    o.ray = _Ray[v.vid];
    return o;
}
fixed4 frag (v2f i) : SV_Target {
    return getColor(i);
}

运行效果

在这里插入图片描述
在这里插入图片描述

总结

主要是步进的步长值有技巧

留意 sceneDF

float sceneDF(float3 pos) {  // 获取场景中所有几何体当中最近距离的
    // 使用自己的 DIY 版
    // sphere d
    // float sphereDist = length(pos - _Sphere.xyz) - _Sphere.w;
    // plane d
    // references : Unity Shader - 模型消融/淡入淡出效果
    // https://blog.csdn.net/linjf520/article/details/98878168
    // float planeDist = dot(pos - _PlanePos, _PlaneNormal.xyz);

    // 使用 IQ 大神SDFs版
    float sphereDist = sdSphere(pos - _Sphere.xyz, _Sphere.w);
    float planeDist = sdPlane(pos - _PlanePos, _PlaneNormal);
    return min(sphereDist, planeDist);
}

这个函数可以看到我们获取了两个几何体:Sphere与Plane与当前步进点的最近距离值。
以此值作为这次步进的距离,这样就不会因为固定的步长值导致精度或是性能的问题。

步进值过大:得到的射线检测表面的细节精度就低。
步进值过小:计算量将大大增加。效果还不一定能还好的适配每次表面的距离。

所以我们才使用上面的算法:
取到当前步进位置与所有几何体的最小距离作为步进,那么每次步进就很可能直接到一个表面的表面值,

Plane 的最小距离的计算方式,先看我注释的部分:// float planeDist = dot(pos - _PlanePos, _PlaneNormal.xyz);,这部分我们可以参考我之前的一篇:Unity Shader - 模型消融/淡入淡出效果,与IQ的SDFs是一样的,不过他的多了一个:n.w的增加值,用于控制平面距离用的。

Sphere 的最小距离,我用GGB画个图,方便理解:

第一次步进

我们步进到最近的距离为:d0,与Plane的距离最近的值作为射线步进的长度,如下图:d0=length(pos0-Pos)就是步进距离,然后我们用这个 d0 距离值来步进射线
在这里插入图片描述

第二次步进

在这里插入图片描述
这次,我们的步进距离是:d1=length(pos1-pos0),这次在pos0点,再次步进距离为d1,步进到了pos1点,现在与sphere的表面原来越近了

第三次

在这里插入图片描述
这次步进距离是:|d2|=length(d2-pos1),这次步进到了d2点,直接就步进到了Sphere的表面上了。
然后我们得到了这次射线的距离检测为:Posd2的距离

可以看到前面两次d0d1的距离都比较近,所以我们使用这个距离来步长处理,直到最后一次与一样表面点有碰撞的更加近,这次就直接一步到位了。

这种方法就不会每次固定步长可能导致穿插入几何体内了。因为每一次都是所有几何体当中最近的一个表面点。

在这里插入图片描述

Project

backup : T2_SimpleSphereAndPlane

GGB

backup : CalculateRaymarchingStepLength.ggb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值