您当前的位置: 首页 >  ar

Unity Shader - Ray Marching - T2 - SimpleSphereAndPlane

发布时间:2020-04-24 16:59:15 ,浏览量:5

文章目录
  • CSharp
  • Shader
  • 运行效果
  • 总结
    • 主要是步进的步长值有技巧
      • 第一次步进
      • 第二次步进
      • 第三次
  • Project
  • GGB

自学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的表面上了。 然后我们得到了这次射线的距离检测为:Pos到d2的距离

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

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

在这里插入图片描述

Project

backup : T2_SimpleSphereAndPlane

GGB

backup : CalculateRaymarchingStepLength.ggb

关注
打赏
1688896170
查看更多评论

暂无认证

  • 5浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0588s