Unity之基于光线投射算法的体渲染技术(二)

接上文https://blog.csdn.net/qq_34980278/article/details/84633767

第二种光线投射算法的实现,基于光线起点(相机位置)。将相机位置由世界坐标通过模型视图投影矩阵反变换到长方体的局部坐标空间。每个fragment的坐标作为光线的入射点,由变换后的相机位置与光线入射点确定光线的方向,在沿着光线方向从入射点前进时,每到达一个新的点,判断其是否在长方体内,如果在,就记录该点像素信息,如果不在,则结束这条光线。与上文本质是一样的,算法实现上有些差异(需要逐点判断,比较花费时间);

Unity之基于光线投射算法的体渲染技术(二)_第1张图片

核心shader代码(这里额外加入了Blinn-Phong光照模型来加强显示效果):

Shader "Custom/RayCast"
{
	Properties
	{
		_Color("MainColor",color)=(1,1,1,1)
	   _Value("value",Range(0,1))=0.5
	   shiness("Shiness",Range(0,3))=0.5
	}
	SubShader
	{
		Tags { "LightMode" = "ForwardBase" "VolumeTag" = "Volume" }
		Pass
		{
			CGPROGRAM
				#pragma target 3.0
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile _ VOLUMEVIEWER_TF_DATA 
				#include"Lighting.cginc"
				struct u2v {
					float4 	pos : POSITION;
				};
				struct v2f {
					float4 	pos 	: SV_POSITION;
					float3	ClipPos 	: TEXCOORD0;
				};
				sampler3D data3D;
				float maxSamples;
				float maxSlices;
				float4 _Color;
				float _Value;
				float shiness;				
				inline half maxRGB(half3 rgb)
				{
					return max(max(rgb.r, rgb.g), rgb.b);
				}	
				fixed cubeIntersection(float3 origin, float3 direction, float3 aabbMax, float3 aabbMin, out float tNear, out float tFar)
				{					
					float3 invDir = 1 / direction;
					float3 t1 = invDir * (aabbMax - origin);
					float3 t2 = invDir * (aabbMin - origin);
					float3 tMin = min(t1, t2);
					float3 tMax = max(t1, t2);
					tNear = max(max(tMin.x, tMin.y), tMin.z);
					tFar = min(min(tMax.x, tMax.y), tMax.z);
					return tNear <= tFar;
				}		
				half4 getColor(float3 pos)
				{					
					half4 data = tex3Dlod(data3D, float4(pos.xyz, 0));	
					if(maxRGB(data.rgb)<_Value){
					data.rgb=0;
					}		
					data=float4(data.rgb,1);
					return saturate(data);
				}
				//通过rgb的变化梯度来计算法线
				float3 getGradient(float3 pos)
				{
					float offset = 1.0 / maxSlices;
					float4 tc1 = getColor(pos + float3(offset, 0, 0));
					float4 tc2 = getColor(pos + float3(-offset, 0, 0));
					float4 tc3 = getColor(pos + float3(0, offset, 0));
					float4 tc4 = getColor(pos + float3(0, -offset, 0));
					float4 tc5 = getColor(pos + float3(0, 0, offset));
					float4 tc6 = getColor(pos + float3(0, 0, -offset));
					float3 g0;
					g0.x = maxRGB(tc2.rgb) - maxRGB(tc1.rgb);
					g0.y = maxRGB(tc4.rgb)- maxRGB(tc3.rgb);
					g0.z = maxRGB(tc6.rgb) - maxRGB(tc5.rgb);
					return g0;
				}
				//Blinn-Phong光照模型
				float4 shadingLight(float4 color, float3 g, float3 v, float3 l)
				{
					float3 n = normalize(g);
					float3 h = normalize(v + l);
					float n_l = dot(n, l);
					float n_h = dot(n, h);
					float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz* color.rgb;
					float3 diffuse = _LightColor0.rgb * max(n_l, 0) * color.rgb;
					float3 specular = _LightColor0.rgb * pow(max(n_h, 0), shiness)*(n_l > 0);
					color.rgb = ambient + diffuse + specular;
					return saturate(color);
				}
				v2f vert(u2v v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.pos);
					o.ClipPos = v.pos.xyz;
					return o;
				}
				half4 frag(v2f i) : COLOR 
				{					
					float3 rayOrigin = mul(unity_WorldToObject,_WorldSpaceCameraPos);
					//逐像素发射线
					float3 rayDirection = normalize(i.ClipPos - rayOrigin);
	    			float tNear, tFar;
					//输入box顶点坐标(本地坐标),确定采样的区域,输出的tNear和tFar为射线和box的相交近交点和远交点
					cubeIntersection(rayOrigin, rayDirection, float3(0.5, 0.5, 0.5) , float3(-0.5, -0.5, -0.5), tNear, tFar);				
					//=>tNear *= (tNear >= 0?tNear:0)  如果近交点长度小于0说明在摄像机(射线起点)的后面,是看不到的,所以最小取0
					tNear *= tNear >= 0;          	    			
					//从近交点处开始逐步向远交点走线
					float3 startRay = rayOrigin + rayDirection * tNear;
	    			//到远交点处停止
					float3 endRay = rayOrigin + rayDirection * tFar;
					//box相交检测算法是以原点的box为基准的,tex3Dlod的采样坐标是0~1,所以需要将-0.5~0.5映射到0~1区间
	    			startRay = startRay+0.5;
	    			endRay = endRay+0.5 ;
					//穿越box的射线长度
					float maxRayLength = length(endRay - startRay);
					//步长  最大距离/采样数    box对角线为最大长度根号3
					float rayLengthStep = 1.732 / maxSamples;
					//当前射线长度
					float rayLength = 0;
					//最终颜色
					half4 dst = 0;
					//当前射线采样数
					half samples = 0;
					[loop]
					while(rayLength < maxRayLength)
					{
						half4 src = getColor(startRay + rayLength * rayDirection);
						//光照模型的实现
						float3 surfaceGradient = getGradient(startRay + rayLength * rayDirection);
						float3 lightDir = mul(float4(_WorldSpaceLightPos0.xyz, 0), unity_ObjectToWorld).xyz;						
					    src = shadingLight(src, surfaceGradient, -rayDirection, lightDir);

						half intensity = maxRGB(src.rgb);
                        src*= intensity;
						//柔性相加(Blend的公式参考这篇文章  https://blog.csdn.net/baicaishisan/article/details/79072127)
						dst += (1.0f - dst.a) * src;
						//记录射线步进和当前采样数
						rayLength += rayLengthStep ;
						samples += 1;
						//当射线穿出box或达到最大采样数时终止循环
						if (samples > maxSamples)
						{
							break;
						}
					}
					return dst*_Color;
				}
			ENDCG
		}
	}
	FallBack "diffuse"
}

百度云链接:https://pan.baidu.com/s/1sU_RgK_xnGtG_J8afVmrsg 
提取码:j3jy 

你可能感兴趣的:(图形学,体渲染,Unity)