You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为3D场景创建带地平线延伸纹理的无限平面背景?

嘿,我之前做户外场景的时候刚好折腾过这个需求,给你分享几个能实现「无限地平线平面+天空盒衔接」的实用方案:

方案1:纹理渐变+透明过渡实现无限平面

这个方案适合你已经有现成地平线纹理的情况,核心是让平面在远处逐渐透明,自然过渡到后方的天空盒:

  • 第一步:准备适配的纹理
    用图像处理工具(比如PS)给你的地面纹理做渐变处理:底部保留完整的地面细节,往上逐渐过渡到和天空盒地平线一致的颜色,同时给纹理添加alpha通道,让上半部分透明度逐渐变为0。这样平面远处会慢慢透出天空盒。
  • 第二步:创建平面并配置Shader
    不要做真的“无限大”平面(会拖垮性能),创建一个足够覆盖相机视野的大平面就行。然后给它写个自定义Shader,核心逻辑是根据距离相机的远近调整透明度
    // Unity Shader 简化示例
    Shader "Custom/HorizonPlane" {
        Properties {
            _MainTex ("Ground Texture", 2D) = "white" {}
            _FadeDistance ("Fade Start Distance", Float) = 500
            _MaxFadeDistance ("Full Fade Distance", Float) = 1000
        }
        SubShader {
            Tags {"Queue"="Transparent" "RenderType"="Transparent"}
            Blend SrcAlpha OneMinusSrcAlpha
            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 worldPos : TEXCOORD1;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                float _FadeDistance;
                float _MaxFadeDistance;
    
                v2f vert (appdata v) {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target {
                    // 计算相机到平面顶点的世界空间距离
                    float dist = length(_WorldSpaceCameraPos - i.worldPos);
                    // 计算透明度衰减:距离超过_FadeDistance后开始透明,到_MaxFadeDistance完全透明
                    float alpha = 1 - saturate((dist - _FadeDistance) / (_MaxFadeDistance - _FadeDistance));
                    // 采样纹理
                    fixed4 col = tex2D(_MainTex, i.uv);
                    col.a = alpha;
                    return col;
                }
                ENDCG
            }
        }
    }
    
  • 第三步:对齐天空盒与平面
    调整天空盒的地平线高度,让它和平面纹理的地平线位置视觉上对齐,这样过渡会更自然,不会出现明显的断层。
方案2:程序化混合天空盒与地面(无现成纹理也能用)

如果没有合适的地平线纹理,可以用Shader直接在平面上程序化生成地面效果,并在远处和天空盒无缝融合:

  • 核心思路是根据距离相机的远近,在地面纹理/颜色和天空盒颜色之间做插值:
    // Unity Shader 简化示例
    Shader "Custom/ProceduralHorizonPlane" {
        Properties {
            _GroundColor ("Ground Color", Color) = (0.3, 0.25, 0.15, 1)
            _GroundTex ("Ground Texture", 2D) = "white" {}
            _TexScale ("Texture Scale", Float) = 10
            _BlendStart ("Blend Start Distance", Float) = 400
            _BlendEnd ("Blend End Distance", Float) = 800
        }
        SubShader {
            Tags {"Queue"="Geometry" "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 worldPos : TEXCOORD1;
                    float3 worldDir : TEXCOORD2;
                };
    
                sampler2D _GroundTex;
                float4 _GroundTex_ST;
                float4 _GroundColor;
                float _TexScale;
                float _BlendStart;
                float _BlendEnd;
    
                v2f vert (appdata v) {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _GroundTex) * _TexScale;
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                    o.worldDir = normalize(o.worldPos - _WorldSpaceCameraPos);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target {
                    float dist = length(_WorldSpaceCameraPos - i.worldPos);
                    // 采样地面纹理并混合底色
                    fixed4 groundCol = tex2D(_GroundTex, i.uv) * _GroundColor;
                    // 采样天空盒颜色
                    fixed4 skyCol = UNITY_SAMPLE_TEXCUBE(unity_Skybox, i.worldDir);
                    // 计算混合系数
                    float blend = saturate((dist - _BlendStart) / (_BlendEnd - _BlendStart));
                    // 混合地面与天空颜色
                    return lerp(groundCol, skyCol, blend);
                }
                ENDCG
            }
        }
    }
    
  • 这种方式不需要额外处理纹理,平面在远处会直接过渡到天空盒的颜色,视觉上完全是无限延伸的效果。
关键注意事项
  • 不要创建过大的平面,只要能覆盖相机的最大视野范围就够了,避免不必要的性能开销。
  • 调整渲染队列:如果用透明过渡的方案,把平面的渲染队列设为Transparent,确保天空盒在平面下方渲染;如果是程序化混合的不透明方案,设为Geometry即可。
  • 不同引擎的实现逻辑一致:比如Unreal Engine里,你可以在材质蓝图里用Distance to Camera节点来控制透明度或颜色混合,配合SkySphere使用。

内容的提问来源于stack exchange,提问作者Daarwin

火山引擎 最新活动