场景辐照度图练习

场景辐照度原理来自下面的网址
漫反射辐照度
首先,选一张hdr的CubeMap。
在这里插入图片描述
目标就是为它生成一份辐射照度图。

一. 场景准备

在原点放一个cube,size为1。在原点同时放一个camera,它的Field of View设置为90,同时把原本天空盒的绘制关掉。

![在这里插入图片描述](https://img-blog.csdnimg.cn/20201122142950225.png#pic_center在这里插入图片描述

我们的计算就是通过camera照射这个立方体的六个面,生成六张辐照度的积分图。

二. 代码框架

然后新建一个monobehaviour。加入如下的代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MakeIrradiance : MonoBehaviour
{
    Camera camera;
    // 每一个面生成的贴图的大小
    int singleWidth = 512;

    // 六个面的rt.
    public RenderTexture[] rt = new RenderTexture[6];

    public enum Dir
    {
        left = 0,
        right,
        front,
        back,
        top,
        bottom,
    }

    // 六个面的朝向
    private Vector3[] forwards = new Vector3[6] 
    {
        new Vector3(-1, 0, 0),
        new Vector3(1, 0, 0),
        new Vector3(0, 0, 1),
        new Vector3(0, 0, -1),
        new Vector3(0, 1, 0),
        new Vector3(0, -1, 0)
    };

    // hdr的环境图
    public Cubemap cube;

    // 计算辐照度积分的shader
    public Shader irradiance;

    void Start ()
    {
        camera = GetComponent<Camera>();
        CaptureIrradiance();
    }
    void CaptureIrradiance()
    {
        camera.transform.position = Vector3.zero;
        camera.transform.up = Vector3.up;
        for (int i = 0; i < 6; ++i)
        {
            rt[i] = new RenderTexture(singleWidth, singleWidth, 0, RenderTextureFormat.ARGBFloat);
            camera.targetTexture = rt[i];
            camera.transform.forward = forwards[i];
            Shader.SetGlobalTexture("_CubeTex", cube);
            camera.RenderWithShader(irradiance, "");
            camera.targetTexture = null;
        }
    }
}

代码比较简单,在一开始就把镜头依次指向立方体的六个面。每个面用irradiance这个shader跑一遍,生成的结果放在rt的数组里面,每张rt大小为512*512。
最重要的计算主在shader中实现。

Shader "Unlit/MakeIrrad"
{
    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            Cull Off
            CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float3 worldPos :TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            samplerCUBE _CubeTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }


            fixed4 frag(v2f i) : SV_Target
            {
                // The world vector acts as the normal of a tangent surface
                // from the origin, aligned to WorldPos. Given this normal, calculate all
                // incoming radiance of the environment. The result of this radiance
                // is the radiance of light coming from -Normal direction, which is what
                // we use in the PBR shader to sample irradiance.

                float3 N = normalize(i.worldPos);

                float3 irradiance = float3(0, 0, 0);

                // tangent space calculation from origin point
                float3 up = float3(0.0f, 1.0f, 0.0f);
                float3 right = cross(up, N);
                up = cross(N, right);

                float3 sampV = float3(0, 0, 0);
                float sampleDelta = 0.025;
                float nrSamples = 0.0;

                float PI = 3.14159265359;
                for (float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
                {
                    for (float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
                    {
                        // spherical to cartesian (in tangent space)
                        float3 tangentSample = float3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
                        // tangent space to world
                        float3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;

                        irradiance += texCUBE(_CubeTex, sampleVec).rgb * cos(theta) * sin(theta);
                        nrSamples++;
                    }

                }
                irradiance = PI * irradiance * (1.0 / float(nrSamples)); // 这个PI的来历下面解释

                float4 FragColor = float4(irradiance, 1.0); 

                return FragColor;
            }
            ENDCG
        }
    }
}

这里的代码比较简单,最复杂的半球积分,是直接抄的LearnOpenGL上的代码。
这里需要解释一下最后一个PI是哪来的,根据LearnOpenGL的离散化公式,

