用程式碼來畫畫 —— Ray-Marching(光線步進)【Unity Shader】
阿新 • • 發佈:2018-11-08
參考自:
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 讓程式碼 “畫畫”