学习OpenGL ES之教你制作迷雾

本系列所有文章目录

获取示例代码


我是忧伤的占位图

前言

本文将为大家介绍一种常见的3D技术-雾(fog)。雾可以让你看不清远处的物体,除了模拟真实环境中的雾效果以外,还可以用来遮挡修饰远处细节精度比较低的模型,让它们看起来没那么粗糙。本文将介绍雾的三种基本实现方式。代码主要集中在Shader中。我们开始吧。

原理

雾效果的制作并不需要额外的几何体或者是粒子发射器,当然如果可以配合一些粒子效果会更好。雾的实现主要依赖于修改已有模型的颜色,根据渲染的顶点与观察者的距离,将雾的颜色与当前颜色混合。下面是2次exp计算模型的效果图,可以先感受一下。地形和天空都是上一篇文章使用的。


线性计算模型

我们先来看雾的线性计算模型,在这个模型下,我们要指定雾的起点,终点。也就是说雾在距离你多少的地方开始,距离多少的地方结束。接着根据当前渲染的fragment在该区域内的位置计算雾的占比因子,距离越远,雾应该越浓。所以随着和观察者距离的增加,占比因子应该从0到1。下面是公式和曲线图。


上面的公式开始的距离值是2,结束的距离值是5,在0到2的区域,是没有雾的,因此雾的占比因子是都是0。超过5的区域,雾占比100%。设开始位置为fogStart,结束位置为fogEnd,当前位置距离观察者distance。可以得到雾的占比因子fogFactor线性计算公式。clamp函数将fogFactor约束在0到1的范围中。

fogFactor = 1 - (fogEnd - distance) / (fogEnd - fogStart)
fogFactor = clamp(fogFactor, 0, 1)

接下来我们可以就很轻松的在Shader中实现了。下面的方法利用上面的公式计算了雾的占比因子。

float linearFogFactor(float fogStart, float fogEnd) {
    vec4 worldVertexPosition = modelMatrix * vec4(fragPosition, 1.0);
    float distanceToEye = distance(eyePosition, worldVertexPosition.xyz);
    // linear
    float fogFactor = (fogEnd - distanceToEye) / (fogEnd - fogStart); // 1.0 ~ 0.0
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);  // 0.0 ~ 1.0
    return fogFactor;
}

计算完占比因子后,使用mix方法将原来的fragment颜色和雾的颜色按照因子混合。最后将混合后的颜色返回给gl_FragColor就大功告成了。

mix(inputColor, fog.fogColor, fogFactor);

exp计算模型

exp计算模型是利用常量e来参与公式计算的一个模型。公式和曲线如下。


公式中的0.5可以表示为雾的浓度,这个值越小,曲线上升的越慢,表现出来的现象就是雾变浓的速度越来越慢。设雾的浓度为fogDensity,当前位置距离观察者distance,得到的公式如下。

fogFactor = 1 - 1 / e^(distance * fogDensity)
fogFactor = clamp(fogFactor, 0, 1)

在Shader中实现如下。计算出来的和上面一样,依然是雾的占比因子。

float exponentialFogFactor(float fogDensity) {
    vec4 worldVertexPosition = modelMatrix * vec4(fragPosition, 1.0);
    float distanceToEye = distance(eyePosition, worldVertexPosition.xyz);
    float fogFactor = 1.0 / exp(distanceToEye * fogDensity);
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);  // 0.0 ~ 1.0
    return fogFactor;
}

2次exp计算模型

相比于exp计算模型,这个模型只是将公式增加了一个2次方。

fogFactor = 1 - 1 / e^((distance * fogDensity)^2)
fogFactor = clamp(fogFactor, 0, 1)

公式曲线如下。



Shader中实现如下。

float exponentialSquareFogFactor(float fogDensity) {
    vec4 worldVertexPosition = modelMatrix * vec4(fragPosition, 1.0);
    float distanceToEye = distance(eyePosition, worldVertexPosition.xyz);
    float fogFactor = 1.0 / exp(pow(distanceToEye * fogDensity, 2.0));
    fogFactor = 1.0 - clamp(fogFactor, 0.0, 1.0);  // 0.0 ~ 1.0
    return fogFactor;
}

最终的Shader

Shader中包含3种雾的计算方式,通过fogType决定使用哪种。fogType和其他雾的相关参数被配置在Fog结构体中。

struct Fog {
    int fogType; // 0: 线性,1: exp 2: 2次exp
    float fogStart;
    float fogEnd;
    float fogIndensity;
    vec3 fogColor;
};

colorWithFog统一处理雾的计算。

vec3 colorWithFog(vec3 inputColor) {
    float fogFactor = 0.0;
    if (fog.fogType == 0) {
        fogFactor = linearFogFactor(fog.fogStart, fog.fogEnd);
    } else if (fog.fogType == 1) {
        fogFactor = exponentialFogFactor(fog.fogIndensity);
    } else if (fog.fogType == 2) {
        fogFactor = exponentialSquareFogFactor(fog.fogIndensity);
    }
    return mix(inputColor, fog.fogColor, fogFactor);
}

传值给Fog

在OC代码中,定义了新的结构体Fog描述雾的信息。

typedef enum : NSUInteger {
    FogTypeLinear = 0,
    FogTypeExp = 1,
    FogTypeExpSquare  = 2,
} FogType;

typedef struct {
    FogType fogType;
    // for linear
    GLfloat fogStart;
    GLfloat fogEnd;
    // for exp & exp square
    GLfloat fogIndensity;
    GLKVector3 fogColor;
} Fog;

接着初始化Fog对象。

Fog fog;
fog.fogColor = GLKVector3Make(1, 1,1);
fog.fogStart = 0;
fog.fogEnd = 200;
fog.fogIndensity = 0.02;
fog.fogType = FogTypeExpSquare;
self.fog = fog;

最后传递给Shader,注意,所有被渲染物体使用的Shader都必须实现Fog。本文中只在frag_terrain.glsl和frag_skybox.glsl中实现了,因为只渲染了天空盒和地形。

- (void)bindFog:(GLContext *)context {
    [context setUniform1i:@"fog.fogType" value:self.fog.fogType];
    [context setUniform1f:@"fog.fogStart" value:self.fog.fogStart];
    [context setUniform1f:@"fog.fogEnd" value:self.fog.fogEnd];
    [context setUniform1f:@"fog.fogIndensity" value:self.fog.fogIndensity];
    [context setUniform3fv:@"fog.fogColor" value:self.fog.fogColor];
}

总结

雾除了这三种基础的计算模型外,还有其他改进型的方案。比如体积雾,Atmospheric Fog等等,但是基本原理都是一样的,只不过使用了更加仿真的公式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容