Packing Lightmaps (整合光照贴图)

 

Pop Quiz: You have 765,618 lightmaps for a scene and very few of them have power of 2 dimensions, what do you do? If your answer was to rescale them and call CreateTexture 765,618 times please slap yourself. If your answer had anything to do with glTexImage2D, you might want to leave now. What you really want to do is smush them all into a couple larger textures, and this text will show you one way of doing it.

What we'll do is recursively divide the larger texture into empty and filled regions. We start off with an empty texture and after inserting one lightmap we get this:

Here we've split the texture in half by line A then split the upper half by B and inserted the first lightmap to the left of B. When we go to insert the next one, we'll first check if it can fit above A and if it can we check to see if it can fit left of B, nope full, then right of B. If it fits there we split B exactly how we split the original texture, otherwise we insert and split below A. After inserting a second lightmap the texture becomes:

Pretty basic. The implementation of the tree is straightforward. Here it is in a C++ pseudocode hybrid:

struct Node
{
    Node* child[2]
    Rectangle rc
    int imageID
}
    

Node* Node::Insert(const Image& img)
    if we're not a leaf then
        (try inserting into first child)
        newNode = child[0]->Insert( img )
        if newNode != NULL return newNode
        
        (no room, insert into second)
        return child[1]->Insert( img )
    else
        (if there's already a lightmap here, return)
        if imageID != NULL return NULL

        (if we're too small, return)
        if img doesn't fit in pnode->rect
            return NULL

        (if we're just right, accept)
        if img fits perfectly in pnode->rect
            return pnode
        
        (otherwise, gotta split this node and create some kids)
        pnode->child[0] = new Node
        pnode->child[1] = new Node
        
        (decide which way to split)
        dw = rc.width - img.width
        dh = rc.height - img.height
        
        if dw > dh then
            child[0]->rect = Rectangle(rc.left, rc.top, 
                                       rc.left+width-1, rc.bottom)
            child[1]->rect = Rectangle(rc.left+width, rc.top, 
                                       rc.right, rc.bottom)
        else
            child[0]->rect = Rectangle(rc.left, rc.top, 
                                       rc.right, rc.top+height-1)
            child[1]->rect = Rectangle(rc.left, rc.top+height, 
                                       rc.right, rc.bottom)
        
        (insert into first child we created)
        return Insert( img, pnode->child[0] )

The insert function traverses the tree looking for a place to insert the lightmap. It returns the pointer of the node the lightmap can go into or null to say it can't fit. Note that you really don't have to store the rectangle for each node, really all you need is a split direction and coordinate like in a kd-tree, but it's more convenient with them.

The code that calls Insert can then use the rectangle from the returned node to figure out where to place the lightmap in the texture and then update the node's imageID to use as a handle for future use.

int32 TextureCache::Insert(const Image& img)
    pnode = m_root->Insert(img)
    if pnode != NULL
        copy pixels over from img into pnode->rc part of texture
        pnode->imageID = new handle
    else
       return INVALID_HANDLE

Once the lightmap is in the larger texture, you'll want to go through any meshes that use it and adjust their texture coordinates based on the lightmap's rectangle in the larger texture.

And now your moment of zen:

Pretty eh? These are some lightmaps from the Near Death Experience tech demo. I've padded each lightmap with white so you can see the separations.Other examples without padding.

Keep in mind that you can apply this same technique not just to lightmaps, but to packing any textures you want into larger ones. For example, the algorithm works like a charm for building font textures.

Anyhow, if you have any questions or comments email jimscott@blackpawn.com. Thanks for reading.

### 如何在 Unity 中复制光照贴图 在 Unity 中,光照贴图是用于优化性能并提供高质量照明效果的重要工具。然而,Unity 并未直接提供简单的“复制”功能来处理光照贴图。为了实现这一目标,通常需要通过手动操作或脚本化方式完成。 #### 方法一:导出与导入光照贴图资源 一种方法是从项目中提取已有的光照贴图文件,并将其应用到另一个场景或其他项目中: 1. **定位光照贴图** 打开 `Window` -> `Rendering` -> `Lighting Settings` 面板,在这里可以看到当前项目的全局光照设置以及生成的光照贴图纹理[^3]。 2. **保存光照贴图** 寻找光照贴图的具体路径,一般位于 `Project` 文件夹下的 `LightingData.asset` 或者特定目录内。可以通过右键点击该资产并选择 “Show in Explorer/Finder”。 3. **迁移光照贴图** 将找到的光照贴图文件拷贝至新的项目位置;如果是在同一项目内部,则可以直接拖拽到所需的目标场景中。 4. **重新链接光照贴图** 在目标场景中打开相同的面板 (`Window` -> `Rendering` -> `Lighting Settings`) ,调整相应的选项使新场景能够识别并使用刚迁入的光照贴图数据。 #### 方法二:编写自定义脚本来批量复制 对于更复杂的多场景管理需求,建议开发一段 C# 脚本来自动化上述流程的一部分工作: ```csharp using UnityEngine; using UnityEditor; public class CopyLightmapUtility : MonoBehaviour { public static void CopyLightmapsToScene(Scene targetScene){ // 获取源场景中的所有静态网格渲染器及其关联的信息 var sourceRenderers = GameObject.FindObjectsOfType<Renderer>().Where(r => r.isPartOfStaticBatch); foreach (Renderer renderer in sourceRenderers){ Renderer correspondingTargetRenderer = FindCorrespondingRendererInOtherScene(renderer, targetScene); if(correspondingTargetRenderer != null){ int lightmapIndex = renderer.lightmapIndex; if(lightmapIndex >= 0 && lightmapIndex < LightmapSettings.lightmaps.Length){ correspondingTargetRenderer.lightmapScaleOffset = renderer.lightmapScaleOffset; correspondingTargetRenderer.lightmapIndex = lightmapIndex; correspondingTargetRenderer.sharedMaterial.SetTexture("_LightMap", LightmapSettings.lightmaps[lightmapIndex].lightmapColor); } } } AssetDatabase.SaveAssets(); EditorSceneManager.MarkAllScenesDirty(); } private static Renderer FindCorrespondingRendererInOtherScene(Renderer originalRenderer, Scene destinationScene){ string objectName = originalRenderer.gameObject.name; Transform foundTransform = destinationScene.GetRootGameObjects().SelectMany(go=>go.transform.Traverse()).FirstOrDefault(t => t.name == objectName)?.transform; return foundTransform?.GetComponent<Renderer>(); } } ``` 这段代码实现了遍历指定场景内的所有带有光照贴图信息的对象,并尝试寻找匹配项以便于将光照属性同步过去。需要注意的是此方案假设两个场景之间存在相似结构的游戏对象命名规则。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值