package arm.nodes; import iron.data.SceneFormat; import arm.ui.UITrait; import arm.ui.UINodes; import arm.nodes.CyclesShader; import arm.Tool; class MaterialBuilder { public static var heightUsed = false; public static var emisUsed = false; public static var subsUsed = false; public static function make_paint(data:CyclesShaderData, matcon:TMaterialContext):CyclesShaderContext { var layered = Context.layer != Project.layers[0]; var eraser = Context.tool == ToolEraser; var context_id = 'paint'; var con_paint:CyclesShaderContext = data.add_context({ name: context_id, depth_write: false, compare_mode: 'always', // TODO: align texcoords winding order // cull_mode: 'counter_clockwise', cull_mode: 'none', vertex_elements: [{name: "pos", data: 'short4norm'}, {name: "nor", data: 'short2norm'}, {name: "tex", data: 'short2norm'}] }); con_paint.data.color_writes_red = [true, true, true, true]; con_paint.data.color_writes_green = [true, true, true, true]; con_paint.data.color_writes_blue = [true, true, true, true]; con_paint.data.color_writes_alpha = [true, true, true, true]; if (Context.tool == ToolBake) { // Bake into base color, disable other slots con_paint.data.color_writes_red[1] = false; con_paint.data.color_writes_green[1] = false; con_paint.data.color_writes_blue[1] = false; con_paint.data.color_writes_alpha[1] = false; con_paint.data.color_writes_red[2] = false; con_paint.data.color_writes_green[2] = false; con_paint.data.color_writes_blue[2] = false; con_paint.data.color_writes_alpha[2] = false; } var vert = con_paint.make_vert(); var frag = con_paint.make_frag(); frag.ins = vert.outs; if (Context.tool == ToolColorId || Context.tool == ToolPicker) { // Mangle vertices to form full screen triangle vert.write('gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);'); frag.add_uniform('sampler2D gbuffer2'); frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); frag.add_uniform('vec4 inp', '_inputBrush'); #if (kha_opengl || kha_webgl) frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;'); #else frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;'); #end if (Context.tool == ToolColorId) { frag.add_out('vec4 fragColor'); frag.add_uniform('sampler2D texcolorid', '_texcolorid'); frag.write('vec3 idcol = textureLod(texcolorid, texCoordInp, 0.0).rgb;'); frag.write('fragColor = vec4(idcol, 1.0);'); } else if (Context.tool == ToolPicker) { frag.add_out('vec4 fragColor[3]'); frag.add_uniform('sampler2D texpaint'); frag.add_uniform('sampler2D texpaint_nor'); frag.add_uniform('sampler2D texpaint_pack'); frag.write('fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);'); frag.write('fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);'); frag.write('fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);'); } con_paint.data.shader_from_source = true; con_paint.data.vertex_shader = vert.get(); con_paint.data.fragment_shader = frag.get(); return con_paint; } var faceFill = Context.tool == ToolFill && UITrait.inst.fillTypeHandle.position == 1; var decal = Context.tool == ToolDecal || Context.tool == ToolText; if (!faceFill && !decal) { // Fix seams at uv borders vert.add_uniform('vec2 sub', '_sub'); vert.write('vec2 subtex = tex + sub;'); } else { vert.write('vec2 subtex = tex;'); } #if (kha_direct3d11 || kha_direct3d12) vert.write('vec2 tpos = vec2(subtex.x * 2.0 - 1.0, (1.0 - subtex.y) * 2.0 - 1.0);'); #else vert.write('vec2 tpos = vec2(subtex.xy * 2.0 - 1.0);'); #end vert.write('gl_Position = vec4(tpos, 0.0, 1.0);'); vert.add_uniform('mat4 WVP', '_worldViewProjectionMatrix'); vert.add_out('vec4 ndc'); vert.write_attrib('ndc = mul(vec4(pos.xyz, 1.0), WVP);'); frag.write_attrib('vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);'); frag.write_attrib('sp.y = 1.0 - sp.y;'); frag.write_attrib('sp.z -= 0.0001;'); // small bias if (UITrait.inst.brushPaint == 1) frag.ndcpos = true; // Project frag.add_uniform('vec4 inp', '_inputBrush'); frag.add_uniform('vec4 inplast', '_inputBrushLast'); frag.add_uniform('float aspectRatio', '_aspectRatioWindowF'); frag.write('vec2 bsp = sp.xy * 2.0 - 1.0;'); frag.write('bsp.x *= aspectRatio;'); frag.write('bsp = bsp * 0.5 + 0.5;'); frag.add_uniform('sampler2D gbufferD'); frag.add_out('vec4 fragColor[4]'); frag.add_uniform('float brushRadius', '_brushRadius'); frag.add_uniform('float brushOpacity', '_brushOpacity'); frag.add_uniform('float brushHardness', '_brushHardness'); if (Context.tool == ToolBrush || Context.tool == ToolEraser || Context.tool == ToolClone || Context.tool == ToolBlur || Context.tool == ToolParticle || decal) { var depthReject = !UITrait.inst.xray; if (UITrait.inst.brush3d && !UITrait.inst.brushDepthReject) depthReject = false; // TODO: sp.z needs to take height channel into account if (heightUsed) depthReject = false; if (depthReject) { #if (kha_opengl || kha_webgl) frag.write('if (sp.z > textureLod(gbufferD, vec2(sp.x, 1.0 - sp.y), 0.0).r) discard;'); #else frag.write('if (sp.z > textureLod(gbufferD, sp.xy, 0.0).r) discard;'); #end } if (decal || Context.tool == ToolParticle) { frag.write('float dist = 0.0;'); } else { if (UITrait.inst.brush3d) { #if (kha_opengl || kha_webgl) frag.write('float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;'); #else frag.write('float depth = textureLod(gbufferD, inp.xy, 0.0).r;'); #end frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix'); frag.write('vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);'); frag.write('winp = mul(winp, invVP);'); frag.write('winp.xyz /= winp.w;'); frag.wposition = true; if (UITrait.inst.brushAngleReject || UITrait.inst.xray) { frag.add_function(CyclesFunctions.str_octahedronWrap); frag.add_uniform('sampler2D gbuffer0'); #if (kha_opengl || kha_webgl) frag.write('vec2 g0 = textureLod(gbuffer0, vec2(inp.x, 1.0 - inp.y), 0.0).rg;'); #else frag.write('vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;'); #end frag.write('vec3 wn;'); frag.write('wn.z = 1.0 - abs(g0.x) - abs(g0.y);'); frag.write('wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);'); frag.write('wn = normalize(wn);'); frag.write('float planeDist = dot(wn, winp.xyz - wposition);'); if (UITrait.inst.brushAngleReject && !UITrait.inst.xray) { frag.write('if (planeDist < -0.01) discard;'); frag.n = true; var angle = UITrait.inst.brushAngleRejectDot; frag.write('if (dot(wn, n) < $angle) discard;'); } } #if (kha_opengl || kha_webgl) frag.write('float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;'); #else frag.write('float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;'); #end frag.write('vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);'); frag.write('winplast = mul(winplast, invVP);'); frag.write('winplast.xyz /= winplast.w;'); frag.write('vec3 pa = wposition - winp.xyz;'); if (UITrait.inst.xray) { frag.write('pa += wn * vec3(planeDist, planeDist, planeDist);'); } frag.write('vec3 ba = winplast.xyz - winp.xyz;'); frag.write('float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);'); frag.write('float dist = length(pa - ba * h);'); frag.write('if (dist > brushRadius) discard;'); // Non-continuous // frag.write('float dist = distance(wposition, winp.xyz);'); } else { // !brush3d frag.write('vec2 binp = inp.xy * 2.0 - 1.0;'); frag.write('binp.x *= aspectRatio;'); frag.write('binp = binp * 0.5 + 0.5;'); frag.write('vec2 binplast = inplast.xy * 2.0 - 1.0;'); frag.write('binplast.x *= aspectRatio;'); frag.write('binplast = binplast * 0.5 + 0.5;'); frag.write('vec2 pa = bsp.xy - binp.xy;'); frag.write('vec2 ba = binplast.xy - binp.xy;'); frag.write('float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);'); frag.write('float dist = length(pa - ba * h);'); frag.write('if (dist > brushRadius) discard;'); // Non-continuous // frag.write('float dist = distance(bsp.xy, binp.xy);'); } } } else { // Fill, Bake frag.write('float dist = 0.0;'); var angleFill = Context.tool == ToolFill && UITrait.inst.fillTypeHandle.position == 2; if (angleFill) { frag.add_function(CyclesFunctions.str_octahedronWrap); frag.add_uniform('sampler2D gbuffer0'); frag.write('vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;'); frag.write('vec3 wn;'); frag.write('wn.z = 1.0 - abs(g0.x) - abs(g0.y);'); frag.write('wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);'); frag.write('wn = normalize(wn);'); frag.n = true; var angle = UITrait.inst.brushAngleRejectDot; frag.write('if (dot(wn, n) < $angle) discard;'); } } if (UITrait.inst.colorIdPicked) { vert.add_out('vec2 texCoordPick'); vert.write('texCoordPick = fract(subtex);'); frag.add_uniform('sampler2D texpaint_colorid'); // 1x1 picker frag.add_uniform('sampler2D texcolorid', '_texcolorid'); // color map frag.add_uniform('vec2 texcoloridSize', '_texcoloridSize'); // color map frag.write('vec3 c1 = texelFetch(texpaint_colorid, ivec2(0, 0), 0).rgb;'); frag.write('vec3 c2 = texelFetch(texcolorid, ivec2(texCoordPick * texcoloridSize), 0).rgb;'); frag.write('if (any(c1 != c2)) discard;'); } else if (faceFill) { // TODO: allow to combine with colorid mask vert.add_out('vec2 texCoordPick'); vert.write('texCoordPick = fract(subtex);'); frag.add_uniform('sampler2D gbuffer2'); frag.add_uniform('sampler2D textrianglemap', '_textrianglemap'); // triangle map frag.add_uniform('float textrianglemapSize', '_texpaintSize'); frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); #if (kha_opengl || kha_webgl) frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;'); #else frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;'); #end frag.write('vec4 c1 = texelFetch(textrianglemap, ivec2(texCoordInp * textrianglemapSize), 0);'); frag.write('vec4 c2 = texelFetch(textrianglemap, ivec2(texCoordPick * textrianglemapSize), 0);'); frag.write('if (any(c1 != c2)) discard;'); } if (UITrait.inst.pickerMaskHandle.position == 1) { // material id mask frag.wvpposition = true; frag.write('vec2 picker_sample_tc = vec2(wvpposition.x / wvpposition.w, wvpposition.y / wvpposition.w) * 0.5 + 0.5;'); #if (kha_direct3d11 || kha_direct3d12) frag.write('picker_sample_tc.y = 1.0 - picker_sample_tc.y;'); #end frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); var matid = UITrait.inst.materialIdPicked / 255; frag.write('if ($matid != textureLod(texpaint_nor_undo, picker_sample_tc, 0.0).a) discard;'); } // TexCoords - project if (UITrait.inst.brushPaint == 1 || decal) { frag.add_uniform('float brushScale', '_brushScale'); frag.write_attrib('vec2 uvsp = sp.xy;'); if (decal) { frag.write_attrib('uvsp -= inp.xy;'); frag.write_attrib('uvsp.x *= aspectRatio;'); frag.write_attrib('uvsp *= 0.21 / (brushRadius * 0.9);'); frag.write_attrib('uvsp += vec2(0.5, 0.5);'); frag.write_attrib('if (uvsp.x < 0.01 || uvsp.y < 0.01 || uvsp.x > 0.99 || uvsp.y > 0.99) discard;'); } else { frag.write_attrib('uvsp.x *= aspectRatio;'); } frag.write_attrib('vec2 texCoord = fract(uvsp * brushScale);'); if (UITrait.inst.brushRot > 0.0) { var a = UITrait.inst.brushRot * (Math.PI / 180); frag.write('texCoord = vec2(texCoord.x * ${Math.cos(a)} - texCoord.y * ${Math.sin(a)}, texCoord.x * ${Math.sin(a)} + texCoord.y * ${Math.cos(a)});'); } } // TexCoords - uvmap else if (UITrait.inst.brushPaint == 0) { vert.add_uniform('float brushScale', '_brushScale'); vert.add_out('vec2 texCoord'); vert.write('texCoord = subtex * brushScale;'); if (UITrait.inst.brushRot > 0.0) { var a = UITrait.inst.brushRot * (Math.PI / 180); vert.write('texCoord = vec2(texCoord.x * ${Math.cos(a)} - texCoord.y * ${Math.sin(a)}, texCoord.x * ${Math.sin(a)} + texCoord.y * ${Math.cos(a)});'); } } else { // Triplanar frag.wposition = true; frag.n = true; frag.add_uniform('float brushScale', '_brushScale'); frag.write_attrib('vec3 triWeight = wnormal * wnormal;'); // n * n frag.write_attrib('float triMax = max(triWeight.x, max(triWeight.y, triWeight.z));'); frag.write_attrib('triWeight = max(triWeight - triMax * 0.75, 0.0);'); frag.write_attrib('vec3 texCoordBlend = triWeight * (1.0 / (triWeight.x + triWeight.y + triWeight.z));'); frag.write_attrib('vec2 texCoord = fract(wposition.yz * brushScale);'); frag.write_attrib('vec2 texCoord1 = fract(wposition.xz * brushScale);'); frag.write_attrib('vec2 texCoord2 = fract(wposition.xy * brushScale);'); } if (Context.tool == ToolClone || Context.tool == ToolBlur) { frag.add_uniform('sampler2D gbuffer2'); frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); frag.add_uniform('sampler2D texpaint_undo', '_texpaint_undo'); frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); frag.add_uniform('sampler2D texpaint_pack_undo', '_texpaint_pack_undo'); if (Context.tool == ToolClone) { frag.add_uniform('vec2 cloneDelta', '_cloneDelta'); #if (kha_opengl || kha_webgl) frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;'); #else frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (sp.y + cloneDelta.y) * gbufferSize.y), 0).ba;'); #end frag.write('vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;'); var base = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb'; var rough = 'texpaint_pack_sample.g'; var met = 'texpaint_pack_sample.b'; var occ = 'texpaint_pack_sample.r'; var nortan = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb'; var height = '0.0'; var opac = '1.0'; frag.write('vec3 basecol = $base;'); frag.write('float roughness = $rough;'); frag.write('float metallic = $met;'); frag.write('float occlusion = $occ;'); frag.write('vec3 nortan = $nortan;'); frag.write('float height = $height;'); frag.write('float opacity = $opac * brushOpacity;'); if (Context.material.paintEmis) { frag.write('float emis = 0.0;'); } if (Context.material.paintSubs) { frag.write('float subs = 0.0;'); } } else { // Blur #if (kha_opengl || kha_webgl) frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, (1.0 - sp.y) * gbufferSize.y), 0).ba;'); #else frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, sp.y * gbufferSize.y), 0).ba;'); #end frag.write('vec3 basecol = vec3(0.0, 0.0, 0.0);'); frag.write('float roughness = 0.0;'); frag.write('float metallic = 0.0;'); frag.write('float occlusion = 0.0;'); frag.write('vec3 nortan = vec3(0.0, 0.0, 0.0);'); frag.write('float height = 0.0;'); frag.write('float opacity = 1.0 * brushOpacity;'); if (Context.material.paintEmis) { frag.write('float emis = 0.0;'); } if (Context.material.paintSubs) { frag.write('float subs = 0.0;'); } #if (kha_direct3d11 || kha_direct3d12) frag.write('const float blur_weight[15] = {0.034619 / 2.0, 0.044859 / 2.0, 0.055857 / 2.0, 0.066833 / 2.0, 0.076841 / 2.0, 0.084894 / 2.0, 0.090126 / 2.0, 0.09194 / 2.0, 0.090126 / 2.0, 0.084894 / 2.0, 0.076841 / 2.0, 0.066833 / 2.0, 0.055857 / 2.0, 0.044859 / 2.0, 0.034619 / 2.0};'); #else frag.write('const float blur_weight[15] = float[](0.034619 / 2.0, 0.044859 / 2.0, 0.055857 / 2.0, 0.066833 / 2.0, 0.076841 / 2.0, 0.084894 / 2.0, 0.090126 / 2.0, 0.09194 / 2.0, 0.090126 / 2.0, 0.084894 / 2.0, 0.076841 / 2.0, 0.066833 / 2.0, 0.055857 / 2.0, 0.044859 / 2.0, 0.034619 / 2.0);'); #end frag.add_uniform('float texpaintSize', '_texpaintSize'); frag.write('float blur_step = 1.0 / texpaintSize;'); frag.write('for (int i = -7; i <= 7; i++) {'); frag.write('basecol += texture(texpaint_undo, texCoordInp + vec2(blur_step * i, 0.0)).rgb * blur_weight[i + 7];'); frag.write('vec3 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * i, 0.0)).rgb * blur_weight[i + 7];'); frag.write('roughness += texpaint_pack_sample.g;'); frag.write('metallic += texpaint_pack_sample.b;'); frag.write('occlusion += texpaint_pack_sample.r;'); frag.write('nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * i, 0.0)).rgb * blur_weight[i + 7];'); frag.write('}'); frag.write('for (int i = -7; i <= 7; i++) {'); frag.write('basecol += texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * i)).rgb * blur_weight[i + 7];'); frag.write('vec3 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * i)).rgb * blur_weight[i + 7];'); frag.write('roughness += texpaint_pack_sample.g;'); frag.write('metallic += texpaint_pack_sample.b;'); frag.write('occlusion += texpaint_pack_sample.r;'); frag.write('nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * i)).rgb * blur_weight[i + 7];'); frag.write('}'); } } else { Cycles.parse_emission = Context.material.paintEmis; Cycles.parse_subsurface = Context.material.paintSubs; Cycles.parse_height = Context.material.paintHeight; Cycles.parse_height_as_channel = true; Cycles.triplanar = UITrait.inst.brushPaint == 2; var sout = Cycles.parse(UINodes.inst.canvas, con_paint, vert, frag, null, null, null, matcon); Cycles.parse_emission = false; Cycles.parse_subsurface = false; Cycles.parse_height_as_channel = false; Cycles.parse_height = false; var base = sout.out_basecol; var rough = sout.out_roughness; var met = sout.out_metallic; var occ = sout.out_occlusion; var nortan = Cycles.out_normaltan; var height = sout.out_height; var opac = sout.out_opacity; var emis = sout.out_emission; var subs = sout.out_subsurface; frag.write('vec3 basecol = $base;'); frag.write('float roughness = $rough;'); frag.write('float metallic = $met;'); frag.write('float occlusion = $occ;'); frag.write('vec3 nortan = $nortan;'); frag.write('float height = $height;'); frag.write('float opacity = $opac * brushOpacity;'); if (Context.material.paintEmis) { frag.write('float emis = $emis;'); } if (Context.material.paintSubs) { frag.write('float subs = $subs;'); } if (height != '0' && !heightUsed) { heightUsed = true; // Height used for the first time, also rebuild vertex shader return make_paint(data, matcon); } if (emis != '0') emisUsed = true; if (subs != '0') subsUsed = true; } if (Context.tool == ToolDecal) { frag.add_uniform('sampler2D texdecalmask', '_texdecalmask'); frag.write('opacity *= textureLod(texdecalmask, texCoord, 0.0).r;'); } else if (Context.tool == ToolText) { frag.add_uniform('sampler2D textexttool', '_textexttool'); frag.write('opacity *= textureLod(textexttool, texCoord, 0.0).r;'); } if (UITrait.inst.brushMaskImage != null && Context.tool == ToolBrush) { frag.add_uniform('sampler2D texbrushmask', '_texbrushmask'); frag.write('vec2 binp_mask = inp.xy * 2.0 - 1.0;'); frag.write('binp_mask.x *= aspectRatio;'); frag.write('binp_mask = binp_mask * 0.5 + 0.5;'); frag.write('vec2 pa_mask = bsp.xy - binp_mask.xy;'); frag.write('pa_mask /= brushRadius;'); if (UITrait.inst.brush3d) { frag.add_uniform('vec3 eye', '_cameraPosition'); frag.write('pa_mask *= distance(eye, winp) / 1.5;'); } frag.write('pa_mask = pa_mask.xy * 0.5 + 0.5;'); frag.write('opacity *= textureLod(texbrushmask, pa_mask, 0).r;'); } if (Context.tool == ToolParticle) { // particle mask frag.add_uniform('sampler2D texparticle', '_texparticle'); #if (kha_opengl || kha_webgl) frag.write('float str = textureLod(texparticle, vec2(sp.x, (1.0 - sp.y)), 0).r;'); #else frag.write('float str = textureLod(texparticle, sp.xy, 0).r;'); #end } else { // brush cursor mask frag.write('float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;'); } // Manual blending to preserve memory frag.wvpposition = true; frag.write('vec2 sample_tc = vec2(wvpposition.x / wvpposition.w, wvpposition.y / wvpposition.w) * 0.5 + 0.5;'); #if (kha_direct3d11 || kha_direct3d12) frag.write('sample_tc.y = 1.0 - sample_tc.y;'); #end frag.add_uniform('sampler2D paintmask'); frag.write('float sample_mask = textureLod(paintmask, sample_tc, 0.0).r;'); frag.write('str = max(str, sample_mask);'); frag.add_uniform('sampler2D texpaint_undo', '_texpaint_undo'); frag.write('vec4 sample_undo = textureLod(texpaint_undo, sample_tc, 0.0);'); var matid = Context.material.id / 255; if (UITrait.inst.pickerMaskHandle.position == 1) { // material id mask matid = UITrait.inst.materialIdPicked / 255; // Keep existing material id in place when mask is set } frag.write('float matid = $matid;'); // TODO: Use emission/subsurface matid // matid % 3 == 0 - normal, 1 - emission, 2 - subsurface if (Context.material.paintSubs) { frag.write('if (subs > 0) { matid = 254 / 255; }'); } if (Context.material.paintEmis) { frag.write('if (emis > 0) { matid = 255 / 255; }'); } if (layered) { if (eraser) { frag.write('fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);'); frag.write('nortan = vec3(0.5, 0.5, 1.0);'); frag.write('occlusion = 1.0;'); frag.write('roughness = 0.0;'); frag.write('metallic = 0.0;'); frag.write('matid = 0.0;'); } else if (decal) { frag.write('fragColor[0] = vec4(mix(sample_undo.rgb, basecol, str), max(str, sample_undo.a));'); } else { frag.write('fragColor[0] = vec4(basecol, max(str, sample_undo.a));'); } frag.write('fragColor[1] = vec4(nortan, matid);'); var height = '0.0'; if (Context.material.paintHeight && heightUsed) { height = 'height'; } if (decal) { frag.add_uniform('sampler2D texpaint_pack_undo', '_texpaint_pack_undo'); frag.write('vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);'); frag.write('fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, $height), str);'); } else { frag.write('fragColor[2] = vec4(occlusion, roughness, metallic, $height);'); } } else { if (eraser) { frag.write('fragColor[0] = vec4(${Layers.defaultBase}, ${Layers.defaultBase}, ${Layers.defaultBase}, 0.0);'); frag.write('fragColor[1] = vec4(0.5, 0.5, 1.0, 0.0);'); frag.write('fragColor[2] = vec4(1.0, ${Layers.defaultRough}, 0.0, 0.0);'); } else { frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); frag.add_uniform('sampler2D texpaint_pack_undo', '_texpaint_pack_undo'); frag.write('vec4 sample_nor_undo = textureLod(texpaint_nor_undo, sample_tc, 0.0);'); frag.write('vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);'); frag.write('fragColor[0] = vec4(mix(sample_undo.rgb, basecol, str), 0.0);'); frag.write('fragColor[1] = vec4(mix(sample_nor_undo.rgb, nortan, str), matid);'); if (Context.material.paintHeight && heightUsed) { frag.write('fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, height), str);'); } else { frag.write('fragColor[2] = vec4(mix(sample_pack_undo.rgb, vec3(occlusion, roughness, metallic), str), 0.0);'); } } } frag.write('fragColor[3] = vec4(str, 0.0, 0.0, 1.0);'); if (!Context.material.paintBase) { con_paint.data.color_writes_red[0] = false; con_paint.data.color_writes_green[0] = false; con_paint.data.color_writes_blue[0] = false; } if (!Context.material.paintNor) { con_paint.data.color_writes_red[1] = false; con_paint.data.color_writes_green[1] = false; con_paint.data.color_writes_blue[1] = false; } if (!Context.material.paintOcc) { con_paint.data.color_writes_red[2] = false; } if (!Context.material.paintRough) { con_paint.data.color_writes_green[2] = false; } if (!Context.material.paintMet) { con_paint.data.color_writes_blue[2] = false; } if (!Context.material.paintHeight) { con_paint.data.color_writes_alpha[2] = false; } // Base color only as mask var isMask = Context.layerIsMask; if (isMask) { // TODO: Apply opacity into base // frag.write('fragColor[0].rgb *= fragColor[0].a;'); con_paint.data.color_writes_red[1] = false; con_paint.data.color_writes_green[1] = false; con_paint.data.color_writes_blue[1] = false; con_paint.data.color_writes_red[2] = false; con_paint.data.color_writes_green[2] = false; con_paint.data.color_writes_blue[2] = false; con_paint.data.color_writes_alpha[2] = false; } if (Context.tool == ToolBake) { if (UITrait.inst.bakeType == 0) { // AO // Apply normal channel frag.wposition = true; frag.n = true; frag.vVec = true; frag.add_function(CyclesFunctions.str_cotangentFrame); #if (kha_direct3d11 || kha_direct3d12) frag.write('mat3 TBN = cotangentFrame(n, vVec, texCoord);'); #else frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);'); #end frag.write('n = nortan * 2.0 - 1.0;'); frag.write('n.y = -n.y;'); frag.write('n = normalize(mul(n, TBN));'); frag.write('const vec3 voxelgiHalfExtents = vec3(1.0, 1.0, 1.0);'); frag.write('vec3 voxpos = wposition / voxelgiHalfExtents;'); frag.add_uniform('sampler3D voxels'); frag.add_function(CyclesFunctions.str_traceAO); frag.n = true; var strength = UITrait.inst.bakeAoStrength; var radius = UITrait.inst.bakeAoRadius; var offset = UITrait.inst.bakeAoOffset; frag.write('float ao = traceAO(voxpos, n, $radius, $offset) * $strength;'); if (UITrait.inst.bakeAxis > 0) { var axis = axisString(UITrait.inst.bakeAxis); frag.write('ao *= dot(n, $axis);'); } frag.write('ao = 1.0 - ao;'); frag.write('fragColor[0] = vec4(ao, ao, ao, 1.0);'); } else if (UITrait.inst.bakeType == 1) { // Curvature var strength = UITrait.inst.bakeCurvStrength * 2.0; var radius = (1.0 / UITrait.inst.bakeCurvRadius) * 0.25; var offset = UITrait.inst.bakeCurvOffset / 10; frag.n = true; frag.write('vec3 dx = dFdx(n);'); frag.write('vec3 dy = dFdy(n);'); frag.write('float curvature = max(dot(dx, dx), dot(dy, dy));'); frag.write('curvature = clamp(pow(curvature, $radius) * $strength + $offset, 0.0, 1.0);'); if (UITrait.inst.bakeAxis > 0) { var axis = axisString(UITrait.inst.bakeAxis); frag.write('curvature *= dot(n, $axis);'); } frag.write('fragColor[0] = vec4(curvature, curvature, curvature, 1.0);'); } else if (UITrait.inst.bakeType == 2) { // Normal (Tangent) frag.n = true; frag.add_uniform('sampler2D texpaint_undo', '_texpaint_undo'); frag.write('vec3 n0 = textureLod(texpaint_undo, texCoord, 0.0).rgb * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);'); frag.add_function(CyclesFunctions.str_cotangentFrame); frag.write('mat3 invTBN = transpose(cotangentFrame(n, n, texCoord));'); frag.write('vec3 res = normalize(mul(n0, invTBN)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);'); frag.write('fragColor[0] = vec4(res, 1.0);'); } else if (UITrait.inst.bakeType == 3) { // Normal (World) frag.n = true; frag.write('fragColor[0] = vec4(n * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);'); } else if (UITrait.inst.bakeType == 4) { // Position frag.wposition = true; frag.write('fragColor[0] = vec4(wposition * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);'); } else if (UITrait.inst.bakeType == 5) { // TexCoord frag.write('fragColor[0] = vec4(texCoord.xy, 0.0, 1.0);'); } else if (UITrait.inst.bakeType == 6) { // Material ID frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); frag.write('float sample_matid = textureLod(texpaint_nor_undo, texCoord, 0.0).a;'); frag.write('float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float matid_g = fract(sin(dot(vec2(sample_matid * 20.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('fragColor[0] = vec4(matid_r, matid_g, matid_b, 1.0);'); } else if (UITrait.inst.bakeType == 7) { // Object ID frag.add_uniform('float objectId', '_objectId'); frag.write('float id_r = fract(sin(dot(vec2(objectId, objectId * 20.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float id_g = fract(sin(dot(vec2(objectId * 20.0, objectId), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float id_b = fract(sin(dot(vec2(objectId, objectId * 40.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('fragColor[0] = vec4(id_r, id_g, id_b, 1.0);'); } } Cycles.finalize(con_paint); Cycles.triplanar = false; con_paint.data.shader_from_source = true; con_paint.data.vertex_shader = vert.get(); con_paint.data.fragment_shader = frag.get(); return con_paint; } static function axisString(i:Int):String { return i == 1 ? "vec3(1,0,0)" : i == 2 ? "vec3(0,1,0)" : i == 3 ? "vec3(0,0,1)" : i == 4 ? "vec3(-1,0,0)" : i == 5 ? "vec3(0,-1,0)" : "vec3(0,0,-1)"; } public static function make_mesh_preview(data:CyclesShaderData, matcon:TMaterialContext):CyclesShaderContext { var context_id = 'mesh'; var con_mesh:CyclesShaderContext = data.add_context({ name: context_id, depth_write: true, compare_mode: 'less', cull_mode: 'clockwise', vertex_elements: [{name: "pos", data: 'short4norm'},{name: "nor", data: 'short2norm'},{name: "tex", data: 'short2norm'}] }); var vert = con_mesh.make_vert(); var frag = con_mesh.make_frag(); frag.ins = vert.outs; vert.add_uniform('mat4 WVP', '_worldViewProjectionMatrix'); vert.write_attrib('gl_Position = mul(vec4(pos.xyz, 1.0), WVP);'); vert.add_out('vec2 texCoord'); vert.add_uniform('float brushScale', '_brushScale'); vert.write_attrib('texCoord = tex * brushScale;'); if (heightUsed) { frag.bposition = true; } Cycles.parse_height = heightUsed; var sout = Cycles.parse(UINodes.inst.canvas, con_mesh, vert, frag, null, null, null, matcon); Cycles.parse_height = false; var base = sout.out_basecol; var rough = sout.out_roughness; var met = sout.out_metallic; var occ = sout.out_occlusion; var opac = sout.out_opacity; var nortan = Cycles.out_normaltan; frag.write('vec3 basecol = pow($base, vec3(2.2, 2.2, 2.2));'); frag.write('float roughness = $rough;'); frag.write('float metallic = $met;'); frag.write('float occlusion = $occ;'); frag.write('float opacity = $opac;'); frag.write('vec3 nortan = $nortan;'); var decal = UITrait.inst.decalPreview; if (decal) { if (Context.tool == ToolDecal) { frag.add_uniform('sampler2D texdecalmask', '_texdecalmask'); frag.write('opacity *= textureLod(texdecalmask, texCoord, 0.0).r;'); } else if (Context.tool == ToolText) { frag.add_uniform('sampler2D textexttool', '_textexttool'); frag.write('opacity *= textureLod(textexttool, texCoord, 0.0).r;'); } var opac = 0.05; frag.write('if (opacity < $opac) discard;'); } frag.add_out('vec4 fragColor[3]'); frag.n = true; frag.add_function(CyclesFunctions.str_packFloatInt16); frag.add_function(CyclesFunctions.str_packFloat2); frag.add_function(CyclesFunctions.str_cotangentFrame); frag.add_function(CyclesFunctions.str_octahedronWrap); // Apply normal channel if (decal) { // TODO } else { frag.vVec = true; #if (kha_direct3d11 || kha_direct3d12) frag.write('mat3 TBN = cotangentFrame(n, vVec, texCoord);'); #else frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);'); #end frag.write('n = nortan * 2.0 - 1.0;'); frag.write('n.y = -n.y;'); frag.write('n = normalize(mul(n, TBN));'); } frag.write('n /= (abs(n.x) + abs(n.y) + abs(n.z));'); frag.write('n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);'); // uint matid = 0; frag.write('fragColor[0] = vec4(n.x, n.y, roughness, packFloatInt16(metallic, uint(0)));'); // metallic/matid frag.write('fragColor[1] = vec4(basecol.r, basecol.g, basecol.b, packFloat2(occlusion, 1.0));'); // occ/spec frag.write('fragColor[2] = vec4(0.0, 0.0, 0.0, 0.0);'); // veloc Cycles.finalize(con_mesh); con_mesh.data.shader_from_source = true; con_mesh.data.vertex_shader = vert.get(); con_mesh.data.fragment_shader = frag.get(); return con_mesh; } public static function make_particle(data:CyclesShaderData):CyclesShaderContext { var context_id = 'mesh'; var con_part:CyclesShaderContext = data.add_context({ name: context_id, depth_write: false, compare_mode: 'always', cull_mode: 'clockwise', vertex_elements: [{name: "pos", data: 'short4norm'}] }); var vert = con_part.make_vert(); var frag = con_part.make_frag(); frag.ins = vert.outs; vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);'); vert.add_uniform('float brushRadius', '_brushRadius'); vert.write_attrib('vec3 emitFrom = vec3(fhash(gl_InstanceID), fhash(gl_InstanceID * 2), fhash(gl_InstanceID * 3));'); vert.write_attrib('emitFrom = emitFrom * brushRadius - brushRadius / 2;'); vert.write_attrib('spos.xyz += emitFrom * 256;'); vert.add_uniform('mat4 pd', '_particleData'); var str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n"; vert.add_function(str_tex_hash); vert.add_out('float p_age'); vert.write('p_age = pd[3][3] - gl_InstanceID * pd[0][1];'); vert.write('p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];'); vert.write('if (pd[0][0] > 0 && p_age < 0) p_age += (int(-p_age / pd[0][0]) + 1) * pd[0][0];'); vert.add_out('float p_lifetime'); vert.write('p_lifetime = pd[0][2];'); vert.write('if (p_age < 0 || p_age > p_lifetime) {'); // vert.write(' SPIRV_Cross_Output stage_output;'); // vert.write(' stage_output.svpos /= 0.0;'); // vert.write(' return stage_output;'); vert.write(' spos /= 0.0;'); vert.write('}'); vert.add_out('vec3 p_velocity'); vert.write('p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);'); vert.write('p_velocity.x += fhash(gl_InstanceID) * pd[1][3] - pd[1][3] / 2;'); vert.write('p_velocity.y += fhash(gl_InstanceID + pd[0][3]) * pd[1][3] - pd[1][3] / 2;'); vert.write('p_velocity.z += fhash(gl_InstanceID + 2 * pd[0][3]) * pd[1][3] - pd[1][3] / 2;'); vert.write('p_velocity.x += (pd[2][0] * p_age) / 5;'); vert.write('p_velocity.y += (pd[2][1] * p_age) / 5;'); vert.write('p_velocity.z += (pd[2][2] * p_age) / 5;'); vert.add_out('vec3 p_location'); vert.write('p_location = p_velocity * p_age;'); vert.write('spos.xyz += p_location;'); vert.add_uniform('mat4 WVP', '_worldViewProjectionMatrix'); vert.write('gl_Position = mul(spos, WVP);'); vert.add_uniform('vec4 inp', '_inputBrush'); vert.write('vec2 binp = vec2(inp.x, 1.0 - inp.y);'); vert.write('binp = binp * 2.0 - 1.0;'); vert.write('binp *= gl_Position.w;'); vert.write('gl_Position.xy += binp;'); vert.add_out('float p_fade'); vert.write('p_fade = sin(min((p_age / 8) * 3.141592, 3.141592));'); frag.add_out('float fragColor'); frag.write('fragColor = p_fade;'); // vert.add_out('vec4 wvpposition'); // vert.write('wvpposition = gl_Position;'); // frag.write('vec2 texCoord = wvpposition.xy / wvpposition.w;'); // frag.add_uniform('sampler2D gbufferD'); // frag.write('fragColor *= 1.0 - clamp(distance(textureLod(gbufferD, texCoord, 0).r, wvpposition.z), 0.0, 1.0);'); // Cycles.finalize(con_part); con_part.data.shader_from_source = true; con_part.data.vertex_shader = vert.get(); con_part.data.fragment_shader = frag.get(); return con_part; } public static function make_mesh(data:CyclesShaderData):CyclesShaderContext { var context_id = 'mesh'; var con_mesh:CyclesShaderContext = data.add_context({ name: context_id, depth_write: true, compare_mode: 'less', cull_mode: UITrait.inst.culling ? 'clockwise' : 'none', vertex_elements: [{name: "pos", data: 'short4norm'},{name: "nor", data: 'short2norm'},{name: "tex", data: 'short2norm'}] }); var vert = con_mesh.make_vert(); var frag = con_mesh.make_frag(); vert.add_out('vec2 texCoord'); frag.wvpposition = true; vert.add_out('vec4 prevwvpposition'); vert.add_uniform('mat4 VP', '_viewProjectionMatrix'); vert.add_uniform('mat4 prevWVP', '_prevWorldViewProjectionMatrix'); vert.wposition = true; // Height // TODO: can cause TAA issues if (heightUsed) { var displaceStrength = UITrait.inst.displaceStrength * 0.02; vert.n = true; vert.write('float height = 0.0;'); var numLayers = 0; for (l in Project.layers) { if (!l.visible) continue; if (numLayers > 16) break; numLayers++; vert.add_uniform('sampler2D texpaint_pack_vert' + l.id, '_texpaint_pack_vert' + l.id); vert.write('height += textureLod(texpaint_pack_vert' + l.id + ', tex, 0.0).a;'); } vert.write('wposition += wnormal * vec3(height, height, height) * vec3($displaceStrength, $displaceStrength, $displaceStrength);'); } // vert.write('gl_Position = mul(vec4(wposition.xyz, 1.0), VP);'); vert.write('texCoord = tex;'); if (heightUsed) { vert.add_uniform('mat4 invW', '_inverseWorldMatrix'); vert.write('prevwvpposition = mul(mul(vec4(wposition, 1.0), invW), prevWVP);'); } else { vert.write('prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);'); } frag.ins = vert.outs; frag.add_out('vec4 fragColor[3]'); frag.n = true; frag.add_function(CyclesFunctions.str_packFloatInt16); frag.add_function(CyclesFunctions.str_packFloat2); if (Context.tool == ToolColorId) { frag.add_uniform('sampler2D texcolorid', '_texcolorid'); frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); // met/rough frag.write('vec3 idcol = pow(textureLod(texcolorid, texCoord, 0.0).rgb, vec3(2.2, 2.2, 2.2));'); frag.write('fragColor[1] = vec4(idcol.rgb, packFloat2(1.0, 1.0));'); // occ/spec } else { frag.add_function(CyclesFunctions.str_octahedronWrap); frag.write('vec3 basecol;'); frag.write('float roughness;'); frag.write('float metallic;'); frag.write('float occlusion;'); frag.write('float opacity;'); frag.write('float specular;'); frag.write('float matid = 0.0;'); frag.vVec = true; frag.add_function(CyclesFunctions.str_cotangentFrame); #if (kha_direct3d11 || kha_direct3d12) frag.write('mat3 TBN = cotangentFrame(n, vVec, texCoord);'); #else frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);'); #end if (Project.layers[0].visible) { if (Context.layer.paintBase) { frag.add_shared_sampler('sampler2D texpaint'); frag.write('basecol = pow(textureLodShared(texpaint, texCoord, 0.0).rgb, vec3(2.2, 2.2, 2.2));'); } else { frag.write('basecol = vec3(0.0, 0.0, 0.0);'); } if (Context.layer.paintNor || emisUsed) { frag.add_shared_sampler('sampler2D texpaint_nor'); frag.write('vec4 texpaint_nor_sample = textureLodShared(texpaint_nor, texCoord, 0.0);'); if (emisUsed) { frag.write('matid = texpaint_nor_sample.a;'); } if (Context.layer.paintNor) { frag.write('vec3 ntex = texpaint_nor_sample.rgb;'); frag.write('n = ntex * 2.0 - 1.0;'); frag.write('n.y = -n.y;'); frag.write('n = normalize(mul(n, TBN));'); } } if (heightUsed || Context.layer.paintOcc || Context.layer.paintRough || Context.layer.paintMet) { frag.add_shared_sampler('sampler2D texpaint_pack'); frag.write('vec4 pack = textureLodShared(texpaint_pack, texCoord, 0.0);'); } // Height if (heightUsed) { frag.write('float bump_res_x = dFdx(pack.a) * 32.0;'); frag.write('float bump_res_y = dFdy(pack.a) * 32.0;'); frag.write('vec3 va = normalize(vec3(1.0, 0.0, bump_res_x));'); frag.write('vec3 vb = normalize(vec3(0.0, 1.0, bump_res_y));'); frag.write('vec3 vc = normalize(vec3(bump_res_x, bump_res_y, 1.0));'); frag.write('n = normalize(mul(n, mat3(va, vb, vc)));'); } // if (Context.layer.paintOcc) { frag.write('occlusion = pack.r;'); } else { frag.write('occlusion = 1.0;'); } if (Context.layer.paintRough) { frag.write('roughness = pack.g;'); } else { frag.write('roughness = 1.0;'); } if (Context.layer.paintMet) { frag.write('metallic = pack.b;'); } else { frag.write('metallic = 0.0;'); } var l = Project.layers[0]; if (l.maskOpacity < 1) { frag.write('basecol *= ${l.maskOpacity};'); frag.write('occlusion *= ${l.maskOpacity};'); frag.write('roughness *= ${l.maskOpacity};'); frag.write('metallic *= ${l.maskOpacity};'); } } else { frag.write('basecol = vec3(0.0, 0.0, 0.0);'); frag.write('occlusion = 1.0;'); frag.write('roughness = 1.0;'); frag.write('metallic = 0.0;'); frag.write('specular = 1.0;'); } if (Project.layers.length > 1) { frag.write('float factor0;'); frag.write('vec4 col_tex0;'); frag.write('vec4 col_nor0;'); frag.write('vec4 col_pack0;'); frag.write('vec3 n0;'); var len = Project.layers.length; var start = len - 1; var maxLayers = getMaxVisibleLayers(); var count = 1; for (i in 1...len) { if (start == 1) break; start--; var l = Project.layers[len - i]; if (l.visible) { count++; if (count >= maxLayers) break; } } for (i in start...len) { var l = Project.layers[i]; if (!l.visible) continue; var id = l.id; if (l.objectMask > 0) { var uid = Project.paintObjects[l.objectMask - 1].uid; frag.add_uniform('int objectId', '_uid'); frag.write('if ($uid == objectId) {'); } frag.add_shared_sampler('sampler2D texpaint' + id); frag.write('col_tex0 = textureLodShared(texpaint' + id + ', texCoord, 0.0);'); frag.write('factor0 = col_tex0.a;'); if (l.texpaint_mask != null) { frag.add_shared_sampler('sampler2D texpaint_mask' + id); frag.write('factor0 *= textureLodShared(texpaint_mask' + id + ', texCoord, 0.0).r;'); } if (l.maskOpacity < 1) { frag.write('factor0 *= ${l.maskOpacity};'); } if (Context.layer.paintBase) { frag.write('basecol = mix(basecol, pow(col_tex0.rgb, vec3(2.2, 2.2, 2.2)), factor0);'); } if (emisUsed || Context.layer.paintNor) { frag.add_shared_sampler('sampler2D texpaint_nor' + id); frag.write('col_nor0 = textureLodShared(texpaint_nor' + id + ', texCoord, 0.0);'); if (emisUsed) { frag.write('matid = col_nor0.a;'); } if (Context.layer.paintNor) { frag.write('n0 = col_nor0.rgb * 2.0 - 1.0;'); frag.write('n0.y = -n0.y;'); frag.write('n0 = normalize(mul(n0, TBN));'); frag.write('n = normalize(mix(n, n0, factor0));'); } } if (Context.layer.paintOcc || Context.layer.paintRough || Context.layer.paintMet) { frag.add_shared_sampler('sampler2D texpaint_pack' + id); frag.write('col_pack0 = textureLodShared(texpaint_pack' + id + ', texCoord, 0.0);'); if (Context.layer.paintOcc) { frag.write('occlusion = mix(occlusion, col_pack0.r, factor0);'); } if (Context.layer.paintRough) { frag.write('roughness = mix(roughness, col_pack0.g, factor0);'); } if (Context.layer.paintMet) { frag.write('metallic = mix(metallic, col_pack0.b, factor0);'); } } if (l.objectMask > 0) { frag.write('}'); } } } if (UITrait.inst.drawTexels) { frag.add_uniform('float texpaintSize', '_texpaintSize'); frag.write('vec2 texel = texCoord * texpaintSize;'); frag.write('basecol *= max(float(mod(int(texel.x), 2.0) == mod(int(texel.y), 2.0)), 0.9);'); } if (UITrait.inst.drawWireframe) { // GL_NV_fragment_shader_barycentric // VK_AMD_shader_explicit_vertex_parameter frag.add_uniform('sampler2D texuvmap', '_texuvmap'); frag.write('basecol *= 1.0 - textureLod(texuvmap, texCoord, 0.0).r;'); // frag.write('if (basecol == vec3(0,0,0)) discard;'); } frag.write('n /= (abs(n.x) + abs(n.y) + abs(n.z));'); frag.write('n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);'); if (UITrait.inst.viewportMode == 0) { // Render if (emisUsed) { frag.write('if (matid == 1.0) basecol *= 10.0;'); // Boost for bloom } frag.write('fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(matid)));'); frag.write('fragColor[1] = vec4(basecol.rgb, packFloat2(occlusion, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 1) { // Basecol frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(basecol.rgb, packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 2) { // Normal Map frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(ntex.rgb, packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 3) { // Occ frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(vec3(occlusion, occlusion, occlusion), packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 4) { // Rough frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(vec3(roughness, roughness, roughness), packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 5) { // Met frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(vec3(metallic, metallic, metallic), packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 6) { // Texcoord frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(texCoord, 0.0, packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 7) { // Normal frag.nAttr = true; frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(nAttr, packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 8) { // MaterialID frag.write('float sample_matid = textureLodShared(texpaint_nor, texCoord, 0.0).a;'); frag.write('float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 2.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float matid_g = fract(sin(dot(vec2(sample_matid * 2.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 4.0), vec2(12.9898, 78.233))) * 43758.5453);'); frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('fragColor[1] = vec4(matid_r, matid_g, matid_b, packFloat2(1.0, 1.0));'); // occ/spec } else if (UITrait.inst.viewportMode == 9) { // Mask frag.write('fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));'); frag.write('float sample_mask = 1.0;'); if (Context.layer.texpaint_mask != null) { frag.add_uniform('sampler2D texpaint_mask_view', '_texpaint_mask'); frag.write('sample_mask = textureLod(texpaint_mask_view, texCoord, 0.0).r;'); } frag.write('fragColor[1] = vec4(sample_mask, sample_mask, sample_mask, packFloat2(1.0, 1.0));'); // occ/spec } } frag.write('vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;'); frag.write('vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;'); frag.write('fragColor[2] = vec4(posa - posb, texCoord.xy);'); Cycles.finalize(con_mesh); con_mesh.data.shader_from_source = true; con_mesh.data.vertex_shader = vert.get(); con_mesh.data.fragment_shader = frag.get(); return con_mesh; } static function getMaxVisibleLayers():Int { #if (kha_direct3d11 || kha_direct3d12) // 128 texture slots available // 4 textures per layer (3 + 1 mask) // 32 layers - base + 31 on top return 31; #else // 32 texture slots available return 4; // base + 4 on top #end } #if rp_voxelao public static function make_voxel(data:iron.data.ShaderData.ShaderContext) { var structure = new kha.graphics4.VertexStructure(); structure.add("pos", kha.graphics4.VertexData.Short4Norm); structure.add("nor", kha.graphics4.VertexData.Short2Norm); structure.add("tex", kha.graphics4.VertexData.Short2Norm); var pipeState = data.pipeState; pipeState.inputLayout = [structure]; data.raw.vertex_elements = [{name: "pos", data: 'short4norm'}, {name: "nor", data: 'short2norm'}, {name: "tex", data: 'short2norm'}]; var ds = UITrait.inst.displaceStrength * 0.02; pipeState.vertexShader = kha.graphics4.VertexShader.fromSource( #if (kha_direct3d11 || kha_direct3d12) "uniform float4x4 W; uniform float3x3 N; Texture2D texpaint_pack; SamplerState _texpaint_pack_sampler; struct SPIRV_Cross_Input { float4 pos : TEXCOORD1; float2 nor : TEXCOORD0; float2 tex : TEXCOORD2; }; struct SPIRV_Cross_Output { float4 svpos : SV_POSITION; }; SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) { SPIRV_Cross_Output stage_output; stage_output.svpos.xyz = mul(float4(stage_input.pos.xyz, 1.0), W).xyz / float3(1, 1, 1); float3 wnormal = normalize(mul(float3(stage_input.nor.xy, stage_input.pos.w), N)); float height = texpaint_pack.SampleLevel(_texpaint_pack_sampler, stage_input.tex, 0.0).a; stage_output.svpos.xyz += wnormal * height.xxx * float3(" + ds + "," + ds + "," + ds + "); stage_output.svpos.w = 1.0; return stage_output; }" #else "#version 450 in vec4 pos; in vec2 nor; in vec2 tex; out vec3 voxpositionGeom; uniform mat4 W; uniform mat3 N; uniform sampler2D texpaint_pack; void main() { const vec3 voxelgiHalfExtents = vec3(1.0, 1.0, 1.0); voxpositionGeom = vec3(W * vec4(pos.xyz, 1.0)) / voxelgiHalfExtents; vec3 wnormal = normalize(N * vec3(nor.xy, pos.w)); float height = textureLod(texpaint_pack, tex, 0.0).a; voxpositionGeom += wnormal * vec3(height) * vec3(" + ds + "); }" #end ); pipeState.compile(); data.raw.constants = [{ name: "W", type: "mat4", link: "_worldMatrix" }, { name: "N", type: "mat3", link: "_normalMatrix" }]; data.constants = [pipeState.getConstantLocation("W"), pipeState.getConstantLocation("N")]; data.raw.texture_units = [{name: "texpaint_pack"}, {name: "voxels", is_image: true}]; data.textureUnits = [pipeState.getTextureUnit("texpaint_pack"), pipeState.getTextureUnit("voxels")]; } #end }