10. Volumetric Shaders
50. Volumetric Rendering体渲
体积渲染技术,是可以应用在一个形状里再画一个shap。
从正方体网格表面发射射线,碰到球表面像素(假想)变色。只着色视角方向的像素。
- 创建一个射线:从摄像机出发到达片元的世界坐标.
- 为什么要从摄像机出发?因为屏幕就是从摄像机位置出发。
2.从表面向内部按照steps延长射线。直到碰到所画图形表面。
- 如果射线击中假想球表面,则该片元返回球的颜色,否则更改位置,进入下一步长。
Shader "NiksShaders/Shader67Unlit"
{
Properties
{
_Radius("Radius", Float) = 0.4
_Center("Center", Vector) = (0,0,0,0)
_SphereColor("Color", Color) = (0,0,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#define STEPS 100
#define STEP_SIZE 0.0175
float _Radius;
float3 _Center;
fixed4 _SphereColor;
struct v2f {
float4 position : SV_POSITION; // Clip space
float4 worldPos : TEXCOORD1; // World position
};
// Vertex function
v2f vert (appdata_base v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 raymarch(float3 posiiton,float3 direction)
{
//通过这个循环来找到要涂色的点
for (int i = 0;i < STEPS;i++)
{
if (length(posiiton - _Center) < _Radius)return _SphereColor;
posiiton += direction * STEP_SIZE;//没找到就加上步长继续找
}
return fixed4(1,1,1,-0.1);
}
// Fragment function
fixed4 frag (v2f i) : SV_Target
{
float3 viewDir = normalize(i.worldPos.xyz - _WorldSpaceCameraPos);
//viewDir = normalize(-UnityWorldSpaceViewDir(i.worldPos.xyz));
fixed4 col = raymarch(i.worldPos,viewDir);
clip(col);
return col;
}
ENDCG
}
}
FallBack off
}
50.1 STEPS 和STEP_SIZE
-
STEPS用来控制步数,也就是精细程度
-
STEP_SIZE是具体算出来刚好覆盖最长边
-
怎么算出来的呢?
- 因为立方体的最长边 1 2 + 1 2 + 1 2 = 3 ≈ 1.73 \sqrt{1^2 + 1^2 + 1^2} = \sqrt{3}\approx1.73 12+12+12=3≈1.73
- 然后我们控制 精细程度 STEPS = 100
- 就能得出STEP_SIZE = 0.0173
50.2 关于WorldSpaceViewDir和UnityWorldSpaceViewDir和透视除法
这里我修改了一下,因为默认的帮助参数_WorldSpaceCameraPos
返回的就是float3,所以用i.worldPos.xyz
。
-
WorldSpaceViewDir(float4 vertex)也就是原生顶点。所以这个函数多用于顶点着色器,如果片元着色器要用就麻烦了,因为我们只有在
v2f
中定义了v.vertex
后才有这个。 -
UnityWorldSpaceViewDir(float3 worldPos)也就是经过变换后的世界空间的坐标,是Unity改进后的函数,方便用于顶点和片元着色器,因为在顶点着色器中,我们通常啥原始数据都有,啥经过坐标变换的数据也有,所以顶点着色器对帮助函数兼容性很高。主要解决片元着色器的使用问题,一般在
v2f
中都会定义worldPos
这就方便了,直接传入就能使用。 -
两者均返回从物体顶点射向摄像机的向量。
-
这里我们使用的是从摄像机射向物体的,所以要取负。记不记得在49.2中我们使用reflect函数也取了负,去看一看吧?
-
这里还有一点,就是经过帮助函数调用之后,返回的射线,向量,位置,大部分都是Vector3,很少有4了(如果得出的是仍需要进行计算的量,很有可能就有4,比如ComputeScreenPos,ComputeGrabScreenPos,计算出来之后得到的是剪裁空间的纹理,需要用透视除法,才能转换到屏幕空间)
51. Using textures in the render
从正方体网格表面发射射线,
奇妙的0.5又出现了,除了在3.2中,这里floor算index也有.这是对于这个物体而言,中心实在(0.5,0.5,0.5)的,我们要将pos范围变到(0,1)方便uv计算。
利用贴图完成体积雾。
具体shader如下:
Shader "NiksShaders/Shader68Unlit"
{
Properties
{
_Radius("Radius", Float) = 0.4
_Center("Center", Vector) = (0,0,0,0)
_Tex("Tex", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Radius;
float3 _Center;
sampler2D _Tex;
struct v2f {
float4 position : SV_POSITION; // Clip space
float3 worldPos : TEXCOORD1; // World position
};
// Vertex function
v2f vert (appdata_base v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
float raymarch(float3 position,float3 direction)
{
float alpha = 0;
int steps = 144;
float step_size = 1.0 / steps;
for (int i = 0; i < steps; i++)
{
int index = floor((position.z - _Center.z + 0.5) * steps);//这里算出来的z是在(0,1)之间的,然后乘以steps,算的是按深度使用哪张贴图
float2 uv = saturate(position.xy - _Center.xy + 0.5);//按照xy来算出uv是多少在(0,1)范围内
float2 offset = float2(fmod(index,12),floor(float(index)/12.0));//余数:x上第几张,商:y上的第几张,再加上uv就得出是哪个offset上的uv
//所以offset用来定位具体的小图片,uv是该图片上的纹理定位,但是这时按照正常大小算的,我们现在的图片是缩小了的,所以每边都要除以12来缩小。
//为什么不是144呢?注意,这里是float2
float2 newuv = (uv + offset)/12;
float texel = tex2D(_Tex,newuv).r;//r通道是1,则不透明
alpha += texel * step_size;//累加从表面开始射入的射线到达每个深度的alpha,最后结果反应在表面
position += direction * step_size;
}
return alpha;
}
// Fragment function
fixed4 frag (v2f i) : SV_Target
{
float3 viewDirection = normalize(i.worldPos - _WorldSpaceCameraPos);
float alpha = raymarch(i.worldPos,viewDirection);
return fixed4(1,1,1,alpha);
}
ENDCG
}
}
FallBack off
}