最近翻到了一篇2005年siggraph的文章:Ambient Occlusion Fields Janne Kontkanen
https://files-cdn.cnblogs.com/files/hont/kontkanen2005i3d_paper.zip
感觉大概思路是通过球体去拟合包裹范围内的模型,得到一个近似点计算AO。
原文方法比较复杂,用了2个Cubemap7个参数。
这里先实现一个简单的,以后有需求再研究,用到了之前CapsuleAO的代码:
https://www.cnblogs.com/hont/p/14965382.html
主要使用环境贴图包裹物体,拿到最近点位置计算AO,效果如下:
第一步使用全景图绘制一圈射线,然后检测最近距离。
第二步生成全景图,交给Shader部分处理。
代码如下,CSharp部分:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class AOFields : MonoBehaviour { [Range(0.01f, 0.1f)] public float xPrecise = 0.1f;//相对全景图x轴的射线精度 [Range(0.01f, 0.1f)] public float yPrecise = 0.1f;//相对全景图y轴的射线精度 public float radius = 1f; public float[] r;//烘焙的最近距离信息 private void OnDrawGizmos() { int k = 0; for (float x = 0; x < 1f; x += xPrecise) { for (float y = 0; y < 1f; y += yPrecise) { float rEnhance = k >= r.Length ? 1f : r[k]; Vector3 point3D = EqMapToDir(new Vector2(x, y)) * radius * rEnhance; Gizmos.DrawLine(point3D, point3D + point3D.normalized * 0.1f); k++; } } } private Vector3 EqMapToDir(Vector2 uv) { float v = 1f - uv.y; float theta = v * Mathf.PI; float u = uv.x; float phi = u * 2f * Mathf.PI; float x = Mathf.Sin(phi) * Mathf.Sin(theta) * -1; float y = Mathf.Cos(theta); float z = Mathf.Cos(phi) * Mathf.Sin(theta) * -1; return new Vector3(x, y, z); } [ContextMenu("Calc R")] private void CalcR()//烘焙距离 { r = new float[Mathf.CeilToInt(1f / yPrecise) * Mathf.CeilToInt(1f / xPrecise)]; int k = 0; for (float x = 0; x < 1f; x += xPrecise) { for (float y = 0; y < 1f; y += yPrecise) { Vector3 point3D = EqMapToDir(new Vector2(x, y)) * radius; if (Physics.Raycast(point3D, -point3D.normalized, out RaycastHit hit)) { r[k] = hit.point.magnitude / radius;//这里的计算其实不正确 } k++; } } } [ContextMenu("Bake Texture")] private void BakeTexture()//烘焙成全景图 { int width = Mathf.CeilToInt(1f / xPrecise); int height = Mathf.CeilToInt(1f / yPrecise); Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, true); int k = 0; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { float rEnhance = k >= r.Length ? 1f : r[k]; tex.SetPixel(x, y, new Color(rEnhance, rEnhance, rEnhance, 1f)); k++; } } tex.Apply(); #if UNITY_EDITOR System.IO.File.WriteAllBytes(System.IO.Directory.GetCurrentDirectory() + "/Assets/AOField.png", tex.EncodeToPNG()); UnityEditor.AssetDatabase.Refresh(); #endif } }AOFields.cs
传入测试平面位置数据:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class AOFieldsTestPlane : MonoBehaviour { public MeshRenderer meshRenderer; public Transform obstacleTransform; private void Update() { meshRenderer.sharedMaterial.SetVector("_ObstaclePos", obstacleTransform.position); } }AOFieldsTestPlane.cs
Shader部分:
Shader "Unlit/AOFields" { Properties { _MainTex ("Texture", 2D) = "white" {} _AOFieldTex("AO Field Tex", 2D) = "white"{} _AOFieldScale("AO Field Scale",float) = 1.0//缩放 _AOLimitValue("AO Limit Value", float) = 0.5//最大AO限制 } SubShader { Tags { "RenderType"="Opaque" } CULL OFF Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 wPos : TEXCOORD1; float3 wNormal : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _AOFieldTex; float _AOFieldScale; float _AOLimitValue; uniform float3 _ObstaclePos; float2 DirToEqMap(float3 a_coords) { float3 a_coords_n = normalize(a_coords); float lon = atan2(a_coords_n.z, a_coords_n.x); float lat = acos(a_coords_n.y); float2 sphereCoords = float2(lon, lat) * (1.0 / UNITY_PI); return float2( (sphereCoords.x * 0.5 + 0.5), 1 - sphereCoords.y); } //一个简单粗暴的AO实现 float Occlusion(float3 pos, float3 nor, float3 sph) { float3 di = pos - sph; float3 diNom = normalize(di); float2 uv = DirToEqMap(diNom); float intensity = tex2D(_AOFieldTex, uv).r; float3 cPoint = sph + diNom * intensity * _AOFieldScale;//得到球体内模型的具体距离点 return smoothstep(0.0, 1.0, distance(pos,cPoint) / _AOLimitValue); } v2f vert(appdata v) { v2f o = (v2f)0; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.wNormal = mul((float3x3)UNITY_MATRIX_M, v.normal); return o; } fixed4 frag(v2f i) : SV_Target { float occ0 = Occlusion(i.wPos, i.wNormal, _ObstaclePos); return float4(occ0,occ0,occ0, 1.0); } ENDCG } } }AOFields.shader