RenPy中文空间

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 1142|回复: 1

[原创] 使用shader绘制海面

[复制链接]
发表于 2021-7-26 11:29:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
最近在研究shader,此贴为中间副产品。需Ren'Py 7.4.5以上版本。

此贴中使用的shader代码来源为
ShaderToy-Seascape

我只是做了一点修改并移植到Ren'Py上。

首先是主体的shader代码(不要问我是怎么来的,问就是我也不懂):
[RenPy] 纯文本查看 复制代码
init python:

    renpy.register_shader("shadertoy.Seascape", variables="""
        uniform float u_time;
        uniform vec2 u_model_size;
        varying vec2 v_tex_coord;
    """,
        fragment_functions="""
        const int NUM_STEPS = 8;
        const float PI = 3.141592;
        const int ITER_GEOMETRY = 3;
        const int ITER_FRAGMENT = 5;
        const float SEA_HEIGHT = 0.6;
        const float SEA_CHOPPY = 4.0;
        const float SEA_SPEED = 0.8;
        const float SEA_FREQ = 0.16;
        const vec3 SEA_BASE = vec3(0.0,0.09,0.18);
        const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6)*0.6;
        const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);

        mat3 fromEuler(vec3 ang)
        {
            vec2 a1 = vec2(sin(ang.x),cos(ang.x));
            vec2 a2 = vec2(sin(ang.y),cos(ang.y));
            vec2 a3 = vec2(sin(ang.z),cos(ang.z));
            mat3 m;
            m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
            m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
            m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
            return m;
        }
        float hash(vec2 p)
        {
            float h = dot(p,vec2(127.1,311.7));	
            return fract(sin(h)*43758.5453123);
        }
        float noise(in vec2 p)
        {
            vec2 i = floor(p);
            vec2 f = fract(p);	
            vec2 u = f*f*(3.0-2.0*f);
            return -1.0+2.0*mix(mix(hash(i + vec2(0.0,0.0)), 
                            hash(i + vec2(1.0,0.0)), u.x),
                            mix(hash(i + vec2(0.0,1.0)), 
                            hash(i + vec2(1.0,1.0)), u.x), u.y);
        }
        
        float diffuse(vec3 n,vec3 l,float p)
        {
            return pow(dot(n,l) * 0.4 + 0.6,p);
        }
        float specular(vec3 n,vec3 l,vec3 e,float s)
        {    
            float nrm = (s + 8.0) / (PI * 8.0);
            return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
        }

        vec3 getSkyColor(vec3 e)
        {
            e.y = (max(e.y,0.0)*0.8+0.2)*0.8;
            return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4) * 1.1;
        }

        float sea_octave(vec2 uv, float choppy)
        {
            uv += noise(uv);        
            vec2 wv = 1.0-abs(sin(uv));
            vec2 swv = abs(cos(uv));    
            wv = mix(wv,swv,wv);
            return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
        }

        float map(vec3 p, float time)
        {
            float freq = SEA_FREQ;
            float amp = SEA_HEIGHT;
            float choppy = SEA_CHOPPY;
            vec2 uv = p.xz; uv.x *= 0.75;
            float SEA_TIME = (1.0 + time * SEA_SPEED);
            
            float d, h = 0.0;    
            for(int i = 0; i < ITER_GEOMETRY; i++) {        
                d = sea_octave((uv+SEA_TIME)*freq,choppy);
                d += sea_octave((uv-SEA_TIME)*freq,choppy);
                h += d * amp;        
                uv *= octave_m;
                freq *= 1.9;
                amp *= 0.22;
                choppy = mix(choppy,1.0,0.2);
            }
            return p.y - h;
        }

        float map_detailed(vec3 p, float time)
        {
            float freq = SEA_FREQ;
            float amp = SEA_HEIGHT;
            float choppy = SEA_CHOPPY;
            vec2 uv = p.xz; uv.x *= 0.75;
            float SEA_TIME = (1.0 + time * SEA_SPEED);
            
            float d, h = 0.0;    
            for(int i = 0; i < ITER_FRAGMENT; i++)
            {        
                d = sea_octave((uv+SEA_TIME)*freq,choppy);
                d += sea_octave((uv-SEA_TIME)*freq,choppy);
                h += d * amp;        
                uv *= octave_m;
                freq *= 1.9;
                amp *= 0.22;
                choppy = mix(choppy,1.0,0.2);
            }
            return p.y - h;
        }

        vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist)
        {  
            float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0);
            fresnel = pow(fresnel,3.0) * 0.5;
                
            vec3 reflected = getSkyColor(reflect(eye,n));    
            vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12; 
            
            vec3 color = mix(refracted,reflected,fresnel);
            
            float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
            color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
            
            color += vec3(specular(n,l,eye,60.0));
            
            return color;
        }

        vec3 getNormal(vec3 p, float eps, float time)
        {
            vec3 n;
            n.y = map_detailed(p, time);    
            n.x = map_detailed(vec3(p.x+eps,p.y,p.z), time) - n.y;
            n.z = map_detailed(vec3(p.x,p.y,p.z+eps), time) - n.y;
            n.y = eps;
            return normalize(n);
        }

        float heightMapTracing(vec3 ori, vec3 dir, out vec3 p, float time)
        {  
            float tm = 0.0;
            float tx = 1000.0;    
            float hx = map(ori + dir * tx, time);
            if(hx > 0.0) return tx;   
            float hm = map(ori + dir * tm, time);    
            float tmid = 0.0;
            for(int i = 0; i < NUM_STEPS; i++)
            {
                tmid = mix(tm,tx, hm/(hm-hx));                   
                p = ori + dir * tmid;                   
                float hmid = map(p, time);
                if(hmid < 0.0)
                {
                    tx = tmid;
                    hx = hmid;
                }
                else
                {
                    tm = tmid;
                    hm = hmid;
                }
            }
            return tmid;
        }

        vec3 getPixel(in vec2 coord, float time, vec2 model_size)
        {    
            vec2 uv = coord / model_size.xy;
            uv = uv * 2.0 - 1.0;
            uv.x *= model_size.x / model_size.y;    

            // ray
            vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);    
            vec3 ori = vec3(0.0,3.5,time*5.0);
            vec3 dir = normalize(vec3(uv.xy,-2.0));
            dir.z += length(uv) * 0.14;
            dir = normalize(dir) * fromEuler(ang);

            // tracing
            float EPSILON_NRM = (0.1 / model_size.x);
            vec3 p;
            heightMapTracing(ori, dir, p, time);
            vec3 dist = p - ori;
            vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM, time);
            vec3 light = normalize(vec3(0.0,1.0,0.8)); 

            // color
            return mix(
                getSkyColor(dir),
                getSeaColor(p,n,light,dir,dist),
                pow(smoothstep(0.0,-0.02,dir.y),0.2));
        }
    """,
        vertex_300="""
        v_tex_coord = a_tex_coord;
    """,
    
        fragment_300="""
        #define AA
        float time = u_time * 0.3;
        #ifdef AA
        vec3 color = vec3(0.0);
        for(int i = -1; i <= 1; i++)
        {
            for(int j = -1; j <= 1; j++) {
                vec2 uv = gl_FragCoord.xy + vec2(i,j) / 3.0;
                color += getPixel(uv, time, u_model_size);
            }
        }
        color /= 9.0;
        #else
            vec3 color = getPixel(gl_FragCoord.xy, time, u_model_size);
        #endif

        gl_FragColor = vec4(pow(color,vec3(0.65)), 1.0);
    """)


