立体渲染 Volumetric Rendering

基础概念

在3D游戏引擎中,球体、立方体以及所有其它复杂的集合体都是由三角面片组成的。引擎只会渲染物体的表面,比如球体,半透明物体等.整个世界由各种空壳构成.

立体渲染(Volumetric Rendering)的基本概念:模拟光线在物体内部的传送,从而实现更震撼也更真实的视觉效果。
片段着色器最后返回的对象,是从特定角度看过去特定位置的颜色。
这种方式计算的颜色是完全随意的,因此返回的内容可以不必匹配几何体的真实渲染情况。
下图展示了一个3D立方体的例子。当片段着色器检测到立方体表面的颜色时,模拟光线传送,使得结果如同一个球体

vol2

立体射线投射 Volumetric Raycasting

用一个函数判断光线与自定义的几何体相交的问题,限制较大,只能模拟简单几何体比如球,圆柱等。

固定步长立体光线追踪 Volumetric Raymarching with Constant Step

不依赖相交函数的,基于迭代的,可以模拟任意几何体
一步一步的检测光线是否已经投射到红色球体

vol3

bool raymarchHit (float3 position, float3 direction)
{
        for (int i = 0; i < STEPS; i++)
        {
                if ( sphereHit(position) )
                        return true;

                position += direction * STEP_SIZE;
        }

        return false;
}

bool sphereHit (float3 p)
{
    return distance(p,_Centre) < _Radius;
}

距离辅助的光线追踪 Distance Aided Raymarching

固定步长的光线追踪非常低效,需要一种方法估算在遇到几何体之前需要走多远,
比如之前的sphereHit函数,不是返回bool值,而是距离球面的距离

float sphereDistance (float3 p)
{
    return distance(p,_Centre) - _Radius;
}

该函数就是一个有向距离函数(signed distance function),正数在几何体外,负数在几何体上,0在几何体表面
距离辅助的光线追踪实现代码:

fixed4 raymarch (float3 position, float3 direction)
{
        for (int i = 0; i < STEPS; i++)
        {
                float distance = sphereDistance(position);
                if (distance < MIN_DISTANCE)
                        return i / (float) STEPS;

                position += distance * direction;
        }
        return 0;
}

在一个比较复杂的场景运行的效果如下

STEPS 最大步数,需要根据图像形状调整
MIN_DISTANCE 不能是0,给一个比较合适的误差值0.01左右

SDF Signed Distance Fields(Functions) 有向距离场(函数) 组合

可以用组合的方式做出比较复杂的图形,例如那个很出名的蜗牛
简单来说 min返回并集,max返回交集
可以用类似Alpha混合的方式做多个形状的混合 as1 + (1-a)s2
还有很多种别的合并方式,如光滑合并

float sdf_smin(float a, float b, float k = 32)
{
    float res = exp(-k*a) + exp(-k*b);
    return -log(max(0.0001,res)) / k;
}

法线预估

Íñigo Quílez的方法是对周围其它点的距离场进行取样,来估算局部表面的曲率

float3 normal (float3 p)
{
    const float eps = 0.01; 
    return normalize
    ( float3 (
       map(p + float3(eps, 0, 0) ) - map(p - float3(eps, 0, 0)),
       map(p + float3(0, eps, 0) ) - map(p - float3(0, eps, 0)),
       map(p + float3(0, 0, eps) ) - map(p - float3(0, 0, eps))
    ) );
}

更多

ShaderToy中有很多效果很好的例子
MERCURY团队创建的hg_sdf库,有很多元物件与操作

一个简单的实例Unity Shader

效果如下,在Cube内绘制了一个球

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/VolumetricText"
{
 Properties
 {
  _BaseColor ("Base Color", Color) = (1,1,1,1)
        _SphereColor ("Sphere Color", Color) = (1,0,0,1)
        _SphereCentre("Sphere Centre",Vector) = (0,0,0)
        _ShpereRange ("Sphere Range", Range(0.1,2)) = 0.8
 }
 SubShader
 {
  Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" }
  LOD 100

  Pass
  {
   CGPROGRAM
            #include "Lighting.cginc"

   #pragma vertex vert
   #pragma fragment frag

   #include "UnityCG.cginc"

   struct appdata
   {
    float4 vertex : POSITION;    
   };

   struct v2f
   {
    float4 vertex : SV_POSITION;
                float3 wPos : TEXCOORD0; //世界坐标
   };

            float4 _BaseColor;
            float4 _SphereColor;
            float3 _SphereCentre;
            fixed _ShpereRange;

            //有向距离函数
            float SphereDistance(float3 p)
            {
                return distance(p, _SphereCentre) - _ShpereRange;
            }

            //光线追踪
            fixed Raymarch(float3 position, float3 direction)
            {
                float STEPS = 10;
                float MIN_DISTANCE = 0.01;

                for (int i = 0; i < STEPS; i++)
                {
                    float distance = SphereDistance(position);
                    if (distance < MIN_DISTANCE)
                        return i / (float)STEPS;

                    position += distance * direction;
                }
                return 0;
            }

            //法线模拟_简单球形测试
            float3 NormalEstimation_Sphere(float3 p)
            {
                return normalize(p - _SphereCentre);
            }

   v2f vert (appdata v)
   {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    return o;
   }

   fixed4 frag (v2f i) : SV_Target
   {
    fixed4 col = _BaseColor;
                float3 viewDirection = normalize(i.wPos - _WorldSpaceCameraPos);
                fixed rayHit = Raymarch(i.wPos, viewDirection);
                if (rayHit >= 0.01)
                    col = _SphereColor;

                //丢弃不在形状内的,测试,不在的改为白色
                clip(col.a - 0.01);

                fixed3 normal = NormalEstimation_Sphere(i.wPos);

                //简单处理下光照
                fixed3 lightDir = _WorldSpaceLightPos0.xyz; // Light direction
                fixed3 lightCol = _LightColor0.rgb; // Light color

                fixed NdotL = max(dot(normal, lightDir), 0);
                col.rgb = col * lightCol * NdotL;
    return col;
   }
   ENDCG
  }
 }
}

参考网页

Unity教程|立体渲染
Unity3D体积烟雾制作思路分享
梯度下降法
comprehensive guide to volume rendering

posted @ 2018-09-04 14:11  Hichy  阅读(925)  评论(0编辑  收藏  举报