第八章 高级游戏材质表现:Unity引擎中的纹理渲染全解析

第八章 高级游戏材质表现:Unity引擎中的纹理渲染全解析

8.1 理解和应用基础纹理技术

纹理是游戏开发中为3D模型表面添加细节和真实感的重要元素。本节将深入探讨单张纹理的基本概念和应用方法,帮助开发者掌握纹理映射的核心技术。

8.1.1 纹理映射实践指南

纹理映射是将2D图像应用到3D模型表面的过程。这一技术能够在不增加几何复杂度的情况下,为模型添加细节、颜色和表面特性。

要在Unity中实现基础纹理映射,我们首先需要了解UV坐标系统。UV坐标是二维纹理空间上的坐标,用于确定3D模型上每个点对应纹理上的哪个像素。

以下是在Unity 2021.3.8f1c1 LTS中实现基础纹理映射的C#脚本示例:

csharp

using UnityEngine;

public class BasicTextureMapper : MonoBehaviour
{
    [Header("纹理设置")]
    public Texture2D mainTexture;
    public Vector2 tiling = Vector2.one;
    public Vector2 offset = Vector2.zero;
    
    [Header("实时调整")]
    public bool animateUVs = false;
    public float scrollSpeedU = 0.1f;
    public float scrollSpeedV = 0.0f;
    
    private Renderer objectRenderer;
    private MaterialPropertyBlock propertyBlock;
    
    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        propertyBlock = new MaterialPropertyBlock();
        
        if (mainTexture != null)
        {
            // 确保纹理具有正确的导入设置
            ConfigureTextureImportSettings();
            
            // 应用纹理到材质
            ApplyTexture();
        }
        else
        {
            Debug.LogWarning("未指定主纹理,请指定一个纹理。");
        }
    }
    
    void Update()
    {
        if (animateUVs)
        {
            // 计算UV偏移
            offset.x = (offset.x + scrollSpeedU * Time.deltaTime) % 1.0f;
            offset.y = (offset.y + scrollSpeedV * Time.deltaTime) % 1.0f;
            
            // 更新材质
            ApplyTexture();
        }
    }
    
    void ApplyTexture()
    {
        if (objectRenderer == null || mainTexture == null)
            return;
            
        // 使用MaterialPropertyBlock避免创建材质实例
        objectRenderer.GetPropertyBlock(propertyBlock);
        propertyBlock.SetTexture("_MainTex", mainTexture);
        propertyBlock.SetVector("_MainTex_ST", new Vector4(tiling.x, tiling.y, offset.x, offset.y));
        objectRenderer.SetPropertyBlock(propertyBlock);
    }
    
    void ConfigureTextureImportSettings()
    {
        // 注意:在编辑器模式下才能修改导入设置
        #if UNITY_EDITOR
        if (mainTexture != null)
        {
            string assetPath = UnityEditor.AssetDatabase.GetAssetPath(mainTexture);
            if (!string.IsNullOrEmpty(assetPath))
            {
                UnityEditor.TextureImporter importer = UnityEditor.AssetImporter.GetAtPath(assetPath) as UnityEditor.TextureImporter;
                if (importer != null)
                {
                    // 检查并配置最佳设置
                    bool needsReimport = false;
                    
                    // 对于大多数颜色纹理,使用压缩格式
                    if (importer.textureCompression != UnityEditor.TextureImporterCompression.Compressed)
                    {
                        importer.textureCompression = UnityEditor.TextureImporterCompression.Compressed;
                        needsReimport = true;
                    }
                    
                    // 启用Mipmap以提高性能和质量
                    if (!importer.mipmapEnabled)
                    {
                        importer.mipmapEnabled = true;
                        needsReimport = true;
                    }
                    
                    // 应用设置并重新导入
                    if (needsReimport)
                    {
                        Debug.Log($"优化纹理导入设置: {mainTexture.name}");
                        importer.SaveAndReimport();
                    }
                }
            }
        }
        #endif
    }
    
    // 提供一个便捷方法以在编辑器中创建测试平面
    public static GameObject CreateTexturedPlane(Texture2D texture, Vector3 position, Vector2 size)
    {
        GameObject plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
        plane.transform.position = position;
        plane.transform.localScale = new Vector3(size.x/10f, 1f, size.y/10f); // 平面默认大小为10x10
        
        Renderer renderer = plane.GetComponent<Renderer>();
        if (renderer != null && texture != null)
        {
            Material material = new Material(Shader.Find("Standard"));
            material.mainTexture = texture;
            renderer.material = material;
        }
        
        return plane;
    }
}

