zoukankan      html  css  js  c++  java
  • CapsuleAO的实现与学习

    1.简介

    这是一个通过在角色骨骼上绑定虚拟胶囊体拟合出胶囊近似AO环境光遮蔽的效果,

    当角色处于阴影中时,CapsuleAO的效果比较明显。当角色在露天环境中,效果较弱。

    下面是其他游戏里截图的效果,以做参考:

    不同项目有不同的实现,UE4中也有类似实现,叫做Capsule Shadow,这里不多做介绍:

    2.CapsuleAO实现尝试

    首先用自己的思路实现一下,首先参考了IQ大神的SphereAO:

    https://www.shadertoy.com/view/4djSDy

    抛开公式的话,其实就是一个点光源的做法,然后把颜色改成黑色加上函数系数进行调节,使其更接近AO的感觉。

    这是尝试实现的效果:

    实现时要将球体变为胶囊,要在胶囊的长度轴上计算投影。投影完后对投影长度进行Clamp约束,约束后的两个端点

    和周围像素进行Distance计算,这样直接就是胶囊的效果了,而不是圆柱。做了个简单的图:

    具体见代码。

    Shader:

    Shader "Unlit/CapsuleAOShader"
    {
        Properties
        {
            _Adjust("Adjust", float) = 2
            _CapsuleRadius("Capsule Radius", float) = 0.3
            
            _DistanceFix("Distance Fix", float) = 0.3
        }
    
        SubShader
        {
            Tags { "RenderType"="Opaque" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
    
                    float3 wPos : TEXCOORD2;
                };
    
                float4 _PlanePos;
                float4 _PlaneNormal;
                float4 _CapsuleP0;
                float4 _CapsuleP1;
                float _Adjust;
                float _CapsuleRadius;
                
                float _DistanceFix;
                
                float3 Formula(float3 sphP0, float3 sphP1, float length, float radius, float3 comparePoint)
                {
                    float3 norm1 = normalize(sphP1 - sphP0);
                    float3 relativeComparePoint = comparePoint-sphP0;
                    float3 projValue = dot(relativeComparePoint, norm1);
                    
                    float x = clamp(projValue, -length, length);
                    float3 projPoint = sphP0 + x * norm1;
                    float3 norm2 = normalize(comparePoint - projPoint);
                    
                    return projPoint + norm2 * radius;
                }
                
                float3 DistanceFix(float3 distancePoint, float wPos, float3 norm, float distanceFix)
                {
                    return distancePoint + norm * distanceFix;
                }
                
                float Occlusion(float3 pos, float3 nor, float3 sphP0, float3 sphP1)
                {
                    float3 finalPoint = Formula(sphP0, sphP1, distance(sphP1,sphP0), _CapsuleRadius, pos);/*Add*/
                    finalPoint = DistanceFix(finalPoint, pos, nor, _DistanceFix);/*Add*/
                    float3  di = finalPoint - pos;
                    float l = length(di);
                    float nl = dot(nor, di / l);
                    float h = l / 0.5;
                    float h2 = h * h;
                    float k2 = 1.0 - h2 * nl*nl;
    
                    float res = pow(clamp(0.5*(nl*h + 1.0) / h2, 0.0, 1.0), 1.5);
    
                    return res;
                }
    
                v2f vert (appdata v)
                {
                    v2f o = (v2f)0;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = v.uv;
    
                    o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    
                    return o;
                }
                
                fixed4 frag (v2f i) : SV_Target
                {
                    float occ0 = Occlusion(i.wPos, _PlaneNormal.xyz, _CapsuleP0.xyz, _CapsuleP1.xyz);
                    return 1.0 - occ0;
                }
                ENDCG
            }
        }
    }

    c#部分,控制胶囊的传入,与球体不同;胶囊需要一个长度向量信息表示长度轴的朝向:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CapsuleAOParamUpdate : MonoBehaviour
    {
        public Transform capsuleP0;
        public Transform capsuleP1;
        Material mMaterial;
    
    
        private void Update()
        {
            mMaterial = GetComponent<MeshRenderer>().sharedMaterial;
    
            mMaterial.SetVector("_PlanePos", transform.position);
            mMaterial.SetVector("_PlaneNormal", transform.up);
    
            mMaterial.SetVector("_CapsuleP0", new Vector4(capsuleP0.position.x, capsuleP0.position.y, capsuleP0.position.z, 1f));
            mMaterial.SetVector("_CapsuleP1", new Vector4(capsuleP1.position.x, capsuleP1.position.y, capsuleP1.position.z, 1f));
        }
    }

    3.Capsule Shadow实现尝试

    看了下UE里的实现代码,比较复杂。这部分自己处理比较简单。

    先看最终效果(支持非平面表面,光照方向改变,但某些光照角度有一定穿帮感):

    首先,以主光平行光的方向作为投影平面,在shader的frag里得到地面每个像素世界坐标时,

    将像素位置,胶囊位置,胶囊方向向量等都投影到平面上进行计算。

    大致如下图:

    胶囊体需要两个参数,以确定胶囊方向。根据投影后的胶囊方向和地面法线位置,得到叉乘位置,然后可以作为x,y坐标采样贴图。

    采样贴图虽然效果好些,但开销较高,所以也可以自己去拟合。

    接着混合3个权重信息:

    1. 对平行光的相反方向做一个点乘处理,防止反方向上也被映射上阴影(FadeDirectionWeight)。
    2. 对主要投影区域做一个权重系数,让只有被投影地面上有阴影图案(MaskWeight)。
    3. 将投影前的地面世界空间坐标和投影前的胶囊位置做一个权重,让阴影有一个深到浅的渐变效果(FadeWeight)。

    最后将所有权重混合:

    return shadowCol * maskWeight * fadeWeight * faceDirectionWeight;

    最后上代码。

    shader部分:

    Shader "Unlit/CapsuleShadowShader"
    {
        Properties
        {
            _MainShapeTex ("Main Shape Tex (RGB)", 2D) = "white" {}
            _UvOffset("Uv Offset", vector) = (0.0, 0.0, 0.0, 0.0)
            _ShadowScaleFactor("Shadow Scale Factor", float) = 1.0
            _FadeWeightFactor("Fade Weight Factor", float) = 10.0
            _FadeDirectionFactor("Fade Direction Factor", float) = 1.0
        }
    
        SubShader
        {
            Tags { "RenderType"="Opaque" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
    
                    float3 wPos : TEXCOORD2;
                };
    
                uniform float4 _LightDirectionVector;
                uniform float4 _CapsulePos1;
                uniform float4 _CapsulePos2;
                uniform float4 _CapsulePos3;
                uniform float4 _CapsulePos4;
                uniform float4 _CapsulePos5;
                uniform float4 _CapsulePos6;
                uniform float4 _CapsulePos7;
                uniform float4 _CapsulePos8;
                uniform float4 _CapsulePos9;
                uniform float4 _CapsulePos10;
                uniform float4 _CapsulePos11;
                uniform float4 _CapsulePos12;
                uniform float4 _CapsulePos13;
                uniform float4 _CapsulePos14;
    
    
                sampler2D _MainShapeTex;
                float4 _UvOffset;
                float _ShadowScaleFactor;
                float _FadeWeightFactor;
                
                float _FadeDirectionFactor;
                
                v2f vert (appdata v)
                {
                    v2f o = (v2f)0;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = v.uv;
    
                    o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    
                    return o;
                }
                
                half3 ProjectOnPlane(half3 vec, half3 planeNormal)
                {
                    float num = dot(planeNormal, planeNormal);
                    float num2 = dot(vec, planeNormal);
                    return half3(vec.x - planeNormal.x * num2 / num, vec.y - planeNormal.y * num2 / num, vec.z - planeNormal.z * num2 / num);
                }
                
                half CapsuleShadow(float3 capsulePos, float3 capsulePosDirectVec, float3 wPos)
                {
                    half faceDirectionWeight = max(_FadeDirectionFactor, dot(normalize(capsulePos - wPos), _WorldSpaceLightPos0.xyz));
    
                    half fadeDistance = distance(wPos, capsulePos);
                    half fadeWeight = (1.0/distance(wPos, capsulePos)) * _FadeWeightFactor;
    
                    half3 proj = ProjectOnPlane(wPos, _WorldSpaceLightPos0.xyz);
                    half3 centerProj = ProjectOnPlane(capsulePos, _WorldSpaceLightPos0.xyz);
                    half3 centerVectorProj = ProjectOnPlane(capsulePosDirectVec, _WorldSpaceLightPos0.xyz);
                    half3 dir1 = normalize(centerVectorProj - centerProj);
                    half3 dir2 = normalize(cross(dir1, _WorldSpaceLightPos0.xyz));
                    
                    half x = dot(proj - centerProj, dir1) * _UvOffset.z + _UvOffset.x;
                    half y = dot(proj - centerProj, dir2) * _UvOffset.w + _UvOffset.y;
                    half shadowCol = tex2D(_MainShapeTex, half2(x, y)).r;
                    
                    half maskWeight = saturate(distance(proj, centerProj) / _ShadowScaleFactor);
                    maskWeight = max(0.4, maskWeight);
                    
                    return shadowCol * maskWeight * fadeWeight * faceDirectionWeight;
                }
                
                fixed4 frag (v2f i) : SV_Target
                {
                    half col = CapsuleShadow(_CapsulePos1, _LightDirectionVector, i.wPos);
                    col = max(col, CapsuleShadow(_CapsulePos2, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos3, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos4, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos5, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos6, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos7, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos8, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos9, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos10, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos11, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos12, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos13, _LightDirectionVector, i.wPos));
                    col = max(col, CapsuleShadow(_CapsulePos14, _LightDirectionVector, i.wPos));
    
                    return lerp(0.5, 0.0, col);
                }
                ENDCG
            }
        }
    }

    c#部分:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CapsuleAOParamUpdate : MonoBehaviour
    {
        public Transform capsuleHead;
        public Transform capsuleBody1;
        public Transform capsuleBody2;
        public Transform capsuleBody3;
    
        public Transform capsuleLeftArm1;
        public Transform capsuleLeftArm2;
        public Transform capsuleLeftArm3;
        public Transform capsuleRightArm1;
        public Transform capsuleRightArm2;
        public Transform capsuleRightArm3;
        public Transform capsuleLeftLeg1;
        public Transform capsuleLeftLeg2;
        public Transform capsuleLeftLeg3;
        public Transform capsuleRightLeg1;
        public Transform capsuleRightLeg2;
        public Transform capsuleRightLeg3;
    
        public Transform lightDirectionVector;
    
        private Material mMaterial;
    
    
        private void Update()
        {
            mMaterial = GetComponent<MeshRenderer>().sharedMaterial;
    
            if (capsuleHead)
                mMaterial.SetVector("_CapsulePos1", capsuleHead.position);
            
            if (capsuleBody1)
                mMaterial.SetVector("_CapsulePos2", capsuleBody1.position);
            if (capsuleBody2)
                mMaterial.SetVector("_CapsulePos2", capsuleBody2.position);
            if (capsuleBody3)
                mMaterial.SetVector("_CapsulePos2", capsuleBody3.position);
            
            if (capsuleLeftArm1)
                mMaterial.SetVector("_CapsulePos3", capsuleLeftArm1.position);
            if (capsuleLeftArm2)
                mMaterial.SetVector("_CapsulePos4", capsuleLeftArm2.position);
            if (capsuleLeftArm3)
                mMaterial.SetVector("_CapsulePos5", capsuleLeftArm3.position);
            
            if (capsuleRightArm1)
                mMaterial.SetVector("_CapsulePos6", capsuleRightArm1.position);
            if (capsuleRightArm2)
                mMaterial.SetVector("_CapsulePos7", capsuleRightArm2.position);
            if (capsuleRightArm3)
                mMaterial.SetVector("_CapsulePos8", capsuleRightArm3.position);
            
            if (capsuleLeftLeg1)
                mMaterial.SetVector("_CapsulePos9", capsuleLeftLeg1.position);
            if (capsuleLeftLeg2)
                mMaterial.SetVector("_CapsulePos10", capsuleLeftLeg2.position);
            if (capsuleLeftLeg3)
                mMaterial.SetVector("_CapsulePos11", capsuleLeftLeg3.position);
            
            if (capsuleRightLeg1)
                mMaterial.SetVector("_CapsulePos12", capsuleRightLeg1.position);
            if (capsuleRightLeg2)
                mMaterial.SetVector("_CapsulePos13", capsuleRightLeg2.position);
            if (capsuleRightLeg3)
                mMaterial.SetVector("_CapsulePos14", capsuleRightLeg3.position);
    
            mMaterial.SetVector("_LightDirectionVector", lightDirectionVector.position);
        }
    }

    4.总结

    这篇文章以学习为主,就不提供下载工程了。具体使用还需自行开发。

    若该方案需要在项目中的落地,我做如下建议:

    1.不要一次性在shader里传入所有胶囊,可以分成多个pass来做,也可以放到屏幕blit里去做,或者先画到一张临时RT里。

    2.当角色暴露在强光下,基本看不出CapsuleAO效果,当角色在阴影中或处于柔和光照环境下,才会有明显的CapsuleAO表现。

    3.也可以只有主角有CapsuleAO效果。

  • 相关阅读:
    Asp.net(c#)导出有表格线的Excel
    精妙SQL语句收集
    SQL定时自动备份,并将备份文件加密压缩并自动下载的实现
    如何跨服务器复制表中数据
    两台Sql server的数据同步
    asp.net response.ContentType 下载文件的四种方法
    屏保显示页面控制
    开博白
    新年有感:如何能真正做好项目
    【转】OpenCV图像处理 图像的点运算 ( 灰度直方图 )
  • 原文地址:https://www.cnblogs.com/hont/p/14965382.html
Copyright © 2011-2022 走看看