效果先行 如果我们想要更加丰富的显示,要怎么做呢?我这里做了如下算法: 此时,如果我们想关注皮肤层,首先我们知道皮肤层的密度很低,骨骼密度很高,那么我们就可以减小_Density值,使得射线刚刚取样到一些值,就立即截止取样,那么我们就可以获得表层低密度皮肤的显示了,效果如下:
Unity中实现体绘制–以CT扫描效果为例
话不多说,先看效果:
前言
最近因为一些需求,研究了一下体绘制。什么是体绘制呢?我的理解是它是一种表现三维空间场的技术。具体说来,我们需要通过一个3d texture,再通过一定的采样方法,最终画出我们想要的图形。体绘制目前应用很广泛,它可以用来模拟烟雾、云或者CT数据等等,目前很多游戏例如Horizon:Zero Down 等,都采用了类似的方法去做体积云,所以这是一个比较有意义的研究。
实现
实现体绘制,我们首先需要一张体纹理,体纹理数据我们可以从扫描数据中得到,例如下面的网站里就有一些扫描好的体纹理我们可以直接用:
体纹理网站
当然我们也可以自己按照一定的规则制作体纹理(我想以后再研究一下单独写一篇,先挖个坑╮(╯▽╰)╭)
体绘制最关键的内容就是Ray Casting光线投射算法(当然也有其它算法只是该算法是目前最常用的一种方法)。 通俗一点解释,就是从屏幕像素发射一条射线,让它进入到几何体内,然后把射线切分成很多段,每一段对体纹理进行取样,最后通过一定的混合方式把颜色混合起来,画在屏幕上。
既然如此,那么我们就会面临一个问题:射线什么时候停止呢?这个问题又可以分为两个问题,一是如何知道射线已经出了几何体呢?我们可以通过求深度的方法来求得视线方向几何体的厚度,这样就可以得知射线是否出了范围。对于一些规则的几何体,尤其是本例中,我采用了一些更加简单高效的方法,就是和每个片元的Object Space的坐标去比,如果超过就说明已经出了射线范围。二是射线有没有必要一定要贯穿整个几何体进行取样?答案是没有必要,如果一路上累加的颜色值已经超过了1,那么继续采样也是白费力气的,所以我们可以通过这个条件去节约取样次数。
混合方式
我认为混合方式也是比较重要的一块,因为直接和显示效果相关。举例来说,射线一路贯穿下去进行取样,最简单的颜色混合方式就是线性叠加,但是效果往往不太好,这里我选择柔性叠加的方式,效果如下:
fixed c = tex3D(_VolumeTex, currPos).r*_Intensity;//取样3dtexture //自定义色彩混合区域 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ fixed4 src = float4(c, c, c, c); src.a *=(1- _Density) * pow(src.a, _Contrast);//_Density值越大,颜色将越早地截止,因而也就不会采样到深层 src.rgb *= src.a; col = (1 - col.a)*src + col;//柔性叠加 //自定义色彩混合区域 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
同理,如果我们想获得深层中的高密度的骨骼,我们就可以通过增大_Density值,同时增大_Contrast值使得密度差拉大,这样我们就可以显示出我们想要的高密度骨骼信息,
效果如下:
进阶显示
既然我们已经得到了空间中每一个点的密度值,那么我们就可以根据Gradient的方法去计算表面法线了,简单来说就是比较周围的密度值大小,例如在x轴上,如果左侧的减右侧的值大于0,那么该点的法线的x轴向就应该是偏向右侧。我们可以根据以下算法得到法线:
我们既然得到了Object Space的法线,我们就可以根据法线来计算光照了,例如我们可以用一个简单的Blinn-Phong模型来照亮我们的物体,效果如下:
我们也可以用更复杂的光照模型去实现更加好的效果,但是这里我决定用一下MatCap的方法,这是一种非常简单高效的着色方法,可以实现非常有趣的材质效果(ZBrush的视窗材质就是典型的MatCap着色),效果如下:
最后我们可能还想获得剖面,那么我们可以通过控制射线取样来实现。简单来说,就是本来射线是从StartPoint出发,一路贯穿几何体进行取样,这里我们进行一些处理,使得射线从我们的剖面位置才开始取样,这样就可以画出剖面,效果如下:
当然我们也可以获得一个水晶剖面的头骨:
展望
其实体绘制还可以做出更多花样,我在一些国外资料中看到可以根据一张查找表去对不同密度的物质绘制不同的颜色,不过本次研究没有尝试,希望后面有机会再试一下。还有就是目前有一些比较好的方法进行优化,例如检测到空区域,射线会自动降采样,等等,这些先留到后面去尝试吧。这次很多内容是受到一个GitHub上的资源的启发,此外还有《GPU编程与CG语言之阳春白雪下里巴人》,都放在参考资料里,有兴趣的可以看一下。
源码
当然还有喜闻乐见的源码,这段代码不包含法线计算和MatCap的内容,如果对那部分感兴趣的话可以评论留言或者私信我。Shader "Hidden/testVolume" { Properties { _TintColor("TintColor",Color) = (1,1,1,1) _VolumeTex("Texture", 3D) = "white" {} _Intensity("Intensity", Range(0.0001, 4.0)) = 1 _Density("Density", Range(0.01, 0.9)) = 0.5 _Contrast("Contrast", Range(0.0, 2.0)) = 1 _SliceMin("SliceMin",Vector)= (0,0,0,1) _SliceMax("SliceMax",Vector) = (1,1,1,1) } SubShader { Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"} Pass { Tags{"LightMode" = "ForwardBase"} ZWrite Off Cull back Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler3D _VolumeTex; float4 _TintColor; float _Intensity; float _Density; float _Contrast; float3 _SliceMin; float3 _SliceMax; float4x4 _AxisRotationMatrix; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 vertexLocal:TEXCOORD1; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.vertexLocal = v.vertex.xyz;//object Space的点 return o; } fixed4 frag(v2f i) : SV_Target { #define NUM_STEPS 128 //迭代次数越高,精度越高 const float stepSize = 1.732f / NUM_STEPS;//1.732f float3 rayStartPos = i.vertexLocal + float3(0.5, 0.5, 0.5);//映射到0~1的box空间 float3 rayDir = normalize(ObjSpaceViewDir(float4(i.vertexLocal, 0)));//归一化射线向量 float4 col = float4(0, 0, 0, 0); float maxDensity = 0; float clipV = 0; [unroll]//循环展开,并行计算 for (uint iStep = 0; iStep < NUM_STEPS; iStep++) {//使用无符号整型,因为负数会破坏循环展开 float t = iStep * stepSize; float3 currPos = rayStartPos - rayDir * t; if (currPos.x < -0.001f || currPos.x>1.001f || currPos.y < -0.001f || currPos.y>1.001f || currPos.z < -0.001f || currPos.z>1.001f) { break; } if (currPos.x < _SliceMin.x || currPos.x>_SliceMax.x || currPos.y < _SliceMin.y || currPos.y>_SliceMax.y || currPos.z < _SliceMin.z || currPos.z>_SliceMax.z) { continue; } fixed c = tex3D(_VolumeTex, currPos).r*_Intensity;//取样3dtexture //自定义色彩混合区域 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ fixed4 src = float4(c, c, c, c); src.a *=(1- _Density) * pow(src.a, _Contrast);//_Density值越大,颜色将越早地截止,因而也就不会采样到深层 src.rgb *= src.a; col = (1 - col.a)*src + col;//柔性叠加 //自定义色彩混合区域 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ if (col.a > 1) break; } clip(col.a - 0.5);//裁切掉边缘不需要的区域 return col * _TintColor; } ENDCG } } }
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算