对应的基础纹理着色器示例:

glsl

Shader "Custom/BasicTexture"
{
    Properties
    {
        _MainTex ("纹理", 2D) = "white" {}
        _Color ("颜色", Color) = (1,1,1,1)
        _Glossiness ("光泽度", Range(0,1)) = 0.5
        _Metallic ("金属度", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // 使用物理光照模型
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;
        fixed4 _Color;
        half _Glossiness;
        half _Metallic;

        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 采样纹理并应用颜色调整
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

如果你需要完全自定义UV坐标的处理方式,可以使用以下更高级的着色器:

glsl

Shader "Custom/AdvancedTextureMapping"
{
    Properties
    {
        _MainTex ("纹理", 2D) = "white" {}
        _Color ("颜色", Color) = (1,1,1,1)
        _Tiling ("平铺", Vector) = (1,1,0,0)
        _Offset ("偏移", Vector) = (0,0,0,0)
        _RotationAngle ("旋转角度", Range(0, 360)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            float4 _Tiling;
            float4 _Offset;
            float _RotationAngle;

            // UV坐标变换:旋转
            float2 rotateUV(float2 uv, float angle)
            {
                // 转换角度为弧度
                float radian = angle * UNITY_PI / 180;
                
                // 将UV中心移到(0,0)
                float2 center = float2(0.5, 0.5);
                float2 uv_centered = uv - center;
                
                // 旋转矩阵
                float2x2 rotation = float2x2(
                    cos(radian), -sin(radian),
                    sin(radian), cos(radian)
                );
                
                // 应用旋转
                float2 rotated_uv = mul(rotation, uv_centered);
                
                // 移回原来的中心
                return rotated_uv + center;
            }

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                
                // 应用缩放和偏移
                float2 uv = v.uv * _Tiling.xy + _Offset.xy;
                
                // 应用旋转
                uv = rotateUV(uv, _RotationAngle);
                
                o.uv = uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 采样纹理
                fixed4 col = tex2D(_MainTex, i.uv);
                
                // 应用颜色调整
                col *= _Color;
                
                return col;
            }
            ENDCG
        }
    }
}

为了更好地展示这些技术,我们可以创建一个编辑器工具来可视化和操作UV坐标:

csharp

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;

public class UVVisualizer : EditorWindow
{
    private GameObject targetObject;
    private Mesh targetMesh;
    private Material previewMaterial;
    private bool showUVs = true;
    private bool showGrid = true;
    private Color uvLineColor = new Color(0, 1, 0, 0.7f);
    private float uvLineThickness = 1.0f;
    
    [MenuItem("工具/UV可视化工具")]
    public static void ShowWindow()
    {
        GetWindow<UVVisualizer>("UV可视化工具");
    }
    
    void OnEnable()
    {
        // 创建用于预览的材质
        previewMaterial = new Material(Shader.Find("Hidden/Internal-Colored"));
    }
    
    void OnGUI()
    {
        EditorGUILayout.LabelField("UV坐标可视化", EditorStyles.boldLabel);
        
        EditorGUI.BeginChangeCheck();
        targetObject = EditorGUILayout.ObjectField("目标对象", targetObject, typeof(GameObject), true) as GameObject;
        if (EditorGUI.EndChangeCheck())
        {
            if (targetObject != null)
            {
                MeshFilter meshFilter = targetObject.GetComponent<MeshFilter>();
                if (meshFilter != null)
                {
                    targetMesh = meshFilter.sharedMesh;
                }
                else
                {
                    targetMesh = null;
                    EditorGUILayout.HelpBox("所选对象没有MeshFilter组件", MessageType.Warning);
                }
            }
            else
            {
                targetMesh = null;
            }
        }
        
        showUVs = EditorGUILayout.Toggle("显示UV线", showUVs);
        showGrid = EditorGUILayout.Toggle("显示网格", showGrid);
        uvLineColor = EditorGUILayout.ColorField("UV线颜色", uvLineColor);
        uvLineThickness = EditorGUILayout.Slider("UV线粗细", uvLineThickness, 0.1f, 3.0f);
        
        if (targetMesh != null)
        {
            EditorGUILayout.LabelField("UV统计", EditorStyles.boldLabel);
            EditorGUILayout.LabelField("顶点数: " + targetMesh.vertexCount);
            EditorGUILayout.LabelField("UV集数: " + targetMesh.uv.Length / targetMesh.vertexCount);
            
            Rect previewRect = GUILayoutUtility.GetRect(200, 200);
            DrawUVPreview(previewRect);
        }
    }
    
    void DrawUVPreview(Rect rect)
    {
        if (targetMesh == null || Event.current.type != EventType.Repaint)
            return;
            
        // 准备绘制UV
        GUI.Box(rect, "");
        
        // 设置材质
        previewMaterial.SetPass(0);
        
        // 开始绘制
        GL.PushMatrix();
        GL.LoadPixelMatrix();
        
        // 绘制背景网格
        if (showGrid)
        {
            DrawGrid(rect, 10, Color.gray);
        }
        
        // 绘制UV线
        if (showUVs)
        {
            DrawUVs(rect);
        }
        
        GL.PopMatrix();
    }
    
    void DrawGrid(Rect rect, int gridSize, Color gridColor)
    {
        GL.Begin(GL.LINES);
        GL.Color(gridColor);
        
        // 绘制垂直线
        for (int i = 0; i <= gridSize; i++)
        {
            float x = rect.x + rect.width * i / gridSize;
            GL.Vertex3(x, rect.y, 0);
            GL.Vertex3(x, rect.y + rect.height, 0);
        }
        
        // 绘制水平线
        for (int i = 0; i <= gridSize; i++)
        {
            float y = rect.y + rect.height * i / gridSize;
            GL.Vertex3(rect.x, y, 0);
            GL.Vertex3(rect.x + rect.width, y, 0);
        }
        
        GL.End();
        
        // 特别标记UV空间的[0,0]到[1,1]区域
        GL.Begin(GL.LINES);
        GL.Color(Color.red);
        
        // 绘制UV单位正方形
        float x0 = rect.x;
        float y0 = rect.y + rect.height; // 注意:Y轴在GUI中是反的
        float x1 = rect.x + rect.width;
        float y1 = rect.y;
        
        // 绘制UV单位正方形的边界
        GL.Vertex3(x0, y0, 0); GL.Vertex3(x1, y0, 0);
        GL.Vertex3(x1, y0, 0); GL.Vertex3(x1, y1, 0);
        GL.Vertex3(x1, y1, 0); GL.Vertex3(x0, y1, 0);
        GL.Vertex3(x0, y1, 0); GL.Vertex3(x0, y0, 0);
        
        GL.End();
    }
    
    void DrawUVs(Rect rect)
    {
        if (targetMesh == null)
            return;
            
        Vector2[] uvs = targetMesh.uv;
        int[] triangles = targetMesh.triangles;
        
        if (uvs.Length == 0 || triangles.Length == 0)
            return;
            
        GL.Begin(GL.LINES);
        GL.Color(uvLineColor);
        
        for (int i = 0; i < triangles.Length; i += 3)
        {
            int idx1 = triangles[i];
            int idx2 = triangles[i + 1];
            int idx3 = triangles[i + 2];
            
            if (idx1 >= uvs.Length || idx2 >= uvs.Length || idx3 >= uvs.Length)
                continue;
                
            Vector2 uv1 = uvs[idx1];
            Vector2 uv2 = uvs[idx2];
            Vector2 uv3 = uvs[idx3];
            
            // 转换UV坐标到屏幕空间
            Vector2 screen1 = new Vector2(
                rect.x + uv1.x * rect.width,
                rect.y + (1 - uv1.y) * rect.height
            );
            
            Vector2 screen2 = new Vector2(
                rect.x + uv2.x * rect.width,
                rect.y + (1 - uv2.y) * rect.height
            );
            
            Vector2 screen3 = new Vector2(
                rect.x + uv3.x * rect.width,
                rect.y + (1 - uv3.y) * rect.height
            );
            
            // 绘制三角形的三条边
            GL.Vertex3(screen1.x, screen1.y, 0);
            GL.Vertex3(screen2.x, screen2.y, 0);
            
            GL.Vertex3(screen2.x, screen2.y, 0);
            GL.Vertex3(screen3.x, screen3.y, 0);
            
            GL.Vertex3(screen3.x, screen3.y, 0);
            GL.Vertex3(screen1.x, screen1.y, 0);
        }
        
        GL.End();
    }
}
#endif

8.1.2 纹理参数与属性解析

纹理有许多重要的属性和参数,这些参数会影响纹理的显示效果和性能。了解这些属性对于优化游戏性能和提高视觉质量至关重要。

以下是一个用于分析和控制纹理属性的工具脚本:

csharp

using UnityEngine;
using System.Collections.Generic;

public class TexturePropertyAnalyzer : MonoBehaviour
{
    [System.Serializable]
    public class TextureInfo
    {
        public Texture2D texture;
        public string name;
        public Vector2Int dimensions;
        public TextureFormat format;
        public bool hasMipMaps;
        public int memorySize; // 单位:KB
        public FilterMode filterMode;
        public TextureWrapMode wrapMode;
        public int anisoLevel;
    }
    
    [Header("纹理分析")]
    public Renderer targetRenderer;
    public List<TextureInfo> analyzedTextures = new List<TextureInfo>();
    
    [Header("纹理设置")]
    public FilterMode defaultFilterMode = FilterMode.Bilinear;
    public TextureWrapMode defaultWrapMode = TextureWrapMode.Repeat;
    public int defaultAnisoLevel = 1;
    
    void Start()
    {
        if (targetRenderer == null)
        {
            targetRenderer = GetComponent<Renderer>();
        }
        
        if (targetRenderer != null)
        {
            AnalyzeTextures();
        }
    }
    
    [ContextMenu("分析纹理")]
    public void AnalyzeTextures()
    {
        if (targetRenderer == null)
            return;
            
        analyzedTextures.Clear();
        
        Material[] materials = targetRenderer.sharedMaterials;
        foreach (Material material in materials)
        {
            if (material == null)
                continue;
                
            // 获取材质的所有属性
            Shader shader = material.shader;
            int propertyCount = ShaderUtil.GetPropertyCount(shader);
            
            for (int i = 0; i < propertyCount; i++)
            {
                ShaderUtil.ShaderPropertyType propertyType = ShaderUtil.GetPropertyType(shader, i);
                
                if (propertyType == ShaderUtil.ShaderPropertyType.TexEnv)
                {
                    string propertyName = ShaderUtil.GetPropertyName(shader, i);
                    Texture texture = material.GetTexture(propertyName);
                    
                    if (texture != null && texture is Texture2D)
                    {
                        Texture2D tex2D = texture as Texture2D;
                        
                        TextureInfo info = new TextureInfo
                        {
                            texture = tex2D,
                            name = propertyName,
                            dimensions = new Vector2Int(tex2D.width, tex2D.height),
                            format = tex2D.format,
                            hasMipMaps = tex2D.mipmapCount > 1,
                            memorySize = Mathf.RoundToInt(GetEstimatedMemorySize(tex2D) / 1024f),
                            filterMode = tex2D.filterMode,
                            wrapMode = tex2D.wrapMode,
                            anisoLevel = tex2D.anisoLevel
                        };
                        
                        analyzedTextures.Add(info);
                    }
                }
            }
        }
        
        Debug.Log($"分析完成,找到 {analyzedTextures.Count} 个纹理");
    }
    
    // 估算纹理内存占用(粗略计算)
    private int GetEstimatedMemorySize(Texture2D texture)
    {
        int bytesPerPixel = GetBytesPerPixel(texture.format);
        int mipMapReduction = texture.mipmapCount > 1 ? 3 : 4; // 有MipMap时内存约为原始大小的1/3
        
        return (texture.width * texture.height * bytesPerPixel) / mipMapReduction;
    }
    
    // 获取每种纹理格式的每像素字节数
    private int GetBytesPerPixel(TextureFormat format)
    {
        switch (format)
        {
            case TextureFormat.Alpha8:
                return 1;
            case TextureFormat.RGB24:
                return 3;
            case TextureFormat.RGBA32:
                return 4;
            case TextureFormat.ARGB32:
                return 4;
            case TextureFormat.RGB565:
                return 2;
            case TextureFormat.DXT1:
                return 1; // 压缩格式,近似值
            case TextureFormat.DXT5:
                return 1; // 压缩格式,近似值
            default:
                return 4; // 默认假设4字节
        }
    }
    
    // 应用纹理设置
    public void ApplyTextureSettings(int textureIndex, FilterMode filterMode, TextureWrapMode wrapMode, int anisoLevel)
    {
        if (textureIndex < 0 || textureIndex >= analyzedTextures.Count)
            return;
            
        TextureInfo info = analyzedTextures[textureIndex];
        Texture2D texture = info.texture;
        
        if (texture != null)
        {
            texture.filterMode = filterMode;
            texture.wrapMode = wrapMode;
            texture.anisoLevel = anisoLevel;
            
            // 更新信息
            info.filterMode = filterMode;
            info.wrapMode = wrapMode;
            info.anisoLevel = anisoLevel;
            
            analyzedTextures[textureIndex] = info;
        }
    }
    
    // 应用默认设置到所有纹理
    [ContextMenu("应用默认设置到所有纹理")]
    public void ApplyDefaultSettingsToAll()
    {
        for (int i = 0; i < analyzedTextures.Count; i++)
        {
            ApplyTextureSettings(i, defaultFilterMode, defaultWrapMode, defaultAnisoLevel);
        }
        
        Debug.Log("已应用默认设置到所有纹理");
    }
}

// 添加编辑器界面以便更好地可视化和控制
#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(TexturePropertyAnalyzer))]
public class TexturePropertyAnalyzerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        TexturePropertyAnalyzer analyzer = (TexturePropertyAnalyzer)target;
        
        DrawDefaultInspector();
        
        EditorGUILayout.Space();
        
        if (GUILayout.Button("分析纹理"))
        {
            analyzer.AnalyzeTextures();
        }
        
        EditorGUILayout.Space();
        
        // 显示分析结果
        if (analyzer.analyzedTextures.Count > 0)
        {
            EditorGUILayout.LabelField("纹理分析结果", EditorStyles.boldLabel);
            
            for (int i = 0; i < analyzer.analyzedTextures.Count; i++)
            {
                var info = analyzer.analyzedTextures[i];
                
                EditorGUILayout.BeginVertical(EditorStyles.helpBox);
                
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField(info.name, EditorStyles.boldLabel);
                
                if (GUILayout.Button("应用默认设置", GUILayout.Width(120)))
                {
                    analyzer.ApplyTextureSettings(i, analyzer.defaultFilterMode, analyzer.defaultWrapMode, analyzer.defaultAnisoLevel);
                }
                EditorGUILayout.EndHorizontal();
                
                EditorGUI.BeginDisabledGroup(true);
                EditorGUILayout.ObjectField("纹理", info.texture, typeof(Texture2D), false);
                EditorGUI.EndDisabledGroup();
                
                EditorGUILayout.LabelField($"尺寸: {info.dimensions.x} x {info.dimensions.y}");
                EditorGUILayout.LabelField($"格式: {info.format}");
                EditorGUILayout.LabelField($"MipMap: {(info.hasMipMaps ? "是" : "否")}");
                EditorGUILayout.LabelField($"内存占用: ~{info.memorySize} KB");
                
                EditorGUI.BeginChangeCheck();
                
                FilterMode filterMode = (FilterMode)EditorGUILayout.EnumPopup("过滤模式", info.filterMode);
                TextureWrapMode wrapMode = (TextureWrapMode)EditorGUILayout.EnumPopup("环绕模式", info.wrapMode);
                int anisoLevel = EditorGUILayout.IntSlider("各向异性过滤", info.anisoLevel, 0, 16);
                
                if (EditorGUI.EndChangeCheck())
                {
                    analyzer.A
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值