1. 程式人生 > >用程式碼來畫畫 —— Ray-Marching(光線步進)【Unity Shader】

用程式碼來畫畫 —— Ray-Marching(光線步進)【Unity Shader】


參考自:

http://blog.csdn.net/baidu_26153715/article/details/46510703

http://imgtec.eetrend.com/blog/8845

http://ogldev.atspace.co.uk/www/tutorial13/tutorial13.html


效果如圖:





完整程式碼及詳細註釋如下【修正了原始碼的一些錯誤】:

Shader "Custom/RayMarching"
{
	Properties
	{
		//_MainTex ("Texture", 2D) = "white" {}
		//_Cube("cubemap", cube) = ""{}
	}

	// 程式碼參考自 http://blog.csdn.net/baidu_26153715/article/details/46510703
	SubShader
	{
		// No culling or depth
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
			// 球體 [Signed Distance Function]
			float sdfSphere(float3 p, float s)
			{
				return length(p) - s;
			}
			// 立方體
			float sdfBox(float3 p, float3 b)
			{
				return length(max(abs(p) - b, 0.));
			}

			// p 從相機空間換算到世界座標系(即世界座標系下光線步進的方向)
			float3 CameraSpace2WorldSpace(in float3 camPos, in float3 p)
			{
				float3 target = float3(0, 0, 0);
				float3 AxisY = float3(0, 1, 0);
				float3 z = normalize(target - camPos); // look
				float3 x = normalize(cross(z, AxisY)); // right
				float3 y = normalize(cross(z, x)); // up
                                // 注意:(right, up, look) * world = camera,Cg 矩陣是按行儲存
				float3 theWorldSpaceP = float3(
					// dot(p, x), dot(p, y), dot(p, z)
					//p.x*x + p.y*y + p.z*z
					//mul(float3x3(x, y, z), p)
					mul(p, float3x3(x, y, z)) // 交換相乘位置相當於轉置
					);
				return  theWorldSpaceP;
			}
			
			// 步進的光線終點和球體表面的距離
			float map(in float3 pos)
			{
				//float d = sdfSphere(pos, 1);
				float d = sdfBox(pos, float3(.5, .5, .5));
				return d;
			}

			float3 normal(in float3 pos)
			{
				float2 offset = float2(.01, 0);
				float3 nDir = normalize(
					float3(
						// 通過計算 xyz 三個方向的差值(梯度),歸一化後得到法線方向
						map(pos + offset.xyy) - map(pos - offset.xyy),
						map(pos + offset.yxy) - map(pos - offset.yxy),
						map(pos + offset.yyx) - map(pos - offset.yyx)
						)
				);
				return nDir;
			}

			float marching(in float3 origin, in float3 dir)
			{
				float t = 1;	// 步進的光線總長度
				int i;
				for (i = 0; i<64; ++i)
				{
					// 隨著光線的步進,檢查是否到達球體的表面
					float3 graphic = origin + t*dir;
					float d = map(graphic);
					// 當距離小於一個最小閾值,或者長度超過一個最大閾值,則中斷迴圈
					if (d < .02 || t>20)
						break;
					// 步進
					t += d;
				}
				return t;
			}

			float3 render(in float3 pos, in float3 p)
			{
				// 距離輔助的 ray-marching
				float d = marching(pos, p);
				// 計演算法線方向(世界座標系)
				float3 nDir = normal(pos + p*d);

				float3 c = 0;
				if (d<30)
				{
					// 光源方向(世界座標系)
					float3 lDir = normalize(half3(0, 1, 0));
					// diffuse
					float diff = max(0, dot(lDir, nDir));

					// 將法線方向作為顏色返回
					c = nDir;
				}
				return c;
			}

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

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

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			//sampler2D _MainTex;

			fixed4 frag (v2f i) : SV_Target
			{
				float time = _Time.y;
				float2 uv = i.uv * 2 - 1;
                                // 相機座標系
				float3 p = normalize(float3(uv, 2));

				// 相機的世界座標(隨時間變化)
				float3 camPos = float3(3 + sin(time), 3, 3 + cos(time));
				
				// 將 p 點換從相機座標系換算到世界座標系
				float3 theNewP = CameraSpace2WorldSpace(camPos, p);

				// 渲染模型的法線
				fixed3 col = render(camPos, theNewP);

				return fixed4(col, 1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}


加上光照計算的效果:






取 並集(Union) 的效果:



實際應用:用 SDF 和 Raymarch 讓程式碼 “畫畫”