在这里插入图片描述
最后求各号后面还跟了两个微分号,
在计算的时候,需要把这个化为积分上下限距离再相乘,就是

2 π ∗ π / 2 = π 2 2\pi * \pi/2 = \pi^2 2ππ/2=π2
再除以公式最前面的PI,就只剩下乘以一个PI了。
注意需要加上Cull Off关键字,不然从立方体内部看,面会被Cull掉。
此时我们把shader也设置给camera上的脚本。点击Play,可以在Camera的属性中看到生成的六面纹理
在这里插入图片描述
现在需要想办法把这六面保存到一个贴图中。
因此加入SaveIrradiance函数

 void SaveIrradiance()
    {
        Texture2D rgbTex;
        Texture2D rgbTexFinal;
        // 拼成一张大的cubemap
        rgbTexFinal = new Texture2D(singleWidth * 4, singleWidth * 3, TextureFormat.RGBAFloat, false);

        RenderTexture.active = rt[(int)Dir.left];
        rgbTex = new Texture2D(singleWidth, singleWidth, TextureFormat.RGBAFloat, false);
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        Color[] rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(0, singleWidth, singleWidth, singleWidth, rawRgbData);

        RenderTexture.active = rt[(int)Dir.front];
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(singleWidth * 1, singleWidth, singleWidth, singleWidth, rawRgbData);

        RenderTexture.active = rt[(int)Dir.right];
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(singleWidth * 2, singleWidth, singleWidth, singleWidth, rawRgbData);

        RenderTexture.active = rt[(int)Dir.back];
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(singleWidth * 3, singleWidth, singleWidth, singleWidth, rawRgbData);



        RenderTexture.active = rt[(int)Dir.top];
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(singleWidth, singleWidth * 2, singleWidth, singleWidth, rawRgbData);

        RenderTexture.active = rt[(int)Dir.bottom];
        rgbTex.ReadPixels(new Rect(0, 0, singleWidth, singleWidth), 0, 0);
        rgbTex.Apply();
        RenderTexture.active = null;
        rawRgbData = rgbTex.GetPixels();
        rgbTexFinal.SetPixels(singleWidth, 0, singleWidth, singleWidth, rawRgbData);

        rgbTexFinal.Apply();

        byte[] _bytes = rgbTexFinal.EncodeToPNG();
        System.IO.File.WriteAllBytes(Application.dataPath + "/myirr.png", _bytes);
    }

再次点击运行,此时会生成辐照度图
在这里插入图片描述
看起来像那么回事,但是比我们在Inspector中观察到的rt要暗。还需要做一次gammar校正。因此在fragment的最后一句加上LinearToGammaSpace

                //float4 FragColor = float4(irradiance, 1.0);
                float4 FragColor = float4(LinearToGammaSpace(irradiance), 1.0);

                return FragColor;

最终效果如下:

在这里插入图片描述

