Let's talk about the 2D implementation of the water surface effect (1) and the 2d implementation of the water surface.
0. Introduction
I have always wanted to write my own understanding about the 2D implementation of water surface effects. Unfortunately, I have been dragging on for various reasons. Fortunately, some things have come to an end recently, and I have some leisure time, so I got this article :)
1. Overview
There are a lot of methods to achieve the effects of water surface, and many games are currently very well presented, among which I am most impressed by the number of Crysis ~
I have been in touch with CryEngine for a period of time due to work reasons, and have a little understanding of Crysis's surface rendering. Of course, the details are very complicated, but as far as the basic principle is concerned, the whole water surface is subdivided into triangles of the appropriate granularity, and then the movement of the water surface is simulated by dynamically changing the vertex position of each triangle surface:
Crysis examples may be slightly "tall" on some, but in fact we are in contact with a lot of cocos2d-x engine, there is a ready-made use of this principle to achieve the surface effect-Waves3D, if you are interested, take a closer look ~ However, it is precisely because Waves3D uses this 3D method to achieve, so it works with a lot of other "2D" elements in the cocos2d-x (such as Sprite), it is somewhat not smooth feeling, in addition, due to the granularity of Triangular Face segmentation, sometimes the effect may be slightly rough, and Waves3D uses CPU computing to achieve the effect. The CPU burden is very heavy when the splitting granularity is meticulous, A waste of GPU suitable for this job ~ (Waves3D actually has another 2D version: Waves, but unfortunately it cannot solve the last two problems mentioned above ~)
Is there any other way to achieve the water surface effect and overcome the problems mentioned above? In fact, the answer is very simple. Many friends may have thought of it, that is, using Shader :)
2. Method
Using Shader to achieve 2D surface effect, there are a lot of information on the Internet, here I am just a simple repeat according to their own understanding, the sample code based on cocos2d-x-3.3rc0 (because the principle Code uses GLSL, so the engine platform is not really important, the code change form should also be used in Unity), the source code content is actually a simple HelloWorld, the only thing worth mentioning is the waterdesktsprite type, complete list here:
//WaterEffectSprite.h#ifndef __WATER_EFFECT_SPRITE_H__#define __WATER_EFFECT_SPRITE_H__#include "cocos2d.h"USING_NS_CC;class WaterEffectSprite : public Sprite {public:static WaterEffectSprite* create(const char *pszFileName);public:bool initWithTexture(Texture2D* texture, const Rect& rect);void initGLProgram();};#endif
//WaterEffectSprite.cpp#include "WaterEffectSprite.h"WaterEffectSprite* WaterEffectSprite::create(const char *pszFileName) {auto pRet = new (std::nothrow) WaterEffectSprite();if (pRet && pRet->initWithFile(pszFileName)) {pRet->autorelease();}else {CC_SAFE_DELETE(pRet);}return pRet;}bool WaterEffectSprite::initWithTexture(Texture2D* texture, const Rect& rect) {if (Sprite::initWithTexture(texture, rect)) {#if CC_ENABLE_CACHE_TEXTURE_DATAauto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event) {setGLProgram(nullptr);initGLProgram();});_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);#endifinitGLProgram();return true;}return false;}void WaterEffectSprite::initGLProgram() {auto fragSource = (GLchar*)String::createWithContentsOfFile(FileUtils::getInstance()->fullPathForFilename("Shaders/WaterEffect.fsh").c_str())->getCString();auto program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource);auto glProgramState = GLProgramState::getOrCreateWithGLProgram(program);setGLProgramState(glProgramState);}
The content of WaterEffectSprite is actually very simple. It only inherits the Sprite type and then changes its fragment shader to use WaterEffect. fsh, while WaterEffect. fsh is the place where we need to truly implement the effect logic ~
OK. After the preparation is complete, we can repeat our sleeves and start to get started :)
# "Rotate" pixels
The first method is similar to "Rotating" pixels. You can refer to the explanations here. In addition, there is also an HLSL implementation, which is written using GLSL, which is probably like this:
varying vec4 v_fragmentColor; varying vec2 v_texCoord;void main() { float timeFactor = 1;float texFactor = 10;float ampFactor = 0.01f; // just like rotate pixel according to texture coordinate v_texCoord.x += sin(CC_Time.y * timeFactor + v_texCoord.x * texFactor) * ampFactor; v_texCoord.y += cos(CC_Time.y * timeFactor + v_texCoord.y * texFactor) * ampFactor; gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor;}
TimeFactor can control the speed of water wave movement, texFactor can control the "granularity" of water wave movement, and ampFactor can control the amplitude of water wave movement:
Of course, because we independently control the texture coordinate offset in the UV direction, the relevant parameters can naturally be different, just like this:
varying vec4 v_fragmentColor; varying vec2 v_texCoord;void main() { float timeFactorU = 1;float texFactorU = 10;float ampFactorU = 0.01f; float timeFactorV = 1;float texFactorV = 10;float ampFactorV = 0.01f; v_texCoord.x += sin(CC_Time.y * timeFactorU + v_texCoord.x * texFactorU) * ampFactorU; v_texCoord.y += cos(CC_Time.y * timeFactorV + v_texCoord.y * texFactorV) * ampFactorV; gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor;}
If you change these parameters to uniform, the scalability will be enhanced :)
# "Offset" pixels
The second method is similar to 3D surface rendering. First, we calculate the "height" of any point on the surface, and then map it directly to the offset of the corresponding texture coordinate. The method is very simple, offset is directly proportional to the "height" value (I am not very sure here, but it seems that this ing method is a simple application of parallax mapping, if you are familiar with it, you can tell me about it ~) (There are also related introductions here)
The shader code is like this:
varying vec4 v_fragmentColor; varying vec2 v_texCoord;// get wave height based on distance-to-centerfloat waveHeight(vec2 p) { float timeFactor = 4.0;float texFactor = 12.0;float ampFactor = 0.01; float dist = length(p); return cos(CC_Time.y * timeFactor + dist * texFactor) * ampFactor;}void main() { // convert to [-1, 1] vec2 p = -1.0 + 2.0 * v_texCoord; vec2 normal = normalize(p);// offset texcoord along dist direction v_texCoord += normal * waveHeight(p); gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor;}
Here, the meanings of timeFactor, texFactor, and ampFactor are the same as those of the first method (in fact, we can better understand the meanings of parameters in the sine curve function y = Asin (ω x + PHI :)), for example:
Like the first method, we can also perform some slight extensions based on the above Code, such as changing the center position of Water Waves:
varying vec4 v_fragmentColor; varying vec2 v_texCoord;// get wave height based on distance-to-centerfloat waveHeight(vec2 p) { float timeFactor = 4.0;float texFactor = 12.0;float ampFactor = 0.01; float dist = length(p); return cos(CC_Time.y * timeFactor + dist * texFactor) * ampFactor;}void main() { vec2 center = vec2(0, 0); vec2 p = (v_texCoord - center) * 2.0; vec2 normal = normalize(p);// offset texcoord along dist direction v_texCoord += normal * waveHeight(p); gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor;}
Another example:
Some of the complexities are the introduction of simple illumination:
The basic idea is to calculate the normal value of the point based on the "height" change of any point on the water surface, and then calculate the normal light usage (the sample code may be incorrect, for reference only ~)
varying vec4 v_fragmentColor; varying vec2 v_texCoord;// get wave height based on distance-to-centerfloat waveHeight(vec2 p) { float timeFactor = 4.0;float texFactor = 12.0;float ampFactor = 0.01; float dist = length(p); return cos(CC_Time.y * timeFactor + dist * texFactor) * ampFactor;}// get point fake normalvec3 waveNormal(vec2 p) { vec2 resolution = vec2(480, 320);float scale = 240; float waveHeightRight = waveHeight(p + vec2(2.0 / resolution.x, 0)) * scale;float waveHeightLeft = waveHeight(p - vec2(2.0 / resolution.x, 0)) * scale;float waveHeightTop = waveHeight(p + vec2(0, 2.0 / resolution.y)) * scale;float waveHeightBottom = waveHeight(p - vec2(0, 2.0 / resolution.y)) * scale; vec3 t = vec3(1, 0, waveHeightRight - waveHeightLeft);vec3 b = vec3(0, 1, waveHeightTop - waveHeightBottom);vec3 n = cross(t, b);return normalize(n);}void main() { vec2 p = -1.0 + 2.0 * v_texCoord; vec2 normal = normalize(p); v_texCoord += normal * waveHeight(p); vec4 lightColor = vec4(1, 1, 1, 1);vec3 lightDir = vec3(1, 1, 1);gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor * lightColor * max(0, dot(lightDir, waveNormal(p)));gl_FragColor.a = 1;}
Only a parallel light is introduced here, and the effect is limited, but it is also given to Zhang :)
# Convex/concave ing
The third method may be familiar to everyone. It is a common convex/concave ing in 3D rendering. The normal map may be the most common convex/concave ing technology, we can also map the normal map to a common Sprite to simulate the water surface ~
Of course, the previously listed WaterEffectSprite classes need some simple modifications, which is to overwrite the initGLProgram method:
void WaterEffectSprite::initGLProgram() {auto fragSource = (GLchar*)String::createWithContentsOfFile(FileUtils::getInstance()->fullPathForFilename("Shaders/WaterEffect.fsh").c_str())->getCString();auto program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource);auto glProgramState = GLProgramState::getOrCreateWithGLProgram(program);setGLProgramState(glProgramState);auto normalMapTextrue = TextureCache::getInstance()->addImage("Textures/water_normal.jpg");Texture2D::TexParams texParams = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT };normalMapTextrue->setTexParameters(texParams);getGLProgramState()->setUniformTexture("u_normalMap", normalMapTextrue);}
We also need to prepare a Normal water surface texture, which is probably used as follows:
The GLSL code roughly implements the refraction effect of the water surface and simple normal UV animation (the Code may be incorrect, for reference only ~)
varying vec4 v_fragmentColor;varying vec2 v_texCoord;uniform sampler2D u_normalMap;vec3 waveNormal(vec2 p) { vec3 normal = texture2D(u_normalMap, p).xyz;normal = -1.0 + normal * 2.0;return normalize(normal);}void main() { float timeFactor = 0.2;float offsetFactor = 0.5;float refractionFactor = 0.7;// simple UV animationvec3 normal = waveNormal(v_texCoord + vec2(CC_Time.y * timeFactor, 0));// simple calculate refraction UV offsetvec2 p = -1 + 2 * v_texCoord;vec3 eyePos = vec3(0, 0, 100);vec3 inVec = normalize(vec3(p, 0) - eyePos); vec3 refractVec = refract(inVec, normal, refractionFactor);v_texCoord += refractVec.xy * offsetFactor;gl_FragColor = texture2D(CC_Texture0, v_texCoord) * v_fragmentColor;}
For example:
Of course, we can continue to introduce light (such as highlight) and other elements to enhance the display of the water surface, but the 3D taste will become more and more intense. If you are interested, please try it in depth :)
# Others
The other 2D Water Surface implementation methods I have seen are mostly variants of the above method. If you still know other methods, please leave it blank ~
3. Postscript
Okay, I 've talked about a lot of things and should have stopped. This time I 've talked about how to implement the 2D Water Surface effects that I classify as Water Effect, in addition, I think it is important to have a 2D Water Surface Effect called Ripple Effect. Let's talk about it next time ~
Chat casually
Sleeping in your car, how can she go home to sleep? If you are so sleepy, you should be comfortable sleeping in your own soft bed!
There are many types of interests, and there are many ways to like them. Which one is what you want and which one is suitable for you?
Will the subtle people in the two relationships be able to develop? After the two relationships continue, will that feeling disappear or become more intense?
How do you feel about her?
Love is always so careful that you are afraid of injury, so you can use ordinary friends to protect your fragile heart!
Chat casually
Good
{@}
{@}*{@}
{@}*{@}*{@}
{@}*{@}*{@}*{@}
\{@}*{@}*{@}/
\\\ L ///
\\\ Y ///
\ L //
\ Y //
>=<
//*\\