然后是在自定义可视组件中应用这个shader:
[RenPy] 纯文本查看 复制代码
init python:

    class Seascape(renpy.Displayable):

        def __init__(self, child, width, height, **kwargs):
            super(Seascape, self).__init__(**kwargs)
            
            self.child = renpy.displayable(child)
            self.width = width
            self.height = height

        def render(self, width, height, st, at):
            render = renpy.Render(self.width, self.height)
            render.place(self.child)
            render.add_shader("shadertoy.Seascape")
            render.add_uniform("u_time", st)
            render.add_uniform("u_model_size", (self.width, self.height))
            renpy.redraw(self, 0)
            return render


script.rpy中使用图像:
[RenPy] 纯文本查看 复制代码
image seascape = Seascape("texture", width = 1280, height = 720)

label main_menu:
    return


label start:

    show seascape
    
    pause


注意这里使用一张名为“texture”的图片,图片内容是什么不重要,尺寸需要为1280×720(或者根据需求修改)。

这个海面效果很不错,可以媲美各种预渲染的视频。缺点是对显卡性能消耗极大……

运行时显卡状态

运行时显卡状态

发表于 2021-9-27 08:43:52 | 显示全部楼层
这个效果太赞了啊!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号 | 苏公网安备 32092302000068号 )

GMT+8, 2022-6-26 07:56 , Processed in 0.040049 second(s), 13 queries , File On.

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表