<think>嗯,用户想绘制光伏辐照度与功率关系的散点,用编程语言或工具。首先,我需要确定用户可能的编程背景。他们可能熟悉Python、R或者Excel之类的工具。不过考虑到通用性,Python应该是比较常见的选择,尤其是在数据可视化方面。 接下来,我需要考虑数据准备。用户可能需要指导如何收集或整理数据,比如数据应该包含辐照度和功率两列。可能需要提到数据格式,比如CSV或者Excel文件,然后如何导入这些数据到编程环境中。 然后是选择工具。Python的话,matplotlib和seaborn是常用的库。或许可以给出示例代码,展示如何用pandas读取数据,然后用matplotlib绘制散点。需要确保代码简洁,并且有注释,方便用户理解每一步的作用。 另外,用户可能对表的细节有要求,比如坐标轴标签、标题、颜色设置等。在示例代码中应该包含这些元素,同时提醒用户可以根据需要调整参数。 可能还需要提到数据分析的部分,比如添加趋势线或者计算相关系数,这样用户不仅能绘,还能分析两者之间的关系。例如使用numpy进行多项式拟合,或者用seaborn的regplot直接添加回归线。 还有,用户可能不太清楚如何安装必要的库,可能需要给出安装命令,比如pip install matplotlib pandas。不过如果用户已经熟悉Python环境,可能不需要这一步,但加上去会更全面。 另外,考虑用户可能需要不同工具的选择,比如Excel也是一个选项。虽然不如编程灵活,但适合快速简单的表。可以简要说明Excel的步骤,比如选择数据、插入散点、调整格式等。 最后,要确保回答结构清晰,分步骤说明,从数据准备到绘到优化,可能还要包括常见问题,比如如何处理数据异常值,或者如何导出像。同时,生成的相关问题要引导用户深入,比如分析影响因素或处理异常数据的方法。 需要检查LaTeX格式是否符合要求,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能用不到太多数学公式,除非在解释趋势线的时候提到回归方程,比如$y = ax + b$,这时候要用正确的格式。 还要注意引用标识,比如在提到相关系数时,如果需要引用某种方法,可以添加[^1],但根据用户提供的示例,可能不需要实际引用,所以可能不需要添加。但按照系统指令,引用仅供参考,所以可能不需要处理。 总结步骤:数据准备(导入、清洗)、Python绘代码示例)、Excel方法、表优化、数据分析(趋势线、相关系数)、相关问题。确保每个部分清晰,代码正确,解释到位。</think>以下是以Python为例的散点绘制方法,分步骤说明光伏辐照度与功率关系的可视化过程: ### 一、数据准备 1. 数据格式要求:需包含两列数值型数据,建议使用CSV格式存储 2. 示例数据结构: ```csv irradiance(W/m²),power(kW) 800,4.2 950,5.1 650,3.4 ... ``` ### 二、Python实现步骤 ```python # 安装必要库(如未安装) # pip install matplotlib pandas import pandas as pd import matplotlib.pyplot as plt # 1. 数据读取 data = pd.read_csv('solar_data.csv') # 2. 创建画布 plt.figure(figsize=(10,6), dpi=150) # 3. 绘制散点 plt.scatter( x=data['irradiance'], y=data['power'], c='#FF9933', # 光伏行业常用橙色 alpha=0.7, # 半透明显示重叠点 edgecolors='#CC5500'# 边界色增强辨识度 ) # 4. 添加趋势线(二阶多项式拟合) import numpy as np z = np.polyfit(data['irradiance'], data['power'], 2) p = np.poly1d(z) plt.plot(data['irradiance'], p(data['irradiance']), "r--") # 5. 表标注 plt.title('光伏辐照度-功率特性曲线', fontsize=14, pad=20) plt.xlabel('辐照度(W/m²)', fontsize=12) plt.ylabel('输出功率(kW)', fontsize=12) plt.grid(alpha=0.3) # 半透明网格线 # 6. 保存输出 plt.savefig('irradiance_power.png', bbox_inches='tight') plt.show() ``` ### 三、Excel快速实现方法 1. 选中两列数据(含表头) 2. 插入 > 表 > 散点 3. 右键数据点添加趋势线 4. 设置趋势线类型(推荐多项式2阶) 5. 调整坐标轴范围:右键坐标轴 > 设置坐标轴格式 6. 添加$R^2$值:勾选趋势线选项中的"显示R平方值" ### 四、表优化建议 1. **数据清洗**:剔除辐照度<200 W/m²的无效数据 2. **颜色映射**:使用渐变色表示不同温度条件 3. **动态交互**:使用Plotly库实现三维可视化(添加温度维度) 4. **分位标注**:用虚线标注P50、P90等典型工况点 ### 五、数据分析延伸 通过计算皮尔逊相关系数可量化两者关系: $$ r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (y_i - \bar{y})^2}} $$ 正常光伏系统相关系数应>0.85,若低于此值需检查阴影遮挡或组件故障[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值